perf(mcp): drop duplicated structured arrays from get_instructions (−51% payload) - #1891
Merged
simple-agent-manager[bot] merged 7 commits intoAug 23, 2026
Conversation
Also files the pre-existing missing-observationId gap discovered during research. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
get_instructions emitted every knowledge observation and every policy twice: once rendered in knowledgeDirectives/policyDirectives and again as structured JSON in knowledgeContext/policyContext. Verified byte-identical (81/81 policy titles+bodies, 50/50 observations). Nothing consumed the structured arrays. On the real SAM project this is 166,814 -> 81,025 chars (-51%, ~21.4k tokens) on every session bootstrap. Policy IDs lived ONLY in the structured array, so formatPolicyDirectives now renders them inline as "(id: <uuid>)". IDs are rendered in FULL because update_policy/remove_policy resolve with WHERE id = ? (exact match); an abbreviated id would not address the row. Cost: +3,483 chars vs 52,157 saved. observationId was never present in knowledgeContext, so the knowledge management tools lose nothing here. That pre-existing gap is filed separately. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
test-engineer:
- add liveness assertions to the 'omits structured arrays' test (rule 62 req 5) —
an absence assertion is also satisfied by a payload that rendered nothing
- add same-entity multi-observation test: formatKnowledgeDirectives joins an entity's
observations with ' | ', and nothing in the repo covered that branch
- add the missing policy-retrieval-failure mirror of the knowledge-failure test
- fix vacuous not.toContain('…') — a [0-9a-f-]+ capture cannot hold an ellipsis, so the
guard now runs against the whole rendered block
- escape the interpolated policy title before building the id-extraction RegExp
- drop the unused recordActivityEvent mock
architecture-reviewer:
- comment explaining why the policy id is intentionally in prose rather than a structured
field, so a future reader does not 'fix' it by re-adding an array (list_policies /
get_policy already serve structured access)
task-completion-validator (CRITICAL, correct):
- the task file had been archived with every box ticked, including the staging acceptance
criterion that has not run. Moved back to tasks/active/ and un-ticked the two criteria
that are not yet empirically verified.
Discriminating property re-verified after the test changes: re-adding the structured
arrays now turns 5 of 14 red; restored -> 14/14.
Co-Authored-By: Claude <noreply@anthropic.com>
Staging deploy 32648180754 green. Measured through the real deployed Worker on identical seeded data: 6,025 -> 5,230 chars, every body 2x -> 1x, both context arrays gone, 3/3 full policy ids rendered (was 0/3). Scraped-id -> update_policy -> remove_policy round-trip proven. All seed data cleaned up. Playwright regression pass green, 0 console errors. Co-Authored-By: Claude <noreply@anthropic.com>
Contributor
CI's Preflight Evidence gate correctly rejected 'N/A' for Documentation & Specs on a public-surface-change: the MCP get_instructions response shape is consumed by every agent, including in self-hosted forks, and no doc described it. Adds an Agent Bootstrap Payload subsection to the architecture overview covering: rendered markdown as the single representation, why the knowledgeContext/policyContext duplicate arrays were removed, list_policies/get_policy and search_knowledge/get_project_knowledge as the machine-readable paths, why policy ids are rendered untruncated (WHERE id = ? is exact), and the still-missing observationId. Co-Authored-By: Claude <noreply@anthropic.com>
|
simple-agent-manager
Bot
deleted the
sam/remove-duplicated-structured-arrays-b6ggy4
branch
August 23, 2026 16:09
This was referenced Aug 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
get_instructions— the tool every SAM agent session must call before doing anything — was sending every knowledge observation and every policy twice: once rendered as markdown inknowledgeDirectives/policyDirectives, and again as structured JSON inknowledgeContext/policyContext, labelled "Also include structured data for programmatic use." Nothing has ever consumed the structured arrays.Verified byte-for-byte on the real SAM payload with
jq: 81/81 policy titles and bodies and 50/50 knowledge observations appear verbatim inside the rendered directives.This PR stops emitting the two arrays. They remain as local variables feeding the formatters and the
hasKnowledge/hasPoliciesinstruction switches.Real SAM project: 166,814 → ~81,025 chars (−51%, ~21.4k tokens saved on every session bootstrap).
Critical implementation note: policy IDs
policyDirectivesrendered- **{title}**: {content}with no id (0 of 81 policy ids appeared anywhere in it), whileinstructions[14]tells the agent to callupdate_policy/remove_policy— both of which requirepolicyId. The id existed only in the array being removed.formatPolicyDirectivesnow renders it inline:- **{title}** (id: {uuid}): {content}.The id is rendered in FULL, not elided. The task brief suggested
(id: 7d24e435…), butpolicies.ts:118,136resolve withWHERE id = ?(exact match), so an abbreviated id would silently fail to address the row. Cost: +3,483 chars against 52,157 saved.Deliberately not fixed here:
observationIdis required byupdate_knowledge/remove_knowledge/confirm_knowledgebut has never been in the response —knowledgeContextmapped onlyentityName/entityType/observation/confidence, though the underlyingSELECT o.*returns the id. So this PR loses nothing those tools could have used. Filed astasks/backlog/2026-08-23-get-instructions-missing-observation-ids.md; fixing it means reshaping the' | '-joined per-entity grouping, which would collide with in-flight sibling R3.Rule 44 — consumer enumeration
instruction-tools.tsapps/web/,packages/vm-agent/, tests,specs/,.claude/,apps/www/tasks/archive/2026-04-26-policy-propagation-phase4.mdtasks/backlog/2026-04-13-knowledge-graph-test-coverage.mdknowledgeDirectivesIndependently re-verified by
architecture-reviewerincludingpackages/vm-agent/**/*.goandapps/web/src/**: the Go/webget_instructionshits all concern the injected system-reminder prompt text, not response parsing.Coordination with in-flight siblings
origin/mainwas unchanged at push time, so this lands first as planned.architecture-reviewerconfirmed viagit merge-treea guaranteed (mechanical, not design-level) conflict with R2sam/add-lifecycle-controls-sam-jrnt7non three spans. The merged template line should read:`- **${item.title}** (id: ${item.id})${item.lifecycle}: ${item.content}`Whoever rebases R2 should re-run this PR's
renders ids in FULL...test, which asserts the exact(id: ${id})string shape. R3 does not conflict — this diff never touches the knowledge block.Validation
pnpm lint— 0 errors (3 pre-existingapps/webwarnings, untouched)pnpm typecheck— 19/19 tasks cleanpnpm test—apps/api: 595 files, 8,037 tests, 0 failed, 0 collection errors (8,025 baseline + 12; now 14 after review fixes)pnpm build9/9;pnpm check:fast13/13 with 0 blocking type-boundary findingsDiscriminating-test proof (rules 02/62): re-adding the two structured arrays turns exactly 5 of 14 tests red — the three "exactly once" body/title/observation tests, the "omits structured arrays" test, and the same-entity multi-observation test. Restored → 14/14 green. Verified twice (before and after the review fixes), and independently reproduced by
test-engineer.Note on reporting: a failed import shows up in vitest as
0 passed / 0 failed / success:false. Totals here were read per-file from the JSON reporter with an explicitassertionResults.lengthcheck, so a collection error cannot masquerade as a pass.Staging Verification (REQUIRED for all code changes — merge-blocking)
32648180754, conclusionsuccessapp.sammy.party, token-login 200/projects(16 listed),/settingsall render at 375x667 and 1280x800;GET /health200; 0 console errors at both viewportspackages/cloud-init/,packages/vm-agent/,scripts/deploy/, DNS, or TLSStaging Verification Evidence
Measured through the real deployed Worker via
POST https://api.sammy.party/mcpwith a live MCP token read from staging KV — the exact code path an agent bootstraps through. Project01KTKXZ4ZZAT6MJFXRW1ZTQ7RBseeded with 3 marked policies (one per category) + 1 observation; identical data measured either side of the deploy.main)knowledgeContextpolicyContextpolicyDirectivesThe absolute delta is small only because the fixture is small — fixed
context/task/project/instructionsoverhead (~4,010 chars) dominates at that size. What was eliminated is the duplication, which scales with knowledge + policy volume: on the real SAM project (81 policies, 50 observations) that is 166,814 → ~81,025.Management round-trip proven end-to-end — the decisive check, and exactly what an elided id would have broken:
c546f67b-…out of- **...** (id: ...)in the rendered markdownupdate_policywith only that id →{"updated":true,...}get_instructions→ new text present, stale text goneremove_policy× 3 →{"removed":true,...}eachIncidentally confirmed the filed
observationIdgap: removing the seeded observation requiredsearch_knowledgeto obtain the id, becauseget_instructionsstill does not expose it.Cleanup: all seeded data removed; payload returned to exactly the 4,010-char empty-path shape with zero
R1VERIFYresidue. No VMs or nodes were created (the MCP endpoint was exercised directly), so the zero-VMs-at-rest rule is respected.UI Compliance Checklist
N/A: no UI changes — diff touches only
apps/api/src/routes/mcp/instruction-tools.ts, one test file, and task markdown.End-to-End Verification
Data Flow Trace
Step 5 is why the id must be full-length; it was verified live on staging, not just reasoned about.
Untested Gaps
No same-process integration test round-trips the rendered id through the real
handleUpdatePolicy.formatPolicyDirectivesdoes a literal${item.id}interpolation with no transformation, so the id is unmodified by construction — and the round-trip was exercised end-to-end against the deployed staging Worker (evidence above), which is stronger than a mocked test. Raised bytask-completion-validatoras MEDIUM and resolved that way.Post-Mortem
N/A: not a bug fix — this is a token/cost optimization (R1 of the token-optimization program, idea
01M0QGZQ15WE9DDQENGV6S9XZ5). No user-visible defect is being corrected, so no process fix is required. One pre-existing gap discovered during research was filed rather than silently inherited:tasks/backlog/2026-08-23-get-instructions-missing-observation-ids.md.Specialist Review Evidence
needs-human-reviewlabel neededcloudflare-specialistget_instructionsdeclares nooutputSchema, so no output contract is violated; DO RPC calls and bothtry/catch → []fallbacks byte-for-byte unchanged; 0 new round-trips (rule 60). Proved(id: undefined)is impossible —parsePolicyRowrequiresv.string(), so a bad id throws upstream and the batch degrades to[]rather than rendering a corrupt line. 2 LOW informational notes, no in-branch action.architecture-reviewerlist_policies/get_policyalready provide structured access, so apolicyIdsarray would be a redundant fourth representation and a correlatable{id,title}[]would cost ~2x the inline form. MEDIUM "add a comment so nobody re-adds the array" applied in62e3bcba1. MEDIUM R2 merge-conflict note is informational (see Coordination above).test-engineer62e3bcba1: missing liveness assertion on the absence test (rule 62 req 5); no coverage anywhere in the repo of the same-entity multi-observation `'task-completion-validatorsedthat ticked every box including the staging criterion before staging had run, and archiving preceded the validator, violating rule 09. Fixed in62e3bcba1: moved back totasks/active/, un-ticked the two non-empirical criteria, relabelled the Outcome section. Both MEDIUMs (projected-not-measured AFTER figure; no live id round-trip) are now resolved by the staging evidence above; task archived in754bcf0bfonly after that evidence existed.Not run, with reason:
go-specialist(no Go),ui-ux-specialist(no UI),security-auditor(no auth/credential/token code),env-validator(no env vars),doc-sync-validator(grep confirmed no doc anywhere references these fields),constitution-validator(no hardcoded values; the(id: ...)format string is presentation, not configuration).Exceptions
mainas/doPhase 1 prescribes.main(Required status check "Durable Object Workers" is expected). Also the safer default per CLAUDE.md's "never commit directly to main."Agent Preflight (Required)
Classification
public-surface-change: the MCPget_instructionsresponse shape changes for every agent.business-logic-change: how policy identifiers reach the agent changes.External References
N/A: no external API involved. Sources were in-repo and empirical — the live
get_instructionspayload (measured withjq),.claude/rules/44(enumerate every consumer),.claude/rules/02/62(discriminating tests), and library research doc/engineering/research/token-optimization-research.md§3.1 and §8/R1.Codebase Impact Analysis
apps/api/src/routes/mcp/instruction-tools.ts(only runtime file: −2 emitted fields, +1 rendered id, comments),apps/api/tests/unit/routes/mcp-instruction-payload-dedup.test.ts(new, 14 tests), 3 task markdown files. Nopackages/change, noapps/webchange, no schema/migration, no new dependency.Documentation & Specs
apps/www/src/content/docs/docs/architecture/overview.md— added an Agent Bootstrap Payload (get_instructions) subsection under Agent Configuration Layers. It documents that knowledge and policies are delivered as rendered markdown only, that there is deliberately no second structured copy (and why theknowledgeContext/policyContextarrays were removed), thatlist_policies/get_policyandsearch_knowledge/get_project_knowledgeare the machine-readable paths, and that policy lines carry full untruncated ids becauseWHERE id = ?is an exact match. It also records that observations still lackobservationIdin this payload.No prior doc described this response shape — grep across
apps/www/,specs/, and.claude/found zero references toknowledgeContext/policyContext, and theget_instructionstool description does not mention either field. The one stale mention (acceptance criteria in an unstarted backlog task) is also updated in this PR.Constitution & Risk Check
Principle XI (no hardcoded values): no new URL, timeout, or limit. The
(id: ...)template is presentation, not configuration.Risks / tradeoffs
list_policies/get_policyremain the structured path, and a comment in the code prevents a future "fix" from re-adding the array.Merge to
mainand production ship for this work were explicitly authorized by Raphaël (2026-08-23), recorded in the SAM task description for01M0QHHVEQY11VC37Y29B6GGY4.🤖 Generated with Claude Code