Source. Derived from docs/audits/M5-managed-agents-refactor-audit.md. Each issue below is self-contained — the audit is background reading, not a prerequisite for implementation. All three issues can land inside the remaining hackathon window (deadline: Sunday 2026-04-26).
Important
Why this refactor exists. The current Managed Agents integration lives on the Q&A agent (agents.qa.managed), which is the wrong fit per the platform's own positioning ("long-running tasks, asynchronous work, minutes or hours with multiple tool calls"). The smoking gun is _trickle_text — 30-character server-side re-chunking at 15 ms intervals to fake a token stream that Managed Agents does not natively emit. We fight the platform's grain for zero architectural win. The migration pivots Managed Agents onto the Investigator (12 turns, 120 s wall clock, async background, tool-heavy) where the platform earns its keep, and reverts Q&A to the Messages API path that was designed for interactive sub-second turns.
flowchart TB
classDef base fill:#e6f3ff,stroke:#2b6cb0
classDef addon fill:#fff5e6,stroke:#dd6b20
M55["M5.5 — Investigator base migration<br/>~1 working day"]:::base
M56["M5.6 — Continue investigation add-on<br/>~3 h"]:::addon
M57["M5.7 — In-sandbox signal diagnostics<br/>~4 h"]:::addon
M55 --> M56
M55 --> M57
subgraph base_work["Base migration (must land first)"]
direction TB
T1["Tunnel plus path-secret URL mount"]
T2["agents.investigator.managed module"]
T3["Hosted MCP wiring"]
T4["investigator_session_id column plus agent.thinking mapping"]
T5["Delete agents.qa.managed (414 LOC)"]
T1 --> T2 --> T3 --> T4 --> T5
end
M55 -.includes.-> base_work
Important
This is the load-bearing issue. It delivers the prize narrative ("Managed Agents runs the Investigator, our backend is a thin orchestrator, Anthropic calls our MCP server directly") and unblocks M5.6 and M5.7. Ship this before attempting either add-on. Gate the new path behind INVESTIGATOR_USE_MANAGED=False so M4.5 stays the default until end-to-end verified; flip the flag for the demo.
Investigator today runs a hand-rolled agent loop in our FastAPI process: agents/investigator/service.py pairs _run_investigator_body (74 LOC, for-loop over 12 turns) with _dispatch_tool_uses (64 LOC, manual tool routing) and _llm_call (streams thinking_delta + MessageStreamEvents from anthropic.messages.stream). Total ~140 LOC of agent-loop boilerplate plus a wall-clock timeout, signed-thinking-block reconstruction across turns, and ContextVar-based turn-id plumbing.
Managed Agents replaces that with a hosted agent loop (client.beta.sessions.events.stream(session_id)) where Anthropic runs the for-loop, holds the conversation history, and calls our MCP endpoint directly. Our process only dispatches the three custom tools that genuinely need our backend: submit_rca (DB write plus handoff to Work Order Generator), ask_kb_builder (handoff to KB Builder), and render_* (three generative-UI tools: render_signal_chart, render_diagnostic_card, render_pattern_match).
sequenceDiagram
autonumber
participant Sentinel
participant Backend as Our FastAPI
participant MA as Managed Agents session
participant MCP as Our MCP server (tunneled)
participant DB
Sentinel->>Backend: asyncio.create_task(run_investigator(wo_id))
Backend->>MA: sessions.events.stream(session_id)
Backend->>MA: send user.message (Anomaly on cell X, WO #N)
loop Until end_turn
MA->>MCP: direct MCP tool call (no backend round-trip)
MCP-->>MA: tool_result
MA-->>Backend: agent.thinking event
Backend-->>Backend: broadcast EventBusMap.thinking_delta
MA-->>Backend: session.status_idle requires_action (custom tool)
Backend->>Backend: dispatch render_* or submit_rca or ask_kb_builder
Backend->>MA: send user.custom_tool_result
end
MA-->>Backend: session.status_idle end_turn
Backend->>DB: UPDATE work_order status=analyzed, investigator_session_id=<id>
Backend-->>Sentinel: asyncio task completes
1. Hosted MCP exposure (/mcp is a public endpoint once tunneled).
- Generate a 32-byte token, persist as
ARIA_MCP_PATH_SECRETin.envand.env.example. - In
backend/main.py, mount the MCP app at/mcp/{settings.aria_mcp_path_secret}instead of/mcp. The URL itself is the secret — Anthropic'smcp_serversconfig does not support custom HTTP headers (documented: "No auth tokens are provided at this stage."), so a FastAPI bearer-auth middleware is not forwardable. - Add
ARIA_MCP_PUBLIC_URLto.env.examplepointing at the tunneled URL (e.g.https://<random>.trycloudflare.com/mcp/<secret>). - Document the tunnel setup in the backend README:
cloudflared tunnel --url http://localhost:8000. - Do NOT log the full public URL. The secret is in the path.
2. agents.investigator.managed module.
Create backend/agents/investigator/managed.py as a mirror of the existing agents/qa/managed.py structure:
run_investigator_managed(work_order_id: int) -> None— same signature asrun_investigator, so Sentinel'sasyncio.create_taskcall is unchanged._ensure_agent_and_env()+_ensure_session()— lazy creation with a process-wide asyncio lock. Reuse the pattern fromqa/managed.py:_bootstrap_lock.- Custom tools registered:
submit_rca,ask_kb_builder,render_signal_chart,render_diagnostic_card,render_pattern_match. No MCP schemas wrapped as custom — MCP goes via hosted MCP. mcp_servers=[{"type": "url", "name": "aria", "url": settings.aria_mcp_public_url}]onagents.create(...).- Extended thinking enabled:
thinking={"type": "enabled", "budget_tokens": 10000}passed to the agent definition (mirror the existing_llm_callconfig inservice.py:74). - Event loop: consume
sessions.events.stream, branch on event type.
3. Event mapping (see sessions.events.stream event catalogue in §1 of the audit).
flowchart LR
E["sessions.events.stream event"] --> T{event type}
T -->|agent.thinking| TD["Broadcast EventBusMap.thinking_delta<br/>one frame per reasoning block"]
T -->|agent.message| AM["Buffer (Investigator does not stream text to user;<br/>final message lands in rca_summary via submit_rca)"]
T -->|agent.custom_tool_use| CTU["Buffer by event id"]
T -->|session.status_idle| SI{stop_reason.type}
SI -->|requires_action| RA["Dispatch each pending custom tool,<br/>send user.custom_tool_result"]
SI -->|end_turn| ET["Loop ends - WO already UPDATEd by submit_rca handler"]
SI -->|retries_exhausted| RE["Raise - fallback_rca path"]
T -->|session.status_terminated| ST["Raise - fallback_rca path"]
T -->|session.error| SE["Raise - fallback_rca path"]
Note on thinking granularity. The Managed Agents events stream does NOT emit per-chunk thinking deltas. It emits whole agent.thinking events per reasoning block (one event per "thought"). We map each to one EventBusMap.thinking_delta WebSocket frame ({agent, content, turn_id}) — same contract as M4.5, zero frontend changes. The only user-visible difference is that the Agent Inspector updates once per block instead of once per token. The character-by-character fill animation is lost; the reasoning trace itself is preserved.
4. Persist session_id on the work_order row.
- New migration:
work_order.investigator_session_id TEXT NULL. Additive, backwards-compatible. - In the
submit_rcacustom-tool handler (migrated fromservice.py:_handle_submit_rca), add the session id to the UPDATE alongsidestatus='analyzed'andrca_summary. - The existing
WorkOrderRepository.updatealready accepts arbitrary fields; just passinvestigator_session_id=session_id.
5. Custom tool handlers — reuse existing logic.
The three requires_action branches wrap existing code:
submit_rca-> callservice._handle_submit_rca(rename to publichandle_submit_rcawhen moving). Writes the RCA, creates thefailure_historyrow, spawns Work Order Generator viahandoff.spawn_work_order_generator. Zero behavioral change.ask_kb_builder-> callhandoff.handle_ask_kb_builder(already public). Zero change.render_*-> callservice._handle_render(rename tohandle_render). Zero change in broadcast shape.
6. Fallback path.
run_investigatorbecomes a thin dispatcher: branches onsettings.investigator_use_managed. DefaultFalseduring rollout, flippedTruefor demo.- The existing
run_investigatorbody stays intact as theFalsebranch. Sentinel callsrun_investigator(work_order_id)unchanged. - Flip takes <5 min (env var), no data-shape impact. The
work_orderrow is the only persisted artifact and its shape is unchanged apart from the additive column.
7. Tests.
Mirror backend/tests/unit/agents/test_qa_agent_managed.py:
_FakeEventsharness forsessions.events.streamandevents.send.- Happy path:
agent.thinkingevent broadcasts onethinking_delta;submit_rcacustom tool call triggers_handle_submit_rca, writes WO update, spawns Work Order Generator. ask_kb_builderrequires_action path mirrors theask_investigatortest in the Q&A managed file — asserts handoff frames.- Error path:
session.errorevent routes to the fallback (flip WO to analyzed with a reason, broadcastrca_readywith confidence 0.0). - Session reuse:
investigator_session_idis persisted on the WO row aftersubmit_rca.
Full test suite (make backend.test) must stay green on both flag values.
8. Cleanup — delete agents.qa.managed.
Once M5.5 is green, remove the Q&A managed path entirely:
backend/agents/qa/managed.py(414 LOC)backend/agents/qa/schemas.py::build_custom_toolsbackend/tests/unit/agents/test_qa_agent_managed.pyuse_managed_agentssetting incore/config.pyrun_qa_turn_managedre-export inagents/qa/__init__.py- The
use_managed_agentsbranch inmodules/chat/router.py— always callsrun_qa_turn. USE_MANAGED_AGENTSenv var in.env.example.
Keep qa.schemas.ASK_INVESTIGATOR_TOOL (still used by messages_api.py).
9. Cold-start measurement.
First Investigator run after process start pays environment plus agent plus session creation latency. Unknown today — measure on the dev stack. If >2 s, pre-warm in the FastAPI lifespan startup (create env and agent at app boot, not on first anomaly).
10. Docs.
- README architecture diagram — flip Investigator from Messages API to Managed Agents, note Q&A stays on Messages API.
- Demo script — new pitch in audit §6 is the reference.
.env.example— addARIA_MCP_PUBLIC_URL,ARIA_MCP_PATH_SECRET,INVESTIGATOR_USE_MANAGED; removeUSE_MANAGED_AGENTS.
backend/
main.py # mount path change
core/config.py # add INVESTIGATOR_USE_MANAGED, ARIA_MCP_*; remove USE_MANAGED_AGENTS
agents/
investigator/
__init__.py # re-export run_investigator_managed
managed.py # NEW
service.py # _handle_submit_rca, _handle_render made public
handoff.py # already public, no change
qa/
__init__.py # drop run_qa_turn_managed re-export
managed.py # DELETE (414 LOC)
schemas.py # drop build_custom_tools
modules/chat/router.py # remove managed branch, always Messages API
infrastructure/database/migrations/
NNN_work_order_investigator_session_id.sql # NEW, additive TEXT NULL
tests/unit/agents/
investigator/ # NEW folder
test_managed.py # NEW
test_qa_agent_managed.py # DELETE
.env.example # add ARIA_MCP_*, INVESTIGATOR_USE_MANAGED; remove USE_MANAGED_AGENTS
README.md # tunnel setup + architecture diagram
-
cloudflared tunnel --url http://localhost:8000exposes/mcp/<secret>and a curl against it returns an MCP tool list. - With
INVESTIGATOR_USE_MANAGED=False, full test suite is green (regression baseline). - With
INVESTIGATOR_USE_MANAGED=True, Sentinel anomaly on P-02 produces the samerca_readyframe,work_order.status='analyzed',failure_historyrow, and Work Order Generator spawn as the M4.5 path. - With
INVESTIGATOR_USE_MANAGED=True,work_order.investigator_session_idis populated on submit_rca. -
agent.thinkingevents surface asthinking_deltaWebSocket frames in the Agent Inspector. At least one frame per reasoning block. - Hosted MCP path is in use — confirm via
ws_managerevent trace that MCP tool calls do NOT pass through_dispatch_custom_toolon the managed path. -
agents.qa.manageddeleted.make backend.teststill green. -
make backend.lintandmake backend.typecheckclean. - Bootstrap latency measured and documented. If >2 s,
lifespanprewarm added. - Rollback drill: flip
INVESTIGATOR_USE_MANAGED=False, restart, verify M4.5 path still works end-to-end in <5 min.
Approximately 1 working day. Effort table:
| Workstream | Rough estimate |
|---|---|
| Tunnel + path-secret URL mount | 45 min |
agents.investigator.managed module |
3-4 h |
Hosted MCP wiring (mcp_servers=[...]) |
30 min |
Migration: work_order.investigator_session_id column |
15 min |
Tests (mirror test_qa_agent_managed.py) |
2 h |
Cold-start measurement plus optional lifespan prewarm |
30 min |
Delete agents.qa.managed (414 LOC) plus its tests |
45 min |
| Docs (README tunnel doc, architecture diagram, demo script) | 30 min |
| Risk | Mitigation |
|---|---|
| Bootstrap cold start >2 s surprises the demo | Measure empirically; if confirmed, lifespan prewarm the env and agent at app boot |
| Tunnel flakes mid-demo | Pre-open the tunnel the morning of, keep the path secret static, monitor with a health curl |
| Path secret leaks via logs | Audit uvicorn access logs; set --log-level warning on the mount; ensure main.py does not log settings.aria_mcp_path_secret |
| Thinking block granularity feels sparse in the Inspector | Acceptable for a 3-min demo - blocks are more readable than 3-char fragments. Do not re-disable thinking |
agent.thinking event shape differs from expectation |
Probe with a minimal script against the beta before wiring the broadcast. Fallback: log the raw event shape and tweak the content extractor |
| Hosted MCP latency on our MCP endpoint (tunneled round-trip) > local FastAPI latency | Expected; measure. If blocking, fall back to USE_MANAGED_AGENTS=False for demo |
| Pricing unknown (§7 risk 5 of the audit) | Not publicly documented. Measure session.usage.* during dry-run. Hackathon volume is low; budget-check post-hackathon if project continues |
- M5.6 (Continue investigation) — needs
investigator_session_idpopulated. - M5.7 (In-sandbox signal diagnostics) — needs the managed path as the execution environment.
- None. Stack reads of the Managed Agents docs done; implementation is self-contained.
Note
Differentiation goal. The base migration in M5.5 is architecturally correct but not uniquely demoable — most submissions will look similar (long-running agent plus hosted MCP). This add-on delivers a capability that is architecturally impossible on the Messages API path: re-opening a finished investigation and continuing the same session days later, with the agent answering from its own memory of what it tried (not from a static rca_summary text field). Messages API would require replaying the full message history (including signed thinking blocks) on every reopen — hosted sessions make it free.
After M5.5 lands, every investigated work order has investigator_session_id set. The Managed Agents session on Anthropic's side holds the full reasoning trace, tool history, and mid-investigation context for 30 days after last activity (documented session checkpoint TTL). This add-on exposes a "Continue investigation" affordance in the UI that reconnects to that session and lets the operator ask follow-up questions against the live agent state.
sequenceDiagram
autonumber
participant Operator
participant Frontend as Frontend WO detail
participant Backend
participant MA as Anthropic session (existing)
Operator->>Frontend: Open WO detail page
Frontend->>Backend: GET /api/v1/work_order/{id}
Backend-->>Frontend: { investigator_session_id: "sess_abc", ... }
Frontend->>Frontend: Show "Continue investigation" button<br/>(enabled if session_id IS NOT NULL and within 30-day TTL)
Operator->>Frontend: Click "Continue investigation", type follow-up question
Frontend->>Backend: POST /api/v1/work_order/{id}/continue_investigation (opens WS)
Backend->>MA: sessions.events.stream(session_id=sess_abc)
Backend->>MA: send user.message (operator question)
loop Events flow
MA-->>Backend: agent.thinking / agent.custom_tool_use / agent.message
Backend-->>Frontend: thinking_delta / tool_call / text_delta frames (existing contract)
end
MA-->>Backend: session.status_idle end_turn
Backend-->>Frontend: done frame
Operator->>Operator: Reads agent response grounded in original investigation
"Day-shift operator triggers the investigation. We jump 8 hours — night-shift operator opens the same work order, asks 'why did you rule out the bearing?', and the agent answers from its own memory of what it tried earlier today. That is architecturally free on Managed Agents. On Messages API, we would have to replay every turn — including signed thinking blocks — and pay full token cost for context that already lived server-side."
Backend.
- New endpoint
POST /api/v1/work_order/{id}/continue_investigation— opens a WebSocket, auth via the same cookie helper used by M5.2 (core/security/ws_auth.py). - On first inbound user frame, resume the session:
anthropic.beta.sessions.events.stream(session_id). Reuse the event-consumer code from M5.5'sagents.investigator.managed— same branch logic onagent.thinking,agent.message,agent.custom_tool_use,session.status_idle. - Stream out the same frames as the primary investigation:
thinking_delta,tool_call,tool_result,ui_render,text_delta,done. Zero new frame types. - Validate the session is still within the 30-day TTL before opening the stream. If expired, return HTTP 410 Gone with
{"error": "session expired", "rca_summary": <row.rca_summary>}and let the frontend fall back to a read-only view. - The session remains live across reopens — no
sessions.deletecall. Checkpoint TTL extends on each activity.
Frontend.
- On the WO detail page, show a "Continue investigation" button when:
investigator_session_id IS NOT NULL- Checkpoint is within 30-day TTL (compute on the frontend from
work_order.updated_ator similar; server also enforces on connect).
- On click, open the Agent Inspector view seeded with the resumed session. Reuse the existing chat component from Q&A (
ChatPanelin M6.5). - Display a banner noting this is a resumed investigation: "Continuing from day-shift investigation, 8 hours ago".
Tests.
- Backend: mirror
test_second_turn_reuses_cached_sessionfrom the existing Q&A managed test file — second WebSocket call to a knownsession_idhitssessions.events.stream(<existing_id>)instead of creating a new session. Assert noagents.createorenvironments.createcalls on the resume path. - Backend:
410 Gonepath when the session is flagged expired. Mock the TTL check. - Frontend: unit test for the button gating logic.
backend/
modules/work_order/router.py # new POST /continue_investigation endpoint
agents/investigator/managed.py # extract the event-consumer into a reusable helper
tests/unit/modules/work_order/
test_continue_investigation_router.py # NEW
frontend/
src/pages/WorkOrderDetail.tsx # "Continue investigation" button gating
src/components/ContinueInvestigationChat.tsx # NEW, reuses ChatPanel internals
- New endpoint
POST /api/v1/work_order/{id}/continue_investigationaccepts a WebSocket upgrade, resumes an existing session, streams the EventBusMap frame contract. - Calling the endpoint twice on the same WO resumes the same session both times — only one session id ever exists for a given WO.
-
investigator_session_id IS NULL→ button disabled (or endpoint returns 404). - Expired session → endpoint returns 410 Gone with the static
rca_summarypayload. - E2E: trigger an anomaly on P-02, wait for submit_rca, click "Continue investigation" in the UI, ask a question, receive a grounded answer.
- Demo script updated with the pitch line above. Pitch wording explicitly acknowledges the 30-day TTL — "reopen until checkpoint expires" — not "reopen indefinitely".
Approximately 3 hours. Most of the complexity is in M5.5 — this add-on reuses the event consumer and frame contract verbatim.
| Risk | Mitigation |
|---|---|
| 30-day TTL expires between demo dry-run and demo day | Trigger a fresh investigation the morning of — gap is minutes, not days |
| Frontend has no WO detail page yet, or the existing chat component is tightly coupled to the Q&A route | Check the state of M6/M7 before scheduling. If the page is missing, scope-limit M5.6 to the backend endpoint and a curl demo for the pitch |
| Session stays "running" if our backend crashes mid-stream | Per docs, running sessions cannot be deleted without an interrupt event. Acceptable for a hackathon — document as post-hackathon hardening |
- None (this is a prize-differentiation feature, not on the critical path).
- M5.5 (needs
investigator_session_idpopulated and the managed path as the primary Investigator implementation).
Note
Framing. The capability is Python signal diagnostics inside Anthropic's cloud container, not "FFT on pump bearings." ARIA's schema is equipment-agnostic: equipment.equipment_type is a free string, thresholds are keyed by signal name (not equipment class), and the signal catalogue covers 13 types (vibration, temperature, pressure, flow, voltage, current, power, torque, speed, force, cycle_time, score, level) — any rotating, fluid, thermal, or electrical asset. The agent picks the diagnostic technique based on which signal breached and what reference values the KB carries. The P-02 seed happens to be a Grundfos centrifugal pump with vibration breaches — so the dry-run demo uses an FFT — but swap the demo cell and the script changes, not the capability.
Messages API cannot offer numerical diagnostics that exceed what the model computes in tokens (trend fits over thousands of samples, frequency content, cross-signal correlation, statistical process control). Managed Agents ships a cloud container with bash plus read / write / edit / glob / grep plus web_fetch / web_search — Python runs inside that container via bash, with arbitrary pre-installed pip packages. This add-on teaches Investigator to lean on that container for math it cannot do in tokens, and to cite the numerical evidence in the RCA.
Menu, not mandate — the agent picks based on the breached signal and the KB's failure_patterns.signal_signature:
| Signal type | Diagnostic technique | Asset examples |
|---|---|---|
| Any time-series | Rolling mean or median, linear or exponential trend fit, rate-of-change | All equipment |
| Any time-series | SPC control limits (+-3 sigma), CUSUM drift detection | All equipment |
| Multi-signal | Cross-correlation (current vs temperature rise, pressure vs flow, etc.) | Motors, compressors, HVAC |
| Vibration, current | FFT plus spectral peak detection | Rotating equipment, VFD-driven motors |
| Vibration on rolling kit | Bearing fault frequencies (BPFO/BPFI/BSF/FTF) if KB has geometry | Pumps, fans, gearboxes with known bearing specs |
| Temperature, pressure | Degradation fit - projected time-to-threshold | Heat exchangers, seals, filters |
sequenceDiagram
autonumber
participant MA as Managed Agents (Investigator)
participant Container as Cloud container<br/>(numpy, pandas, scipy)
participant Backend as Our FastAPI
participant DB
MA->>Backend: MCP get_signal_anomalies - breached signal + window
Backend-->>MA: anomaly metadata
MA->>MA: Decide: this is a vibration breach on rotating kit, try FFT
MA->>Container: bash: curl http://<tunnel>/api/v1/signal/<id>/csv?start=...&end=...
Container->>Backend: GET signal CSV (path-secret auth)
Backend-->>Container: raw samples (CSV)
MA->>Container: bash: python - <<'EOF' ... numpy.fft.rfft ... EOF
Container-->>MA: dominant frequency 142 Hz
MA->>Backend: MCP get_equipment_kb - read bearing geometry
Backend-->>MA: n_balls, pitch_diameter, ball_diameter
MA->>MA: Compute BPFO at 1480 RPM - compare to 142 Hz
MA->>Backend: custom tool submit_rca with numerical evidence
Backend->>DB: UPDATE work_order rca_summary with "dominant freq matches BPFO"
The current seed is a centrifugal pump with vibration breaches. The demo clip reads:
"The agent suspects bearing wear. It writes a Python script in its sandbox, runs an FFT on the raw vibration signal, compares the dominant frequency against the bearing's outer-race fault frequency, and concludes outer-race spalling with 0.87 confidence. None of that math happens in tokens — it runs as actual Python in Anthropic's container."
If the demo cell changes (e.g. a motor or heat exchanger is added before submission), the script the agent writes changes — rolling-mean trend on temperature, FFT on motor current signature analysis, SPC on pressure — but the capability line does not. That is the point.
1. Environment dependencies.
When agents.investigator.managed._ensure_agent_and_env creates the environment, declare Python pip dependencies in the environment config: numpy, pandas, scipy. Per docs, environments allow pre-installed packages. This is a one-time bootstrap; subsequent sessions reuse the same environment.
2. Prompt update.
Extend agents/investigator/prompts.py::INVESTIGATOR_SYSTEM with a new guidance section:
"For numerical anomalies, you may write Python inside the sandbox container via the bash tool to compute trends, statistics, frequency content, or cross-correlations on raw signals. Numpy, pandas, and scipy are pre-installed. Cite the numerical results (dominant frequency, trend slope, correlation coefficient, whatever is relevant) in your submit_rca call."
Include at least two worked examples mapped to the demo cell's signature signals — one time-series example (e.g. rolling-mean trend on temperature or pressure) plus one frequency or cross-signal example (e.g. FFT on vibration, current-temperature correlation). The examples anchor the behavior; without them the model tends to describe the analysis instead of running it.
3. Signal CSV endpoint.
GET /api/v1/signal/{signal_id}/csv?start=<iso>&end=<iso>returns the raw signal window as CSV:timestamp,valueper line.- Mount behind the same path-secret prefix as
/mcp— i.e./api/<secret>/signal/...or a separateARIA_SIGNAL_CSV_SECRET. Reuse the secret from M5.5 for simplicity unless the threat model separates them. - Stream the response — do not materialize the full window in memory.
- Error model: 404 if signal id unknown, 400 if window is malformed, 413 if the window exceeds a sane cap (e.g. 1 million rows).
4. KB reference values.
Ensure the demo cell's equipment_kb.failure_patterns[*].signal_signature dict carries whatever reference values the chosen diagnostic technique needs. The schema is already flexible (free-form dict); no migration needed. Examples:
- Rotating kit with FFT-to-BPFO:
n_balls,pitch_diameter_mm,ball_diameter_mm, nominalshaft_rpm. - Thermal kit with degradation fit:
design_temperature_c,alert_threshold_c,trip_threshold_c. - Any SPC-amenable signal: recent
mean,stddev, andsample_countof a reference window.
These go in via the KB Builder onboarding flow, not via this issue. If the demo cell's KB is missing values, add them as a one-line SQL fixture in the seed.
5. Tests.
- Unit: assert
INVESTIGATOR_SYSTEMcontains the signal-diagnostics guidance and at least two worked examples. - Unit: signal CSV endpoint — happy path, path-secret rejection, 404 for unknown signal id, 400 for malformed window.
- Integration skipped if the
bashtool is not available in the test fake — the real validation is the dry-run trace.
6. Dry-run validation.
Before the demo, run the scenario end-to-end once on the dev stack and confirm:
- The agent actually invokes
bashwith a Python heredoc — not just describes the analysis in prose. - The script pulls the CSV via
bash curl, runs the math, prints numerical output that lands in the RCA summary. - Container cold-start on first run is within the 2 s budget (coordinate with M5.5's
lifespanprewarm).
backend/
agents/investigator/
prompts.py # extend INVESTIGATOR_SYSTEM with diagnostics guidance
managed.py # declare numpy/pandas/scipy in env config (depends on M5.5)
modules/signal/router.py # new GET /signal/{id}/csv endpoint
tests/unit/agents/investigator/
test_prompts.py # NEW (or extended) - assert guidance present
tests/unit/modules/signal/
test_csv_router.py # NEW
.env.example # reuse ARIA_MCP_PATH_SECRET or add ARIA_SIGNAL_CSV_SECRET
- Investigator environment created with
numpy,pandas,scipypre-installed. Container boot completes within cold-start budget. -
INVESTIGATOR_SYSTEMcontains signal-diagnostics guidance plus at least two worked examples matching the demo cell's signature signals. -
GET /api/v1/signal/{id}/csv?start=&end=returns the window as CSV behind path-secret auth. Rejects unknown signal id (404) and malformed windows (400, 413). - Demo cell's
equipment_kbcarries the reference values the demo technique needs (bearing geometry for rotating kit, temperature band for thermal kit, etc.). - Dry-run: the agent writes a Python script in
bashand prints numerical output that lands in thesubmit_rcapayload. Verified by tracing the stream events for a manual trigger. -
make backend.testgreen.make backend.lintandmake backend.typecheckclean.
Approximately 4 hours. The agent writes the diagnostic code — we do not. Our work is plumbing: deps declaration, prompt examples, CSV endpoint, KB fixture, dry-run trace.
| Risk | Mitigation |
|---|---|
Agent describes the analysis in prose instead of running bash |
Two concrete worked examples in the system prompt. Dry-run before the demo. If still flaky, add an ask_sandbox custom tool that explicitly wraps bash python -c "..." to nudge the behavior |
| numpy / scipy install latency on first container boot | Declare deps in the environment config so they are pre-installed (per docs). Measure during M5.5 cold-start work; if still slow, lifespan prewarm includes one dummy session |
| Signal CSV endpoint leaks data if path secret leaks | Same threat model as /mcp in M5.5 — rotate the secret by remounting. Do not expose any signal the operator is not already authorized to see |
| Window size blows up container memory | Enforce a row cap (e.g. 1M rows, 413 Payload Too Large). Most diagnostic techniques care about a targeted window (breach window plus context), not the full history |
| Demo cell KB missing reference values | Add a one-line SQL fixture in the seed before demo day. Budget 15 min for this |
- None (differentiation feature).
- M5.5 (needs
agents.investigator.managed._ensure_agent_and_envas the place to declare environment deps).
flowchart LR
classDef critical fill:#fed7d7,stroke:#c53030
classDef nice fill:#c6f6d5,stroke:#2f855a
Start(["Day 1 morning"]) --> M55
M55["M5.5 base migration<br/>(critical - prize anchor)"]:::critical
M55 --> Checkpoint{"End-to-end green<br/>on both flag values?"}
Checkpoint -->|yes| M56
Checkpoint -->|no| Stop(["Halt - fix M5.5 first"])
M56["M5.6 continue investigation<br/>(memorable, low risk)"]:::nice
M56 --> M57
M57["M5.7 in-sandbox diagnostics<br/>(memorable, higher risk)"]:::nice
M57 --> Demo(["Demo dry-run + submission"])
M55 -.flag-off fallback.-> M45["Revert to M4.5 Messages API path<br/>(5 min env var flip)"]
Caution
If under time pressure, ship M5.5 and M5.7 — skip M5.6. The audit's judgment (§6.5 "If only one ships, ship B") is that the Python-in-container clip is the more memorable 30-second demo moment. M5.6 is useful and clean but less visually striking on stage.
Across all three issues, before submission:
- Demo script (
docs/demo/demo-script.mdor equivalent) updated with the three pitch lines from audit §6 and §6.5. - README architecture diagram shows Investigator on Managed Agents, Q&A on Messages API, hosted MCP as the tool-execution path.
-
make backend.test && make backend.lint && make backend.typecheckgreen. - Rollback drill executed at least once —
INVESTIGATOR_USE_MANAGED=False, restart, M4.5 still works. Document the drill outcome in the demo script so the on-stage operator knows the exact commands. -
session.usage.*measured during a full dry-run. Numbers logged in the planning folder alongside the cold-start latency figures.
- Source audit: docs/audits/M5-managed-agents-refactor-audit.md
- Prior M5 issues (M5.1-M5.4, for context — this slate supersedes M5.4): docs/planning/M5-workorder-qa/issues.md
- Managed Agents docs: platform.claude.com/docs/en/managed-agents/overview
- Prior MCP security audit (path-secret rationale): docs/audits/M2-mcp-server-audit.md
- Prior agent-chain audit (Investigator profile): docs/audits/M4-M5-sentinel-investigator-workorder-qa-audit.md