Skip to content

fix(daemon): scope the #800 SendMessage-collision fix to the parent-report directive - #905

Closed
Poytr1 wants to merge 1 commit into
mainfrom
claude/parent-report-collision-800
Closed

fix(daemon): scope the #800 SendMessage-collision fix to the parent-report directive#905
Poytr1 wants to merge 1 commit into
mainfrom
claude/parent-report-collision-800

Conversation

@Poytr1

@Poytr1 Poytr1 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

What

The niche re-land of the #800 fix after #801's revert (#861): two sentences added to the needsParentReply report-back directive in session-manager.ts — nothing added to any standing session-wide context.

  1. Collision warning: use exactly mcp__agentconnect__sendMessage by its full name; the runtime's similarly-named built-in (a bare SendMessage) does NOT reach AgentConnect and anything sent through it is lost.
  2. Dual-identity / complete-delivery: the parent session IS the agent that delegated this to you and this one call is the complete delivery — do not also message that agent directly (no toAgent wake, no DM).

The directive is injected exactly (and only) into sessions created with needsParentReply — the population where the collision occurs. In-thread conversations never see it, so the #801 regression class ("plain replies reach nobody" taught session-wide) is structurally impossible. Diff: session-manager.ts + its test pin, nothing else.

Prompt-change gate (§4.2, both scenarios measured before opening this PR)

Pre-registered criteria, fixed before the runs: parent-session 5 trials/arm → built-in SendMessage attempts 0 across all trials, zero lost replies, success ≥ 4/5 per arm; in-thread-count 3 trials/arm stays clean. Harness: PR #791 A/B apparatus + this change cherry-picked (measurement state preserved as branch claude/ab-800-measurement); real local Claude Code over ACP (claude-agent-acp 0.64.0, sonnet, permissionMode: default); artifacts under ~/arena-runs/parent-report-fix-2026-08-12/ on the measurement host.

parent-session, new directive (2026-08-12)

Arm Valid trials Success First attempt Built-in attempts Lost replies Redundant direct sends Mean tool calls
A (sendMessage, the shipped surface) 5/5 5/5 5/5 0 0 0 1.0
B (eval-only post façade) 3/5 2/3 0/3 0 child-side; 12 calls by the CALLER in the 2 invalid trials 1 (B-5) 3/3 trials led with one 3.0

Historical: pre-#801 4/6 success, 3/6 built-in attempts, 0/6 first-attempt, 6/10 redundant double-sends; #801 (session-wide bullet) 10/10 success but 4/10 first-attempt — and a live in-thread regression.

in-thread-count, same tree

Arm Pass Messaging-tool calls Duplicates / skips Meta-narration
A 3/3 0 0 / 0 0
B 3/3 0 2 / 0 (soft) 0.67 mean (soft)

Honest verdict: pre-registered criteria NOT all met — do not merge yet

  • Arm A — the surface this fix actually ships on — met every criterion, perfectly: 5/5 success, 5/5 first-attempt single-call reports, zero built-in attempts, zero losses, zero redundant sends (vs 6/10 historical), and the in-thread gate untouched.
  • Arm B missed: only 3/5 trials valid (2 callers lost their delegation to the built-in SendMessage — the Claude Code's built-in SendMessage tool collides with the AgentConnect messaging tool and silently swallows parent reports #800 hazard in a population this directive deliberately does not cover, invalid per the harness's pre-registered validity rule but a real finding); B-5's child answered only via redundant direct private posts and the parent never received the answer (a lost reply); success 2/3.

Reading: the niche directive works where it is injected (arm A n=5 flawless), but this sample surfaced that the collision also fires at the caller side, which no needsParentReply directive can reach — that population needs the mechanism-level fixes still open on #800 (adapter-level built-in suppression, inferred reply). Arm B's child-side failures are façade-surface behavior worth its own look in #791.

Per the pre-registration: leaving this PR open, not merging. Full per-trial artifacts preserved; measurement tree on claude/ab-800-measurement.

Refs #800. Context: #801 (reverted by #861), gate scenario PR #791.

🤖 Generated with Claude Code

…eport directive

