Skip to content

fix: subagent hardening batch — eviction order, args-buffer scoping, field-based lookup, AIMOCK_MODE warning - #960

Merged
blove merged 5 commits into
mainfrom
blove/subagent-hardening
Sep 2, 2026
Merged

fix: subagent hardening batch — eviction order, args-buffer scoping, field-based lookup, AIMOCK_MODE warning#960
blove merged 5 commits into
mainfrom
blove/subagent-hardening

Conversation

@blove

@blove blove commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Four small review-specified hardening fixes, one commit each (TDD: failing test → fix → green).

1. Strands emitter evicts finished sessions before in-flight ones

cockpit/runtimes/aws-strands/python/src/subagent_emitter.py — with more than _MAX_SESSIONS (512) concurrent delegations, insertion-order eviction could drop an UNFINISHED session, whose next event then re-emitted SUBAGENT_STARTED. Eviction now prefers the oldest finished session, falls back to the oldest in-flight one only when none is finished (the cap still bounds memory), and never evicts the session being registered. The generated copy under deployments/ag-ui-dev is regenerated in a separate commit.

Tests: uv run pytest -q → 16 passed (2 new: preference test + no-finished fallback; the preference test failed before the fix).

2. AG-UI adapter: subagent args buffers keyed by run id, cleared on RUN_STARTED

libs/ag-ui/src/lib/reducer.ts — the TOOL_CALL_ARGS accumulation buffer for subagent events was keyed by toolCallId alone, so two children reusing an id interleaved fragments into one buffer and neither parsed. Now keyed subagent:<subagentRunId>:<toolCallId>. RUN_STARTED also clears all args buffers so a dangling fragment from an aborted run (no TOOL_CALL_END) cannot prefix a same-id call in the next run.

Tests: npx vitest run → 243 passed (2 new, both failed before the fix).

3. LangGraph getSubagentsByMessage resolves through Subagent.toolCallId

libs/langgraph/src/lib/agent.fn.ts — the lookup used the subagents map KEY; the neutral contract anchors on the toolCallId field (same rule <chat-tool-calls> applies since #956). Lookup extracted into resolveSubagentsByMessage() (test-only export, same precedent as computeMessageCheckpoints) which re-indexes by the field. Every LangGraph path keys by the id today, so the mismatch cannot be produced through the transport; the unit test drives the helper with a map keyed call_x-sub / field call_x and returned [] before the change.

Tests: npx vitest run → 359 passed (1 new; mutation check confirmed).

4. Warn on unrecognized AIMOCK_MODE

libs/e2e-harness/src/aimock-mode.ts — only exact 'record' flipped record mode; a typo like Record silently replayed. Now warns once ([aimock-harness] unrecognized AIMOCK_MODE="Record" — falling back to replay) then replays. replay and unset do not warn.

Tests: npx vitest run → 29 passed (2 new).

Lint: nx lint ag-ui | langgraph | e2e-harness — 0 errors.

🤖 Generated with Claude Code

blove and others added 5 commits September 2, 2026 11:08
…ght ones

With more than _MAX_SESSIONS concurrent delegations the insertion-order
eviction could drop an UNFINISHED session, whose next event then re-emitted
SUBAGENT_STARTED. Eviction now prefers the oldest finished session and only
falls back to the oldest in-flight one when none is finished (so the cap
still bounds memory), never the session being registered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n RUN_STARTED

The subagent TOOL_CALL_ARGS accumulation buffer was keyed by toolCallId
alone, so two children reusing an id interleaved fragments into one buffer
and neither ever parsed. Key it by subagentRunId + toolCallId. RUN_STARTED
now also clears every args buffer, so a dangling fragment from an aborted
run (TOOL_CALL_END never arrived) cannot prefix a same-id call next run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…olCallId field

The lookup used the subagents map KEY, but the neutral contract anchors a
subagent on the `toolCallId` field (the key is an adapter detail — AG-UI
keys activities by `<toolCallId>-sub`). Extract the lookup into
resolveSubagentsByMessage(), which re-indexes the map by the field before
resolving the message's tool-call ids, matching <chat-tool-calls>.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Only the exact string 'record' flips record mode; anything else (a typo
like `Record` or `recording`) silently replayed against stale fixtures.
Warn once with the offending value before falling back to replay.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
threadplane Ready Ready Preview Sep 2, 2026 6:15pm UTC

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove enabled auto-merge (squash) September 2, 2026 18:17
@blove
blove merged commit 029d5a9 into main Sep 2, 2026
76 checks passed
@blove
blove deleted the blove/subagent-hardening branch September 2, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant