You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Full agentic loop using MCPClient + extended thinking (M4.5) + agent-as-tool handoffs (M4.6)
Note
Depends on M3.1 (#17) for anthropic_client.py, M3.5 (#21) for answer_kb_question, M4.1 (#23) for ws_manager, and M4.2 (#24)
for Sentinel calling this function.
1. Initial context load
asyncdefrun_investigator(work_order_id: int) ->None:
# Load work order contextwo=awaitmcp_client.call_tool("get_work_order", {"work_order_id": work_order_id})
# Load failure history for memory flex (M4.7)past=awaitmcp_client.call_tool("get_failure_history", {
"cell_id": wo_data["cell_id"], "limit": 5
})
2. Tool registry
tools_schema= (
awaitmcp_client.get_tools_schema()
+INVESTIGATOR_RENDER_TOOLS# from agents/ui_tools.py+ [SUBMIT_RCA_TOOL, ASK_KB_BUILDER_TOOL] # local tools (M4.6)
)
INVESTIGATOR_RENDER_TOOLS is already defined in backend/agents/ui_tools.py (M2.9).
3. Handle is_error from tool results
Important
get_signal_anomalies raises ValueError when KB is misconfigured — the MCPClient
wraps this as ToolCallResult(is_error=True). If the Investigator does not check is_error, the exception propagates and crashes the investigation Task.
Return is_error=True tool results to the LLM so it can self-correct:
INVESTIGATOR_SYSTEM="""You are an industrial maintenance expert agent.An anomaly has been detected on equipment. Investigate freely using the available tools.You decide what to consult and in what order.When you have enough evidence, call submit_rca with:- root_cause: single-sentence conclusion- confidence: 0.0-1.0- contributing_factors: ordered list (most to least significant)- similar_past_failure: reference a past failure if the pattern matches (or null)- recommended_action: what the operator should do nextPast failures context: {past_failures}"""
7. Acceptance
Investigator runs to completion and calls submit_rca — test_happy_path_submit_rca_persists_and_broadcasts (live P-02 run deferred to M4.4 — Lifespan integration #26 lifespan + simulator)
work_order.rca_summary is populated after investigation — same test, via mocked WorkOrderRepository.update
rca_ready WS event is broadcast — assertions on payload shape {work_order_id, rca_summary, confidence, turn_id}
run_work_order_generator is spawned after RCA submission — test_work_order_generator_lazy_import_logs_when_missing (stub path until M5.1 — Work Order Generator agent #30 lands; lazy-import will route automatically then)
is_error=True tool results are forwarded to the LLM (not raised as exceptions) — test_is_error_tool_result_forwarded_not_raised
get_signal_anomaliesis_error response logs and does not crash the Task — same test above + outer try/except fallback covered by test_crash_fallback_flips_status_and_broadcasts_rca_ready
tool_call_started / tool_call_completed events broadcast for each tool call — test_tool_call_events_carry_expected_fields (both payloads checked; duration_ms int mandatory)
(added) Timeout + outer try/except fallbacks — test_timeout_fallback_* and test_crash_fallback_*
(added) failure_history row inserted on submit_rca (audit) — assertions on KbRepository.create_failure fields
Note
Milestone: M4 — Sentinel + Investigator
Planning doc:
docs/planning/M4-sentinel-investigator/issues.mdScope.
backend/agents/investigator.py:async def run_investigator(work_order_id: int) -> NoneNote
Depends on M3.1 (#17) for
anthropic_client.py, M3.5 (#21) foranswer_kb_question, M4.1 (#23) forws_manager, and M4.2 (#24)for Sentinel calling this function.
1. Initial context load
2. Tool registry
INVESTIGATOR_RENDER_TOOLSis already defined inbackend/agents/ui_tools.py(M2.9).3. Handle
is_errorfrom tool resultsImportant
get_signal_anomaliesraisesValueErrorwhen KB is misconfigured — the MCPClientwraps this as
ToolCallResult(is_error=True). If the Investigator does not checkis_error, the exception propagates and crashes the investigation Task.Return
is_error=Truetool results to the LLM so it can self-correct:4.
SUBMIT_RCA_TOOL— local tool definitionWhen the LLM calls
submit_rca:work_orderwithrca_summary,confidence,status="investigated"ws_manager.broadcast("rca_ready", {work_order_id, rca_summary, confidence, turn_id})asyncio.create_task(run_work_order_generator(work_order_id))5.
ASK_KB_BUILDER_TOOL— local tool definition (M4.6)Handler (see also M4.6 #28):
6. System prompt
7. Acceptance
submit_rca—test_happy_path_submit_rca_persists_and_broadcasts(live P-02 run deferred to M4.4 — Lifespan integration #26 lifespan + simulator)work_order.rca_summaryis populated after investigation — same test, via mockedWorkOrderRepository.updaterca_readyWS event is broadcast — assertions on payload shape{work_order_id, rca_summary, confidence, turn_id}run_work_order_generatoris spawned after RCA submission —test_work_order_generator_lazy_import_logs_when_missing(stub path until M5.1 — Work Order Generator agent #30 lands; lazy-import will route automatically then)is_error=Truetool results are forwarded to the LLM (not raised as exceptions) —test_is_error_tool_result_forwarded_not_raisedget_signal_anomaliesis_errorresponse logs and does not crash the Task — same test above + outertry/exceptfallback covered bytest_crash_fallback_flips_status_and_broadcasts_rca_readytool_call_started/tool_call_completedevents broadcast for each tool call —test_tool_call_events_carry_expected_fields(both payloads checked;duration_msint mandatory)test_timeout_fallback_*andtest_crash_fallback_*failure_historyrow inserted onsubmit_rca(audit) — assertions onKbRepository.create_failurefieldsask_kb_builderdynamic handoff withagent_handoff+ childagent_start/agent_end—test_ask_kb_builder_broadcasts_handoff_and_returns_answerget_work_order(id)MCP tool shipped alongside — inaria_mcp/tools/context.pyAgent loop
sequenceDiagram autonumber participant Sent as Sentinel participant Inv as run_investigator participant Opus as Claude Opus (extended thinking) participant MCP as MCPClient participant KB as answer_kb_question participant WSMgr as WSManager Sent->>Inv: asyncio.create_task(run_investigator(wo_id)) Inv->>MCP: get_work_order, get_failure_history Inv->>WSMgr: broadcast agent_start loop until submit_rca or max_turns Inv->>Opus: messages.create(stream=True, thinking=enabled) Opus-->>Inv: thinking_delta chunks Inv->>WSMgr: broadcast thinking_delta (each chunk) Opus-->>Inv: tool_use blocks loop per tool_use alt render_* tool Inv->>WSMgr: broadcast ui_render else ask_kb_builder Inv->>WSMgr: broadcast agent_handoff Inv->>KB: answer_kb_question(cell_id, question) KB-->>Inv: {answer, source, confidence} else MCP tool Inv->>MCP: call_tool(name, args) MCP-->>Inv: ToolCallResult (check is_error) end Inv->>WSMgr: broadcast tool_call_completed end alt submit_rca called Inv->>MCP: update work_order (rca_summary) Inv->>WSMgr: broadcast rca_ready Inv->>Inv: spawn run_work_order_generator end end Inv->>WSMgr: broadcast agent_end