fix(bundle): foundation's ROOT body was documentation prose sent as system instruction (A) + pin the tool-skills double-mount semantics (C) - #368
Conversation
…ose, sent as system instruction (A) Everything below a bundle's frontmatter is the system instruction whenever that bundle is composed as the ROOT (docs/BUNDLE_GUIDE.md: "Everything below the frontmatter is sent to the model as system instruction. It is not documentation, and it is not free."). Root bundle.md carried a "# Foundation Bundle v2.0" title, a feature table and an MCP config sample below the frontmatter -- so every foundation-root session opened its system prompt with product documentation rather than with common-system-base. This is the same defect class as the notify-README body fixed by app-cli #315/#316. Measured with the validator FIXED in aaa5c47 (whose extractor previously returned an empty string that satisfied every check): main a0decc6 : verdict FLAG, prose_chars 499 "This bundle provides the standard Amplifier foundation with the enhanced delegate tool for agent orchestration.\n| Feature | ..." this branch : verdict OK, prose_chars 0 "body carries no prose (mentions/scaffolding only)" repo-wide : 1 prose_below_frontmatter WARNING -> 0, over 32 files checked Real session, foundation as ROOT (project-scope source override; ~/.amplifier/cache untouched), raw.system[0].text head: before (5dfe9c07, 130,055 chars): "# Foundation Bundle v2.0\n\nThis bundle provides the standard Amplifier foundation... | Feature | Description |..." after (268a6e56, 129,178 chars): "@foundation:context/shared/common-system-base.md \n\n---\n\n<context_file ...># Primary Core Instructions\n\nYou are Amplifier..." The prose is not deleted -- it moves into the frontmatter `description:`, which is manifest metadata and is never sent to the model. The body is now the single @mention it should always have been. (C) tool-skills is mounted twice -- behaviors/agents.yaml (foundation's own skills dir) and bundle.md:33 via amplifier-bundle-skills' skills.yaml. Measured first, per the goal: NOTHING IS DROPPED. merge_module_lists collapses the two declarations by module id and deep_merge concatenates list-typed config, so both skills sources survive in both compose orders; a real foundation-root session lists all three foundation skills alongside the curated collection. That is a finding, not a bug -- so no code changed. But it is safe only because of the list-concat rule added in 70d521f (#120), which is invisible at both declaration sites and fails SILENTLY if it regresses. tests/test_tool_skills_double_mount.py pins it: disabling list-concat fails 2 of its 7 tests; restored, 7 pass. Suite: 2038 passed, 3 skipped, 2 failed -- both failures reproduce unchanged on a0decc6 with these changes stashed (test_sources.py::test_resolve_existing_file, test_grpc_adapter_main.py::test_non_isinstance_object_with_mount_passes). Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…at the $10 authority Terminal outcome: RESOLVED AT THE CAP (goal branch B). A and C are DONE and ship in this PR. B's code half is DONE and measured on #369; B's eval is NOT-POSSIBLE at $10 -- 12 valid runs / 0.67 observed validity = 18 launches x blended $4.08 = $73.44, priced before spending, $0 spent on it, design not shrunk and relabelled. Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Manager verification — A and C both hold, each re-run by me in a scratch clone. Merging.Head (A) The prose is gone, and the real session proves itThe fail-before is trustworthy only because The real session is the check a unit test could not make:
No feature table. This is the notify-README defect one layer down — a bundle's documentation silently becoming the session's system prompt, exactly what app-cli #315/#316 fixed — and the same class the anchors family got weeks of scrutiny for while this base got none. (C) Measured before fixing, and the answer was "no defect"The spec required reading I checked the load-bearing claim rather than take it: And I confirmed the test's shape independently. It passes at The residual risk it recorded rather than fixed is the sharpest part: the two mounts are order-insensitive only because they name the same On (B), for the recordHeld as draft #369 with its arithmetic rather than shrunk to fit. That is the correct answer and it does not block A or C, exactly as the sequencing required. |
What this fixes
(A) Foundation's ROOT bundle body was 499 characters of documentation prose — and that prose was the system instruction.
docs/BUNDLE_GUIDE.md:138:Root
bundle.mdcarried a# Foundation Bundle v2.0title, a "Key Features" table, a delegate-parameter snippet and an MCP config sample below the frontmatter — ahead of@foundation:context/shared/common-system-base.md. So every foundation-root session opened its system prompt with product documentation.This is the same defect class as the notify-README body that app-cli #315 (
dab0f7c) and #316 (1e47c38) fixed. This is that defect in the composition base every non-anchors bundle inherits.The prose is not deleted — it moves into the frontmatter
description:. The body becomes the single@mentionit should always have been.(C) The
tool-skillsdouble mount was measured before anything was touched. It is a finding, not a bug — no code changed. Details below.(A) Fail-before / pass-after
Measured with the validator fixed in
aaa5c47— its extractor previously returned an empty string that satisfied every check, so these counts are honest only now.check_body_is_instructiononbundle.md:a0decc6FLAGno second-person address -- reads as documentation, not instructionOKbody carries no prose (mentions/scaffolding only)Flagged preview on main:
Repo-wide
body-instruction-check(same code the recipe step runs,validate-bundle-repo.yaml:4224):(A) Real-session check — foundation as ROOT
amplifier run "hi" --bundle foundationfrom a scratch dir carrying a project-scope.amplifier/settings.yamlsource override (bundle.added.foundation: file://<worktree>,bundle.app: []).~/.amplifier/cachewas not edited.llm:request.data.raw.system[0].text, head:Before — session
5dfe9c07-d95d-41a8-815b-962c0ce5a0dd, total 130,055 chars:After — session
268a6e56-d64d-4958-9ea4-2c2bdfd94a2e, total 129,178 chars:No feature table. The first content is common-system-base. (The bare
@mentionline itself remains — that is how mention expansion renders; the resolved file follows immediately.)(C) Compose-semantics finding —
tool-skillsis mounted twiceFull write-up:
docs/lanes/8rug-foundation-root-hygiene/C-compose-semantics-finding.md.Measured first, per the spec. Verdict: nothing is silently dropped.
behaviors/agents.yaml:36-40contributesconfig.skills: [amplifier-foundation#subdirectory=skills]bundle.md:33→amplifier-bundle-skills#subdirectory=behaviors/skills.yamlcontributesconfig.skills: [amplifier-bundle-skills#subdirectory=skills]+config.visibilityBundle.compose()(_dataclass.py:250) →merge_module_lists()keys byid or module, so the two collapse to one mount, anddeep_merge()resolves the config: dicts recurse, lists concatenate with dedup, scalars are replaced by the later value. Effective composed config:{"module":"tool-skills", "source":"...amplifier-bundle-skills@main#subdirectory=modules/tool-skills", "config":{"skills":["...amplifier-foundation@main#subdirectory=skills", "...amplifier-bundle-skills@main#subdirectory=skills"], "visibility":{"enabled":true,...,"visibility_token_budget":5000}}}Both orders keep both entries.
Why a test was still warranted. The double mount is safe only because of the list-concat rule added in
70d521f(#120) — a rule invisible at both declaration sites, with no failure signal if it regresses (the skills just stop being discoverable).tests/test_tool_skills_double_mount.pypins it. Disabling list-concat indeep_merge:Restored:
7 passed.Residual risk, recorded not fixed: the two mounts are order-insensitive only because they name the same
source.sourceis a scalar, so a future divergence means whichever composes last silently wins.test_both_mounts_agree_on_the_module_sourceturns that into a test failure instead of a session mystery.(C) Real-session check — foundation's skills are discoverable
Same session
268a6e56, livehooks-skills-visibilitylist:Both mounts reach the model.
Tests
Both failures are pre-existing and reproduce unchanged on
a0decc6with this branch's changes stashed:tests/test_sources.py::TestFileSourceHandler::test_resolve_existing_filetests/test_grpc_adapter_main.py::TestVerifyModuleType::test_non_isinstance_object_with_mount_passesSpend
$0.14 — two
amplifier run "hi"sessions onhaikufor the real-session checks ($0.07 each). No DTUs, no ledger rows.Scope
This is PR 1 of 2 for
model_performance-8rug. PR 2 carries (B) — thedelegation-instructions.mdsplit — and is gated on its own pre-registered eval. A and C are independent of that eval and land on verification.Generated with Amplifier
Co-Authored-By: Amplifier 240397093+microsoft-amplifier@users.noreply.github.com