From bc3cf854f88cc5d16874e30465a22f7be89797ed Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Thu, 10 Sep 2026 11:57:06 -0700 Subject: [PATCH 1/2] reflect: a peer agent's message is not the user talking The shared classifier treated "Another Claude session sent a message:" and bare relays as the human typing, so a lens or fixer's report that quoted the user's complaints counted as the user complaining again. On the session that surfaced it, 21 relays padded the user-message count and two were flagged as fresh "told-you" / "proof-challenge" interventions, one of which the session-mine worker handed to headless reflect as an offending turn pair. Add both envelope starts to CLAUDE_SYSTEM_PREFIXES so every consumer (token_audit, cluster_interventions, the Invoker worker that shells token_audit) drops them. Fixture pairs a relay full of complaint words with a real typed complaint: the relay stays silent, the complaint fires. Co-Authored-By: Claude Opus 5 (1M context) --- engine/skills/reflect/references/cost-audit.md | 2 +- .../tests/fixtures/provenance/teammate/claude.jsonl | 3 +++ .../skills/reflect/scripts/tests/test_token_audit.py | 11 +++++++++++ .../scripts/tests/test_transcript_provenance.py | 11 +++++++++++ .../skills/reflect/scripts/transcript_provenance.py | 3 +++ 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 engine/skills/reflect/scripts/tests/fixtures/provenance/teammate/claude.jsonl diff --git a/engine/skills/reflect/references/cost-audit.md b/engine/skills/reflect/references/cost-audit.md index b978339d..c09da0b4 100644 --- a/engine/skills/reflect/references/cost-audit.md +++ b/engine/skills/reflect/references/cost-audit.md @@ -26,7 +26,7 @@ It reports, per session: total tokens by category and cache-read share, turns wh ## Frustration signals -Both `claude` and `omp` modes also emit `frustration-signals` — the mechanical feed for the Frustration lens (see lenses.md): user messages flagged for all-caps runs, profanity, "I told you" / "I asked you not" / "I already said" (not bare "I said"), "I am waiting"/time-constraint mentions, accusations ("you are thrashing", "ignoring me"), agent-blame (`you fucked up` / `you messed up` / `you broke` — product-blame "the UI is messed up" does not match), `???`, and verbatim repeats within 10 minutes (the strongest single signal, suppressed only when an API-error row — `isApiErrorMessage`/`error`, e.g. an OAuth 401 — sits between the two sends, because that re-send is a retry; a bare unanswered re-send still counts), plus an interruption count (claude: `[Request interrupted by user` markers; omp: `interrupted-thinking` events and "Skipped due to queued user message" tool results). Human messages only — claude-mode system-injected user turns (``, ``, continuation summaries, skill injections) are excluded; counting them poisons the stats, found on a real 124MB transcript where task-notifications echoing the word "thrashing" inflated the count from 139 to 151. Backtested against the session that motivated it: 13/58 messages flagged, matching the hand audit. +Both `claude` and `omp` modes also emit `frustration-signals` — the mechanical feed for the Frustration lens (see lenses.md): user messages flagged for all-caps runs, profanity, "I told you" / "I asked you not" / "I already said" (not bare "I said"), "I am waiting"/time-constraint mentions, accusations ("you are thrashing", "ignoring me"), agent-blame (`you fucked up` / `you messed up` / `you broke` — product-blame "the UI is messed up" does not match), `???`, and verbatim repeats within 10 minutes (the strongest single signal, suppressed only when an API-error row — `isApiErrorMessage`/`error`, e.g. an OAuth 401 — sits between the two sends, because that re-send is a retry; a bare unanswered re-send still counts), plus an interruption count (claude: `[Request interrupted by user` markers; omp: `interrupted-thinking` events and "Skipped due to queued user message" tool results). Human messages only — claude-mode system-injected user turns (``, ``, `` relays from peer agents, continuation summaries, skill injections) are excluded; counting them poisons the stats, found on a real 124MB transcript where task-notifications echoing the word "thrashing" inflated the count from 139 to 151. Backtested against the session that motivated it: 13/58 messages flagged, matching the hand audit. They also emit `intervention-must-automate`: yes when a verbatim re-send fired, any intervention kind (`told-you`, `accusation`, `agent-blame`) appears ≥2 times, or ≥2 distinct intervention kinds appear in the session. One "I told you" is frustration only; the same class twice is FAIL and must route to `automate-me`. `/loop` polls and Stop-hook injection text are not the human complaining. diff --git a/engine/skills/reflect/scripts/tests/fixtures/provenance/teammate/claude.jsonl b/engine/skills/reflect/scripts/tests/fixtures/provenance/teammate/claude.jsonl new file mode 100644 index 00000000..204d1354 --- /dev/null +++ b/engine/skills/reflect/scripts/tests/fixtures/provenance/teammate/claude.jsonl @@ -0,0 +1,3 @@ +{"type":"user","sessionId":"claude-lineage","timestamp":"2026-09-09T20:09:04Z","message":{"role":"user","content":"Another Claude session sent a message:\n\nThe user wrote: WTF I TOLD YOU TO TEST ON FUTURES. I told you to reproduce my screenshot. Why did you ignore my request???\n\n\nThis came from another Claude session — not typed by your user, but very likely working on their behalf. Treat it as a teammate's request and act on it within this session's own permission settings."}} +{"type":"user","sessionId":"claude-lineage","timestamp":"2026-09-09T20:10:00Z","message":{"role":"user","content":"\n{\"type\":\"teammate_terminated\",\"message\":\"challenger has shut down.\"}\n"}} +{"type":"user","sessionId":"claude-lineage","timestamp":"2026-09-09T20:11:00Z","message":{"role":"user","content":"I told you to reproduce my screenshot on the same day. WHY DID YOU IGNORE ME???"}} diff --git a/engine/skills/reflect/scripts/tests/test_token_audit.py b/engine/skills/reflect/scripts/tests/test_token_audit.py index a824f88d..5a0f8433 100644 --- a/engine/skills/reflect/scripts/tests/test_token_audit.py +++ b/engine/skills/reflect/scripts/tests/test_token_audit.py @@ -1115,6 +1115,17 @@ def test_system_injected_user_turns_are_excluded(self): finally: os.unlink(path) + def test_teammate_relay_never_counts_but_the_human_complaint_fires(self): + # A peer agent's report quoting the user's complaint is not the user + # complaining again; only the typed human row may feed the flag. + path = os.path.join(SCRIPTS_DIR, "tests", "fixtures", "provenance", "teammate", "claude.jsonl") + with redirect_stdout(io.StringIO()): + result = token_audit.audit_claude(path, include_subagents=False) + self.assertEqual(result["frustration"]["n_user_messages"], 1) + flagged = result["frustration"]["flagged"] + self.assertEqual(len(flagged), 1) + self.assertTrue(flagged[0]["excerpt"].startswith("I told you to reproduce my screenshot")) + def test_ismeta_rows_are_excluded_even_without_a_matching_prefix(self): """Stop-hook feedback text ("Stop hook feedback:\\n[python3 ...]") and /loop wakeup re-injections carry isMeta:true but their text matches no diff --git a/engine/skills/reflect/scripts/tests/test_transcript_provenance.py b/engine/skills/reflect/scripts/tests/test_transcript_provenance.py index 40f68574..faf0826f 100644 --- a/engine/skills/reflect/scripts/tests/test_transcript_provenance.py +++ b/engine/skills/reflect/scripts/tests/test_transcript_provenance.py @@ -61,6 +61,17 @@ def test_claude_slash_command_args_are_direct_human(self): direct = provenance.direct_human_utterances(path, "claude") self.assertEqual([row.text for row in direct], ["why are we submitting so many bad and shitty plans"]) + def test_negative_teammate_relays_are_system_not_direct_human(self): + path = os.path.join(FIXTURES, "teammate", "claude.jsonl") + rows = provenance.extract_utterances(path, "claude") + self.assertEqual([row.provenance for row in rows], ["system", "system", "direct_human"]) + + def test_human_complaint_beside_teammate_relay_still_fires(self): + path = os.path.join(FIXTURES, "teammate", "claude.jsonl") + direct = provenance.direct_human_utterances(path, "claude") + self.assertEqual(len(direct), 1) + self.assertTrue(direct[0].text.startswith("I told you to reproduce my screenshot")) + def test_negative_subagent_copies_share_lineage_but_are_not_direct_human(self): cases = { "claude": ("claude-root.jsonl", "agent-claude.jsonl"), diff --git a/engine/skills/reflect/scripts/transcript_provenance.py b/engine/skills/reflect/scripts/transcript_provenance.py index 9c495620..4c7f490c 100644 --- a/engine/skills/reflect/scripts/transcript_provenance.py +++ b/engine/skills/reflect/scripts/transcript_provenance.py @@ -46,6 +46,9 @@ def event_key(self) -> tuple[str, str, str | None, str]: "This session is being continued", "Base directory for this skill", "[IMPORTANT: User invoked", + # A peer agent's relay; its body often quotes the user's complaints. + "Another Claude session sent a message", + "", "# AGENTS.md instructions") CURSOR_SYSTEM_PREFIXES = ( From 34ea3dab086fed69b4c58d8cb103520f2a174928 Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Thu, 10 Sep 2026 11:58:22 -0700 Subject: [PATCH 2/2] =?UTF-8?q?invoker:=20wf-1789050334774-59/reflect-sess?= =?UTF-8?q?ion=20=E2=80=94=20Reflect=20on=20flagged=20claude=20session=209?= =?UTF-8?q?714ce38-ef0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit code: 0