Read-only demoLive view of gallirohik/research-canvas at 1ebb712, scanned 20 September 2026 — nothing here can be edited.
Join the waitlistTwo unguarded index sites per agent:
agents/python/src/lib/search.py) — search.py:66 (incoming Search call) and search.py:130 (forced-extraction response at search.py:109). The second site is the real live risk; the first is defense-in-depth against a routing/caller change (chat.py:147-148).agents/typescript/src/search.ts) — search.ts:49 and search.ts:129, using the runtime-unsafe non-null assertion tool_calls![0]. In scope per rule agent-typescript-parity.Both agents are the same 5-node StateGraph (rule langgraph-agent-convention).
Both child tasks pass their Done-checks; an empty/missing tool_calls at any of the four index sites degrades gracefully (logged + early return) instead of aborting the graph run, and the normal-path flow is byte-for-byte unchanged.
status: done. search-guard-py needed one correction round — prism's first pass FAILED because guard 2's early return skipped the trailing ToolMessage resolving the original Search tool_call (dangling tool_call would 400 the next chat_node re-invoke); fixed and re-validated PASS. search-guard-ts applied that lesson from the start (guard 2 resolved the tool_call id in the first cut) and passed prism first try. All four index sites now degrade gracefully; happy path unchanged; active pointer cleared.Guard the two tool_calls[0] index sites in the Python search_node
ai_message/ai_message_response with tool_calls == [] or None no longer raises IndexError; a warning is appended to state["logs"] and the node returns current state.:66, :130) guarded; happy path unchanged.tool_calls[0] index sites in agents/python/src/lib/search.py, mirroring chat.py's defensive idiom.ToolMessage(tool_call_id=ai_message.tool_calls[0]["id"], ...) before the early return.Guard the two tool_calls![0] index sites in the TypeScript search_node (parity)
aiMessage/aiMessageResponse with empty/undefined tool_calls no longer throws TypeError; a warning is pushed to logs and the node returns a valid { messages, resources, logs } state object.:49, :129) guarded; behavior matches the Python child node-for-node (agent-typescript-parity).tool_calls![0] index sites in agents/typescript/src/search.ts. Applied the CRITICAL LESSON from the sibling search-guard-py prism FAIL from the start — guard 2's early return appends a ToolMessage({ tool_call_id: aiMessage.tool_calls![0]["id"]! }) so the message history stays valid for the next chat_node re-invoke. TS returns partial state (MessagesState reducer appends), so guards return only NEW messages.prism · search-guard-py · 7/23/2026, 11:43:20 AM
The Python agent is a 6-node StateGraph (download→chat→search/delete/fact-check loop); tools are empty @tool schemas the model CALLS but the node handles; the concrete LLM is chosen at runtime by get_model from state.model. The TS agent still mirrors the older 5-node shape (fact_check_node not yet ported, verifiable-report plan decision)
agents/typescript mirrors an OLDER Python graph with @langchain/langgraph — 5 nodes, no fact_check_node, no citations state; it is a lightly-used alternate that npm run dev does NOT start, so treat Python as the source of truth and expect the port to lag