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
42 changes: 41 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pnpm workspace, six published packages in dependency order:
@relayburn/ledger — append-only JSONL ledger + content sidecar at ~/.relayburn/
@relayburn/analyze — pricing + per-record cost derivation + comparison aggregator
@relayburn/mcp — stdio MCP server exposing read-only ledger queries for in-session self-query
@relayburn/cli — `burn` binary (summary, by-tool, compare, claude/codex/opencode wrappers, mcp-server, …)
@relayburn/cli — `burn` binary (summary, by-tool, compare, `burn run <harness>` wrapper, mcp-server, …)
relayburn — thin install-wrapper so `npm i -g relayburn` exposes the same `burn` bin as `@relayburn/cli`
```

Expand Down Expand Up @@ -75,6 +75,46 @@ The workflow:

A separate `Verify Publish` workflow smoke-tests installs from npm afterward.

## Adding a harness

`burn run <harness>` dispatches through a `HarnessAdapter` registered in `packages/cli/src/harnesses/registry.ts`. Adding a new harness is a one-file addition + one-line registration:

```ts
// packages/cli/src/harnesses/cursor.ts
import type { HarnessAdapter } from './types.js';

export const cursorAdapter: HarnessAdapter = {
name: 'cursor',
sessionRoot: () => '/path/to/cursor/sessions',

// Compute the spawn plan. Inject session ids or transport-level args here;
// populate `sessionId` on the returned plan so beforeSpawn / afterExit can
// see it (claude path).
async plan(ctx) {
return { binary: 'cursor', args: [...ctx.passthrough] };
},

// Pre-spawn side effects. Stamp now if the session id is known up front;
// otherwise drop a pending-stamp manifest the post-spawn ingest can resolve.
async beforeSpawn(ctx, plan) {},

// Optional. Return a controller from `startWatchLoop` to drain a session
// store while the child runs; omit for adapters that ingest a single
// pre-known file at exit.
startWatcher(ctx, onReport) { return null; },

// Final ingest pass after child exits. Return an IngestReport so the driver
// can fold it into the unified `[burn] <name> ingest: ...` line.
async afterExit(ctx, plan) {
return { scannedSessions: 0, ingestedSessions: 0, appendedTurns: 0 };
},
};
```

Then add it to the registry array in `harnesses/registry.ts`. The CLI help block reads `listHarnessNames()` so it updates automatically.

The codex / opencode adapters share the pending-stamp + watch-loop shape; both are constructed via `createPendingStampAdapter` in `harnesses/pending-stamp.ts`. New harnesses with the same shape can reuse it.

## When in doubt

- **Architecture / API surface:** read `README.md` first, then the package's `src/index.ts` for exports.
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

- **Codex compaction parity with Claude.** Codex passive ingest now detects `compacted` session-log records, persists them as ledger compaction events, and anchors them to the preceding Codex turn so compaction-loss analysis can cover Codex sessions instead of only Claude Code.

### Changed

- **`burn run <harness>` consolidates the spawn wrappers** ([#154](https://github.com/AgentWorkforce/burn/issues/154)). `burn claude`, `burn codex`, and `burn opencode` are folded into a single `burn run <claude|codex|opencode>` subcommand backed by a `HarnessAdapter` registry. Adding a new harness becomes a one-file addition + one-line registration; the unified driver also emits a uniform `[burn] <name> ingest: ...` report line. **Breaking change** — the legacy `burn claude` / `burn codex` / `burn opencode` verbs are removed; callers must migrate to `burn run <name>`.

### Removed

- **Legacy `burn claude` / `burn codex` / `burn opencode` verbs** ([#154](https://github.com/AgentWorkforce/burn/issues/154)). Replaced by `burn run <name>`.
- **`burn rebuild-index`** ([#151](https://github.com/AgentWorkforce/burn/issues/151)). The standalone subcommand has been dropped — it was a thin alias for `burn rebuild --index` with identical behavior. Run `burn rebuild --index` instead.

## [0.34.0] - 2026-04-27
Expand Down
29 changes: 10 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,19 @@ This is the composability surface. Burn stays small; the spawner owns the contex

### Spawner-integrated ingest

For Claude Code specifically, burn generates session UUIDs up-front so metadata can be stamped before the agent starts:
All three supported harnesses (Claude, Codex, OpenCode) ship under one verb:

```
burn claude [--tag k=v ...] [-- <claude args>]
burn run <claude|codex|opencode> [--tag k=v ...] [-- <harness args>]
```

The wrapper pre-assigns a session ID, passes `--session-id` to Claude, applies any `--tag k=v` pairs as stamps, and ingests the session into the ledger when Claude exits. If you are building an orchestrator, the same pattern applies: generate the UUID, stamp first, spawn with the UUID, let burn pick up the session log on ingest.
For Claude specifically, the adapter generates a session UUID up-front so metadata can be stamped before the agent starts. It passes `--session-id` to Claude, applies any `--tag k=v` pairs as stamps, and ingests the session into the ledger when Claude exits. If you are building an orchestrator, the same pattern applies: generate the UUID, stamp first, spawn with the UUID, let burn pick up the session log on ingest.

```
burn claude --tag workflow=refactor --tag persona=senior-eng -- --resume abc
burn run claude --tag workflow=refactor --tag persona=senior-eng -- --resume abc
```

The same wrapper pattern applies to Codex and OpenCode:

```
burn codex [--tag k=v ...] [-- <codex args>]
burn opencode [--tag k=v ...] [-- <opencode args>]
```

Codex and OpenCode do not expose Claude-style hooks or a pre-spawn session ID. Their wrappers write a v1 pending-stamp manifest under `$RELAYBURN_HOME/pending-stamps/` before spawning, then resolve it against the first matching session file before the first turn is appended. The wrappers also run burn's foreground watch loop for the child process lifetime, so long sessions become visible incrementally instead of only after exit. Abandoned pending manifests are cleaned up after 24 hours.
Codex and OpenCode do not expose Claude-style hooks or a pre-spawn session ID. Their adapters write a v1 pending-stamp manifest under `$RELAYBURN_HOME/pending-stamps/` before spawning, then resolve it against the first matching session file before the first turn is appended. They also run burn's foreground watch loop for the child process lifetime, so long sessions become visible incrementally instead of only after exit. Abandoned pending manifests are cleaned up after 24 hours.

For passive ingest without a wrapper, run:

Expand All @@ -99,7 +92,7 @@ burn watch [--interval <ms>]

### Spawner env-var contract (workflow / agent attribution)

For orchestrators that spawn many agent sessions, threading `--tag` through every wrapper invocation is awkward. All three wrappers also read a fixed set of `RELAYBURN_*` env vars and fold them into the stamp bag:
For orchestrators that spawn many agent sessions, threading `--tag` through every wrapper invocation is awkward. All three adapters also read a fixed set of `RELAYBURN_*` env vars and fold them into the stamp bag:

| Env var | Stamp key |
|-------------------------------|-----------------|
Expand All @@ -115,15 +108,15 @@ For orchestrators that spawn many agent sessions, threading `--tag` through ever
```bash
export RELAYBURN_WORKFLOW_ID=wf-refactor-auth
export RELAYBURN_AGENT_ID=ag-42
burn codex # workflowId=wf-refactor-auth, agentId=ag-42 stamped
burn opencode --tag agentId=ag-43 # --tag wins → agentId=ag-43, workflowId still inherited
burn run codex # workflowId=wf-refactor-auth, agentId=ag-42 stamped
burn run opencode --tag agentId=ag-43 # --tag wins → agentId=ag-43, workflowId still inherited
```

Other `RELAYBURN_*` variables (`RELAYBURN_HOME`, `RELAYBURN_SESSION_ID`, `RELAYBURN_CONTENT_STORE`, `RELAYBURN_CONTENT_TTL_DAYS`) are burn internals and are **not** treated as stamp tags.

### Hook-based ingest for orchestrators

If your code already controls the Claude Code spawn, you can install burn's hooks per-invocation via Claude's `--settings` flag — no global `~/.claude/settings.json` mutation needed. Hook payloads land on stdin and get forwarded to `burn ingest`, which incrementally parses the transcript with the same cursor+dedup machinery as `burn claude`.
If your code already controls the Claude Code spawn, you can install burn's hooks per-invocation via Claude's `--settings` flag — no global `~/.claude/settings.json` mutation needed. Hook payloads land on stdin and get forwarded to `burn ingest`, which incrementally parses the transcript with the same cursor+dedup machinery as `burn run claude`.

```ts
import { buildClaudeHookSettings, stamp } from '@relayburn/ledger';
Expand Down Expand Up @@ -269,9 +262,7 @@ burn summary [--since 7d] [--project <path>] [--session <id>] [--workflow <id>]
burn by-tool [--since 7d] [--project <path>] [--session <id>] [--provider <p>]
burn waste [--since 7d] [--project <path>] [--session <id>] [--workflow <id>] [--provider <p>]
burn compare [--models a,b] [--since 7d] [--project <path>] [--session <id>] [--workflow <id>] [--agent <id>] [--min-sample <n>] [--fidelity <class>] [--include-partial] [--json|--csv]
burn claude [--tag k=v ...] [-- <claude args>]
burn codex [--tag k=v ...] [-- <codex args>]
burn opencode [--tag k=v ...] [-- <opencode args>]
burn run <claude|codex|opencode> [--tag k=v ...] [-- <harness args>]
burn watch [--interval <ms>] [--once]
```

Expand Down
5 changes: 5 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Codex ingest persists compaction events.** The Codex passive ingest path now appends parser-emitted compactions through the existing ledger compaction writer, so `burn waste --kind compaction` can see Codex context compactions with the same event shape Claude uses.

### Changed

- **`burn run <harness>` consolidates the spawn-wrappers** ([#154](https://github.com/AgentWorkforce/burn/issues/154)). The three top-level verbs `burn claude`, `burn codex`, and `burn opencode` are folded into a single `burn run <claude|codex|opencode>` subcommand, dispatched through a `HarnessAdapter` registry at `packages/cli/src/harnesses/`. Adding a new harness is a one-file addition + one-line registration — no driver changes, no help-block edits. The unified driver also emits a uniform `[burn] <name> ingest: N sessions (+M turns)` report line across all three harnesses (previously claude printed a per-file `[burn] ingested ... turns from <file>` line).

### Removed

- **Legacy `burn claude` / `burn codex` / `burn opencode` verbs** ([#154](https://github.com/AgentWorkforce/burn/issues/154)). The standalone harness verbs are removed in favor of `burn run <name>`. Callers must migrate to the new dispatcher.
- **`burn rebuild-index`** ([#151](https://github.com/AgentWorkforce/burn/issues/151)). The standalone subcommand has been dropped — it was a thin alias for `burn rebuild --index` with identical behavior. Run `burn rebuild --index` instead.

## [0.34.0] - 2026-04-27
Expand Down
25 changes: 10 additions & 15 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@
import { parseArgs } from './args.js';
import { runArchive } from './commands/archive.js';
import { runByTool } from './commands/by-tool.js';
import { runClaudeWrapper } from './commands/claude.js';
import { runCodexWrapper } from './commands/codex.js';
import { runCompare } from './commands/compare.js';
import { runContent, opportunisticPrune } from './commands/content.js';
import { runContext } from './commands/context.js';
import { runDiagnose } from './commands/diagnose.js';
import { runIngest } from './commands/ingest.js';
import { runLimits } from './commands/limits.js';
import { runMcpServer } from './commands/mcp-server.js';
import { runOpencodeWrapper } from './commands/opencode.js';
import { runPlans } from './commands/plans.js';
import { runRebuild } from './commands/rebuild.js';
import { runWrapper } from './commands/run.js';
import { runSummary } from './commands/summary.js';
import { runWaste } from './commands/waste.js';
import { runWatch } from './commands/watch.js';
import { listHarnessNames } from './harnesses/registry.js';

const HARNESS_LIST = listHarnessNames().join('|');

const HELP = `burn — token usage & cost attribution for agent CLIs

