Skip to content

Every session boot inlines 52 KB of bundled tool reference, including daemon-owned sessions that never call a native tool #563

Closed

Description

@jillesmc

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_000

Measured 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

  1. Make the tools section session-class aware. The sessionCtx is 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.
  2. Decouple the doctrine from the gateway. For example tools.prompt_section_enabled, default true. No
    behaviour change by default; it makes the above measurable without patching Go.
  3. Inject the router instead of the references. The compozy skill is already a router. SKILL.md
    says "This body routes to the matching reference. Load it before acting", and the startup prompt
    already teaches compozy__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.

Activity

  1. added theissue type on Sep 9, 2026
  2. moved this from Todo to In Progress in CompozyOS Devon Sep 9, 2026
  3. moved this from In Progress to Done in CompozyOS Devon Sep 9, 2026
  4. jillesmc commented on Sep 9, 2026

    @jillesmc
    Author

    Independent validation on main at 744a29ac, built locally with make build-go, run before
    beta.24 ships in case any of it is useful to you.

    Validated by measurement, on both paths.

    Interactive session:

    beta.21/23 main
    startup prompt 72,820 chars (median of 482 boots) 32,714
    tools-and-skills.md inlined yes, in 97% of boots no
    native-tools.md inlined yes, in 97% of boots no
    ## Required Reading Router present no yes

    The 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 including compozy.

    Method note: every run used a throwaway COMPOZY_HOME. The store that backs the original report was
    copied first and left untouched, so the evidence still exists if you ever want more from it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions