Every command and flag
Every command tldrx has, every flag it declares, the allowed values of every closed set, and the exit codes each one can return.
TIP
This page is generated at build time from src/cli/helpText.ts — the one registry that tldrx <command> --help renders, that the argv guard rejects unknown flags from, and that a drift test holds against the code. It cannot describe a flag that does not exist, and a flag cannot be added without appearing here.
tldrx <command> --help on your own machine is the authority, and it needs no workspace, no run and no network. This page is that output, for a reader who has not installed anything yet.
For a shorter, curated tour of the same surface, see CLI overview.
Exit codes
One table, defined in src/cli/exitCodes.ts. Each command lists below the subset it can return. Ctrl-C on a spawning command exits 130.
0 | ok |
1 | usage or schema error, or a check ran and failed |
2 | refused: a gate said no, or several runs are open and it will not guess |
3 | not found: no workspace, no run, no card by that name |
4 | awaiting a human: the stage ran and stopped at its gate |
5 | the sub-agent failed |
64 | not implemented (reserved; no command in this build returns it) |
Rules that hold everywhere
- An unknown flag is refused, not ignored.
tldrx status --nopeis exit1. --jsonis supported or it is an error. Where a command has no JSON shape, passing it is exit1with--json is not supported by <cmd>— never a silent no-op.--helpon any command prints its usage, flags, allowed values, examples and exit codes, and exits0without needing a workspace.--root <path>works on every command that touches a workspace. Omitted, they use the nearest.tldrx/at or above the cwd.- Ambiguity is refused. With several runs open and no id, a run-targeting command lists them and refuses rather than guessing.
run statusis the exception: it lists them and exits0, because it is the screen you read to find the id. - stdout is data, stderr is progress. The progress view (
--ui) never touches stdout, sotldrx run status --json | jqis unaffected by it.
Environment variables
TLDRX_UI | The progress view, same values as --ui. The flag wins where both are given. |
TLDRX_AGENT_PROVIDER | Which automated runner spawns: claude (default) or codex. |
TLDRX_CLAUDE_BIN | Which binary a Claude sub-agent spawn executes. Default claude, taken off PATH. It replaces the executable NAME only — the arguments are still Claude Code's, so whatever it points at has to speak them. |
TLDRX_CODEX_BIN | Which Codex binary the runner executes. Default codex, taken off PATH. Same late-bound wrapper use as TLDRX_CLAUDE_BIN. |
TLDRX_UPDATE_CHECK | off (also 0, false, no, never) silences the new-version notice. update_check: off in ~/.tldrx/config.yml does it for the machine. |
TLDRX_SUBAGENT | Set to 1 by tldrx on every sub-agent it spawns, and read by the session-start hook, which stays silent for a spawned agent: the "N runs are open" nudge orients a human, and a sub-agent already has its run in its prompt. Not something to set by hand. |
TLDRX_LEARN_SCRIPT | Feeds tldrx learn a scripted sequence of keypresses instead of a terminal. For the tutorial's own tests; not part of ordinary use. |
The commands
tldrx init
Detect the workspace, build the code map, and write down the questions detection could not answer.
| Flag | Meaning |
|---|---|
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
--out <path> | Where to write .tldrx/. Default: the same directory as --root. |
--no-interview | Skip .tldrx/init-questions.md entirely; nothing is asked. |
--process <name> | How the team plans, recorded in .tldrx/process.yml. Values: scrum kanban shape-up none. |
--stack <a,b,…> | Declare the stack instead of detecting it. Comma-separated, e.g. ts,dotnet,python. |
--mcp | Also ask claude mcp list which servers are configured. Slower: it health-checks each one. |
--no-probe | Do not run the detected build/test/lint/typecheck commands. Each one is recorded as skipped rather than measured. Use it on a repo you have not read: probing EXECUTES that repo's own commands. |
--provider <name> | Which map provider to use. auto picks graphify when it is on PATH, else static. Values: auto graphify static. |
--ui <mode> | What to show while it works; every byte of it goes to stderr. Default: auto. TLDRX_UI sets it too. Values: auto scene compact plain off. |
--quiet | No live progress. The report at the end is still printed. |
Exit codes: 0 1
tldrx init
tldrx init --process scrum --stack ts,dotnet
tldrx init --quiet- Deterministic: filesystem, git, and the repo's own build/test/lint/typecheck commands, each run ONCE to record whether it works (
--no-probeskips them). No model runs and tldrx itself sends nothing anywhere. - Most of the wait is the code map:
graphify updateruns once per repo.--provider staticis much faster and still cites every claim. - It PROBES what it detected: each build/test/lint/typecheck command is run once, and the outcome is written to
command_probes:in workspace.yml.runis never probed — it starts a server.
tldrx install
Install the tldrx skill, hooks and status line into .claude/.
| Flag | Meaning |
|---|---|
--claude | The install target. Required — it is the only one today. |
--project | Install into ./.claude/ (the default). |
--user | Install into ~/.claude/ instead, for every project on this machine. |
--skill-only | Install the skill and neither the hooks nor the status line. |
--no-hooks | Skip the hooks. |
--no-statusline | Skip the status line. |
--force-statusline | Replace an existing statusLine setting instead of leaving it alone. |
--uninstall | Remove what a previous install wrote, and nothing else. |
--dry-run | Print what would be written or removed. Writes nothing. |
Exit codes: 0 1
tldrx install --claude
tldrx install --claude --user --dry-runtldrx update
Update tldrx to the latest published version, and print the CHANGELOG between the two.
| Flag | Meaning |
|---|---|
--dry-run | Print the exact npm command and install nothing. |
Exit codes: 0 1
tldrx update
tldrx update --dry-run- It is
npm i -g tldr-experts@latestand nothing more clever — the same command you would type, run for you. - The version it reports is READ BACK from what npm installed (
$(npm root -g)/tldr-experts/package.json), never assumed: this process is the OLD build and has no way to know what the new one is. If that read fails it says so and prints no changelog. - The changelog delta comes from the CHANGELOG.md that shipped with the version just installed, so the text you read is the text that came with the code now on disk.
- On any invocation, a cached one-line notice tells you when a newer version exists. The registry is never called on the hot path: the check runs in a detached child after the output, caches its answer under
~/.tldrx/for a day, and the NEXT invocation reads the cache. It is silent on any network failure, and never appears in--jsonoutput or during hook execution. - Opt out with
TLDRX_UPDATE_CHECK=offfor one shell, orupdate_check: offin~/.tldrx/config.ymlfor the machine.
tldrx doctor
Check the local environment against env.yml and say what is missing.
| Flag | Meaning |
|---|---|
--mcp | Also run claude mcp list. Slow: it live-health-checks every server. |
--json | Print the check results as JSON on stdout instead of the table. |
Exit codes: 0 1
tldrx doctor
tldrx doctor --json- Exit 1 means a REQUIRED tool is missing or below its min_version. An optional tool is reported, never fatal.
- It also reports two things about the WORKSPACE, both warnings that never move the exit code: committed state a
.gitignorerule is swallowing, and any repo whosedefault_branchin.tldrx/workspace.ymldoes not resolve in that repo. The second one is otherwise invisible — it makes the Watch stage refuse and leaves theboundarygate conditionn/aat every Build gate.
tldrx learn
Play the framework in a throwaway sandbox: real commands, a stand-in agent, $0.00.
| Flag | Meaning |
|---|---|
--chapter <n> | Start at this chapter instead of where you left off. An unfinished chapter it depends on is played first. |
--reset | Delete the sandbox and build it again. The only way to start over. |
--list | Print the chapters and which are done, and run nothing. |
--sandbox <path> | Where the throwaway workspace lives. Default: ~/.tldrx-learn. Refused if it sits inside a real workspace. |
--ui <mode> | How much to draw. Same rules as init: a pipe, NO_COLOR or CI degrades to plain whatever you ask for. Values: auto scene compact plain off. |
Exit codes: 0 1
tldrx learn
tldrx learn --chapter 2
tldrx learn --reset- It spends nothing and needs no key: the sandbox installs a stand-in
claudeand points TLDRX_CLAUDE_BIN and PATH at it, so the real CLI is unreachable from a tutorial step. - Nothing is ever written outside the sandbox directory, and it is refused outright if that directory sits inside a tldrx workspace.
- Progress lives in <sandbox>/progress.json, so a bare
tldrx learnresumes at the first unfinished chapter. - With no terminal on stdin (a pipe, CI,
< /dev/null) the chapters play straight through instead of waiting for a keypress.
tldrx status
Everything in this workspace that is waiting on a human, and the command that moves each one.
| Flag | Meaning |
|---|---|
--json | Print the report as JSON on stdout instead of the table. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 3
tldrx status
tldrx status --json- A report: it exits 0 whether or not anything is pending. The only non-zero finding is 3, which means there is no .tldrx/ here at all.
- Untrained experts are printed as advice under the blockers and are NOT counted in the header; they degrade a stage, they do not block one.
tldrx run
Create a piece of work, look at one, drive one to its next human gate, hand it to a host session or back, or get a stuck one moving again.
Subcommands: new · attend · status · estimate · gates · auto · unlock · cancel
| Argument | Meaning |
|---|---|
<slug> | run new: the short name. The id becomes <yymmdd>-<slug>. |
[<run>] | run attend / run status / run estimate / run auto / run unlock / run cancel: a run id. Omit it and the one open run is used. |
<host|--none> | run attend: which way to flip it. host hands the run to a host session and the framework will not spawn on it again; --none hands it back. |
<stage>:<policy> | run gates set: which stage's gate, and who may close it from now on. Qualified always — a bare stage id is refused, because a signature must not rest on a default. |
| Flag | Subcommand | Meaning |
|---|---|---|
--title <t> | new | Human title for the run. Default: the slug. |
--scope <s> | new | Which workflow preset to open the run with. Default: feature. Values: bugfix docs feature hotfix integration migration performance prototype refactor retro security-patch spike upgrade. |
--budget <usd> | new | Total ceiling for the run. Default: the preset's default_budget_usd. |
--repos <a,b> | new | Limit the run to these repos of the workspace. Comma-separated. |
--from <dir> | new | Distil an AI-DLC intent folder into 01-what/ as the run is created. |
--seed <file|dir> | new | Import a document, or a directory of them, as the run's seed. Repeatable — passing it twice adds a second value. |
--gates <a,b|a:agent|all|none> | new | Which stages a PERSON approves; every other gate closes automatically. A qualified entry names the policy outright — plan:agent is a gate an agent may close over an evidence note. Overrides the workflow's gates: wholesale. |
--attended-by <host> | new | Open the run with a host session driving it: the framework writes prompt bundles and judges results, and never spawns. tldrx next then refuses the headless mode (exit 4) and names the --prepare command; run auto is refused outright. Absent (the default) the framework may spawn, exactly as before. Values: host. |
--none | attend | Hand the run back to the framework: it may spawn on it again. The opposite direction to tldrx run attend host. |
--run <id> | attend | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--json | status | Print the run view as JSON on stdout instead of the table. |
--verbose | status | Under each gate row: the two instants behind a stage's duration — or the sentence naming which end run.yml is missing — and the words on a signed gate. The default screen marks a signed note with ✎ and does not quote it. |
--run <id> | status | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--json | estimate | Print the estimate as JSON on stdout instead of the table. |
--run <id> | estimate | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--note <text> | gates | Why this stage's gate may now be closed that way. Required — a gate policy that changed for no recorded reason is the one gate mutation nobody would find later. It is recorded on the gate.policy_changed event. |
--run <id> | gates | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--run <id> | auto | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--max-usd <n> | auto | Stop after the turn that crosses this many dollars. A ceiling on the run, not a brake on the turn in flight. |
--until <stage> | auto | Stop the loop before this stage rather than at the first human gate. |
--parallel <n> | auto | How many stories of ONE build wave run at once. waves.yml already guarantees a dependency is in an earlier wave, so a wave's stories are independent by construction. Merges into the epic still happen in the wave's listed order, after every story of that wave has finished, and each sub-agent keeps its own budget share. The shipped stages/build/stage.yml declares parallel: 2, so two at a time is what a workspace overriding nothing gets; the code fallback, for a stage file that declares none, is 1. Overrides the workflow's build: {parallel: N} and stage.yml's parallel:. |
--prompt-max-bytes <n> | auto | Passed to EVERY next this loop makes: the ceiling on the assembled prompt, over which a stage is refused (exit 2) with the biggest sections named, before a cent is spent. Same precedence as on tldrx next — the flag beats the stage file. Default: the stage's prompt_max_bytes, else 400 KB. |
--max-reads <n> | auto | Passed to EVERY next this loop makes: how many Read/Glob/Grep calls a sub-agent may complete before it is stopped mid-turn. Same precedence as on tldrx next — the flag beats the stage file. Default: the stage's max_reads (120; 200 on build, 60 on watch). |
--gate-agent | auto | When the loop stops for a person, print a DECISION CARD instead of the ordinary status block: the question, its options, the agent's recommendation if an evidence note carried one, and the one command to type. Rendering only — it never upgrades a stage to gates_policy: agent, which is frozen at run new. |
--notify-every <duration> | auto | Send the workspace's declared notify hook a status payload this often while the loop runs — 30s, 10m, 2h, or a bare number of seconds. Off by default, and it does nothing at all unless .tldrx/workspace.yml declares a notify: command (§2.18). A status payload carries what tldrx run status prints. It asks for nothing while the run is moving — and when the run is PARKED on an open question it says so and repeats the literal answer command, because a heartbeat that keeps saying nothing is waiting on you while a run waits on you is worse than silence. |
--retry-failed <n> | auto | How many times in a row the loop may run a FAILED stage again before it stops. 0 — the default, and what every invocation before this got — means one attempt and then exit 5. A retry is the same tldrx next a person would have typed: the stage is on disk as failed with its reason recorded, and the next attempt is told what the last one did. It bounds EXIT 5 AND NOTHING ELSE — a usage error (1), a money refusal (2) and an awaiting-human park (4) are attempted once however large the bound, because each is a decision a person owns; a phase ceiling especially, which means "a human decides about money" and would otherwise become a delay. Only CONSECUTIVE failures count: a stage that succeeds puts the count back to zero. A retry SPENDS — it is a fresh metered stage under the same phase ceiling and the same --max-usd — and when the bound is spent the loop stops on the failure's own exit 5, with the count in the last line. |
--wait-gates <duration> | auto | Instead of exiting 4 the moment a stage parks on a pending GATE, poll the run for this long and resume if somebody signs it. --wait-answers' sibling for the other half of exit 4: a gate is closed by tldrx approve / tldrx reject, not by an answer. Approved → the loop carries on; rejected → it stops and prints the note; lapsed → exit 4 with the same lines it always had, after one gate.timeout notification. It WAITS FOR a signature and never produces one. A stage on gates_policy: agent has already had the engine's own gate signer run on it before this flag ever sees the gate (see the gates_policy: agent note below), so what is left to wait for here is a PERSON — the same wait a human gate gets. Nothing is spent while it waits. Both wait flags may be given together. |
--wait-answers <duration> | auto | Instead of exiting 4 the moment a stage parks on an open question, poll the run's question files for this long and resume if somebody answers. A lapsed wait exits 4 with the same lines it always did, after one question.timeout notification. Nothing is spent while it waits, and the loop never answers its own question — the answer is an ordinary tldrx answer run by a person. |
--model <m> | auto | Model for the sub-agent, passed through to claude --model. Default: the stage's own model:. |
--effort <level> | auto | Reasoning effort for the sub-agent. This is the cost lever: it changes what the turn costs, where --max-usd only ends one late. Values: low medium high xhigh max. |
--yolo | auto | Let the sub-agent run without per-tool permission prompts. It still cannot push. |
--ui <mode> | auto | What to show while a sub-agent runs; every byte of it goes to stderr. Default: auto. TLDRX_UI sets it too. Values: auto scene compact plain off. |
--run <id> | unlock | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--force | unlock | Remove a .lock a LIVE process still holds. Without it a live pid is refused (exit 2): 'the pid was recycled' and 'a colleague is running the stage right now' look identical from here, and only one of them is safe. |
--run <id> | cancel | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--note <text> | cancel | Why the run is being abandoned. Required — an empty note is a usage error (exit 1). Kept in the event log and in run.yml's cancelled:. |
--force | cancel | Cancel a run that a live process still holds the .lock on, and release it. Without it a live pid is refused (exit 2) rather than closed out from under the process working on it. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3 4 5
tldrx run new checkout-v2 --scope feature --budget 40
tldrx run new checkout-v2 --attended-by host
tldrx run attend host 260101-checkout
tldrx run attend --none 260101-checkout
tldrx run status --json
tldrx run status --verbose
tldrx run estimate
tldrx run gates set plan:agent --note "this run predates the agent policy; the pilot signs with evidence"
tldrx run gates set build:human --note "the owner wants to read every merge from here"
tldrx run auto --max-usd 15 --until build
tldrx run auto --parallel 3
tldrx run auto --prompt-max-bytes 500000 --max-reads 300
tldrx run auto --notify-every 10m
tldrx run auto --retry-failed 2
tldrx run auto --wait-answers 30m
tldrx run auto --wait-answers 4h --wait-gates 4h
tldrx run unlock 260101-checkout --force
tldrx run cancel 260101-checkout --note "superseded by the v2 spec"run attend hostis a LOCK, not an engine. It sets one field, spends nothing, runs no stage and touches no branch — and from then on THE FRAMEWORK WILL NOT SPAWN on that run: every turn is atldrx next --prepare/tldrx next --commithandshake with the session driving it, the Build reviewer included.run attend --nonehands it back.run autois an ENGINE, not a lock. It callsnextHEADLESS over and over, so THE FRAMEWORK spawns a metered sub-agent stage after stage, and it stops at the first thing it may not decide: a human gate or an open question (4), a stage failure (5) — unless--retry-failed <n>lets it run that stage again, bounded, up to n times in a row — a phase ceiling or this loop's own --max-usd (2). It is REFUSED ON AN ATTENDED RUN (exit 1, before the event log is opened) — a lock and an engine are alternatives, never layers.run autocan also TELL SOMEBODY. When.tldrx/workspace.ymldeclares anotify:command (§2.18), the loop hands that command oneversion: 1JSON object on stdin at every moment a person is needed — an open question with its options and the literaltldrx answerline, a gate with the literal approve line, a finished or failed run with its exit code and family — plus a periodicstatusunder--notify-every. The framework names no chat tool: the command is the owner's own, run as argv with no shell, and its exit code is recorded asnotify.sent/notify.failedand NEVER changes the run's outcome.- Under
run auto, a stage whosegates_policyisagentgets one bounded GATE-SIGNER turn of its own. When the stage's checks have passed, the engine spawns a single sub-agent at the stage's model and effort, on a quarter of the stage's per-agent ceiling, allowed to read and to write exactly one file:.agent/<stage>/evidence.md. The note then goes through the UNCHANGEDapprove --as-agentpath — the same validator a person's note goes through — soverdict: signplus every condition holding closes the gate under the note's ownby:, and anything else leaves it pending for a person with the reasons named. The turn is recorded like any other (agent.spawned/agent.result,role: gate-signer) and shows up intldrx cost. There is no flag for it:gates_policy: agentis already the owner's recorded decision that an agent may close this gate, andhumangates are never touched. run statuswith several runs open LISTS them and exits 0 — it is the screen you read to find the id every other command wants.run estimateis the one command here that GUESSES, and it says so in its own output. The input half is measured — the next stage's prompt, assembled by the same codenextuses and weighed by the same context ledger. The output half is the median output tokens of past attempts at that stage id, and with no history it prints no estimate rather than inventing one. For what was actually spent, usetldrx cost.run gates setis the ONLY sanctioned way to movegates_policyafterrun newfroze it. It is human-signed likestory reopen: one stage per invocation, the policy named outright, a required --note, and onegate.policy_changedevent carrying actor, moment, note and the old→new value. It changes who may CLOSE a gate from then on; gates already signed are untouched, and a no-op is refused rather than recorded.run unlockdrops a .lock nobody is behind and puts the stage it stranded back to ready. It spends nothing and touches no stage output.run cancelcloses a run for good: cancelled is terminal, sotldrx statusand every id-less command stop seeing it. Nothing is deleted — the stages, outputs, events and money spent stay on disk andtldrx replay <id>still reads them.
tldrx seed
Triage a seed too big for one run into several, then create them.
Subcommands: triage · answer · apply
| Argument | Meaning |
|---|---|
<path> | seed triage: the document or directory to inventory. |
<split.yml> | seed answer / seed apply: the proposal to act on. |
<Qid> <text> | seed answer: the question to answer, and the answer. |
| Flag | Subcommand | Meaning |
|---|---|---|
--out <dir> | triage | Where to write the triage folder. Default: .tldrx/triage/<yymmdd>-<name>/. |
--json | triage | Print the inventory as JSON on stdout instead of the table. |
--threshold-tokens <n> | triage | Size above which a seed is called big enough to split. |
--propose | triage | Spawn a sub-agent to propose the split, instead of only inventorying it. |
--model <m> | triage | Model for the sub-agent, passed through to claude --model. Default: the stage's own model:. |
--effort <level> | triage | Reasoning effort for the sub-agent. This is the cost lever: it changes what the turn costs, where --max-usd only ends one late. Values: low medium high xhigh max. |
--max-usd <n> | triage | Stop after the turn that crosses this many dollars. A ceiling on the run, not a brake on the turn in flight. |
--ui <mode> | triage | What to show while a sub-agent runs; every byte of it goes to stderr. Default: auto. TLDRX_UI sets it too. Values: auto scene compact plain off. |
--prepare | triage | Write the prompt and stop, spawning nothing. Pair with --commit to run the two halves separately. |
--commit | triage | Record the result of a --prepare cycle that was run by hand. Spawns nothing. |
--yolo | triage | Let the sub-agent run without per-tool permission prompts. It still cannot push. |
--dry-run | apply | Say which runs would be created. Creates nothing. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3 5
tldrx seed triage docs/
tldrx seed triage docs/ --propose --max-usd 2
tldrx seed apply .tldrx/triage/260101-docs/split.yml --dry-runtldrx next
Run the run's next stage and stop at its gate.
| Argument | Meaning |
|---|---|
[<run>] | A run id. Omit it and the one open run is used. |
| Flag | Meaning |
|---|---|
--run <id> | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--dry-run | Say which stage would run, with its inputs and budget. Spawns nothing and writes nothing. |
--prepare | Write the prompt and stop, spawning nothing. Pair with --commit to run the two halves separately. |
--commit | Record the result of a --prepare cycle that was run by hand. Spawns nothing. |
--review | --prepare/--commit only: this half of the handshake is for the story's REVIEWER, not its developer. --prepare --review writes the reviewer bundle (prompt, diff refs, the DoD already re-run, and the result schema) into .agent/<stage>/<story>/review/ and spawns nothing; --commit --review reads that bundle's result.json as the {verdict, summary, findings} envelope and settles the story by the same rules a spawned review does. Bare --prepare already routes here on its own when a story is waiting on a review. |
--check | --commit only: REHEARSE the commit. Validates the prepared bundle's result.json through the same reader --commit uses, prints every refusal with the offending line, and WRITES NOTHING — no state, no event, no attempt spent. Exit 0 when --commit would read the envelope, 1 when it would not. Its reason for existing is the reviewer envelope: a refuted finding whose [src: …] citation is not the last thing on its line is refused at --commit --review, and until now that refusal could only arrive after the turn had been paid for. On a DEVELOPER bundle the reader is deliberately tolerant — a missing outputs is read as [], not refused — so --check exits 0 and NAMES what is about to be coerced. It checks the result envelope, not the stage's gate. |
--fixlist <path> | --prepare only: re-prepare the story's DEVELOPER bundle around a reviewer's fix list (04-build/fixlist/<story>-<n>.md). The numbered findings still marked fix-now are rendered under ## Fix list in the prompt with their Do NOT lines verbatim, and the prior turn's session_id is carried in pending.json as resume_session so the host can resume that sub-agent rather than pay to rebuild its context — the framework resumes nothing itself. Omit it and the latest round on disk is carried by itself; the flag is for naming a different file. |
--model <m> | Model for the sub-agent, passed through to claude --model. Default: the stage's own model:. |
--effort <level> | Reasoning effort for the sub-agent. This is the cost lever: it changes what the turn costs, where --max-usd only ends one late. Values: low medium high xhigh max. |
--max-usd <n> | Stop after the turn that crosses this many dollars. A ceiling on the run, not a brake on the turn in flight. |
--prompt-max-bytes <n> | Ceiling on the ASSEMBLED PROMPT for this run. Over it the stage is refused (exit 2) with the biggest sections named — before a cent is spent, which is the difference between this and --max-usd. Default: the stage's prompt_max_bytes, else 400 KB. |
--max-reads <n> | How many Read/Glob/Grep calls the sub-agent may complete before it is stopped mid-turn. The brake --max-usd is not: it ends a turn that has already been paid for. Default: the stage's max_reads (120; 200 on build, 60 on watch). |
--cost-usd <n> | --commit only: what the host session's sub-agent actually cost. An in-session turn has no meter of its own — it was billed to the host — so with nothing declared the task is recorded cost_usd: null, metered: false rather than $0.00, which would be a measurement and a false one. |
--tokens <n> | --commit only: tokens the host session used, recorded beside the declared cost. Optional. |
--yolo | Let the sub-agent run without per-tool permission prompts. It still cannot push. |
--keep-worktrees | Leave the per-story worktrees on disk after the build stage finishes with them, and the run's epic worktrees on disk after the run closes. The epic checkouts survive the Build stage either way (a later Watch stage cites code that is committed on the epic branch and merged nowhere); this flag is what makes them survive the run itself. Remembered on the run, so a close by tldrx approve or tldrx run cancel honours it too. |
--parallel <n> | How many stories of ONE build wave run at once. waves.yml already guarantees a dependency is in an earlier wave, so a wave's stories are independent by construction. Merges into the epic still happen in the wave's listed order, after every story of that wave has finished, and each sub-agent keeps its own budget share. The shipped stages/build/stage.yml declares parallel: 2, so two at a time is what a workspace overriding nothing gets; the code fallback, for a stage file that declares none, is 1. Overrides the workflow's build: {parallel: N} and stage.yml's parallel:. |
--discard-pending | Bin an orphaned --prepare bundle and run the stage again. Without it a stage left running with a bundle on disk is refused (exit 2) rather than re-spawned, because that would throw away a sub-agent turn this run has already paid for. On a build stage running off an implicit plan (a scope that skips Plan) it also DERIVES THE PLAN AGAIN from the run's handoff and answers — but only while nothing has been built off it: a plan with recorded evidence, or whose story branch already carries a commit, is kept and the reason said. |
--reuse-epic | Let the build stage adopt an existing epic/<slug> branch this run did not cut. Without it a foreign epic branch is refused rather than stacked onto. |
--ui <mode> | What to show while a sub-agent runs; every byte of it goes to stderr. Default: auto. TLDRX_UI sets it too. Values: auto scene compact plain off. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3 4 5
tldrx next
tldrx next --dry-run
tldrx next 260101-checkout --effort high --max-usd 8
tldrx next --discard-pending
tldrx next --prompt-max-bytes 120000 --max-reads 60
tldrx next --parallel 3
tldrx next --prepare --review
tldrx next --prepare --fixlist 04-build/fixlist/S5-1.md
tldrx next --commit --review
tldrx next --commit --review --check- Exit 4 is the normal end of a successful stage: it ran, it wrote its outputs, and a person now has to approve.
- Both halves of the handshake now write
result_schemainto pending.json — the reviewer's REVIEW_SCHEMA and the developer's {outputs, questions_asked, notes} envelope plus thecost_usd/session_ida host may declare. Read the shape out of the bundle; do not reconstruct it from memory or from a sibling story. - --prepare and --dry-run print the CONTEXT LEDGER: bytes per section of the prompt, the total against prompt_max_bytes, and any declared input that had to be truncated.
tldrx run estimateprints the same ledger with a price on it. - On
--commit --reviewthere is no turn left to configure, so --model and --effort are read as the host's DECLARATION of what judged the diff — the same reading --cost-usd and --tokens already get on --commit — and are recorded on the verdict as basis: host-declared. Declare nothing and the record saysnot recorded: the reviewer bundle's own model is a suggestion tldrx made, and repeating it back would be quoting a suggestion as a measurement of your session. Everywhere else the two flags override the stage's model:/effort: for every sub-agent of the invocation, including a reviewer a stage.yml reviewer:/reviewer_by_stakes: block would otherwise have moved.
tldrx answer
Answer one open question from the command line, recording it as a fact.
| Argument | Meaning |
|---|---|
<Qid> | The question id, e.g. Q3. |
<text> | The answer. Quote it if it has spaces. |
| Flag | Meaning |
|---|---|
--supersede | REVERSE a decision this question already recorded. Only valid on an ANSWERED question. The old fact keeps its text and gains superseded_by; a new fact carries this answer with the same area, and with the repos its predecessor bound to unless --repo or the question's own affects: rescopes it; the block keeps its original [Answer]: line and gains a superseding one plus a footer. Everything that FEEDS a decision — no-re-ask, every block, the training miner, the implicit plan — then reads the new fact and not the old one. Without it, answering an answered question is refused, because an answer is recorded once. |
--decided-by <who> | Who decided, as against who typed it. OPTIONAL here, and required on facts add: this command is also driven by the answer-capture hook, which fires on an agent's own Write and on a human's edit and so cannot honestly say either. Absent means “not stated”, never “owner”, and the command says so on stdout. Values: owner driver. |
--repo <name> | Scope the answered fact to one repo, so every block outside it stops carrying a decision that was never about it. Repeatable. A name no repo in workspace.yml answers to is refused before anything is written. Repeatable — passing it twice adds a second value. |
--run <id> | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx answer Q3 "Redis sorted set, one key per tenant"
tldrx answer Q3 "Postgres table after all — the contention risk was refuted" --supersede
tldrx answer Q4 "B — rankings are global" --decided-by owner --repo api- A second reversal supersedes the SECOND answer, not the first: the chain is walked to its head, so
--supersedecan be used as many times as an owner changes their mind and facts.yml stays a single-link reciprocal chain. - Nothing is erased.
tldrx replayrenders the reversal as its own line (fact.superseded),tldrx retrostill lists the old fact and labels it(superseded by F<n>), and the words originally typed stay in questions.md. - Without
--repo, the fact is scoped by the question's ownaffects:when an entry there names a repo (apiorapi:src/db.ts), and by nothing otherwise —repos: []means “no repo was named”, never “every repo”. Anaffects:entry that looks likerepo:pathand matches no repo is named on stdout — for every block the invocation captured, not just the one it named, each line carrying its question id. The answer-capture hook reports the same thing through the context it posts. - A recorded answer is checked against the facts already live, on both paths, and a hit RAISES — it never refuses. The answer stands, this command still exits 0, the new fact carries
conflicts_with, one question is appended to the same questions.md asking which of the two holds, and onefact.conflict_raisedgoes on the ledger. That block is markedadvisory:, so it does not hold an auto gate, does not park a run atawaiting_answer, is not counted byskip_ifand is not whattldrx statussays the run is waiting on — the gate says how many it skipped, and every reader that LISTS questions (tldrx questions, the run close, the decision cards,tldrx replay, the status line) names it like any other open question. - What that check can and cannot see, stated because a raise you cannot calibrate is worse than none: it is LEXICAL — Jaccard ≥ 0.6 on tokens of 4 characters or more, scoring the QUESTION's title against each candidate fact's whole text, and only within the SAME
area. So it cannot see two differently-worded answers that contradict in meaning, it drops short words entirely, and an answer identical to the recorded one is read as agreement rather than a clash. Refusing on a signal like that could deadlock an unattended run, which is why it raises instead.
tldrx interview
Work through the open questions in the terminal, one at a time.
| Flag | Meaning |
|---|---|
--run <id> | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--init | Answer .tldrx/init-questions.md instead of a run's questions.md. |
--yes-to-defaults | Take the first option of every question that offers one. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx interview --init
tldrx interview --run 260101-checkout- This is the only way to answer the INIT questions: editing .tldrx/init-questions.md by hand fills the slot but records no fact and writes no process.yml.
tldrx questions
Read this run's open questions as decision cards, or check the file the §2.7 parser reads.
Subcommands: lint · cards
| Argument | Meaning |
|---|---|
[<run>] | A run id. Omit it and the one open run is used. |
| Flag | Subcommand | Meaning |
|---|---|---|
--run <id> | (all) | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--fix | lint | Rewrite the blocks the parser cannot see into the grammar, without changing a word: the title, the reason, every option and any answer already typed come across verbatim. What is added is the heading separator, the metadata comment and the [Answer]: slot. |
--area <a> | lint | The area stamped on a block --fix has to write metadata for, when the prose form recorded none. Default: general. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx questions cards
tldrx questions cards 260101-checkout
tldrx questions lint
tldrx questions lint --run 260101-checkout --fix- A heading that misses
## Qn · Titleis not half-read, it is read as ABSENT — so everything downstream reports "0 open questions" and an auto gate signs itself over them.lintnames every block in that state and exits 2. cardsrenders each OPEN question as a printable decision card: two lines of context, the question's ownWhy asked:note verbatim (the slot for what the binding docs already decide), and the file's lettered options — or a NEEDS-OPTIONS marker when it carries none, because inventing the choices would be answering the question in the act of asking it. It reads only: answers still flow throughtldrx answer, and every card prints the exact line to type. No open question is a sentence and an exit 0.
tldrx approve
Approve the gate the run is sitting at.
| Flag | Meaning |
|---|---|
--run <id> | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--note <text> | What you are approving and why. Kept in the event log. |
--as-agent | Sign an agent gate with the evidence note at .agent/<stage>/evidence.md. The note is validated by the §2.8 machinery first, the gate records the note's by: as the actor, and the note is copied to <phase>/gate-evidence/<stage>.md where it is committed. Refused (exit 1) on a stage whose policy is not agent. |
--evidence <path> | Read the evidence note from here instead of .agent/<stage>/evidence.md. Only means something with --as-agent. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3 4
tldrx approve --note "design lands on real paths"
tldrx approve --as-agent- A person may always approve an agent-gated stage with no flag at all. That is an override, it is recorded as a person, and it is the point of the split: an agent gate is one an agent MAY close, never one a person may not.
- --as-agent has two refusals and they mean different things. Exit 2 is "this note is broken" — fix the file. Exit 4 is "a person decides": the note parsed perfectly and its verdict is
refuseorsign-with-fixlist, which is the agent doing its job.
tldrx gate
Write the skeleton evidence note an agent gate is closed over.
Subcommands: template
| Argument | Meaning |
|---|---|
[<run>] | A run id. Omit it and the one open run is used. |
| Flag | Subcommand | Meaning |
|---|---|---|
--run <id> | (all) | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--force | template | Replace an evidence.md that is already on disk. Without it an existing note is left alone and the command exits 2 — a written note is the artefact a gate rests on, and a blank form is not worth destroying it for. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx gate template
tldrx gate template --run 260101-checkout- It writes
.agent/<stage>/evidence.mdwith the MEASURED fields filled — the gate at the cursor, the time, how many citations the §2.8 resolver found in this stage's outputs, and how many touched paths the plan declares — and every judgement blank. The blank form deliberately does not validate: a template that parsed clean out of the box would be a signature nobody had to earn. - Non-signing. It spends nothing, spawns nothing, approves nothing and moves no cursor.
tldrx reject
Send the current stage back with a note saying what has to change, or revoke an approval already given.
| Flag | Meaning |
|---|---|
--note <text> | What has to change. Required — a rejection with no reason is not actionable. |
--stage <phase>/<stage> | Revoke an approval already given, whoever signed it: the cursor moves back to that stage, one gate.revoked is appended carrying signed_by, and later stages that had run are marked stale — their files stay on disk and stop counting as current. Nothing is deleted and no cost is refunded. The one verb that may reopen a finished run. |
--run <id> | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx reject --note "contracts.md does not name the events"
tldrx reject --stage 02-how/design --note "the auto gate signed over four open questions"tldrx story
Give one Build story another run of attempts, open a fix round on a done one, or widen the paths it declares — each signed with a note.
Subcommands: reopen · widen
| Argument | Meaning |
|---|---|
<id> | The story id, e.g. S3. |
<path>… | widen only: one or more repo-relative paths to add to the story's touches:. Positional, and repeatable by writing them one after another. |
| Flag | Subcommand | Meaning |
|---|---|---|
--note <text> | reopen | Why this story must be built anyway — or, with --for-fix, WHICH DEFECT is being fixed. Required — a reopen with no reason is not actionable. It is recorded on the story.reopened event and printed by the Build stage when the story runs again. |
--note <text> | widen | WHY the surface grew — what the story turned out to have to touch, and why that is this story's work and not another's. Required, and recorded on the story.touches_widened event beside the list before and after, so a surface never grows without a stated reason. |
--for-fix | reopen | Open a FIX ROUND on a story that is done: one named defect in work a reviewer already approved. No attempt is consumed, the fix passes the same dod and the same reviewer as the original, and the story's acceptance criteria are not touched — it is not a way to relitigate scope. Refused when the story is not done, when --note is missing, and when that story already has a fix round open (the bound is one). |
--run <id> | (all) | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx story reopen S3 --note "it gates wave 3 (S4, S6) and the owner has decided it ships"
tldrx story reopen S11 --for-fix --note "linkEmail succeeds then setDisplayName fails: account linked, score never claimable"
tldrx story widen S3 platform/Auth.cs --note "the tenancy check the story is for lives here too"- Reopenable states are
blocked,reviewandin_progress. Adonestory refuses: undoing finished work is a decision about the STAGE, so it istldrx reject --stage <phase>/<stage>— or, for ONE named defect in it,--for-fix. Atodostory refuses too — it is already pending. --for-fixis the arc the other two do not cover:done→ fix round. It recordsstory.reopenedwithreason: fix, consumes no attempt, and the round stays open until the story isdoneagain, which is what bounds it to one at a time. It exists because an accepted defect in a done story otherwise has no sanctioned path: rejecting the whole Build stage destroys every other story's closure, and fixing it outside the story machinery leaves an epic-level commit with no story provenance.- The story goes back to
todoand its attempt counter restarts at 1 of 2. Nothing is erased to make that true:story.reopenedis a reset boundary the review ledger reads, every earlier attempt stays in events.jsonl, and the event records how many verdicts the closed run consumed. - It runs no agent, spends nothing, deletes nothing and refunds nothing. The story's branch is kept — that is what carries the last developer's commits forward — and its worktree is left exactly as the build left it, to be reopened from the branch if the build had removed it.
- It does NOT make the stage runnable. If the Build stage is at its gate,
tldrx reject --note "…"sends it back toreadyfirst; if the gate is already signed,tldrx reject --stagetakes that back. widenadds paths to a story'stouches:— the sanctioned form of the advice the boundary decision card gives when a Build stage changed a path nobody scoped. Widenable states aretodo,in_progress,reviewandblocked. Adonestory refuses: its evidence was written against the surface it DECLARED, and widening it afterwards would make the record say the plan declared a path it did not — reopen it with--for-fixfirst. A path the story already declares refuses too, because a widening that widened nothing is a record of something that did not happen.widenruns no agent, spends nothing, consumes no attempt, changes no status and moves no cursor — it declares scope and nothing else. No gate code knows about it: the boundary condition re-readstouches:off disk at evaluation time, so the same run, the same branch and the same diff simply stop counting the widened path as outside the surface at the next evaluation. Onestory.touches_widenedrecords the paths, the note and the list before and after.
tldrx note
Record one operator annotation on a run's event log, at the moment it happened.
| Argument | Meaning |
|---|---|
[<run>] | A run id. Omit it and the one open run is used. |
<text> | The annotation. Required — an empty note is a usage error. |
| Flag | Meaning |
|---|---|
--stage <id> | Key the note to one stage of this run, as plan or 03-plan/plan. Absent, the note is about the run. A stage this run does not have is refused and nothing is written. |
--run <id> | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx note "owner-delegated resync of 8 story dod blocks, done by hand"
tldrx note 260829-scoring --stage build "S1..S8 dod blocks resynced from workspace.yml"- It appends exactly one
operator_noteevent and touches NOTHING else: run.yml and budget.yml are byte-identical across the call, no gate is signed or revoked, no cursor moves and no money is spent. - It exists because there was no honest carrier for a maintenance action at the moment it happened. The alternatives people reached for were a FUTURE gate note (late, and attached to a decision the note is not about) and
tldrx reject(which undoes work). - The note shows up in
tldrx run status(the last few) and intldrx replay(every one, in place).
tldrx facts
Record one durable, provenanced fact the later prompts will read.
Subcommands: add
| Argument | Meaning |
|---|---|
"<text>" | The assertion, one sentence. Required. |
| Flag | Subcommand | Meaning |
|---|---|---|
--area <id> | add | Which area the fact is about. Required — it is how every reader of facts.yml scopes a match. |
--decided-by <who> | add | Who decided, as against who typed it. Required — a driver default is never cited as the owner's. Values: owner driver. |
--kind <kind> | add | What sort of fact this is. Values: answer observed derived. |
--confidence <level> | add | How well it is known. measured means you ran the check. Values: measured inferred stated. |
--repo <name> | add | Scope the fact to one repo. Repeatable. A name no repo in workspace.yml answers to is refused before anything is written. Repeatable — passing it twice adds a second value. |
--run <id> | add | Attribute it to this run. An id no run in tldrx-work/ answers to is refused (exit 3) before anything is written — asking for provenance by name and getting run: null instead is worse than not asking. Without it, one open run is used; with several open, the fact is still recorded and its run is left absent with a named reason on stdout, because provenance nobody can establish is written as missing, never guessed. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 3
tldrx facts add "The outbox lives in the billing repo." --area billing --decided-by owner --kind observed --confidence measured
tldrx facts add "Retries are capped at three." --area billing --decided-by driver- A fact is one assertion, capped at 2000 characters. Over the cap it is cut, ends in
…, and carriestruncated: true— and the command says so on stdout, because a marker only a later reader sees is one the author never acts on. - It writes through
FactsStore, under the workspace lock: load, mint the id, cap, validate, save. Editing.tldrx/memory/facts.ymlby hand walks past all four. --decided-byis required, never defaulted: a fact gets cited later, and a row that cannot say which of the two decided it must not imply the stronger one (the owner's) by silence.--run <id>that names nothing is exit 3, not a silently unattributed fact:RunStore.resolveanswersnoneboth to 'no run is open' and to 'that id is not here', and writing the second one as the first printed a sentence that is false whenever a run IS open.
tldrx ship
Open a pull request from the run's epic branch — one per repo the branch is in — with a body written from the run's handoff.
| Argument | Meaning |
|---|---|
[<run>] | A run id. Omit it and the one open run is used. |
| Flag | Meaning |
|---|---|
--branch <name> | Which epic branch to open the PR from, when the run cut more than one. It must be one of the run's own — an unrelated branch is refused. |
--repo <name> | Ship to ONE repo only. Without it, a branch that exists in several repos gets one PR in each. |
--base <branch> | What to open the PR against. Default: that repo's default_branch from .tldrx/workspace.yml. |
--draft | Open it as a draft PR (gh pr create --draft). |
--dry-run | Run every check and print the exact gh command, creating nothing. |
--run <id> | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx ship
tldrx ship 260829-scoring --dry-run
tldrx ship --branch epic/leaderboard --draft- It NEVER pushes. tldrx does not publish a branch on its own (spec §5), so a branch the remote has not seen is a refusal that names the
git pushcommand rather than running it. - The body is WRITTEN for a PR (#167): an Outcome line saying how much of the plan landed (
partial: 1 of 3 stories delivered; S2 — …); what shipped and what did not, from the handoff's own done/not-done split; the reviewer findings still open, read from the run's fix lists; and the LAST phase handoff the run has on disk —04-build/handoff.mdon a run that built something — verbatim and complete, inside a<details>block. It goes toghas a file, never as an argument, so a long body cannot overflow an argv limit. - It REFUSES a run that delivered no story (exit 1 — nothing behind it, #210).
shipalready knew: the body it renders keeps only the handoff bullets that saydone, and with none it wrote(nothing settleddonein this run)into a PR it opened anyway. The refusal names the counts and the first blocked story's own reason, and--dry-runis refused in the same words. A run that delivered at least one story ships exactly as it did before. - It is read-only about the run: no event, no gate, no cursor. To mirror the plan's epics and stories to a ticket tool,
tldrx tickets syncis the verb that does that, and it stays separate. - It refuses cleanly, in a sentence, when there is no epic branch, no handoff, no remote, no
ghon PATH, or when several epic branches leave the choice open. - When the branch exists in SEVERAL repos — the normal shape of a chained multi-repo run, whose epics share one integration branch — it opens one PR per repo: the same body, the repo name in the title, and every URL listed at the end.
--reponarrows it to one. - A partial failure names both sides: the PRs that were opened, with their URLs, and the repos that failed, with the reason. Run it again to retry the rest — a repo whose PR is already open is skipped, so re-running opens nothing twice.
- It refuses an epic branch that carries changes under
tldrx-work/or.tldrx/, and names them. Those paths are written LIVE into the workspace checkout for the length of a run, so a PR that merges them makes the nextgit pullthere refuse. The refusal prints the two commands that take them back off the branch — a forward commit, never a rebase. A path a story atstatus: doneDECLARES in itstouches:is subtracted first and the refusal says which story excused it; an unsettled story's declaration excuses nothing.
tldrx plan
Carry an edited workspace.yml into approved stories' dod blocks, or print the plan schema.
Subcommands: sync-dod · schema
| Flag | Subcommand | Meaning |
|---|---|---|
--dry-run | sync-dod | Print the same per-story diff summary and write nothing. |
--story | schema | plan schema: print only the story example — a file the check accepts as it stands. |
--epic | schema | plan schema: print only the epic example. |
--waves | schema | plan schema: print only the waves.yml example. |
--run <id> | (all) | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx plan sync-dod --dry-run
tldrx plan sync-dod --run 260101-checkout
tldrx plan schema --storyplan schemaprints the story/epic/waves contract — the SAME bytes the Plan agent is given, generated from the validators theplancheck runs, so it cannot drift from what will be accepted. At most one of --story/--epic/--waves; without one the whole contract is printed. It resolves no workspace and no run, touches no disk and spends nothing, because the question comes before any of those exist.- A story dod command must equal a
workspace.ymlcommand verbatim, so editing workspace.yml orphans every approved story that cited the old string. This is the mechanical repair, and it does not weaken that rule by a byte. - Four outcomes per line, and only the first three write anything: a line the current workspace still declares is left alone; a line a PREVIOUS version declared under a role the current file still has becomes that role's command; a line whose role is gone is dropped; and a line no version of workspace.yml ever declared is FLAGGED and its story is left untouched — that is real drift, not a rename, and guessing at it is the one thing this must not do.
- The ancestry comes from git's history of
.tldrx/workspace.yml. In a workspace with no history there are no ancestors, so every non-current line is flagged rather than rewritten. - Nothing else in a story moves: the front matter, the prose and the fences come back byte-identical, the previous version is kept at
<story>.md.bak, and the result is validated by the same plan check the drift came from. It runs no agent, spends nothing and moves no cursor.
tldrx budget
What the run may still spend, where to move a ceiling from, and what the owner authorized.
Subcommands: show · raise · grant
| Argument | Meaning |
|---|---|
[<run>] | budget show: a run id. Omit it and the one open run is used. |
<phase> | budget raise: the phase whose ceiling goes up, e.g. 04-build. |
<usd> | budget raise: how much to ADD to that phase's ceiling — a delta, not a new ceiling. raise 04-build 5 turns a $20 ceiling into $25. budget grant: the CEILING the owner authorized — a total, not a delta, and it moves no money. |
| Flag | Subcommand | Meaning |
|---|---|---|
--run <id> | (all) | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--json | show | Print the budget view as JSON on stdout instead of the table. |
--take-from <phase> | raise | Move the money out of this phase instead of raising the run's total. |
--note <text> | raise | Why the ceiling moved. Recorded on the budget.raised event beside the before/after and the actor. |
--note <text> | grant | Why the grant was recorded. Kept on the budget.granted event beside the amount, the fact and the actor. |
--fact <F> | grant | REQUIRED by grant: the live fact id the authorization cites, e.g. F031. A grant with no decision behind it is a number nobody said. |
--phase <phase> | grant | Scope the grant to one phase instead of the whole run. The fact id is still recorded at run level. |
--on-exceed <policy> | grant | What a ceiling ABOVE the grant does. Default: warn. Never on_exceed, which governs spending past a ceiling rather than writing one. Values: warn block. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx budget show
tldrx budget raise 04-build 25 --take-from 02-how
tldrx budget grant 20 --fact F031 --on-exceed blockraiseADDS.raise 04-build 25on a phase already ceilinged at $10 leaves it at $35, not $25 — the amount is a delta, and the run ceiling grows with it unless --take-from moves the money.budget showprints the exact command, already sized to the shortfall, when a stage is blocked; pasting that is the way to raise without doing the arithmetic.grantRECORDS, it does not spend: it writes authorized_usd, authorized_by, authorized_at and on_grant_exceed into budget.yml and appends a budget.granted event. No ceiling moves, and a grant the current ceiling already exceeds is still recorded — the money is committed, there is nothing left to refuse.raisethen measures the ceiling it is about to write against it: a PHASE grant against the phase ceiling, the RUN grant against the run ceiling.- Two exit families, two conditions. A bad amount, an unknown phase, an unknown --on-exceed value, or a --fact naming no live fact is a USAGE error: exit 1, nothing written. A ceiling above the recorded grant under on_grant_exceed: block is a GATE refusal: exit 2, budget.yml byte-identical. Under the default warn the ceiling is written and one sentence names the grant, the fact and the figure.
tldrx cost
What the work actually cost — per attempt, per stage, per run.
| Argument | Meaning |
|---|---|
[<run>] | A run id. Omit it and the one open run is used; several open runs is a refusal, never a guess. Ignored with --all. |
| Flag | Meaning |
|---|---|
--run <id> | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--all | Every run in the workspace, finished ones included, totalled per economy. The run argument is ignored. |
--stories | Per story: what it measurably cost, beside the ceiling its spawn was given (agent.spawned.max_budget_usd), and the ratio. Off events.jsonl only; no plan document carries a per-story dollar figure, so none is invented. |
--json | Print the cost breakdown as JSON on stdout instead of the table. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 3
tldrx cost
tldrx cost --all
tldrx cost --stories
tldrx cost 260101-checkout --json- Read off
events.jsonland nothing else, and the log holds two kinds of number that are never added to each other: the MEASURED dollars a metered turn reported on anagent.resultline, and — with--stories— the SPAWN CEILINGS the executor handedagent.spawned, which are caps it computed rather than charges. No token count is ever multiplied by a price —tldrx run estimateis the command allowed to guess, and it says ESTIMATE in words. - Attempts are never merged. A stage that failed twice cost three turns, and that retry is usually the money you are looking for.
- Work this process never saw a cost for is reported as UNMETERED rather than summed as $0.00 — a missing number and a free turn are not the same claim.
--alland--storiesare two different reports and cannot be combined: the pair is refused (exit 1), never silently resolved in favour of one.--storieschanges no ceiling and spends nothing. It is the measurement side: the ceiling a story is reported against is the one the executor computed and handed the spawn, never a share of a plan — story files carry no budget key at all — and it is the input a recalibration of those ceilings would need.
tldrx map
Build, refresh or drift-check the code knowledge base under .tldrx/map/.
Subcommands: --refresh · --check
| Flag | Meaning |
|---|---|
--refresh | Re-detect the workspace and rewrite .tldrx/map/**. |
--check | Resolve every [src: path:line] citation in the map against the filesystem. Exit 1 lists the ones that no longer land. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
--provider <name> | Which map provider to use. auto picks graphify when it is on PATH, else static. Values: auto graphify static. |
Exit codes: 0 1
tldrx map --refresh
tldrx map --check- One of --refresh or --check is required; they are the subcommands, spelled as flags.
tldrx expert
List or create experts, recompute or rescore their levels, train one, or switch the stack packs on.
Subcommands: list · create · train · recompute · rescore · packs
| Argument | Meaning |
|---|---|
<name> | expert create / train: the expert. expert recompute / rescore: optional — all of them by default. |
<enable|disable|status> | expert packs: turn the stack packs on or off for this workspace, or print their state. |
| Flag | Subcommand | Meaning |
|---|---|---|
--json | list | Print the table as JSON on stdout instead of the table. |
--area <id> | create | Seed the new expert's first competency area, at level 0. Without an area there is nothing to train, and expert train refuses it. |
--title <text> | create | Name that area. Light mode greps the words of the area title to choose which files the expert is shown, so it is worth writing. |
--role <slug> | create | Create a ROLE expert with the shipped body for that role. |
--domain <slug> | create | Add one domain area to the new expert, at level 0. |
--stack <lang> | create | Add one stack area to the new expert, at level 0. |
--area <area> | train | Which competency area to train. Required. |
--mode <mode> | train | light reads the code; full mines finished runs' handoffs. A role expert only trains full. Values: light full. |
--max-usd <n> | train | Stop after the turn that crosses this many dollars. A ceiling on the run, not a brake on the turn in flight. |
--model <m> | train | Model for the sub-agent, passed through to claude --model. Default: the stage's own model:. |
--effort <level> | train | Reasoning effort for the sub-agent. This is the cost lever: it changes what the turn costs, where --max-usd only ends one late. Values: low medium high xhigh max. |
--prepare | train | Write the prompt and stop, spawning nothing. Pair with --commit to run the two halves separately. |
--commit | train | Record the result of a --prepare cycle that was run by hand. Spawns nothing. |
--yolo | train | Let the sub-agent run without per-tool permission prompts. It still cannot push. |
--print-prompt | train | Print the training prompt and stop. Spawns nothing and costs nothing. |
--ui <mode> | train | What to show while a sub-agent runs; every byte of it goes to stderr. Default: auto. TLDRX_UI sets it too. Values: auto scene compact plain off. |
--json | recompute | Print the results as JSON on stdout instead of the table. |
--area <area> | rescore | Rescore only this area. Every area a knowledge file names, by default. |
--json | rescore | Print the results as JSON on stdout instead of the table. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3 5
tldrx expert list
tldrx expert create billing --area money --title "Invoicing, proration and refunds"
tldrx expert train billing --area money --mode light --print-prompt
tldrx expert train billing --area money --mode full --model sonnet
tldrx expert recompute --json
tldrx expert rescore
tldrx expert packs enable
tldrx expert packs status--max-usddefaults to $2.00 in light mode and $3.00 in full, because full mode spawns TWO sub-agents and splits the ceiling between them. Measured full trainings cost $1.21-$1.60 end to end on a mid model.--preparegets the same line and the same arithmetic: it does not spawn, but it freezes the ceiling intopending.jsonand into the prompt a host session spends against. An explicit--modelthat cannot fit is refused there too; an inherited one warns, because tldrx cannot see which model the host session will pick.- An expert with no competency area cannot be trained:
expert trainrefuses it and prints theareas:block to add to.tldrx/experts/<name>/competencies.yml.create --area <id>writes that block for you.createalso writes the front-matterrepos:from.tldrx/workspace.yml— the## Domainbullets are paths RELATIVE to those repos, with no repo prefix. recomputeandrescoreare different remedies and neither spends money.recomputeis arithmetic over the evidence rows already incompetencies.yml, for a level that drifted.rescoreRE-READSknowledge/*.mdand derives their evidence again under today's rules — the one to reach for after a change to what counts as evidence, so a workspace does not have to buy readings it already paid for. Rescored rows are dated by the file's owntrained_at, or by the expert'slast_trainedwhen it has none; never by the clock, because §2.6 weighs recency. Each also carriesrescored_at— when it was SCORED, as againstat, when the claim was READ — and the run appends oneevidence.rescoredline totraining.jsonlat $0.00, dated when it ran, whenever it actually moved something. Without that record a workspace whose ledger saysevidence_added: 0would end up holding two files that contradict each other.- With no
--model, the sub-agent inherits whatever your claude CLI is set to — which can be a premium tier at premium prices.trainnow says which model it resolved and what tier that is BEFORE it spawns, and REFUSES (exit 2, nothing spent) when the per-sub-agent share cannot reach what a pass on that tier costs. Pass--model sonnetor an explicit--max-usdto proceed. packs enableis the one switch for the stack packs, off by default. It re-runs detection, seeds any missing<lang>-stackexpert, gives each one whose body is still the seeded stub the shipped pack body (an edited body is kept and said so — delete the body to re-seed), writes every framework overlay detection can prove underoverlays/with its evidence inworkspace.yml, and names the project's.claude/skills.disableremoves the overlays and touches neither bodies nor knowledge.statusprints all of it and always exits 0;enableexits 1 when no repo has a detectable language.disableis idempotent: no workspace.yml yet means nothing to disable (exit 0, named line, same asstatus) — it exits 1 only when workspace.yml exists but is too broken to read.
tldrx dashboard
Watch the workspace live in a browser, or export it as one static page.
| Flag | Subcommand | Meaning |
|---|---|---|
--serve | (all) | Serve the page and keep watching the files — the default. Refused together with --static. |
--port <n> | (all) | Port to serve on. Default 4477; 0 takes any free one. |
--open | (all) | Open the page in the default browser once the server is up. |
--static | (all) | Write one self-contained page instead of serving. No server, no watcher. |
--out <dir> | static | Where --static writes the page. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1
tldrx dashboard --open
tldrx dashboard --static --out ./public- Read-only: it serves GET and writes nothing into the workspace. Ctrl-C exits 0.
tldrx replay
Render a run's events.jsonl as a narrative of what happened and what it cost.
| Argument | Meaning |
|---|---|
[<run-id>] | Which run to narrate. Omit it and the newest run is used; several runs OPEN is a refusal (exit 2), never a guess. |
| Flag | Meaning |
|---|---|
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx replay
tldrx replay 260101-checkout- Read-only: every line comes from an event that was actually logged. Nothing is written.
tldrx retro
Close a run and write down what it learned — or, with --all, what every run keeps catching.
| Argument | Meaning |
|---|---|
[<run-id>] | Which run to close. Omit it and the newest run is used; several runs open is a refusal (exit 2). Refused together with --all, which reads every run. |
| Flag | Meaning |
|---|---|
--apply | Also append the practice proposals to .tldrx/memory/practices.md. Refused together with --all, which writes nothing. |
--all | Aggregate ACROSS every run under tldrx-work/ instead of closing one, and print the trends table: finding class × count × how many runs it appeared in × one example with its citation. Reads the review logs, the fix lists, retro.md and the story.reopened reasons; writes nothing anywhere. |
--json | Print the cross-run aggregate (--all only; closing one run writes a file and has nothing to parse) as JSON on stdout instead of the table. |
--root <path> | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx retro
tldrx retro 260101-checkout --apply
tldrx retro --all
tldrx retro --all --json- --all is strictly read-only: no retro.md, no practices.md, no cache, no state. A run missing any of the four sources contributes what it has and is still counted; an empty workspace is an empty answer at exit 0, not a failure.
- Classification is deterministic keyword rules over the finding text — no model runs — so the same tree always produces the same table.
otheris a real row: a table it dominates is telling you the taxonomy is too small. - A repeat of one finding WITHIN a run is collapsed (retro.md quotes the fix list verbatim); the same finding in two runs is two occurrences, which is what the table is for.
- The taxonomy is workspace-extensible:
.tldrx/memory/finding-classes.yml(version: 1,classes:of{name, rules}) adds classes for defects the seven do not name. Extensions are tried AFTER every built-in rule, so they can only claim findings that would otherwise beother. A file that will not load is a refusal naming the class and the rule, never a silent fallback. - The top three classes (never
other) are also injected into every adversarial reviewer prompt the Build phase renders, so a review starts from what this team keeps getting wrong instead of from zero. No runs, no findings, or a broken finding-classes.yml — no section at all.
tldrx drive
Print the session mandate for driving a run — the discipline, not the manual.
| Argument | Meaning |
|---|---|
[<run>] | The run id to write into the mandate's <run> slots. Omit it and the one open run of this workspace is used; with no workspace, no run, or two open runs, <run> is left as it is. |
| Flag | Meaning |
|---|---|
--run <id> | The same thing as the positional, in the spelling every other command takes. The positional wins if both are given. Substituted textually and never validated — an id that names no run is yours to notice. |
--attended | A person is at the keyboard and closes every gate. The mandate tells the session to do the checking anyway and hand the decision over — it never signs. |
--unattended | Nobody is watching. The mandate is for a session driving an attended_by: host run with agent gates: it drives every turn, signs a gate only over a written evidence note, and wakes a person for the four things that are still theirs. |
--tldr | Essentials only, for a run whose trail you will not read. The mandate gains a reporting contract: after every commit and at every gate the session shows what tldrx run status prints plus at most three bullets of delta, and nothing else — no recaps, no diff summaries, no tldrx note. Sub-agents are briefed to keep handoffs and evidence notes at the minimum claim-sources still validates. Prose is trimmed; citations and gates are not. Works with either mode. |
Exit codes: 0 1
tldrx drive --unattended
tldrx drive --unattended 260901-leaderboard
tldrx drive --unattended --tldr
tldrx drive --attended- Read-only in the strongest sense: it needs no workspace, opens no run, spawns nothing and writes nothing. The output is plain text to paste into the session that will drive the run.
- The mandate's commands all name a run. Given an id it fills every <run> in — there is no partial substitution, which is the point: a hand find-replace across them only has to miss one to send a session at the wrong run. Given none it looks for the ONE open run here, and where the CLI would refuse to choose between two it declines to substitute and says which ids were on offer, because a mandate silently aimed at the wrong run is worse than a placeholder. It still exits 0 with no workspace at all.
- A mode is required and never guessed (exit 1). The two mandates differ in exactly the place a wrong guess costs most — who may close a gate.
- It is versioned with the package: the header carries the framework version that printed it.
tldrx watch
List the watcher cards a run produced, work through them as a post-merge checklist, or wait for the shipped PR to merge and print it.
Subcommands: list · check · arm
| Argument | Meaning |
|---|---|
[<feature>] | watch check: which card to check. Omit it and every card in the run is checked; unused by list and arm. |
| Flag | Subcommand | Meaning |
|---|---|---|
--run <id> | (all) | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--json | list | Print the card list as JSON on stdout instead of the table. |
--execute | check | Re-run the $ <cmd> → exit <n> sources the cards recorded, through the same workspace.yml allowlist a stage check uses, and report the exit each one gets NOW. Off by default: without it every signal is printed, never run. A ## Query block is never run — it belongs to the console named under ## Where. Never offered by arm. |
--interval <s> | arm | How often arm asks gh pr view whether the PR merged. Default 60. Anything under 10 is REFUSED rather than quietly raised — a PR does not merge twice, and a tighter loop only hammers the API. |
--timeout <s> | arm | How long arm keeps asking before it gives up and prints the command that re-arms it. Default 3600, maximum 86400. It holds the terminal it was typed in; there is no background poller. |
--branch <name> | arm | Which of the run's epic branches to watch, when Build cut more than one. The same list tldrx ship picks from (build.epic_branch in run.yml), read by the same code. |
--repo <name> | arm | Narrow to one repo of the workspace. By default every repo of the run that has the branch is watched, and the checklist fires only when ALL of their PRs have merged. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3 4
tldrx watch list
tldrx watch list --json
tldrx watch check
tldrx watch check checkout-flow
tldrx watch check --execute
tldrx watch arm --run 260101-checkout
tldrx watch arm --interval 120 --timeout 7200watch checkexits 1 when a citation no longer resolves — a check that reported rot on stdout and exited 0 would be invisible to CI.- It exits 3 when there is nothing to check, and says which nothing: a run whose Watch stage never ran, or a Watch stage that shipped no feature. A green that means "I read no cards" is the failure this command exists to stop.
watch armis a bounded FOREGROUND poller, not a daemon: it reads the branch Build cut, asksgh pr view <branch> --json state,mergedAt, and prints thewatch checkchecklist the moment every PR for that branch has merged. It never pushes, opens or merges anything. No epic branch, no PR for the branch, and a PR CLOSED without merging are all refusals with a sentence in them (exit 2); a window that expires with the PR still open exits 4 and says how to re-arm.
tldrx tickets
Mirror the plan's epics and stories to a ticket tool. The files stay the source of truth.
Subcommands: sync · status
| Argument | Meaning |
|---|---|
[<run>] | A run id. Omit it and the one open run is used. |
| Flag | Subcommand | Meaning |
|---|---|---|
--run <id> | (all) | Which run to act on. Omit it and the one open run is used; several open runs is a refusal (exit 2), never a guess. |
--apply | sync | Actually create and edit issues. Without it sync previews and calls nothing — the one verb here that reaches a third party does not write by default. |
--dry-run | sync | The default, kept as an explicit alias for it: say what would be created or edited, call nothing, write nothing. Passing it also cancels an --apply on the same line. |
--provider <kind> | sync | Override process.yml's ticket_tool.kind for this call. Values: github jira. |
--root <path> | (all) | Workspace to act on. Default: the nearest directory at or above the cwd holding .tldrx/. |
Exit codes: 0 1 2 3
tldrx tickets status
tldrx tickets sync
tldrx tickets sync --apply- No --json: the sync report and the status table are prose, and a JSON shape nobody consumes is a promise this would then have to keep.
tldrx hook
Run one tldrx hook script: payload on stdin, decision on stdout.
Subcommands: claim-sources · no-reask · dod-gate · budget-gate · answer-capture · session-start · statusline
| Argument | Meaning |
|---|---|
<script> | Which hook to run. See Subcommands below. |
Everything after the script name is forwarded to it unchanged, so this command judges no flags of its own.
Exit codes: 0 1
echo '{}' | tldrx hook session-start- Everything after the script name is forwarded to it unchanged, so this command judges no flags of its own.
tldrx statusline
Render the tldrx status line, for Claude Code's statusLine setting.
Everything after the script name is forwarded to it unchanged, so this command judges no flags of its own.
Exit codes: 0
tldrx statuslinetldrx version
Print the tldrx version.
Exit codes: 0
tldrx --versiontldrx help
Print the command list, the loop, and the exit-code table.
Exit codes: 0
tldrx --help
tldrx status --helpGenerated from src/cli/helpText.ts. Prose that a flag table cannot hold — worked examples, the reasoning behind a refusal — lives in the repo's guide.