Expand All @@ -31,9 +32,7 @@ Usage:
burn plans [add|remove|set-reset-day] … (run \`burn plans help\` for full usage)
burn context [advise] [--project <path>] [--since 7d] [--kind <k>] [--top <n>] [--json]
burn compare [--models a,b] [--since 7d] [--project <path>] [--session <id>] [--workflow <id>] [--agent <id>] [--min-sample <n>] [--json|--csv]
burn claude [--tag k=v ...] [-- <claude args>]
burn codex [--tag k=v ...] [-- <codex args>]
burn opencode [--tag k=v ...] [-- <opencode args>]
burn run <${HARNESS_LIST}> [--tag k=v ...] [-- <harness args>]
burn watch [--interval <ms>] [--once]
burn ingest --runtime claude [--quiet] (reads hook payload on stdin)
burn mcp-server [--session-id <uuid>] (stdio MCP server for in-session self-query)
Expand All @@ -60,9 +59,9 @@ Examples:
burn context --kind claude-md
burn context advise --top 3
burn compare --since 30d --models claude-sonnet-4-6,claude-haiku-4-5
burn claude --tag workflow=refactor -- --resume
burn codex --tag workflow=refactor
burn opencode --tag workflow=refactor
burn run claude --tag workflow=refactor -- --resume
burn run codex --tag workflow=refactor
burn run opencode --tag workflow=refactor
burn watch
burn content prune --days 30
burn archive status
Expand Down Expand Up @@ -104,12 +103,8 @@ async function main(): Promise<number> {
return runContext(args);
case 'compare':
return runCompare(args);
case 'claude':
return runClaudeWrapper(args);
case 'codex':
return runCodexWrapper(args);
case 'opencode':
return runOpencodeWrapper(args);
case 'run':
return runWrapper(args);
case 'watch':
return runWatch(args);
case 'ingest':
Expand Down
106 changes: 0 additions & 106 deletions packages/cli/src/commands/claude.ts

This file was deleted.

Loading