Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ These landed after phase 10, and all are documented in `design/orchestration.md`
- **`scc rtk`, and `scc init --rtk`.** Wires in [RTK](https://github.com/rtk-ai/rtk), the CLI proxy that filters command output: `cargo install` when the binary is missing, plus a splice of RTK's marker-delimited usage block into the entry file. **scc's block wins by default**, replacing whatever sits between the markers; `--keep` is the separate decision. The reason is size, not authorship: `rtk init` (measured on 0.42.4) writes 139 lines / 5140 bytes, scc ships 18 lines / ~900, both stamped `v2`, both saying the same thing — and the entry file is preloaded into every request of the session, so the difference is paid continuously rather than once. Between two blocks of the same version the condensed one is simply better, and leaving the larger one because it got there first is a standing cost. Where that costs something is a block claiming a *newer* version, which is a real downgrade: the run names the version it displaced and points at `--keep`. Opt-in in both places, since the block tells the agent to prefix every command with a binary the machine may not have. `--check` reports without writing and exits 2 when the block is missing.

**scc uses RTK's own markers rather than namespacing its own**, and that is load-bearing: `rtk init` writes `<!-- rtk-instructions v2 -->` … `<!-- /rtk-instructions -->` into the project entry file (verified against rtk 0.42.4), so addressing the block by that pair is what makes `rtk init` and `scc rtk` converge on one copy. A namespaced `scc:rtk-instructions` would make each tool blind to the other's block and leave the file carrying both — which is the bug, not the fix. Headroom is the counter-example: it *does* namespace (`headroom:rtk-instructions`), which is why a workspace wired by both can end up with two blocks. scc detects that one and names it in the report (`rtkFile.Foreign`), with `headroom unwrap <agent>` as the fix — reported, never touched, because that block belongs to Headroom.
- **`scc launch <harness>`.** Starts the harness this workspace was scaffolded for, from the workspace root, behind [Headroom](https://github.com/headroomlabs-ai/headroom)'s compression proxy (`headroom wrap <slug>`), with the workspace's symbol graph brought up to date first. Headroom is the *default* here, which is the deliberate opposite of how RTK is wired: RTK edits a file the user owns and changes how every later command is typed, while Headroom wraps one process for the length of one session. So it degrades instead of failing — missing binary, declined install, unattended run, or a harness Headroom does not wrap all end in the agent starting bare with a warning saying why. `--no-headroom` forces that path, and a missing binary prompts for `uv tool install` (`--yes`/`--no-install` are the unattended answers). This is the one command that does not obey the 0/1/2 exit-code contract; see the convention below.
- **`scc launch <harness>`.** Starts the harness this workspace was scaffolded for, from the workspace root, with the workspace's symbol graph brought up to date and its entry file wired for RTK first. What a bare `scc launch` does, and does not, is settled by one question: **does this leave anything behind after the session ends?** The graph and the RTK block do, and both are what the agent needs. [Headroom](https://github.com/headroomlabs-ai/headroom)'s `wrap` does too — MCP registrations in the agent's own config — but what it buys is one session of compression, so it is **opt-in behind `--headroom`**. This is the one command that does not obey the 0/1/2 exit-code contract; see the convention below.

Everything it does degrades instead of failing: a missing binary, a declined install, an unattended run, or a harness Headroom does not wrap all end in the agent starting anyway with a warning saying why. A missing Headroom prompts for `uv tool install` (`--yes`/`--no-install` are the unattended answers).

**RTK preflight is on by default, bounded by the block.** `scc launch` makes sure the entry file the agent is about to load actually mentions RTK — the same setup `scc rtk` does standalone — and the *trigger is the block, not the binary*: it runs when an entry file carries none and does nothing at all when they all do. That bound is what lets it be the default. It fires once per workspace rather than once per session, so a wired workspace pays no cargo prompt and no edit at the top of every session. The write is the point: an agent that never read the block never types the prefix, so installing the binary and leaving the file alone would buy nothing. It splices with `keep`, so a block that is already there is left exactly as it is even when scc ships a different one — replacing somebody's block is a real trade-off, and `scc rtk` is where it is made deliberately. `--no-rtk` opts out, and a plan-only run (`--json`/`--dry-run`) reports the splice without performing it.

Two things about `wrap` are load-bearing and were originally documented wrong here:

**It does write to disk.** `headroom wrap` registers MCP servers into the agent's own config (`~/.claude.json` and the Codex/opencode equivalents), and those registrations outlive the session that made them — which is why Headroom ships `unwrap` at all. So `scc launch` defaults to **`--headroom-mcp none`**: the only thing a bare `scc launch` wants from Headroom is the compression proxy, so no MCP server is registered on the agent's behalf, not even Headroom's own retrieve tool. The cost of that is real — the proxy's compression markers go unactionable without retrieve — and `--headroom-mcp retrieve` hands that back for anyone who wants the markers expanded; `all` keeps Headroom's defaults, `none` (the default) drops the retrieve tool too.
**It does write to disk.** `headroom wrap` registers MCP servers into the agent's own config (`~/.claude.json` and the Codex/opencode equivalents), and those registrations outlive the session that made them — which is why Headroom ships `unwrap` at all. That is the reason `--headroom` is opt-in at all, and why even then `scc launch` defaults to **`--headroom-mcp none`**: the only thing it wants from Headroom is the compression proxy, so no MCP server is registered on the agent's behalf, not even Headroom's own retrieve tool. The cost of that is real — the proxy's compression markers go unactionable without retrieve — and `--headroom-mcp retrieve` hands that back for anyone who wants the markers expanded; `all` keeps Headroom's defaults, `none` (the default) drops the retrieve tool too. Any `--headroom-*` flag implies `--headroom`, because accepting a flag and then ignoring it is how somebody spends a session believing they configured something.

**It also wants the entry file, and scc says no.** `headroom wrap`'s context-tool setup appends RTK guidance to `$PWD/CLAUDE.md` or `$PWD/AGENTS.md` — the same file `scc rtk` splices — behind its own marker pair, `<!-- headroom:rtk-instructions -->`. Neither marker is a substring of the other, so both tools' idempotency checks pass and both append: an entry file carrying the same RTK instructions twice, in every request of the session. So `scc launch` passes `--no-context-tool` by default. Headroom already gates that injection behind `HEADROOM_RTK`, which makes this belt-and-braces — but only until an environment exports that variable for its own reasons, and `wrap claude` resolves `setup_context_tool = (context_tool or _rtk_opt_in()) and not no_rtk`, so the flag wins over both the env var and `--context-tool`. `--headroom-context-tool` hands it back.

**RTK preflight is opt-in too.** `scc launch` can also make sure the agent it starts has RTK's binary and usage block ready — the same setup `scc rtk` does standalone — but only when asked: `--rtk`. A bare `scc launch` leaves the entry file and PATH untouched on that front, for the same reason MCP and the context tool default off: the only thing Headroom is there for by default is the compression proxy.

**The opt-out flags are discovered, not hardcoded.** `internal/headroom` reads `headroom wrap <agent> --help` and picks the spelling that build advertises. This is not defensiveness for its own sake: Headroom renamed this exact control once already (`--no-serena` → `--code-memory none`, and `--no-tokensave` in between), and the harness profiles disagree today — `wrap opencode` still takes `--no-serena` while `wrap claude` and `wrap codex` take `--code-memory`. A flag name compiled into scc turns that kind of release into a launch that dies on `no such option`, which is strictly worse than one unwanted MCP server. A build advertising no opt-out is reported, not overridden.

**`--` reaches `wrap`, not only the agent.** `headroom wrap` parses every flag it recognizes out of the tail and forwards only the rest, so a pass-through argument that collides with one of Headroom's — `--verbose`, which both Claude Code and `wrap` define — is silently eaten. `WrapArgs` therefore takes scc's options and the pass-through as separate parameters and puts scc's first, so a colliding argument the user typed lands last and wins. To force something past `wrap` to the agent, use a second terminator: `scc launch claude -- -- -p`.
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Commands:
init Scaffold a workspace: rules, agents, skills, commands, layout, manifest
update Bring the managed files onto this build's templates, after showing the plan
rtk Install RTK if missing and put its usage block in the entry file
launch Start a harness in this workspace, through Headroom's compression proxy
launch Start a harness in this workspace, with its symbol graph and RTK block current
graph The workspace's symbol graph — build | sync | status | query | explore
spec Create and inspect specs — new | list | show | delete | validate
plan Create and inspect plans — new | list | delete | validate
Expand Down
Loading
Loading