Repository navigation
Every session boot inlines 52 KB of bundled tool reference, including daemon-owned sessions that never call a native tool #563
Description
CompozyOS version
compozy 0.3.0-beta.23
Operating system
Linux (x86_64)
Area
Core / daemon, Sessions
ACP agent
Claude Code
Model / provider
anthropic/claude-sonnet-5
Steps to reproduce
1. Start a daemon with factory config (tools.enabled = true).
2. Start any managed session, including a daemon-owned one (memory extraction, context summarisation).
3. Inspect the startup prompt the daemon sends. With Claude Code as the ACP agent it is the first
user message of the harness transcript, under $CLAUDE_CONFIG_DIR/projects/<slug>/*.jsonl
4. Search that message for the literal contents of
skills/compozy/references/tools-and-skills.md and skills/compozy/references/native-tools.md
Both files are present verbatim and in full, in every session class.
What happened, and what did you expect?
Context, since this arrives without one. I was running a structured comparison of
agent-orchestration tools, and CompozyOS was one of them. That is why the numbers below are exact, why the
arrangement sometimes looks artificial, and why several reports from me land close together: they came out
of one measurement pass on a dedicated Linux box, not from a production incident. The token and cost figures come from reading the harness transcripts on disk, not from an estimate. Happy to re-run any
of it with a different arrangement if that would be more useful to you.
What happens. The tools startup section loads two bundled reference documents in their entirety:
// internal/daemon/prompt_sections.go:162-166
Provider: bundledReferencesPromptSectionProvider(
bundledCompozySkillName, // "compozy"
bundledToolsReference, // "references/tools-and-skills.md"
bundledNativeToolsReference, // "references/native-tools.md"
),On main those two files are 53,836 bytes (19,804 + 34,032), and the budget is sized to fit them
whole:
// internal/daemon/prompt_sections.go:37-38
// Keep enough headroom to avoid truncating canonical tool guidance.
startupToolsSectionBudget = 64_000Measured over a real workload. 482 session boots on beta.21, the same code path as main:
| boots carrying both files verbatim | 467 of 482 (97%) |
| the two files as a share of the median boot prompt | 72% |
| sessions of 1 turn | 325 of 459 |
| share of the billable cost of the workload spent on these two files | 21.7% |
Why the cost lands where it does. The section is part of the cached prefix, so a long session reads it
cheaply after the first turn. But a short session writes it and never amortises it, and cache writes
cost more than reads. Splitting the same measurement by session length:
| session length | sessions | share of the cost of these two files |
|---|---|---|
| 1 turn | 325 | 59.3% |
| 2–3 turns | 60 | 12.4% |
| 4–10 turns | 12 | 2.8% |
| 11+ turns | 62 | 25.5% |
The sessions that pay most do not use what the documents describe. The short sessions are
daemon-owned routines: memory-candidate vetting and context summarisation. Counting native tool calls:
| sessions | compozy__* calls |
|
|---|---|---|
| ≤3 turns | 385 | 2 |
| 4+ turns | 74 | 312 |
383 of 385 made zero native tool calls. The two exceptions made one call each, of a single tool.
What I expected. That a session class which never calls a native tool would not receive the native
tool manual, and that there would be a way to keep the tools while dropping the doctrine.
Neither is available today, and the resolver already has what it needs.
// internal/daemon/harness_context_policy.go:3-24
func (r *HarnessContextResolver) resolveSections(sessionCtx HarnessSessionContext) []HarnessPromptSection {
…
if r.runtime.ToolsPromptSectionEnabled { // :17, global flag
sections = append(sections, HarnessPromptSectionTools)
}
if sessionCtx.NetworkLive { // :20, the only section that reads the session
sections = append(sections, HarnessPromptSectionNetwork)
}sessionCtx carries SessionClass, and the five classes already exist
(internal/daemon/harness_context.go:42-50): interactive, dream, system, coordinator, spawned.
Five of the six sections ignore it.
And the flag is tied to the tool gateway itself:
// internal/daemon/boot.go:315
ToolsPromptSectionEnabled: state.cfg.Tools.Enabled,The same cfg.Tools.Enabled also switches off hosted MCP and native tool policy
(internal/daemon/hosted_mcp.go:23, internal/daemon/native_tool_clone_policy.go:119). So there is no
configuration that keeps the tools and drops their 52 KB manual.
Suggested fixes, smallest first
- Make the
toolssection session-class aware. ThesessionCtxis already in hand, and the change
has the same shape as the network gate three lines below. Excluding daemon-owned classes would remove
~72% of what these documents cost, with no effect on interactive sessions. - Decouple the doctrine from the gateway. For example
tools.prompt_section_enabled, defaulttrue. No
behaviour change by default; it makes the above measurable without patching Go. - Inject the router instead of the references. The
compozyskill is already a router.SKILL.md
says "This body routes to the matching reference. Load it before acting", and the startup prompt
already teachescompozy__skill_view. 13 of the 15 reference files are loaded on demand; these two
are the exception, and both also appear in the router table (skills/compozy/SKILL.md, rows for
agent definitions, native tool discovery, command palette, Loops, extensions, worktrees). Replacing
52 KB of inline text with the ~3 KB router would shrink every session's boot.
I understand (3) is the one with a real quality question. An agent may act without loading the
reference. (1) and (2) do not depend on it.
Still present on main: prompt_sections.go, harness_context_policy.go and
config_memory_defaults.go are byte-identical between v0.3.0-beta.21 and main (checked 2026-09-08);
the two reference documents grew 3.3% over the same span.
Logs
# not a log-visible defect. the evidence is the startup prompt itself.
# Size check on the current tree:
$ wc -c skills/compozy/references/tools-and-skills.md skills/compozy/references/native-tools.md
19804 skills/compozy/references/tools-and-skills.md
34032 skills/compozy/references/native-tools.md
53836 total
# Presence check across 482 recorded session boots (both files, verbatim):
467 of 482 (97%)Final checks
- I searched existing issues and this is not a duplicate.
- I can reproduce this on the latest release.
Duplicate search performed (title + body, open and closed): cost · token · context ·
expensive · cache · prompt size · prompt sections · prompt_sections · startup prompt ·
boot prompt · system prompt · startupToolsSectionBudget · skill injection · reduce tokens ·
session class. No issue or PR matches. The two cost-adjacent issues are #229 and #473,
both closed and both about model routing (price per token), not about prompt volume.
Metadata
Metadata
Assignees
Labels
Type
Fields
Projects
- StatusShow more project fieldsDone
Activity
jillesmc commentedon Sep 9, 2026
Independent validation on
mainat744a29ac, built locally withmake build-go, run beforebeta.24ships in case any of it is useful to you.Validated by measurement, on both paths.
Interactive session:
tools-and-skills.mdinlinednative-tools.mdinlined## Required Reading RouterpresentThe two manuals total 54,268 chars; the router is 12,171.
Input-only routine: my first attempt hit a provider auth failure, and the prompt I ended up reading was a
checkpoint summary processing that failed session. It had no tool section at all, neither router nor
manuals, which is the role gate doing exactly what you described.
Before reading anything I confirmed the mechanism was reachable:
tools.enabled=true,hosted_mcp_enabled=true, and 14 skills registered includingcompozy.Method note: every run used a throwaway
COMPOZY_HOME. The store that backs the original report wascopied first and left untouched, so the evidence still exists if you ever want more from it.