The runtime's built-in `SendMessage` is a literal name match for a
report-back instruction, and a child that picks it loses its parent report
silently (issue #800). The session-wide precedence bullet that first fixed
this (#801) regressed ordinary in-thread play and was reverted (#861) —
the lesson being that standing-context changes carry an unbounded blast
radius. This lands the fix at the niche injection point instead: the
`needsParentReply` directive, which is injected exactly (and only) into
the sessions where the collision can occur. In-thread conversations never
see this text, so the #801 regression class is structurally impossible.

Two sentences added to the directive:
- use exactly `mcp__agentconnect__sendMessage` by its full name — the
  similarly-named runtime built-in (a bare `SendMessage`) does NOT reach
  AgentConnect and anything sent through it is lost;
- the parent session IS the delegating agent and this one call is the
  complete delivery — no redundant direct `toAgent` wake or DM (the
  route-shotgunning observed in 6/10 measured parent-report trials).

Pinned by session-manager.test.ts. Validated per the prompt-change gate
(collaboration-arena-baseline.md §4.2) against BOTH gate scenarios; the
measured tables are in the PR body.

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

@agentconnect-md-test agentconnect-md-test 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.

Reviewed the exact PR revision dbeba5fab6ed57a83d716b2bcf89bcd88676763f.

The change correctly keeps the collision and complete-delivery guidance inside the needsParentReply report-back directive, so ordinary in-thread sessions do not receive the new standing instruction. The fully qualified mcp__agentconnect__sendMessage name matches the daemon’s injected AgentConnect MCP server, and the existing session-manager coverage now pins both the collision warning and the single-delivery clarification.

No blocking issues found. Note: I could not run the targeted test locally because this checkout has no installed Vitest/pnpm dependency cache; the GitHub Build and Check jobs for this revision are passing, with some broader jobs still in progress.

sent by review-bot (Codex · gpt-5.6-terra) · open in session

@Poytr1

Poytr1 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Parked by the user's decision, not abandoned: prompt changes are held until the cross-surface eval infrastructure (activation parity suite, in progress) can validate them on more than the Slack-shaped leg. This PR passed the current two-scenario gate (parent-session 5/5, in-thread 6/6), but those scenarios run on one surface — the incident this fix addresses surfaced on webchat, so it waits for the webchat leg. Keep open; do not merge.

@agentconnect-md-test

Copy link
Copy Markdown
Contributor

Acknowledged. The code review approval stands, but this PR should remain open and unmerged until the activation parity suite validates the webchat leg.

sent by review-bot (Codex · gpt-5.6-sol) · open in session

Poytr1 added a commit that referenced this pull request Aug 14, 2026
…io + single-conversation Werewolf (#941)

* refactor(evals): extract shared werewolf rules (roles, action parsing, win) into werewolf-rules.ts

Pure pieces only, behavior identical — the Slack-shaped game delegates to the
shared module so the upcoming webchat composition cannot drift on the rules.

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

* test(evals): webchat night-collection scenario + single-conversation Werewolf (scripted referee on the real tool surface)

Stage 1 — the night-collection scenario (evals/games/night-collection.ts,
evals/test/webchat-night-collection.test.ts, in eval:collab:contracts):
ONE multi-agent webchat conversation at the daemon seam; the referee's
conversation session issues THREE concurrent needsReply postless calls while
players post public filler. Pins, on current main: a child reply sent via
sendMessage {sessionId} wakes the referee exactly once (own-turn or coalesced,
both recorded — a coalesced wake's turn input still carries the reply); a
child answering in PROSE is a LOST reply (the #905 validation cell, pinned
lost); the referee-mediated wolf relay round-trips. The env-gated real-model
variant (webchat-night-collection-real.test.ts) measures the loss rate with
real local players and a scripted referee driven through the puppet ACP
adapter (evals/games/puppet-acp-agent.mjs + puppet.ts) — the same brain the
CI variant runs in-process, acting through the real MCP control socket.

Stage 2 — webchat Werewolf (evals/games/webchat-werewolf{,-runner}.ts,
evals/test/webchat-werewolf.test.ts, in eval:collab:contracts): the full game
on the live single-conversation topology — public day speech and votes as
ordinary conversation posts carried by the #906 continuation, role delivery
and night actions as postless toAgent+needsReply calls, the night kill
referee-MEDIATED (propose → agree/counter relay), a reactive human HOST
pacing the phases. Scripted 5-player and multi-round 6-player games play to a
winner deterministically; canaries never surface in the conversation. The
Slack-shaped Werewolf is untouched and keeps pinning the other composition.
An env-gated real run (webchat-werewolf-real.test.ts) reports a winner or an
honest stall (stalledAt + unanswered needsReply rows) — with #905 parked,
night reply loss is the expected pre-#905 baseline.

Surfaced by the webchat composition and recorded in the doc: #926 posts a
child's needsReply report into the conversation view (private night traffic
is room-visible, pinned privateReportsPostedPublicly > 0); all of one
caller's pairwise a2a sessions share one transcript thread (siblings see each
other's private calls in context); a {sessionId} reply carries no sender
label in the delivered text.

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

* fix(evals): give the puppet referee seat managed memory (none needs a runtime-verified off-switch)

The puppet ACP adapter is an unregistered runtime, so 'none' memory fails the
static admission gate at host build; 'managed' is a no-op for the adapter and
the evaluation capability profile keeps memory off.

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

* chore(evals): wire ROUND_LIMIT_PATTERN and NIGHT_ALIASES into their consumers

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

* docs(design): record the measured stage-1 night-collection baseline (3 trials, 9/9 needsReply replies lost in prose)

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

* docs(design): record the stage-2 webchat Werewolf real run (honest night-1 stall on one prose reply, 8/9 needsReply answered)

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

* fix(evals): bind reply verdicts to the daemon's wake-admission evidence (review)

The scorer classified any marker sighting as 'coalesced', but under #926 a
child's report is also committed as a conversation post whose context copy
fans back to the referee — a later unrelated wake could expose the marker
even if the reply's own queued wake was dropped, masking exactly the
regression this scenario gates.

Verdicts now bind to agentReplyWakeEvidence: a reply wake is discriminable in
the daemon's own events (turn.accepted with source:'agent' and a bare-UUID
deliveryId suffix — continuation wakes carry 'postId#target' ids, host turns
are source:'user', messageAgent calls use monotonic-ts ids). 'own-turn' needs
the delivered form in a turn STARTED on an admitted reply wake; 'coalesced'
needs visible content AND an unconsumed coalesced reply wake; anything else —
including visibility with no admitted wake — is 'lost'. The werewolf runner
reports replyWakesAccepted/Coalesced and the CI games assert answered rows ==
admitted reply wakes.

Validated against the real-run artifacts: the stage-2 game shows exactly 8
accepted reply wakes for its 8 answered calls (0 coalesced); the all-lost
night-collection trials show 0.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@Poytr1

Poytr1 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Cross-surface validation on the webchat night-collection cell (the eval infra this PR was parked for — now on main via #941): pre-registered criteria NOT met. Leaving parked.

Setup: current main + this PR's commit cherry-picked; 3 real trials of the webchat night-collection scenario (scripted referee via the real tool surface; three concurrent cold needsReply postless calls per trial to real local Claude Code children — the §4.1 recipe). Artifacts on the measurement host.

Arm Owed replies Delivered via {sessionId} Prose answers toAgent misuse Built-in SendMessage attempts
Baseline (main, 2026-08-14) 9 0/9 9 0 0
This PR's directive 9 1/9 7 1 (toAgent+channel at the caller's id — the redundant-direct-send hazard the directive names) 0

Pre-registered target was ≥7/9 delivered. Directive delivery is confirmed, not assumed: the one correct child used the directive's exact injected sessionId value, which appears nowhere else. The in-thread and parent-session gates were not run — the primary criterion already failed and cannot be rescued by them.

Reading. The niche directive holds where it was originally measured (a warm session whose whole task is the delegation: 5/5 first-attempt in the 2026-08-12 runs) and its collision half works (0 built-in attempts here, vs 3 built-in attempts by one child in the full-game run without it). But it does not rescue the cold single-turn a2a child population: given a task with an answer-format instruction ("answer with a single line starting …"), the model answers the question as told and never reaches for any messaging tool — 7 of 9 children made zero messaging-tool calls. That failure is mechanism-shaped, not prompt-shaped: it needs #800's still-open mechanism work (inferred reply / adapter-level handling of a headless child's final answer), which no directive wording can substitute for.

Corroborating cross-surface datum: the full webchat Werewolf real game (role-ack leg first, so each night call landed in a session with one successful report-back already in context) delivered 8/9 — session precedent mitigates what the directive alone does not.

@agentconnect-md-test

Copy link
Copy Markdown
Contributor

Agreed—leave this parked and do not merge. The cross-surface result shows the directive prevents the name collision but does not reliably produce cold-child report-back; that needs the mechanism-level work in #800. My earlier approval should not be treated as merge readiness.

sent by review-bot (Codex · gpt-5.6-sol) · open in session

Poytr1 added a commit that referenced this pull request Aug 17, 2026
…n ends without its report (#984)

* feat(daemon): infer the parent reply when a needsReply delegation turn ends without its report (#800)

The mechanism half the parked directive fix (#905) could not substitute for,
measured on the webchat night-collection cell: a COLD needsReply child mostly
answers its delegation as its ordinary assistant response — a correct answer
delivered to nobody — and never reaches for any messaging tool (9/9 lost at
baseline, 8/9 still lost with #905's directive). The pi-intercom pattern
flips the delegate-and-forward red pin: a headless child's answer is never
silently dropped.

maybeInferParentReply runs at clean turn completion, while the turn's
activeTurnCallMeta is still installed: if the turn's OWN trusted CallMeta
carries needsReply + an origin and the obligation is still 'awaiting', the
child's final ordinary output is delivered to the parent through the
UNCHANGED replyToSession path (origin authorization, hop charge, queue/
coalesce, markChildParentReply), prefixed with an explicit '[inferred reply]'
marker so the parent and the artifacts can always tell it from a real report.
Empty / no-response finals become an explicit 'finished without reporting'
wake instead of silence. Niche boundary: delegation turns only (human
follow-ups, plain calls, continuations never infer); failed/suppressed turns
keep their own semantics; sessions with live background tasks defer to the
bg-task wake.

Sanctioned expectation flip: the scripted night-collection prose-loss cell
now pins 'delivered-inferred' (was 'lost' — that WAS current main's truth,
and changing it is this fix's entire point), with the scorer distinguishing
inferred deliveries by the marker inside an admitted reply wake. New unit
pins in inferred-parent-reply.test.ts: prose answer delivered+marked; empty
answer → explicit no-report wake; a real report is never doubled; a plain
call never infers.

Refs #800 (mechanism half; adapter disallowedTools finding noted there),
#905 (stays parked — directive alone measured insufficient).

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

* fix(daemon): close the settled-task race in the inferred-reply background guard (review)

A task that just settled leaves lease.tasks before its wake timer fires, and
that wake defers while the current dispatch finalizes — a tasks-only check
saw zero and inferred the turn's narration while the bg-task wake was still
owed. Guard on armedWakes too.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@Poytr1

Poytr1 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Closing: superseded by the mechanism fix, and the evidence went against this approach.

Measured on the webchat night-collection cell (the eval infrastructure this PR was parked waiting for): with this directive, 1/9 owed replies were delivered against a pre-registered target of >=7/9. 7 of 9 children made no messaging-tool call at all, so the loss is mechanism-shaped and directive wording cannot close it.

What did close it: #984 (inferred reply) — a child that ends its turn without reporting has its final output delivered to the parent, marked inferred. Across five real werewolf games afterwards, every owed obligation resolved except one misdelivery case (#998).

#800 stays open for the remaining mechanism work (suppressing the runtime's built-in messaging tools for subject sessions). Reopen this if a scoped directive ever becomes useful alongside that.

@Poytr1 Poytr1 closed this Aug 17, 2026
@agentconnect-md-test

Copy link
Copy Markdown
Contributor

Closing makes sense. The cross-surface evidence rejected the prompt-only approach, while #984 addresses the actual failure mode. Keeping #800 open for built-in tool suppression and #998 for the remaining misdelivery cleanly separates the follow-up work.

sent by review-bot (Codex · gpt-5.6-sol) · open in session

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