Skip to content

Add output projection: result shapes and recall(n) (0.6.0) - #11

Merged
gabe4coding merged 2 commits into
mainfrom
claude/output-projection-design-48f330
Oct 10, 2026
Merged

gabe4coding merged 2 commits into
mainfrom
claude/output-projection-design-48f330

Conversation

@gabe4coding

@gabe4coding gabe4coding commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Problem

A run_code program is the filter: the model writes it for the task, so it keeps exact values. But the model writes the filter before it sees the data. When the filter misses (a wrong field, a missed page, a cut result), the model cannot see what it removed, and it calls the MCP tool again to fix the filter.

What this PR does

  • After each run, the footer has one line for each MCP call, with a number, the size and the shape:
    --- 2 MCP calls: 184k characters in, 2k out ---
    #4 mcp__slack__search 181k {messages: [312 × {ts, text, user, …+5}], next_cursor}
    #5 mcp__slack__user failed
    await recall(n) returns result #n again, with no new call.
    
  • await recall(n) in the sandbox returns a stored result. It makes no MCP call and no permission check.
  • The footer also says:
    • when a call is the same as an earlier call;
    • when the program returned nothing, but its calls returned data;
    • how old a recalled result is;
    • the recall number of a cut result.
  • The option projection is on by default. With projection: false, the footer is the old one.
  • The option metrics (off by default) writes sizes and counts for each run. scripts/projection-report.mjs compares runs with projection on and off.
  • bench/ contains a fake MCP server, its data with traps, and a runner that runs six tasks in headless Claude Code with projection off and on.

Safety

  • Permissions: recall returns only data that an approved call already got in the same session. Deny rules still apply to every new call. recall never runs a tool again, so it cannot repeat a write.
  • Session isolation: results stay in their own session. If the plugin cannot read the session id, it keeps results only until the run ends, so they cannot reach another session.
  • Data in the context: the shape shows only field names. If the keys are data (emails, ids, ticket keys, or maps such as counts by service), it shows only {N keys}.
  • Memory: at most 50 results and 8 million characters for each session, stored as JSON text. I measured 38 MB for 32 million characters stored as objects, so the limit and the text form keep it at about 10 MB per session.

Evidence: the bench

The bench ran 6 tasks, 5 times each, in each arm, against the fake server. The runs had no user settings, no other plugins and no other MCP servers, and direct MCP calls were blocked. In total: 120 runs, $7.07.

Opus 5.5 off Opus 5.5 on Haiku 5.5 off Haiku 5.5 on
Correct answers 30/30 30/30 30/30 30/30
MCP calls per task 3.37 2.10 3.90 3.30
Duplicate MCP calls per task 1.37 0.10 1.90 1.47
Runs with at least one duplicate 21/30 3/30 19/30 16/30
Runs that used recall – 19/30 – 4/30
Turns per task 5.00 5.03 5.10 4.90
Cost per task $0.113 $0.113 $0.005 $0.005
Seconds per task 15.4 16.0 11.7 11.4

What the bench shows:

  • Opus: duplicate calls go down by 93% and MCP calls by 38%. Cost, turns and time do not change.
  • Haiku: the effect is small. Haiku mostly ignores recall and calls the tools again.
  • No change in correct answers: both models found every trap in both arms, because they first run a program that looks at the data.
  • Not tested: no program failed after it created a ticket, so the duplicate-write trap never triggered.
  • Scope: the bench blocked direct MCP calls (blockDirectMcp: true) in both arms, so every call went through run_code. In a normal session, the model can call an MCP tool directly, and projection does not apply to that call.

Checks

  • claude plugin test .: 116 pass, 0 fail. This includes a test for each safety rule and for the default.
  • node tests/runner.integration.mjs: all pass, with the recall bridge and a recall.constructor escape case.
  • node tests/docs.check.mjs, tests/version.check.mjs (0.5.4 → 0.6.0), tsc and claude plugin validate .: pass.
  • A live headless run with no projection option (Opus 5.5) showed the new footer, recalled: #1 (3 s old), 1 server call and the correct answer.

🤖 Generated with Claude Code

gabe4coding and others added 2 commits October 10, 2026 11:18
After each run, the footer shows one line for each MCP call: its number,
its size and its shape. The plugin keeps the newest results of the session,
so a later program can read one again with await recall(n), with no new
MCP call. The footer also names a call that repeats an earlier call, an
empty result after calls that returned data, and the number of a cut result.

- projection is on by default; projection: false gives the old footer.
- The shape hides keys that are data (emails, ids, maps), so the data stays
  out of the context.
- Results stay in one session only. Without a session id, they stay only
  for the run.
- At most 50 results and 8 million characters for each session.
- The metrics option (off) writes sizes and counts for each run;
  scripts/projection-report.mjs compares on and off.
- bench/ runs six tasks with traps against a fake MCP server, in headless
  Claude Code, with projection off and on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion-design-48f330

# Conflicts:
#	.claude-plugin/plugin.json
#	tests/code-mode.test.ts
@gabe4coding
gabe4coding merged commit 8d9a39e into main Oct 10, 2026
6 checks passed
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