CLI Commands

The foundry CLI communicates with a running foundryd daemon over gRPC.

Global Options

OptionDefaultDescription
--addr <url>FOUNDRY_DAEMON_ADDR or http://127.0.0.1:50051Daemon address

foundry resolves the daemon URL in this order:

  1. Explicit --addr
  2. FOUNDRY_DAEMON_ADDR
  3. http://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]
ArgumentRequiredDescription
event_typeYesEvent type name (positional)
--projectYesTarget project
--throttleNofull or dry_run (default: full)
--payloadNoJSON string with event-specific data
--waitNoBlock 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>]
OptionRequiredDescription
--projectNoFilter 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>]
OptionRequiredDescription
--projectNoLimit run to a single project by name; omit to run all projects
--throttleNofull 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>]
ArgumentRequiredDescription
projectYesRegistered project name
descriptionYesOne concrete objective, supplied as a positional string
--agentNoOverride the registered agent provider for this task
--originNoFree-text note recorded with the work item, beside this machine's hostname
--afterNoStart only after this work item has settled landed; repeatable. An id not in the ledger is refused
--not-beforeNoEarliest 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:

VerdictMeaning
completeRequired gates and skeptical review passed; deliverable changes landed on trunk, or no landing was required
remainderThe objective is incomplete; gaps[] names what remains
defectThe implementation or evidence is wrong; diagnosis explains why
blocked_on_decisionA human choice is required; finding and options[] carry it
runner_errorThe 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]
ArgumentRequiredDescription
projectYesRegistered project name
--policyNoPreview 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
SubcommandDaemon required?Description
addYes unless --offlineValidate and atomically add one JSON definition
listYes unless --offlineShow campaign status and cycle counts
showYes unless --offlineShow 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
advanceYesRe-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
pauseYes unless --offlineHalt future automatic and manual advancement
decideYes unless --offlineRecord 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
completeYes unless --offlineMark an authorized campaign complete with an auditable owner reason
cancelYes unless --offlineStop a campaign permanently, optionally killing the in-flight cycle
resumeYes unless --offlineReturn 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:

FlagEffect
--nowAbort the in-flight workflow immediately, killing the running agent process
--discard-workThrow 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>]
SubcommandDaemon required?Description
(none)Yes unless --offlinePrint running, queued, open and the newest 20 settled items on one screen
showYes unless --offlinePrint one item's full durable record, then its work_item_* events
openYes unless --offlinePrint only the open group — preserved, needs_decision and failed — then every escalated campaign
close <id> --reason <text> [--origin <text>]YesDischarge an open obligation; --offline refused
cancel <id> [--origin <text>]YesCancel submitted/queued/held work; --offline refused
hold <id> [--origin <text>]YesTake a queued item out of the scheduler's hands; --offline refused
release <id> [--origin <text>]YesReturn 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>]YesResume preserved work through a queued continuation; --offline refused
ArgumentDescription
<id>Work-item id, e.g. wi_0123456789abcdef01234567 (show, close, cancel, hold, release, resume)
OptionDescription
--jsonEmit machine-readable JSON instead of human output
--sourceList only one source's items, as <kind>:<ref>; campaign:<name> also selects that campaign's escalation (overview and open)
--offlineRead 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>]
SubcommandDaemon required?Description
showYes unless --offlinePrint the limits in force, every running item with the repository it occupies, every waiting item with its reason, and the paused lanes
pauseYesStop new starts in a lane (default all); running items are untouched; emits pacing_paused
resumeYesAllow starts in a lane again (default all); emits pacing_resumed
drainYesPause every lane, then wait until nothing is running, printing each item as it settles; exit 0 when idle
OptionDescription
--jsonshow only: emit the PacingStatus as JSON
--offlineshow only: read the ledger and the pacing files directly instead of calling the daemon
--lanepause/resume: interactive, campaign, maintenance or all
--timeoutdrain: give up after this duration (30m, 2h) and exit 1 naming what still runs
--originOperator 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]
SubcommandDaemon required?Description
listYes unless --offlineRender every daemon-owned sentinel entry
showYes unless --offlineRender one exact-name daemon-owned sentinel
enableYes unless --offlineMark one sentinel enabled and wake the in-process scheduler immediately
disableYes unless --offlineMark 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
ArgumentRequiredDescription
projectYes (unless --all)One or more project names (positional)
--allNoValidate 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]
ArgumentRequiredDescription
event_idYesRoot event ID returned by foundry emit (positional)
--verboseNoShow trigger payloads, emitted payloads, raw shell output, and audit artifact paths
--flatNoForce 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]
ArgumentRequiredDescription
dateNoDate in YYYY-MM-DD format; omit to show the last 7 days
--projectNoFilter results by project name
--offlineNoExplicitly 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]
OptionRequiredDescription
--nameYesUnique project name
--pathYesAbsolute path to the project
--stackYesTechnology stack: rust, python, typescript, elixir, cpp, swift, kotlin
--agentYesAI agent name (e.g. claude)
--repoYesGitHub slug (owner/repo)
--branchNoDefault branch (default: main)
--iterateNoEnable iterate action
--maintainNoEnable maintain action
--pushNoEnable push action
--auditNoEnable audit action
--releaseNoEnable release action
--install-commandNoShell command to run for local install
--install-brewNoHomebrew formula name
--notesNoHuman-readable notes
--timeout-secsNoCommand timeout in seconds (default: 3600)
--update-policyNopatch, 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-skillNotrue (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
OptionDescription
--pathUpdate the project path
--stackUpdate the technology stack
--agentUpdate the agent name
--repoUpdate the GitHub slug
--branchUpdate the default branch
--skipSet a skip reason (pass empty string "" to clear the skip)
--iterateSet iterate action (true/false)
--maintainSet maintain action
--pushSet push action
--auditSet audit action
--releaseSet release action
--install-commandSet install command
--install-brewSet Homebrew formula
--clear-installRemove the install configuration (cannot be combined with --install-command or --install-brew)
--notesSet notes (pass empty string "" to clear)
--timeout-secsSet command timeout in seconds
--update-policySet the dependency update policy: patch, minor or major
--installs-skillSet 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.