Skip to content

perf: store project slash commands as delta rows beside the capability cache - #1059

Open
zxch3n wants to merge 1 commit into
mainfrom
perf/acp-capability-sync-cost
Open

zxch3n wants to merge 1 commit into
mainfrom
perf/acp-capability-sync-cost

Conversation

@zxch3n

@zxch3n zxch3n commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Related issue

Problem / pressure

Every created session wrote its agent's slash-command list into the per-config machine capability row (['acpCapability', configId]). That list depends on the project directory the session runs in, so every session started in a different project rewrote the whole 25–40 KB row and synced it to every client in the workspace (one dev machine: 207 whole-row writes on 2026-09-25; rows total ~700 KB).

Probing builtin Claude and Codex from four directories (home, this repo, two others) showed availableCommands was the only field that changed. configOptions (including every currentValue), models and modes were byte-identical. Claude: 105 common commands + 0–7 per project. Codex: 75 common + 0–32 per project.

Summary

  • Base list: the capability row's availableCommands belongs to probes. A created session keeps the stored list for the same sourceVersion, and only seeds it when none exists, as before.
  • Project delta rows: ['acpCommandScope', configId, scopeKey] holds { sourceVersion, added, removed }. The key is local:<localProjectId> or github:<owner/repo>. The value has no timestamp, so repeat sessions in a project write nothing. An empty delta deletes the row, and a session that reported no commands leaves it alone.
  • Readers: MachineViewMeta.acpCommandScopes. The session composer, draft tabs and chat landing pass their project's key, and resolveAvailableCommands merges the delta only when its sourceVersion matches the base.
  • Cleanup: deleting an agent config (CLI or renderer) removes its scope rows.
  • Observability:
    • [acp-capabilities] write debug line on every write attempt: source, outcome, changed fields, bytes, scope outcome, and cumulative counters since daemon start.
    • [acp-runtime-config] write debug line with writesThisTurn.
    • lody machine list --json --include-acp-capabilities now includes the scope rows.
  • The dedupe comparison now includes goalActions, so a change there alone is no longer skipped.
  • Adds the per-model ACP controls proposal note (proposed), updated with this measurement. The measurement showed its "snapshot stabilization" idea saves nothing.

Decision record: .agents/notes/implemented/architecture/2026-09-27-acp-command-scope-rows.md.

Visual explanation

flowchart LR
  P[probe, daemon cwd] -->|base list| C[(acpCapability row\n25-40 KB, stable)]
  S[created session in project X] -->|same sourceVersion: keep base| C
  S -->|added/removed vs base| D[(acpCommandScope row X\n0-12 KB, written once)]
  C --> M[resolveAvailableCommands]
  D --> M
  M --> UI[slash menu for project X]
Loading

Before / after

Measured by replaying real probe output through computeAcpCommandScopeDelta:

Agent / project Before: synced per project switch After: synced once per project, then 0
Claude, this repo 38.8 KB 0.5 KB
Claude, private repo 41.4 KB 3.2 KB
Claude, loro 38.3 KB 0
Codex, this repo 29.4 KB 0
Codex, private repo 41.6 KB 12.5 KB
Codex, loro 29.4 KB 0

Test plan

  • corepack pnpm typecheck: pass. corepack pnpm lint: 0 errors.
  • vitest run: packages/shared 108 files, apps/cli 284 files (1 skipped), packages/components 526 files, all passing.
  • New MachineDocument tests run against an in-memory Flock. Each new mechanism was ablated and its test failed: keeping the base, the source-version gate, and the probe/session split.
  • Shared tests cover row parse/reject, config-delete cleanup and delta math. A component test runs rows through the machine overlay into resolveAvailableCommands.
  • pnpm run docs check: no errors.
  • Not run: the full pnpm check/test:ci sequence; real-traffic write counts (the new log line collects them).

Mixed versions: composers built before this change read only the base list, so they lose project-specific commands from the slash menu until they update. Typing a command still reaches the agent. New composers with an old daemon see no scope rows and behave as today.

Context handoff

Internal same-repository branch.

🤖 Generated with Claude Code

…y cache

Every created session wrote its agent's slash-command list into the
per-config machine capability row. That list depends on the project
directory, so each session started in another project rewrote the whole
25-40 KB row and synced it to every workspace client. Probing builtin
Claude and Codex from four directories showed availableCommands was the
only field that changed; configOptions, models and modes were identical.

The capability row now keeps one base list per source version, owned by
probes. A created session records only its project's additions and
removals in an `acpCommandScope` Flock row keyed by local project id or
GitHub repo, and composers merge that delta over the base. Replaying real
probe output, a project switch syncs 0-12.5 KB once per project instead of
29-42 KB every time. Deleting an agent config removes its scope rows.

Capability writes now log source, outcome, changed fields, bytes and
cumulative counters; session runtime-config snapshot writes log a per-turn
count. `lody machine list --include-acp-capabilities` shows the scope rows.

Also records the per-model ACP controls proposal, updated with this
measurement.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant