CLI Commands
The foundry CLI communicates with a running foundryd daemon over gRPC.
Global Options
| Option | Default | Description |
|---|---|---|
--addr <url> | FOUNDRY_DAEMON_ADDR or http://127.0.0.1:50051 | Daemon address |
foundry resolves the daemon URL in this order:
- Explicit
--addr FOUNDRY_DAEMON_ADDRhttp://127.0.0.1:50051
foundry emit
Emit an event into the system. May trigger a workflow chain.
foundry emit <event_type> --project <project> [--throttle <level>] [--payload <json>] [--wait]
| Argument | Required | Description |
|---|---|---|
event_type | Yes | Event type name (positional) |
--project | Yes | Target project |
--throttle | No | full or dry_run (default: full) |
--payload | No | JSON string with event-specific data |
--wait | No | Block until processing completes, then display the trace |
By default, emit returns immediately after the daemon accepts the event. Use
--wait to block until the full event chain finishes and then display the trace
output (equivalent to running foundry trace after completion).
Output (default):
Event emitted: evt_47fcb603e1b18c8435b8cc3b
Output (with --wait):
Event emitted: evt_47fcb603e1b18c8435b8cc3b
Waiting for processing to complete...
greet_requested (evt_47fcb603e1b18c8435b8cc3b) project=hello
→ Compose Greeting (1ms): ok — composed greeting for Stacey
greeting_composed (evt_a1b2c3d4e5f6) project=hello
→ Deliver Greeting (0ms): ok — delivered greeting: Hello, Stacey!
---
Total: 2ms (blocks: 1ms)
foundry status
Show status of active workflows. Queries the daemon for workflows that are
currently being processed in the background. The daemon tracks these via an
in-memory WorkflowTracker that is populated when each Emit request spawns a
background task and cleared on completion. This is a live view only; completed
work moves to durable trace history and no longer appears here.
foundry status [workflow_id] [--span <span-id>]
Without an argument, shows all active workflows. With a workflow ID, shows
details for that specific workflow. --span first resolves the span through
the daemon's Span RPC and then keeps only active workflows in that span's
trace. If the daemon is unreachable, the command fails; there is no offline
status cache or trace-file fallback.
Output example:
evt_47fcb603e1b18c8435b8cc3b [iteration_requested] foundry — running
If no workflows are currently running:
No active workflows.
foundry watch
Stream live events as they are emitted in real time.
foundry watch [--project <project>]
| Option | Required | Description |
|---|---|---|
--project | No | Filter by project name; omit to see all projects |
Server-side streaming — stays open until interrupted (Ctrl-C). Each line shows
the event type, event ID, project, and payload (when non-empty).
Output example:
maintenance_run_started evt_abc project=my-tool
project_validation_completed evt_def project=my-tool
payload: {"status":"ok","has_gates":true}
project_iteration_completed evt_ghi project=my-tool
foundry run
Trigger a maintenance run for all active projects or a single named project.
foundry run [--project <project>] [--throttle <level>]
| Option | Required | Description |
|---|---|---|
--project | No | Limit run to a single project by name; omit to run all projects |
--throttle | No | full or dry_run (default: full) |
foundry run emits a maintenance_run_started event which triggers the
maintenance workflow chain: validate → iterate (if enabled) → maintain (if
enabled) → commit and push → post-push audit.
The command streams progress events in real time and exits automatically
when the daemon broadcasts a maintenance_run_completed event at the end of the
processing chain. This differs from foundry watch, which streams indefinitely.
When --project is omitted, the project name sent to the daemon is "system",
which causes all active (non-skipped) projects to be processed.
Output:
Triggered maintenance run for my-tool
Event: evt_47fcb603e1b18c8435b8cc3b
[my-tool] maintenance_run_started
[my-tool] project_validation_completed (ok)
[my-tool] maintenance_run_completed (ok)
Use foundry trace <event_id> to inspect the full trace after the run
completes.
foundry task
Run one concrete coding objective against a registered project.
foundry task <project> "<description>" [--agent <provider>] [--origin <text>] [--after <id>]... [--not-before <RFC3339|duration>]
| Argument | Required | Description |
|---|---|---|
project | Yes | Registered project name |
description | Yes | One concrete objective, supplied as a positional string |
--agent | No | Override the registered agent provider for this task |
--origin | No | Free-text note recorded with the work item, beside this machine's hostname |
--after | No | Start only after this work item has settled landed; repeatable. An id not in the ledger is refused |
--not-before | No | Earliest start: an RFC 3339 timestamp, or a duration from now (90s, 30m, 2h, 1d) |
The task is recorded queued and the pacing scheduler starts it when the rules
allow: one mutating item per repository, the host running cap, --not-before,
--after, and the lane not paused. The command waits through that queue time;
work_item_submitted on its trace carries the reason it waits on. See
Pacing.
The work item this dispatch records always carries the CLI client's hostname in
its origin, plus any --origin text verbatim. Origin is opaque: it changes
nothing about how the task runs, and an empty --origin is accepted. See
The Work Queue.
The command waits for task_run_completed, streams block progress, and renders
the full trace. Execution and verification occur in an isolated Git worktree.
The terminal event contains one structural verdict:
| Verdict | Meaning |
|---|---|
complete | Required gates and skeptical review passed; deliverable changes landed on trunk, or no landing was required |
remainder | The objective is incomplete; gaps[] names what remains |
defect | The implementation or evidence is wrong; diagnosis explains why |
blocked_on_decision | A human choice is required; finding and options[] carry it |
runner_error | The agent, workspace, Git, or provider failed |
Task execution has no retry route. All task work is committed before terminal
state. Non-complete work is preserved on a remote branch or, if push fails, in a
Git bundle. For landed work, preservation_ref carries the landed commit SHA;
otherwise it identifies the preserved branch or bundle artifact.
foundry deps
Show a project's outdated dependencies, what maintenance would apply under its update policy, and what the majors lane would dispatch. Nothing is applied and nothing is dispatched.
foundry deps <project> [--policy patch|minor|major]
| Argument | Required | Description |
|---|---|---|
project | Yes | Registered project name |
--policy | No | Preview under this policy instead of the registered one; the registry does not change |
The command emits dependency_review_requested and waits for
major_upgrades_planned. It prints a table of outdated direct dependencies
(current, constraint, in range, newest non-major, major), the updates
maintenance would apply, the updates held back by the policy or by holds,
lapsed holds, anything not classified, and each major with its status and
foundry task command. See
Dependency Update Policy.
foundry campaign
Manage durable, evidence-terminated engineering objectives.
foundry campaign add <definition.json>
foundry campaign list
foundry campaign show <name> [--json]
foundry campaign advance <name> [--origin <text>]
foundry campaign pause <name>
foundry campaign decide <name> --decision "Use the generated tonic client path."
foundry campaign decide <name> --decision "Keep the current design." --add-cycles 1
foundry campaign complete <name> --reason "Production evidence confirms the mission shipped."
foundry campaign cancel <name> --reason "Superseded by the new architecture."
foundry campaign cancel <name> --reason "Wrong approach." --now --discard-work
foundry campaign resume <name>
foundry campaign resume <name> --add-cycles 2
| Subcommand | Daemon required? | Description |
|---|---|---|
add | Yes unless --offline | Validate and atomically add one JSON definition |
list | Yes unless --offline | Show campaign status and cycle counts |
show | Yes unless --offline | Show the complete stored campaign record; for an escalated campaign, why it waits on its owner; the owner's options as command lines; then its ledger cycles. --json emits one object |
advance | Yes | Re-evaluate done evidence and dispatch one next task, complete, or escalate. --origin <text> records a free-text note, beside this machine's hostname, on the work item of the cycle it dispatches |
pause | Yes unless --offline | Halt future automatic and manual advancement |
decide | Yes unless --offline | Record a binding owner decision on an active, paused or escalated campaign. An escalated campaign also returns to active, and --add-cycles <n> extends its budget |
complete | Yes unless --offline | Mark an authorized campaign complete with an auditable owner reason |
cancel | Yes unless --offline | Stop a campaign permanently, optionally killing the in-flight cycle |
resume | Yes unless --offline | Return an authorized paused or escalated campaign to active state |
The store defaults to ~/.foundry/campaigns.json and can be overridden with
FOUNDRY_CAMPAIGNS_PATH. A definition requires non-empty name, project, and
mission fields plus at least one done_evidence item. authorized_by is
required before decide, complete, or resume. decide appends an owner
decision record, and every later formation run treats it as binding. On an
active or paused campaign it changes nothing else: the status, budget and
any stored run result stay as they are. --add-cycles is refused there; use
resume --add-cycles N for a paused campaign. On an escalated campaign,
decide also returns it to active. On a spent budget, an escalated decide
requires --add-cycles N, which it applies in the same transition; without it
the command is refused and records nothing. decide is refused on a staged,
completed or cancelled campaign, and when its text and owner repeat the
campaign's latest decision, so a retry records nothing twice.
resume --add-cycles N is required when an exhausted budget needs an explicit
positive extension.
Without --offline, all nine campaign subcommands are daemon-authoritative:
they require a reachable foundryd daemon, fail without touching the
client-side FOUNDRY_CAMPAIGNS_PATH if the daemon is unreachable, and render
the daemon's typed response or workflow output directly rather than re-reading
the client store. See Tasks and Campaigns for the
definition schema and lifecycle.
advance has no offline formation path. It re-runs campaign done-evidence
against delivered trunk, inspects recent objective history and accumulated
preserved work, and chooses exactly one of done, one next objective, or
escalation. An advance whose campaign is paused, cancelled, completed or
escalated before its outcome applies stands down instead (stood_down), and
its work item settles landed. A dispatched task runs the project's resolved gates in its isolated
worktree; campaign gate commands are not copied into task acceptance evidence.
Each advance prints and watches a root event ID. Its events carry trace and span
context, while task-side events also carry campaign_cycle.
campaign_advance_completed retains the formation prompt, agent provider, and
done-evidence gate results, allowing the cycle to be reconstructed through
foundry trace <event-id> --verbose.
For online mutations, daemon-side persistence is atomic at the control-plane
boundary: if a save fails, the command surfaces the typed gRPC INTERNAL error,
the daemon-owned campaign store stays byte-identical on disk, and
campaign complete does not emit a terminal completion event.
complete is the owner-authorized external terminal path. It accepts any
non-completed campaign state, requires a non-empty --reason, clears any stale
pending run result, records the reason with the authorizing owner and timestamp,
and emits the normal campaign_completed terminal event. Repeating it for an
already-completed campaign is idempotent.
cancel is the other external terminal path, and it means the opposite of
complete: the campaign is being abandoned before its done evidence was met.
It is a distinct cancelled status rather than a flavour of completed,
because in Foundry completion is an evidence claim — recording an abandoned
campaign as complete would put a false assertion into the audit trail and the
ops digest. Cancellation is terminal and not resumable; use pause for a
campaign you intend to come back to.
Unlike complete, cancel does not require authorized_by. An
unauthorized campaign can never be advanced to completion either, so requiring
an owner would strand it with no reachable terminal state. The --reason is
still mandatory and always reaches the campaign_cancelled event; it is
additionally recorded as an owner decision when the campaign has an owner.
Repeating a cancellation is idempotent and emits no second event, and cancelling
an already-completed campaign is refused.
By default the cancellation is graceful: the in-flight cycle runs to completion, Foundry commits and preserves its work exactly as it would normally, and no successor cycle is dispatched. Two flags change that:
| Flag | Effect |
|---|---|
--now | Abort the in-flight workflow immediately, killing the running agent process |
--discard-work | Throw the terminated cycle's uncommitted work away instead of preserving it |
With --now and no --discard-work, the orphaned worktree is committed and
pushed to its task branch (falling back to a bundle under ~/.foundry/preserved),
then removed. With --discard-work the worktree and its local branch are deleted
outright; any remote branch an earlier cycle already pushed is deliberately left
alone, since that is the audit trail for work which did reach a durable ref.
--discard-work requires --now. A graceful cancellation lets the cycle finish,
and FinalizeTask has already committed and preserved its work by then, so there
is nothing uncommitted left to discard — the flag would silently do nothing.
--now terminates the agent process, not every process descended from it.
kill_on_drop reaches only the direct child, so tool subprocesses spawned by
claude or codex are reparented and keep running until they exit on their own.
--offline cancellation is graceful-only and emits no event, matching offline
complete. --offline --now is refused rather than silently downgraded: there
is no daemon holding a workflow to abort, and reporting a kill that never
happened would be worse than failing.
foundry queue
Inspect the daemon-owned work-item ledger — the durable record of every unit of
work Foundry dispatched. The overview, show and open are read-only.
foundry queue [--source <kind>:<ref>] [--json] [--offline]
foundry queue show <id> [--json] [--offline]
foundry queue open [--source <kind>:<ref>] [--json] [--offline]
foundry queue hold <id> [--origin <text>]
foundry queue release <id> [--origin <text>]
| Subcommand | Daemon required? | Description |
|---|---|---|
| (none) | Yes unless --offline | Print running, queued, open and the newest 20 settled items on one screen |
show | Yes unless --offline | Print one item's full durable record, then its work_item_* events |
open | Yes unless --offline | Print only the open group — preserved, needs_decision and failed — then every escalated campaign |
close <id> --reason <text> [--origin <text>] | Yes | Discharge an open obligation; --offline refused |
cancel <id> [--origin <text>] | Yes | Cancel submitted/queued/held work; --offline refused |
hold <id> [--origin <text>] | Yes | Take a queued item out of the scheduler's hands; --offline refused |
release <id> [--origin <text>] | Yes | Return a held item, or one in needs_decision over a dependency, to the queue; --offline refused |
resume <id> [--origin <text>] [--after <id>]... [--not-before <RFC3339|duration>] | Yes | Resume preserved work through a queued continuation; --offline refused |
| Argument | Description |
|---|---|
<id> | Work-item id, e.g. wi_0123456789abcdef01234567 (show, close, cancel, hold, release, resume) |
| Option | Description |
|---|---|
--json | Emit machine-readable JSON instead of human output |
--source | List only one source's items, as <kind>:<ref>; campaign:<name> also selects that campaign's escalation (overview and open) |
--offline | Read FOUNDRY_WORK_ITEMS_PATH (and FOUNDRY_EVENTS_DIR; for the overview and open, also FOUNDRY_CAMPAIGNS_PATH) directly instead of calling the daemon |
The four groups are running, queued (submitted, queued or held), open
(preserved, needs_decision, failed — settled but still needing a person),
and the newest 20 settled items (landed, cancelled). The 20-item cap
applies to the settled group alone; older terminal items are omitted. Each line
carries the item's id, project, kind, lane, state, the timestamp that placed it
in its group, and its one-line reason. Group order and the order within each
group come from the daemon's ListWorkItems response — the CLI groups by state
and never re-sorts.
The open group ends with one row per escalated campaign, from ListCampaigns:
campaign, its name, project, escalated, when and why it escalated, and a
next: foundry campaign show <name> (<controls>) line. A campaign escalation is
campaign state, not a ledger item, so --json for the overview and open
emits {"items": [...], "escalated_campaigns": [...]}; through 0.44.4 it was a
bare array of items. See The work queue.
show renders every durable field, and an optional field the ledger never
recorded produces no line at all rather than an empty string or false. A
recorded worktree_removed: false therefore prints Worktree removed: no,
while an unrecorded one prints nothing. A Depends on: line lists the
--after ids when there are any, and a Not before: line the earliest start
when one was set; --json carries them as depends_on and not_before. After the record, show prints an
Events: heading and the item's own work_item_* events, one line each
(occurred at, event type, state, event id, reason), oldest first. They are
selected from the durable event log by the item id in the event payload — never
by trace or project — across every monthly file, however old. An item with no
events prints (no events). A malformed line in the log is skipped with a
daemon-side warning; a fault reading the log is an error, never an empty list.
With --json, the list forms emit a JSON array and show emits a single
object: the record's keys, unchanged, plus an events array whose entries carry
id, event_type, occurred_at, state, reason and an optional
trace_id. Optional fields are absent when unset, so the JSON round-trips the same
facts the record carries. The human and JSON forms of one command render from
the same fetched data.
The ledger defaults to ~/.foundry/work-items.json and can be overridden with
FOUNDRY_WORK_ITEMS_PATH. Without --offline, all three commands are
daemon-authoritative: the list forms render ListWorkItems and show renders
GetWorkItem followed by ListWorkItemEvents. The online path never reads,
creates or mutates the client-side ledger or events files, so an absent
FOUNDRY_WORK_ITEMS_PATH or FOUNDRY_EVENTS_DIR stays absent and an existing
ledger is left byte-identical. A NOT_FOUND from GetWorkItem surfaces as an
id-not-found error with a non-zero exit, not as an empty record.
If foundryd is unreachable, the command fails with a stable actionable error
naming the matching offline recovery command. There is no silent fallback. Pass
--offline only when the daemon is stopped and you intentionally want to read
the ledger JSON file (and, for show, the events directory) directly; it
applies the same grouping order and event selection the RPCs document. A missing ledger renders four empty groups and exits zero without
creating the file; a malformed one exits non-zero and names the parse failure.
Close or cancel an item
foundry queue close wi_0123456789abcdef01234567 --reason "Reviewed; no further work required" --origin "owner review"
foundry queue cancel wi_0123456789abcdef01234567 --origin "withdrawn request"
close discharges an open obligation in preserved, needs_decision or
failed; it requires a nonblank --reason. cancel stops a submitted,
queued or held item and records the reason cancelled by operator, without
requiring a reason argument. Both settle exactly the requested id as cancelled.
Both require the daemon, reject --offline, and never fall back to local
writes. They record this CLI's hostname and optional --origin in
operator_action, separately from the original submission origin. The action
retains the previous state, reason and settlement timestamp. Submission
identity, objective, trace and known disposition remain intact, including
preserved refs and worktree-removal observations. No agent starts, running
workflow stops, or preserved work is disposed of by either command.
The daemon reloads the ledger under its shared write gate and saves by atomic
replacement before emitting work_item_cancelled to Watch and the durable
log. Earlier event bytes stay untouched. Unknown ids return NOT_FOUND,
blank input returns INVALID_ARGUMENT, and other states return
FAILED_PRECONDITION. Malformed ledgers also return FAILED_PRECONDITION;
read or save failures return INTERNAL and a failed save emits no cancellation.
Concurrent requests for one item can succeed only once.
Hold or release an item
foundry queue hold wi_0123456789abcdef01234567 --origin "wait for the review"
foundry queue release wi_0123456789abcdef01234567
hold takes a queued item out of the scheduler's hands: it sits held with
the reason held by operator until released, and nothing starts it. release
returns a held item, or one the scheduler settled needs_decision because a
dependency settled some way other than landed, to queued; the settled
dependencies are dropped from depends_on, and the recorded reason is the
scheduler's verdict for the item as it now stands. Any other state is
FAILED_PRECONDITION. Both require the daemon, reject --offline, record
operator_action as close and cancel do, and emit work_item_held or
work_item_released on Watch and in the durable log after the ledger is saved.
Resume preserved work
foundry queue resume wi_0123456789abcdef01234567 --origin "finish preserved work"
foundry queue resume wi_0123456789abcdef01234567 --after wi_89abcdef0123456789abcdef --not-before 30m
Owner-directed preserved-work continuation uses foundry queue resume <id> [--origin <text>] [--after <id>]... [--not-before <RFC3339|duration>]. It
requires a live daemon and refuses --offline. Only a preserved item with a
usable preservation ref and a registered project can be resumed. Foundry
records a new task with the original objective, queued for the pacing
scheduler, that starts from the preserved local branch, remote ref or
bundle:<path> through the existing continuation path. When the preserved work does not contain current trunk, Foundry rebases it onto freshly fetched trunk before the agent starts (the preservation ref itself is not rewritten). If that rebase conflicts, the child is refused before any agent session: it settles failed with land_blocked: continuation_conflict and a reason that names the conflicting files, and the original stays preserved. --after and
--not-before mean what they mean on foundry task; an --after id not in
the ledger, or an unparsable --not-before, is INVALID_ARGUMENT. The new record and its lifecycle payloads expose
resumes, the exact original id. Queue reads show this link in human and JSON
output. Submission identity and the parent's prior evidence remain intact;
the child records the hostname and optional origin in its resume operator
action.
The original obligation stays preserved until the linked task actually lands.
Then Foundry records its landing commit and appends work_item_settled for the
original. Failed, blocked, preserved and no-landing results leave it open.
An original cancelled by its owner stays cancelled even if its child lands.
Unknown ids return NOT_FOUND, blank inputs INVALID_ARGUMENT, ineligible
states or unusable evidence FAILED_PRECONDITION, and persistence failures
INTERNAL. A rejected admission dispatches no execution and invokes no agent.
An initial ledger-save failure creates no child or lifecycle events. Otherwise,
admission stages a failed child with reason resume admission incomplete; execution not dispatched, no started_at, a settled_at and no disposition.
The child becomes queued only after the work_item_submitted root is
persisted and the final ledger save succeeds; the scheduler then starts it and
emits work_item_started. Later failures leave that failed child and preserve
the parent and unrelated records. Successfully written roots remain in history
and on Watch even if a later admission step fails; failed writes are never
advertised on Watch. Earlier bytes, including partial appends, are never
truncated or rewritten.
See The Work Queue for the full model.
foundry pacing
Inspect and steer the pacing stage: the scheduler that starts queued work.
foundry pacing show [--json] [--offline]
foundry pacing pause [--lane interactive|campaign|maintenance|all] [--origin <text>]
foundry pacing resume [--lane interactive|campaign|maintenance|all] [--origin <text>]
foundry pacing drain [--timeout <duration>] [--origin <text>]
| Subcommand | Daemon required? | Description |
|---|---|---|
show | Yes unless --offline | Print the limits in force, every running item with the repository it occupies, every waiting item with its reason, and the paused lanes |
pause | Yes | Stop new starts in a lane (default all); running items are untouched; emits pacing_paused |
resume | Yes | Allow starts in a lane again (default all); emits pacing_resumed |
drain | Yes | Pause every lane, then wait until nothing is running, printing each item as it settles; exit 0 when idle |
| Option | Description |
|---|---|
--json | show only: emit the PacingStatus as JSON |
--offline | show only: read the ledger and the pacing files directly instead of calling the daemon |
--lane | pause/resume: interactive, campaign, maintenance or all |
--timeout | drain: give up after this duration (30m, 2h) and exit 1 naming what still runs |
--origin | Operator context recorded beside this CLI's hostname |
show lists running items oldest start first and waiting items in the
scheduler's priority order (interactive lane first, then oldest submission),
then held items. A waiting item's reason is the scheduler's current verdict:
repository busy: wi_…, host at capacity N/N, not before <time>,
waits on wi_…, lane paused, ready, or held by operator.
The pause state is persisted to ~/.foundry/pacing-state.json
(FOUNDRY_PACING_STATE_PATH) through an atomic rename and is in force after a
daemon restart. The host running cap is read from ~/.foundry/pacing.json
(FOUNDRY_PACING_PATH, {"max_running": 2}); a missing file means the default.
drain does not stop the daemon and does not resume the lanes when it finishes;
run pacing resume when work may start again. It refuses --offline.
foundry sentinel
Inspect or toggle the daemon-owned scheduled sentinels.
foundry sentinel list [--offline]
foundry sentinel show <name> [--offline]
foundry sentinel enable <name> [--offline]
foundry sentinel disable <name> [--offline]
| Subcommand | Daemon required? | Description |
|---|---|---|
list | Yes unless --offline | Render every daemon-owned sentinel entry |
show | Yes unless --offline | Render one exact-name daemon-owned sentinel |
enable | Yes unless --offline | Mark one sentinel enabled and wake the in-process scheduler immediately |
disable | Yes unless --offline | Mark one sentinel disabled and wake the in-process scheduler immediately |
The store defaults to ~/.foundry/sentinels.json and can be overridden with
FOUNDRY_SENTINELS_PATH. Without --offline, all four commands are
daemon-authoritative: list renders SentinelList, show renders
SentinelShow, enable calls SentinelEnable, and disable calls
SentinelDisable. The online path never reads the client-side sentinel file,
so an absent FOUNDRY_SENTINELS_PATH stays absent and a malformed trap file is
left byte-identical.
If foundryd is unreachable, the command fails with a stable actionable error
that names the matching offline recovery command. There is no silent fallback.
Pass --offline only when the daemon is stopped and you intentionally want to
read or mutate the sentinel JSON file directly.
Online enable and disable are persistence-atomic at the daemon boundary:
the daemon writes a same-directory temporary file and renames it into place only
after the full JSON payload is ready. If that save fails, the RPC returns
INTERNAL, the daemon-owned in-memory sentinel store remains unchanged, list
and show continue to reflect the pre-mutation state, the scheduler is not
notified, and the on-disk sentinels.json bytes remain unchanged.
foundry validate
Validate quality gates for one or more projects without running iterate or maintain workflows. This is a read-only operation — no code changes are made.
foundry validate <project>...
foundry validate --all
| Argument | Required | Description |
|---|---|---|
project | Yes (unless --all) | One or more project names (positional) |
--all | No | Validate all active projects in the registry |
For each project, emits a validation_requested event which triggers:
Resolve Gates → Run Preflight Gates → Route Validation Result →
validation_completed. No Mutator blocks are involved, so throttle level is
irrelevant.
A failed gate shows the start of its output, followed by one full log: line
for each file that holds its complete stdout or stderr under
~/.foundry/evidence/gate-logs/.
Output example:
Validating mojentic-ts...
mojentic-ts: PASS
lint: ok (required)
format: ok (required)
test: ok (required)
build: ok (required)
security: ok (optional)
validation_requested (evt_007572156d627d7b1211d76f) project=mojentic-ts
→ Resolve Gates (0ms): ok — mojentic-ts: resolved 5 gates for validate workflow
gate_resolution_completed (evt_92531a666649d6464e569dc2) project=mojentic-ts
→ Run Preflight Gates (6931ms): ok — mojentic-ts: preflight gates passed
preflight_completed (evt_08b0f626599a23ee8c648a8c) project=mojentic-ts
→ Route Validation Result (3ms): ok — mojentic-ts: validation passed
validation_completed (evt_e60a246dfa9072414890fa24) project=mojentic-ts
---
Exits with code 0 if all projects pass, non-zero if any required gate fails. Optional gate failures are reported but do not affect the exit code.
foundry trace
View one completed event chain from the daemon-owned trace store.
foundry trace <event_id> [--verbose] [--flat]
| Argument | Required | Description |
|---|---|---|
event_id | Yes | Root event ID returned by foundry emit (positional) |
--verbose | No | Show trigger payloads, emitted payloads, raw shell output, and audit artifact paths |
--flat | No | Force the legacy chronological event tree instead of the default span tree |
By default this renders the daemon's span tree view. --flat forces the
legacy chronological event tree. The CLI asks the daemon for the trace; it does
not inspect FOUNDRY_TRACES_DIR directly unless you explicitly choose offline
history browsing. Traces are persisted under ~/.foundry/traces/YYYY-MM-DD/,
survive daemon restarts, and are available through the daemon even after the
in-memory cache has expired.
Output (default):
greet_requested (evt_47fcb603e1b18c8435b8cc3b) project=hello
→ ComposeGreeting: ok — composed greeting for Stacey
greeting_composed (evt_a1b2c3d4e5f6) project=hello
→ DeliverGreeting: ok — delivered greeting: Hello, Stacey!
greeting_delivered (evt_f6e5d4c3b2a1) project=hello
---
Total: 2ms (blocks: 1ms)
Output (with --verbose):
greet_requested (evt_47fcb603e1b18c8435b8cc3b) project=hello
→ ComposeGreeting (1ms): ok — composed greeting for Stacey
trigger: {"name":"Stacey"}
emitted[0]: {"greeting":"Hello, Stacey!"}
greeting_composed (evt_a1b2c3d4e5f6) project=hello
→ DeliverGreeting (0ms): ok — delivered greeting: Hello, Stacey!
---
Total: 2ms (blocks: 1ms)
If the trace is unknown:
No trace found for evt_unknown (expired or unknown).
foundry history
Browse durable completed traces from the daemon-owned trace store.
foundry history [<date>] [--project <project>] [--offline]
| Argument | Required | Description |
|---|---|---|
date | No | Date in YYYY-MM-DD format; omit to show the last 7 days |
--project | No | Filter results by project name |
--offline | No | Explicitly read local trace files instead of the daemon |
Without --offline, history is daemon-authoritative: it calls the daemon's
typed History RPC, renders daemon-owned results directly, and never reads or
creates a client-side FOUNDRY_TRACES_DIR. If the daemon is unreachable, the
command fails and suggests rerunning with --offline. Use --offline
deliberately when you want direct file diagnostics against
~/.foundry/traces/ (or FOUNDRY_TRACES_DIR).
Each row shows the event ID, trace ID, success status, duration, event type, and project. Dates with no traces are omitted. Within a day, rows are rendered in deterministic newest-first order.
Output example:
2026-03-22
┌──────────────────────────────┬────────┬──────────┬──────────────────────────┬───────────┐
│ Event ID │ Status │ Duration │ Type │ Project │
╞══════════════════════════════╪════════╪══════════╪══════════════════════════╪═══════════╡
│ evt_47fcb603e1b18c8435b8cc3b │ ok │ 312ms │ maintenance_run_started │ my-tool │
│ evt_a1b2c3d4e5f6789012345678 │ ok │ 48ms │ greet_requested │ hello │
└──────────────────────────────┴────────┴──────────┴──────────────────────────┴───────────┘
If no traces are found:
No traces found in the last 7 days.
foundry registry
Manage the project registry without editing the JSON file directly.
foundry registry <subcommand>
foundry registry init
Create an empty registry file at the default path (~/.foundry/registry.json).
This is an explicit offline recovery command and requires --offline. It
rejects runs without --offline before contacting the daemon or touching the
registry path. Does nothing if the file already exists. This command never uses
the daemon, even when foundryd is running.
foundry --offline registry init
foundry registry list
List all projects in the daemon-owned registry as a table. By default this
requires a reachable foundryd daemon. Use --offline to read the local
registry file directly for recovery. Without --offline, an unreachable daemon
returns an error and leaves the client-side registry file untouched. The online
path renders the daemon response directly and does not create
FOUNDRY_REGISTRY_PATH. Typed daemon failures render as
daemon error: <Code> — <message>.
foundry registry list
Output example:
┌──────────┬────────────┬──────┬──────────────────────────┬───────┐
│ Name │ Stack │ Skip │ Actions │ Skill │
╞══════════╪════════════╪══════╪══════════════════════════╪═══════╡
│ my-tool │ rust │ no │ iterate, maintain, push │ auto │
│ frontend │ typescript │ yes │ maintain, push │ │
└──────────┴────────────┴──────┴──────────────────────────┴───────┘
The Skill column shows auto (default derived command), cmd (custom
command), off (explicitly disabled), or blank (not configured).
foundry registry show <name>
Show all details for a single project from the daemon-owned registry. By default
this requires a reachable foundryd daemon. Use --offline to read the local
registry file directly for recovery. Without --offline, an unreachable daemon
returns an error and leaves the client-side registry file untouched. The online
path renders the daemon response directly and does not create
FOUNDRY_REGISTRY_PATH. Missing projects surface the daemon's typed NotFound
status.
foundry registry show my-tool
Output example:
Name: my-tool
Path: /Users/alice/projects/my-tool
Stack: rust
Agent: claude
Repo: alice/my-tool
Branch: main
Skip: no
Actions: iterate, maintain, push
Install: brew: my-tool
Installs skill: yes (default -- runs my-tool init --global --force)
Timeout: 3600s (default)
When installs_skill is true but the project has no install config, there is
no binary to derive the command from. The line then reports the skip and the
reason, for example
Installs skill: skipped -- no install config, so there is no binary to derive ....
Set a command with --installs-skill "<command>" to fix it.
foundry registry add
Add a new project to the daemon-owned registry. By default this requires a
reachable foundryd daemon. Use --offline to write the local registry file
directly for recovery. Without --offline, an unreachable daemon returns an
error and leaves the client-side registry file untouched. In offline mode, if
the registry file does not exist, it is created automatically. The online path
mutates daemon-owned state only and does not create FOUNDRY_REGISTRY_PATH. If
daemon persistence fails, the command surfaces the daemon's stable INTERNAL
error and leaves the daemon-owned registry unchanged in memory and on disk.
Duplicate names and invalid inputs surface the daemon's typed AlreadyExists
and InvalidArgument statuses.
foundry registry add \
--name my-tool \
--path /Users/alice/projects/my-tool \
--stack rust \
--agent claude \
--repo alice/my-tool \
--branch main \
[--iterate] [--maintain] [--push] [--audit] [--release] \
[--install-command "cargo install --path ."] \
[--install-brew my-formula] \
[--notes "Human-readable notes about the project"] \
[--timeout-secs 3600] \
[--update-policy minor] \
[--installs-skill true]
| Option | Required | Description |
|---|---|---|
--name | Yes | Unique project name |
--path | Yes | Absolute path to the project |
--stack | Yes | Technology stack: rust, python, typescript, elixir, cpp, swift, kotlin |
--agent | Yes | AI agent name (e.g. claude) |
--repo | Yes | GitHub slug (owner/repo) |
--branch | No | Default branch (default: main) |
--iterate | No | Enable iterate action |
--maintain | No | Enable maintain action |
--push | No | Enable push action |
--audit | No | Enable audit action |
--release | No | Enable release action |
--install-command | No | Shell command to run for local install |
--install-brew | No | Homebrew formula name |
--notes | No | Human-readable notes |
--timeout-secs | No | Command timeout in seconds (default: 3600) |
--update-policy | No | patch, minor or major: how far maintenance may move dependencies. Unset behaves as minor and is flagged in the maintenance summary. See Dependency update policy |
--installs-skill | No | true (run <binary> init --global --force, binary from the brew formula or the project name; skipped when there is no install config), false (no skill install), or a command to run verbatim. See Skill install |
foundry registry remove <name>
Remove a project from the daemon-owned registry. By default this requires a
reachable foundryd daemon. Use --offline to mutate the local registry file
directly for recovery. Without --offline, an unreachable daemon returns an
error and leaves the client-side registry file untouched. The online path
mutates daemon-owned state only and does not create FOUNDRY_REGISTRY_PATH. If
daemon persistence fails, the command surfaces the daemon's stable INTERNAL
error and leaves the daemon-owned registry unchanged in memory and on disk.
Missing projects surface the daemon's typed NotFound status.
foundry registry remove my-tool
foundry registry edit <name>
Update settings for an existing project. Only the fields you pass are changed;
all others are left as-is. By default this requires a reachable foundryd
daemon. Use --offline to mutate the local registry file directly for recovery.
Without --offline, an unreachable daemon returns an error and leaves the
client-side registry file untouched. The online path mutates daemon-owned state
only and does not create FOUNDRY_REGISTRY_PATH. If daemon persistence fails,
the command surfaces the daemon's stable INTERNAL error and leaves the
daemon-owned registry unchanged in memory and on disk. Missing projects surface
the daemon's typed NotFound status.
foundry registry edit my-tool \
--skip "Waiting for CI to stabilise" \
--timeout-secs 3600
| Option | Description |
|---|---|
--path | Update the project path |
--stack | Update the technology stack |
--agent | Update the agent name |
--repo | Update the GitHub slug |
--branch | Update the default branch |
--skip | Set a skip reason (pass empty string "" to clear the skip) |
--iterate | Set iterate action (true/false) |
--maintain | Set maintain action |
--push | Set push action |
--audit | Set audit action |
--release | Set release action |
--install-command | Set install command |
--install-brew | Set Homebrew formula |
--clear-install | Remove the install configuration (cannot be combined with --install-command or --install-brew) |
--notes | Set notes (pass empty string "" to clear) |
--timeout-secs | Set command timeout in seconds |
--update-policy | Set the dependency update policy: patch, minor or major |
--installs-skill | Set the skill install: true, false, or a command to run verbatim. An empty value is rejected |
foundry queue reconcile
Runs the canonical work reconciler through daemon-authoritative ReconcileWork
gRPC and prints that invocation's Markdown digest. Requires the daemon and
rejects --offline; it never reads client-side ledger or digest files.
Unreachable transport is an error. Inspection, fetch, ledger-save and digest-write
failures surface as INTERNAL with diagnostics. Verified settlements can already
be durable when a later digest write fails. Reports default to
~/.foundry/reconcile/YYYY-MM-DD.md (FOUNDRY_RECONCILE_DIR overrides the directory).
See Work reconciler.