Skip to main content

Gitmoot CLI Reference

Use these commands from an agent session only when the user asks for Gitmoot setup, status, agent coordination, or PR-comment workflow help.

Install And Update

curl -fsSL https://gitmoot.io/install.sh | sh
gitmoot version
gitmoot update --check
gitmoot update --restart-daemon
gitmoot doctor [--repo <path>]

Verify GitHub access before PR workflows:

gh auth status

gitmoot doctor is the environment preflight: it validates gh auth (with an actionable remediation hint) and live-probes the Claude credential selected by runtime-auth.env, so a bad credential is caught before jobs stall on it. Run it after install and before starting the daemon. It also reports delegation worktree count and logical disk size, warning at 10 stale worktrees or 1 GB and distinguishing aged-final reclaimable owners from pinned non-final owners. A running job whose directly recorded runtime PID is confirmably dead is a required stuck jobs failure; legacy jobs with no recorded PID and hosts where process identity cannot be verified are neutral and produce no ghost-job finding. It also reports the SQLite auto-vacuum mode. New homes use bounded incremental reclaim automatically. A legacy home remains a non-blocking warning until an operator deliberately converts it during an idle maintenance window:

sqlite3 "$HOME/.gitmoot/gitmoot.db" \
'PRAGMA auto_vacuum=INCREMENTAL; VACUUM;'

The one-time command fully rewrites the database; the daemon never runs a full VACUUM automatically. sqlite3 is an optional operator tool, not a Gitmoot runtime dependency. For a non-default home, use the exact path printed by gitmoot doctor.

One-shot onboarding: gitmoot setup registers the repo and an agent in one command (--repo owner/repo --agent <name> --runtime codex|claude|shell --session <ref> [--role <role>] [--path .] [--start-daemon]). --repo, --agent, --runtime, and --session are all required — setup errors out if any is missing; --session takes a runtime session reference, last, or a shell command. --watch-issues is on by default in setup, so the daemon comes up tagging-ready for @<agent> issue mentions.

Home And Config

Local state lives in the Gitmoot home (default ~/.gitmoot): the SQLite store, logs/, workspaces/, evals/, artifact_blobs/, and config.toml.

gitmoot init # create the Gitmoot home
gitmoot config path # print the config file location
gitmoot config show # print the effective config

Set the GITMOOT_HOME environment variable (or pass the global --home <path> flag, accepted by nearly every command) to relocate everything — useful for isolated test homes.

Runtime Plugins

Install Gitmoot's Agent Skill into Codex or Claude Code when the user wants the runtime to discover Gitmoot workflow guidance from its plugin system:

gitmoot plugin install codex
gitmoot plugin install claude
gitmoot plugin doctor

Inspect or build packages without installing:

gitmoot plugin build codex
gitmoot plugin build claude
gitmoot plugin path codex
gitmoot plugin path claude
gitmoot plugin doctor codex
gitmoot plugin doctor claude
gitmoot plugin codex-launch --repo .
gitmoot plugin codex-launch --config-snippet

Claude scopes are supported with --scope user|project|local. Codex ignores --scope because the current Codex plugin install command does not use it. Use plugin codex-launch when Codex needs sandbox access to the resolved Gitmoot home on Linux, macOS, or Windows. It prints a codex-face --cd ... --add-dir ... -s workspace-write launch command, or a persistent config snippet with --config-snippet.

Runtime Metadata Registry

Gitmoot drives four built-in runtimes (codex, claude, kimi, plus the subscribe-only shell; the legacy kimi-cli is also compiled in). Each carries declarative metadata — advertised capabilities, default model and effort values, an advisory list of known-valid models, and a descriptor of where token usage is read from. Inspect the resolved registry:

gitmoot runtime list
gitmoot runtime list --json

The values come from the compiled built-in defaults, overlaid with any [runtimes.<name>] overrides in config.toml. Override a built-in runtime's recorded metadata without recompiling — for example to retarget its default model/effort or record its known models:

[runtimes.codex]
default_model = "gpt-5.5-codex"
default_effort = "high"
models = ["gpt-5.5-codex", "gpt-5.4-codex"]
capabilities = ["review", "implement", "ask"]
usage_source = "codex exec --json turn.completed usage"

Two fields are behavioral. default_model is the model fallback when neither the agent nor the job pins --model: agent/job --model, then default_model, then the runtime CLI's own default. default_effort follows the same precedence after job/agent --effort; for Codex, Gitmoot emits -c model_reasoning_effort=<value>. Claude and Kimi do not expose a reasoning effort argument, so the resolved value is a no-op for those adapters. Every other field is inspection-only, surfaced by gitmoot runtime list but changing nothing at runtime: models is advisory (Gitmoot never rejects a --model based on it), and capabilities gates nothing at dispatch. Adapter behavior (auth, sandbox policy, session resume, stream parsing) always stays in Go. With no [runtimes.*] section, and with both defaults unset, no model or effort is forced.

A [runtimes.<name>] section can only tweak a built-in runtime's metadata; it cannot add a new first-class runtime (that requires a code change). An unknown runtime name is a config error surfaced by gitmoot runtime list.

Transcript Retention

Runtime transcript retention is opt-in and invalid or missing configuration fails closed to disabled capture:

[transcripts]
enabled = true
retain = "168h"
max_total_bytes = 2147483648

Enabled capture appends every engine-delivered job attempt (foreground, daemon, temporary session, ephemeral, and delegated jobs) to a private canonical log under <home>/logs/jobs/. Externally driven session jobs have no runtime subprocess and therefore no log. A home-scoped sweep removes settled logs after retain, then evicts the oldest settled logs when the total exceeds max_total_bytes; queued/running jobs and recently finalized jobs are protected. Seat logs remain transient. Expect roughly 440 MB/week at this host's observed rate, though workload output varies.

Raw retained logs are mode 0600 and unredacted on disk. Treat the Gitmoot home as sensitive. JSONL exports redact known credential patterns best-effort, but that redaction is not a vault and cannot guarantee removal of every secret.

Runtime Launch Sandbox

gitmoot sandbox probe

sandbox probe prints whether this Linux host can enforce Gitmoot's strict Landlock launch sandbox and includes the detected ABI. It runs the real hidden re-exec shim and verifies both an allowed write and a denied outside write; unsupported kernels return non-zero. Claude/Kimi produce pipeline stages require this probe to pass and otherwise retain the explicit Codex-only refusal. Codex produce remains on its native sandbox. Landlock confines filesystem writes but does not govern network access; network policy remains the runtime CLI's. Wrapped Claude may write its runtime-owned $HOME/.claude state and $XDG_CACHE_HOME/claude-cli-nodejs cache; wrapped Kimi may write its runtime-owned $HOME/.kimi-code state. Apart from runtime state/cache and standard device nodes, only declared data paths, the disposable workdir, and temp roots are writable.

Repo And Daemon Status

gitmoot status --repo owner/repo
gitmoot events --repo owner/repo
gitmoot daemon start --poll 30s --workers 1
gitmoot daemon start --session <root-job-id>
gitmoot daemon start
gitmoot daemon status
gitmoot daemon logs
gitmoot daemon restart
gitmoot daemon stop

For structured local state, use gitmoot dashboard --json or gitmoot task list --repo owner/repo --json. gitmoot status --json and gitmoot task show are not valid commands.

gitmoot daemon status always prints the configured daemon log path. For a running daemon, it also compares that file's last write with the daemon's recorded start time. A missing file or an older last write produces a warning with both times and suggests journalctl --user -u gitmoot-daemon -f if the daemon runs under systemd. gitmoot doctor reports the same confirmed condition as a non-fatal daemon log check. Fresh, stopped, and indeterminate cases add no output.

Build Skew (Upgraded But Not Restarted)

The daemon is a long-lived process: replacing the binary does not change the code it is executing. It keeps running the old build until you restart it, which is easy to miss because gitmoot version describes the binary you just invoked, not the one the daemon is running.

The daemon records the build it started from in <home>/.gitmoot/daemon.json (version + commit). gitmoot daemon status prints that build and compares it against the build of the binary now sitting at the daemon's own path — the one a restart would load:

build: dev-cd43a49 (cd43a495)
WARNING: daemon running dev-cd43a49 (cd43a495); /root/.local/bin/gitmoot is dev-56ba1c7 (56ba1c74) — restart the daemon to pick it up

gitmoot doctor reports the same comparison as a non-fatal build check. It compares the daemon against the daemon's own binary — not against whatever binary you happen to be invoking, which need not be the daemon's at all.

Unknown is never reported as skew — and never as agreement either. The comparison is skipped when the daemon is not running, when it was started by an older gitmoot (which recorded no build), or when either side is an unidentifiable build. A build is identifiable if it was stamped (any release, and the documented deploy recipe) or if Go's VCS stamping supplied a commit, which a plain go build in a git tree does. Two unstamped builds with no commit are both just dev: indistinguishable, so comparing them would prove nothing.

The web dashboard's /api/health reports the daemon's recorded build — what the daemon process is actually running, not the version of whatever binary now sits at its path — plus, separately, the serving dashboard process's own build. Its daemon.versionSource is recorded when daemon.version came from daemon startup metadata, or unknown when an older daemon recorded no build; in the latter case daemon.version is empty and must never be treated as either skew or agreement. This keeps a stale dashboard or daemon visible rather than silently wrong. The update badge remains relative to the binary on disk, since that is what an update replaces.

Watched Repos

gitmoot repo add owner/repo --path <path> [--poll <duration>]
gitmoot repo list
gitmoot repo set-interval owner/repo (<duration>|default)
gitmoot repo set-interval --all (<duration>|default)
gitmoot repo remove owner/repo
gitmoot repo doctor owner/repo

The gitmoot repo commands manage the watched-repo registry: one daemon per Gitmoot home supervises every enabled registered repo. Omitting repo add --poll stores inherit, so the repo follows the daemon's resolved --poll / [daemon].poll cadence; an explicit value is a per-repo override. repo list prints inherit. Use repo set-interval with a duration to change an override, with default to restore inheritance, or with --all to update all registered repos. repo doctor owner/repo checks checkout/config health. If the registered checkout is missing or is no longer a Git worktree, Gitmoot verifies the recorded primary checkout, repairs the registration, and reports the self-heal. Implicit registration from inside a linked task worktree pins the repo to its primary checkout; an existing valid linked checkout remains usable.

Use daemon start for the background daemon. Use daemon run only when the user explicitly wants a foreground process. Keep the default --workers 1 unless the Gitmoot home has multiple independent runtime sessions or managed agent types with max_background greater than one.

daemon start --repo owner/repo scopes the daemon to a single repo: it polls only that repo's PRs and claims only that repo's queued jobs. Omit --repo to supervise every enabled registered repo from one daemon (#581). Do not start one daemon per repo on the same home expecting parallel isolation: a second daemon on the same home is refused (daemon already running with pid …; a stale pidfile from a dead owner is liveness-checked, so restarts work cleanly). To cap one repo's parallelism on a shared (no---repo) daemon, use the per-repo config keys below instead.

Both daemon run and daemon start accept --session <root-job-id> (alias --root) to pin the worker to one orchestration run. With --session set, the worker runs only jobs whose root_job_id matches that value plus the root coordinator job itself, and ignores every other queued job. Leaving it empty keeps the default behavior of matching all jobs.

Both daemon run and daemon start also accept three opt-in flags (all off by default, so leaving them unset is byte-identical to before): --watch-issues watches open issues for @<agent> ask … mentions and routes them to jobs, mirroring the PR-comment watcher; --scheduler pool selects the continuous worker-pool scheduler that re-queries the queue as workers free and auto-isolates a contended same-repo read job into an ephemeral worktree (fixing a same-repo dependent-job deadlock), versus the default --scheduler barrier; --watch-skillopt-reviews polls watched SkillOpt review issue comments and imports valid feedback automatically.

To run a repo's queued jobs N-wide, use --parallel N (sugar for --workers N --scheduler pool; it cannot be combined with --workers or --scheduler). Raising --workers above 1 without an explicit --scheduler now auto-selects pool, since multiple workers under barrier serialize same-repo jobs anyway; an explicit --scheduler barrier is still honored. gitmoot daemon status reports the live scheduler mode and worker count (e.g. scheduler: pool, workers: 5), and the daemon logs a preflight warning — with the exact relaunch command — when ≥2 parallelizable jobs are queued under a serializing config. Same-repo parallelism is bounded by distinct runtime sessions as well as distinct checkouts; see Run Jobs In Parallel On A Repo. One repo's concurrency can also be capped from config, without any relaunch, via a [repos."owner/repo"] section with max_parallel = N (#576) — see Cap one repo's parallelism from config.

Reconfigure Without Restarting

kill -HUP <daemon-pid> re-reads the [daemon] config section (poll, workers, scheduler, parallelism, idle_grace_ticks, and idle_max_multiplier) live (#577) — no teardown, no dropped jobs, no environment re-inheritance. Values pinned by explicit launch flags win over the re-read config. Prefer SIGHUP over a restart when only tuning throughput.

The default-on [disk_guard] section pauses normal queued-job dispatch when the filesystem holding the Gitmoot home and worktrees has less than either min_free_bytes (default 2147483648, 2 GiB) or min_free_percent (default 5) available. Both checks apply when both are non-zero, so the more conservative floor wins:

[disk_guard]
enabled = true
min_free_bytes = 2147483648
min_free_percent = 5

The guard fails closed: an unreadable config, missing path, statfs error, or invalid filesystem measurement pauses dispatch instead of assuming the disk is healthy. Paused jobs remain queued and retry automatically on the next healthy daemon pass. The daemon writes a greppable DISK GUARD REFUSED JOB DISPATCH log and a dispatch_refused_disk_guard job event containing the measured free space, configured floors, and measured path. gitmoot daemon status always reports the current measurement and prints UNHEALTHY, dispatch paused when the measurement cannot be established or a floor is breached. The guard applies only to normal agent-job dispatch; daemon maintenance/reconciliation remains runnable so an internal reclaim pass can free space.

Claude Runtime Auth

Claude runtime auth lives in owner-only (0600) runtime-auth.env and is re-read for every adapter build. Manage it without placing secrets on argv:

claude setup-token
gitmoot auth set claude
gitmoot auth status
gitmoot auth probe claude
gitmoot auth unset claude

Rotation takes effect on the next delivery without a daemon restart. Status is local and masked; probe is a paid fresh-session liveness check. When the file selects one managed Claude variable, Gitmoot injects all three names and blanks the absent ones so an ambient API key cannot override OAuth. Unset writes an explicit-empty file; do not delete it, because a missing file can trigger the one-time legacy/environment bootstrap.

Opt Claude into the daemon-owned credential-terminating loopback gateway with:

[credentials]
model_gateway = true
model_gateway_allow_hosts = ["api.anthropic.com"]

Each delivery receives a job-scoped placeholder and ANTHROPIC_BASE_URL; the gateway holds the snapshotted real credential, forwards only to an allowlisted host, streams the response, and revokes the placeholder when delivery ends. Failures are fail-closed. The option is off by default and does not cover Codex or Kimi.

env-var routing is cooperative, not a hard egress boundary — a malicious agent can unset it; this buys credential custody/policy/attribution, not enforcement. The strong "agents never hold real credentials" claim also requires Landlock read-rules for runtime-auth.env (same-UID read is currently possible) — that is P3.

GitHub Poll Budget And Idle Cadence

[github].conditional_requests defaults to true. The daemon sends ETag validators on its four per-tick repository reads and reuses the cached raw JSON on 304 Not Modified; set it to false as a kill switch. The cache is in memory, so the first sweep after a process restart is unconditional. The same section's calls_per_hour_warn is a count-and-warn threshold (0 disables it). It is an approximate daemon-local sliding-hour count and excludes foreground and agent-owned gh processes.

After [daemon].idle_grace_ticks all-304 polls (default 3), a quiet repo moves to 2x and then up to [daemon].idle_max_multiplier (default 4; 1 disables decay). A miss, poll error, queued job, or in-flight job resets it immediately. Open-PR repos remain at base cadence because their per-PR comment reads are not conditional. Decay gates repository GitHub calls only; heartbeat, pipeline, and chat maintenance still wakes at the base interval.

gitmoot dashboard shows local state — daemon health, repos, agents and runtime sessions, jobs by state, worktrees, branch locks, SkillOpt train phase/candidate, and pending interactive prompts.

On a real terminal (stdin and stdout both a TTY) and with no other output/mutation flag, gitmoot dashboard launches an interactive TUI: a sidebar of pages (Attention, Activity, Trains, Agents, Workers, Jobs, Locks, Health, Config) that auto-refreshes. Navigate with tab/shift+tab or ←/→; ↑/↓ selects a row; ? opens a per-page key reference; r refreshes, q quits. The TUI is the cockpit — every page can act, and each action runs the same store/workflow code as its CLI equivalent:

  • Attention lists pending prompts (a answer inline, d dismiss) and the actual blocked/failed/cancelled jobs with their latest event message (enter detail, R retry, B report bug). A red banner appears when the daemon is stopped; s restarts it with its previously persisted flags.
  • Activity shows the live orchestras: each active delegation root with its children, so "what are my agents working on right now" is answered at a glance; enter opens the request/result detail of a root or a specific delegate.
  • Trains lists every train session: enter opens ANY session's live phase view (not just the newest), s stops a live session (a reason is required; same path as skillopt train stop), d deletes a finished session and its history — and, if gitmoot created GitHub repos for that session, a second confirm offers to delete those too (never offered for repos gitmoot did not create; a missing delete_repo token scope shows its gh auth refresh remedy and can be retried in place).
  • Agents lists registered agents: enter opens a detail with the template, recent jobs, and the template's version history — in the detail ↑/↓ selects a recent job (then a version) and enter opens it: a recent job opens that job's detail (esc returns), and the list scrolls when an agent has many jobs; n registers a new agent (name, codex/claude/kimi runtime, installed template); o starts a training session for the agent's template via a pre-filled form (review/workspace repos, request, codex/claude backend, optional model — the backend/model are stored in the session's optimizer defaults so train continue inherits them), then drops into the live phase view; D deletes the agent (refused while jobs reference it); v in the detail reverts the template to a previous version (same as gitmoot agent template revert).
  • Jobs lists every job with a state summary: enter shows the event history, R retries failed/blocked/cancelled jobs (same path as gitmoot job retry), c cancels queued, running, AND blocked jobs (same as gitmoot job cancel; running ones show cancelling… until the daemon settles them), and B opens a redacted bug-report preview for failed/blocked/cancelled jobs. In the preview, g creates or reuses the GitHub issue and keeps the issue URL visible.
  • Locks explains and lists locks, stale resource locks first in red (the owning process died; a running daemon reclaims them automatically); active locks collapse to a count. Branch locks are released with gitmoot lock release owner/repo <branch> --owner <agent>.
  • Workers lists runtime workers (agent sessions). Health shows the daemon block (running state, persisted flags, log error tail) plus environment checks. Config renders the effective config with inline edits for scalar fields (or $EDITOR for the full file).

Form questions are also published as interactive prompt records, so an agent can answer them with gitmoot interactive answer while the form is open. Inline answers/dismissals use the same store APIs as gitmoot interactive answer / clear.

Everywhere else — pipes, redirects, CI, --plain, --json, --all, --watch, --answer, --dismiss — it prints the one-shot snapshot instead, unchanged. Set GITMOOT_NO_TUI=1 or TERM=dumb to force the non-interactive path globally.

gitmoot dashboard # interactive TUI on a terminal
gitmoot dashboard --plain # one-shot snapshot on a terminal
gitmoot dashboard --json
gitmoot dashboard --all
gitmoot dashboard --answer <prompt-id> --value <value>
gitmoot dashboard --dismiss <prompt-id>
gitmoot dashboard --watch # plain redraw until Ctrl-C (terminal only)
gitmoot dashboard --watch --interval 2s
gitmoot dashboard --web [--addr 127.0.0.1:8080]

In the one-shot styled output the dashboard leads with a "needs attention" block, colors and truncates long lists, and groups near-identical runtime sessions; --all shows everything. --watch redraws on an interval (default 5s) and cannot be combined with --json, --answer, or --dismiss.

gitmoot dashboard --web serves the read-only web dashboard until interrupted: a live orchestration/delegation graph plus whole-history Galaxy, Jobs, Agents, Charts, and Health views with run summaries and prompt/output inspection — the browser view of a running orchestration. --addr sets the listen address (default 127.0.0.1:8080). To expose it beyond localhost, bind to an internal address and put an authenticating reverse proxy in front — the dashboard itself has no authentication. See the Dashboard section for the views, routes, refresh cadences, and mobile support.

Bug Reports

Use gitmoot report bug to build a redacted GitHub-ready issue from local Gitmoot error state. Job reports are fully supported; daemon, dashboard, and train selectors are reserved and return clear unsupported-source errors until their source collectors are implemented.

gitmoot report bug --job <job-id> [--preview]
gitmoot report bug --job <job-id> --create --yes
gitmoot report bug --source daemon --preview
gitmoot report bug --source dashboard --preview
gitmoot report bug --train <session-id> --create --yes

Default behavior is preview. Agents should run preview first, show or summarize the redacted draft, and create an issue only when the user explicitly asks or the active workflow policy already permits filing reports. Non-interactive creation requires --create --yes.

Created reports target gitmoot/gitmoot, include the labels gitmoot-dashboard-report and bug, and carry a fingerprint marker in the body so duplicate open issues can be reused instead of creating another report. If duplicate search fails in the CLI path, Gitmoot prints a warning and still creates the issue; dashboard creates fail closed and keep the preview open so the user can retry.

After creation, report the printed issue URL back to the user. If Gitmoot says an existing issue was found, report that URL instead of presenting it as a new issue.

Agent Setup

Start a new runtime session managed by Gitmoot:

gitmoot agent start reviewer \
--runtime codex \
--repo owner/repo \
--path . \
--role reviewer \
--capability ask \
--capability review \
--model gpt-5-codex \
--effort high \
--start-daemon

agent start accepts --memory[=true|false]. When omitted, [memory].default_enroll controls enrollment; explicit --memory=false overrides a true default. This applies only to agents created by manual agent start. A successful start always prints memory: on, memory: off (enable with --memory), or memory: enrolled but globally disabled by [memory].disabled.

agent start, agent subscribe, and agent type set accept an optional --model <name> flag that sets the agent's default runtime model. It is a free-form, runtime-scoped string (a Codex, Claude Code, or Kimi Code model name) with no allow-list; both --model X and --model=X are accepted. A per-job --model (or a delegation's model field) overrides this default, and an omitted model preserves the runtime's own default. The same default can be set in config under [agents.<type>].model.

The same commands accept --effort <value> as the agent's default reasoning effort, and agent run, ask, implement, review, and orchestrate accept it as a per-job override. A delegation's effort field is the child-job override, while an ephemeral worker spec's effort is that worker's default. The resolution order mirrors model selection: job/delegation effort, agent/worker effort, [runtimes.<runtime>].default_effort, then no explicit override. Values are free-form pass-through strings. Codex receives -c model_reasoning_effort=<value>; Claude and Kimi ignore the setting.

agent start and agent subscribe accept --policy (default auto). The policy maps to the runtime permission mode and decides what a headless job may do:

--policyClaude --permission-modeHeadless capability
read-onlyplaninspect/report only, no writes
workspace-writeacceptEditsfile edits only — does NOT unblock Bash (go/git/gh)
danger-full-accessbypassPermissionsfull implementation: file writes plus Bash
auto (default)(no flag)non-deterministic — inherited from ambient Claude config

Because of this, an agent that carries the implement capability must be started/subscribed with a write policy. Gitmoot fails closed: --capability implement with auto/empty or read-only is refused at agent start, agent subscribe, and at implement-job dispatch with an actionable message. Set --policy danger-full-access for full headless implementation (file writes plus go/git/gh), or --policy workspace-write for edits-only (Bash stays blocked). read-only/ask/review agents are unaffected.

agent subscribe accepts --preset-delivery full|referenced|auto (default full), and agent update <name> --preset-delivery <mode> flips it in place on an already-registered agent. The mode is a sticky per-agent preference: re-running agent subscribe on an existing agent WITHOUT --preset-delivery (e.g. to refresh its session/repo) preserves the stored mode; only brand-new agents default to full. It controls how the agent's installed preset (template) prompt is delivered on each job:

modebehavior
full (default)always inline the full preset prompt every job — the pre-existing behavior, byte-identical
referencedsend a short "use your installed <preset> preset (commit <c>)" reference instead of the whole body, but only when Gitmoot has recorded that the exact resumed session already loaded the same preset at the same commit; any doubt (a new / last / fresh session, an unknown session, or a changed commit) falls back to full
autolike referenced, and additionally only when the runtime persists sessions (codex/claude); shell/kimi/custom always send full

The optimization is correctness-first and additive: the job payload always snapshots the exact preset id, resolved commit, and content regardless of mode, so auditability and retry determinism are unchanged, and a preset commit change invalidates the recorded loaded-state so the next job re-sends the full preset. Leave it at full unless you repeatedly resume a stable persisted session and want to save preset tokens.

--runtime accepts codex, claude, kimi, or kimi-cli. Kimi Code is a first-class runtime adapter alongside Codex and Claude Code; kimi targets the current Kimi Code CLI (stream-json output) while kimi-cli is the opt-in legacy Kimi CLI adapter (#546) — choose kimi unless you specifically run the legacy CLI. The two count as the same runtime family for cross-family review. Before starting a Kimi-backed agent, authenticate the Kimi CLI with kimi login, then restart the Gitmoot daemon so it inherits the logged-in session:

kimi login
gitmoot daemon restart
gitmoot agent start reviewer \
--runtime kimi \
--repo owner/repo \
--path . \
--role reviewer \
--capability ask \
--capability review

A Kimi agent's runtime reference must be a Kimi session id (session_<uuid>) or empty; Gitmoot parses the session id from the Kimi CLI's stream-json output.

Subscribe an existing runtime session:

gitmoot agent subscribe reviewer \
--runtime codex \
--session <session-id-or-last> \
--repo owner/repo \
--role reviewer \
--capability ask \
--capability review \
--model gpt-5-codex \
--effort high

agent subscribe additionally accepts --runtime shell, the deterministic no-LLM adapter whose --session is a command (the job prompt arrives as $1; stdout must carry the gitmoot_result envelope) — useful for deterministic end-to-end tests.

Inspect and manage agents:

gitmoot agent list
gitmoot agent show reviewer
gitmoot agent show reviewer --json
gitmoot agent repos reviewer
gitmoot agent allow reviewer --repo owner/other-repo
gitmoot agent deny reviewer --repo owner/other-repo
gitmoot agent doctor reviewer
gitmoot agent restart reviewer
gitmoot agent remove reviewer

gitmoot agent restart <name> abandons the agent's runtime session and binds a fresh one in place — the fix for a dead or stranded session that would otherwise tempt a re-register. It refuses while the session is live or the agent has in-flight jobs (finish or cancel those first). gitmoot agent remove <name> unregisters the agent.

Delegate to a registered agent from the current local chat:

gitmoot agent run project-planner --repo owner/repo "Return the plan status."
gitmoot agent run lead --repo owner/repo --task task-001 --background "Implement this task."
gitmoot agent run reviewer --repo owner/repo --pr 12 --background "Review this PR."
gitmoot agent run lead --repo owner/repo --action implement --pr 12 "Fix findings on the existing PR."
gitmoot agent review reviewer --repo owner/repo --pr 12 "Review this PR."
gitmoot agent implement lead --repo owner/repo --task task-001 "Implement this task."
gitmoot agent implement lead --repo owner/repo --pr 12 "Fix findings on the existing PR."
gitmoot agent implement lead --repo owner/repo --task task-002 --base origin/main "Implement from current origin/main."
gitmoot agent ask project-planner --repo owner/repo "Return the plan status."
gitmoot agent ask project-planner --repo owner/repo --background "Write the implementation plan and goal file."
gitmoot agent run lead --repo owner/repo --model gpt-5-codex "Implement this task."
gitmoot agent run lead --repo owner/repo --effort xhigh "Implement this task."
gitmoot job watch <job-id>

agent run --action ask|review|implement explicitly selects the job action and wins before the usual inference order (--task -> implement, then --pr/review --head-sha -> review, then message heuristics). --type <name> has a separate meaning: it selects a managed agent type. The flags can be used together. Invalid actions and contradictions are rejected before enqueue; notably, --action review requires --pr, while --action implement --pr is the explicit existing-PR fix-pass route.

For agent implement --pr <number> (or the equivalent agent run --action implement --pr <number>), Gitmoot resolves the PR and reuses its existing task and worktree only when the PR is open, its head is in the same repository, and its head branch matches that task. This is the only route that lets a pr_open task re-enter implementation; changes_requested remains reusable as before. reviewing and ready_to_merge, closed/merged PRs, fork or unrelated heads, branch mismatches, active implement jobs, live processes, dirty worktrees, and foreign branch locks are refused. The existing PR number stays on the job payload so finalization adopts that PR instead of creating a second task or PR.

For agent implement, --base <ref> selects the commit used to create a new branch worktree. agent run accepts the same flag when it routes to implement. An origin/* ref is fetched before it is resolved, and an unknown ref fails before a job is enqueued. --base HEAD explicitly follows the registered checkout's current commit. On implement, --head-sha <sha> is a compatibility alias for --base <sha>; passing both with different values is an error.

Set a default for implement dispatches in config.toml:

[workflow]
implement_base = "origin/main"

The flag wins over the config value. The config value "HEAD" keeps checkout-following behavior. With no flag and no config value, Gitmoot still uses checkout HEAD, but refuses when the checkout is on a non-default branch that is behind origin/<default>. The error reports the branch and behind count and offers both explicit choices: --base origin/<default> or --base HEAD.

gitmoot agent run, ask, implement, and review (and orchestrate) accept an optional --model <name> flag that pins the runtime model for that one job, overriding the agent's configured default. It is a free-form, runtime-scoped string (a Codex, Claude Code, or Kimi Code model name) with no allow-list; an omitted --model leaves the agent's default model in effect. Both --model X and --model=X are accepted.

--effort <value> and --effort=<value> select reasoning effort for one job with the same job-over-agent-over-registry precedence. Gitmoot does not validate an allow-list; Codex validates the forwarded value.

The same commands accept an optional per-job --runtime codex|claude|kimi|kimi-cli|shell override: that ONE job runs through the named runtime while the agent's registered default runtime stays untouched (agent show is unchanged afterwards). An overridden job never resumes — and never writes back to — the agent's default-runtime session: it runs on a fresh session of the override runtime, or on an explicit --session <ref> (a Codex/Claude session id, a Kimi session id, or — required for shell — a command; last is rejected because it resumes whichever session is most recent rather than a concrete one), and its runtime-session lock names the override runtime so it cannot collide with the default session's lock. Model rule: --model combined with --runtime is interpreted for the OVERRIDE runtime; an override without --model uses the override runtime's default model — the agent's configured default model is never applied to a different runtime. The same rule applies to --effort: an explicit job value belongs to the override runtime, while the agent's default effort does not cross runtimes. An unknown --runtime fails before any job is enqueued, background (daemon) jobs honor the override identically to foreground, and a coordinator's delegation-tree continuations (synthesis, corrective, replan, finalize) inherit the override, so an orchestrate --runtime tree stays on the override runtime across generations:

# Retry a hard review through Claude without re-registering the reviewer:
gitmoot agent review reviewer --repo owner/repo --pr 123 "Re-review this PR." --runtime claude
gitmoot agent ask reviewer "Compare the approaches." --repo owner/repo --runtime kimi --model kimi-k2

gitmoot orchestrate, agent run, and agent implement also accept an optional --skip-native-review-fanout flag. By default an implement job that opens a pull request fans the PR out to Gitmoot's native reviewers (the configured required reviewers, or the ones passed for the task). With --skip-native-review-fanout set, the coordinator owns review orchestration instead: the implement→PR step still records the PR baseline, runs the merge gate, and records the implemented decision, but it enqueues no native review jobs. The skip is honored on both PR-open paths — the engine's implement-advance and the daemon's GitHub PR-watcher — so a PR opened either way stays free of native review fan-out. The flag defaults off; leave it off for the full native review fan-out, which is byte-identical to prior behavior.

When a synchronous agent implement/run/ask/review/orchestrate job delivers and succeeds terminally but a benign post-success advancement step errors — for example a merge-gate block on the freshly-opened PR, or a 422 "a pull request already exists" race — the command no longer discards the result. It exits 0 with the agent result on stdout (in JSON mode this includes an additive advance_error field carrying the advance warning, omitted when there is none), prints advance warning: … to stderr, and shows an advance_error: line in human output. Genuine non-terminal failures (the job did not reach succeeded) still exit non-zero as before. A normal success with no advance error is byte-identical to prior behavior — no advance_error field is emitted.

Review resilience under branch churn. A review job is pinned to the PR head SHA it was queued against; in an active dev loop the branch often advances (a new commit is pushed) before the queued review runs, leaving the registered checkout on a newer head. Rather than failing the review on that head-SHA mismatch, Gitmoot re-syncs it: when the PR is still open, the review is re-targeted to the checkout's current head — reviewing the newest commit is exactly what a human reviewer does — and a review_head_resynced job event records the old→new head. The mismatch is only allowed to fail cleanly when the PR is closed/merged (a stale review of a dead PR is not useful) or when the checkout is dirty. Relatedly, when a foreground agent review finds the agent's serialized runtime session busy, the review is now left queued for the daemon to run when the session frees (a requeued_runtime_busy event is recorded) instead of being cancelled and dropped; agent ask/implement stay synchronous and keep their existing busy-session cancel behavior.

Start an orchestra of agents with gitmoot orchestrate:

gitmoot orchestrate project-planner "Plan and split this work across agents." --repo owner/repo
gitmoot orchestrate project-planner "Plan and split this work." --repo owner/repo --model gpt-5-codex
gitmoot orchestrate project-planner "Plan and split this work." --repo owner/repo --effort high
gitmoot orchestrate project-planner "Review PR #123 in this repo." --repo owner/repo --recipe review-panel

gitmoot orchestrate <agent> "..." [--repo R] [--recipe id] is sugar for gitmoot agent run <agent> --background "...". It starts a conductor (coordinator) that returns a delegations[] score; the players (child agents) then run in parallel or in dependency order, and a finale (continuation) reconvenes and synthesizes the results. --recipe review-panel|decompose-and-verify|verifier (#477, also accepted on agent run) routes the coordinator through a named built-in recipe prompt without changing the agent's identity — see Coordinator Recipes.

This uses the same agent registry, repo access grants, cached template snapshot, runtime adapter, and local job history as PR-comment jobs. agent run is the default coordinator-safe entrypoint because it routes to ask, review, or implement and keeps branch, worktree, commit, push, PR, and workflow lifecycle inside Gitmoot. agent ask is for analysis, planning, and questions only; it is read-only, so when the message reads like branch/commit/push/PR orchestration it prints a non-fatal note and still runs (pass --force to suppress the note). The runtime plugin helps Codex or Claude Code discover Gitmoot guidance, but it does not replace the Gitmoot CLI. Synchronous jobs and queued jobs both use the same runtime session locks.

Configure managed background agent types:

gitmoot agent type list
gitmoot agent type show planner
gitmoot agent type set planner --runtime codex --template planner --max-background 2 --idle-timeout 20m
gitmoot agent type set planner --model gpt-5-codex
gitmoot agent type set planner --effort high
gitmoot agent gc

agent type set --model <name> (or [agents.<type>].model in config) sets the default runtime model for that managed agent type.

agent type set --effort <value> (or [agents.<type>].effort) sets its default reasoning effort.

Schedule recurring agent work (heartbeats, off by default):

gitmoot agent heartbeat add repo-maintainer daily-status \
--repo owner/repo --interval 24h --prompt "Daily status report." --enabled
# implement is policy-gated; --runtime pins a runtime for this schedule.
gitmoot agent heartbeat add builder nightly-tidy \
--repo owner/repo --interval 24h --action implement --runtime codex \
--prompt "Fix the top lint error and open a small PR."
gitmoot agent heartbeat list [--agent <agent>]
gitmoot agent heartbeat show repo-maintainer daily-status
gitmoot agent heartbeat enable|disable repo-maintainer daily-status
gitmoot agent heartbeat remove repo-maintainer daily-status

A heartbeat enqueues a normal background job on its interval. Actions: read-only ask (default) or review (review needs the agent's review capability), plus the policy-gated write action implement — it only runs for an agent that holds the implement capability AND a write-granting policy (--policy workspace-write or danger-full-access); otherwise it is refused at add and no-op'd (last_status = policy_readonly) by the daemon scan. An optional --runtime codex|claude|kimi runs the scheduled job on that runtime (fresh session) instead of the agent default. gitmoot daemon status surfaces each schedule's last-run/next-due/last-status. See Heartbeat Schedules for the full reference.

A registered single instance shadows a managed type of the same name: dispatch resolves gitmoot agent <name> to a registered single instance before a type, so force the type with --type <name> (or do not register a single instance of that name). Since v0.5.1 a foreground gitmoot agent ask <type> (the ask action) dispatches to the managed type synchronously; background run/review/implement to a type and [parallel_sessions] temp-session forking use the background path. See Running one agent's jobs concurrently.

Agent Templates

Install or refresh the built-in thermo review template:

gitmoot agent template update thermo-nuclear-code-quality-review
gitmoot agent start thermo-review \
--runtime codex \
--repo owner/repo \
--template thermo-nuclear-code-quality-review \
--start-daemon

Install or refresh the built-in full planner/goal template:

gitmoot agent template update planner
gitmoot agent start project-planner \
--runtime codex \
--repo owner/repo \
--path . \
--template planner \
--start-daemon

For fast current-chat planning, use the Gitmoot skill with the same packaged agent-templates/planner.md instructions instead of starting a background job:

Use the Gitmoot planner here. Write the implementation plan.

The current chat can also import any cached custom agent or template prompt:

gitmoot agent prompt frontend-reviewer
gitmoot agent prompt frontend-reviewer --json

This prints the prompt content for the current chat to apply locally. It does not create a job, start a daemon, resume a runtime session, or post a PR comment — a free read-only peek.

To track the here-method work by default, add --record: it opens a session job on import (see "Session jobs" below) and returns the prompt with a header line naming the job id, so the imported work shows in job list / the dashboard once you clock out:

gitmoot agent prompt frontend-reviewer --record [--repo owner/repo] [--type ask|review|implement] [--json]
# prints: [gitmoot session job <id> — when this work is complete, run:
# gitmoot job close <id> --decision <approved|changes_requested|implemented|blocked|failed|skipped> --summary "..."]
# followed by the prompt body.

--record accepts either a registered agent or a bare template id:

  • Registered agent: the repo comes from --repo, else the agent's repo_scope (error if neither is set); the session job records the agent name.
  • Bare template (no agent of that name registered, e.g. the packaged planner): --repo owner/repo is required — a template has no repo_scope to fall back on — and the session job records the template id as its agent identity (#673). The repo must be tracked (gitmoot repo add owner/repo first).

--type defaults to implement. When the imported work is done, close the job with gitmoot job close <id> --decision …. --json includes the opened job_id. Without --record, behavior is unchanged (no job).

Draft and validate a captured template before installing it:

gitmoot agent template draft release-planner
gitmoot agent template validate .gitmoot/templates/release-planner.md
gitmoot agent template add release-planner --file .gitmoot/templates/release-planner.md

agent template draft only creates the standard markdown structure. For current-chat capture, the active Codex or Claude chat reads references/TEMPLATE_CAPTURE.md and fills that structure from visible conversation context. Gitmoot does not extract hidden runtime memory.

Create a local custom prompt template:

mkdir -p agents
gitmoot agent template draft frontend-reviewer --output agents/frontend-reviewer.md
$EDITOR agents/frontend-reviewer.md
gitmoot agent template validate agents/frontend-reviewer.md
gitmoot agent template add frontend-reviewer --file agents/frontend-reviewer.md
gitmoot agent start frontend-reviewer \
--runtime codex \
--repo owner/repo \
--template frontend-reviewer \
--role reviewer \
--capability ask \
--capability review

After editing a local template file, refresh Gitmoot's cached snapshot:

gitmoot agent template diff frontend-reviewer
gitmoot agent template update frontend-reviewer

Template updates are versioned locally. gitmoot agent template show <id> prints the current version, content hash, source commit, and promotion state. Agents use the current promoted version by default, or a pinned version when configured with a reference such as --template frontend-reviewer@v1.

A bad promotion can be undone: gitmoot agent template revert <template-id> --version <version-id> makes a superseded version current again (the dashboard's Agents page does the same with v in the agent detail). Queued jobs keep the exact template content snapshot they were created with.

Discover templates by metadata:

gitmoot agent template list --runtime codex --output goal_file
gitmoot agent template list --tag review --capability ask
gitmoot agent template show frontend-reviewer

Back Up And Share Templates Via GitHub

Templates can be backed up to and pulled from a GitHub repo (#476):

gitmoot agent template export [<id>...] [--all] [--to <dir>] [--dry-run]
gitmoot agent template publish [<id>...] [--all] [--repo <owner/repo>] [--path <subdir>] [--ref <branch>] [--message <msg>] [--create] [--dry-run]
gitmoot agent template pull [<id>...] [--all] [--repo <owner/repo>] [--ref <ref>] [--path <subdir>] [--dry-run]
gitmoot agent template add <id> --from-repo <owner/repo> [--ref <ref>] [--path <file>]
gitmoot agent template remote set <owner/repo> [--ref <ref>] [--path <subdir>]
gitmoot agent template remote show

export writes template .md files to a local directory; publish commits them to a GitHub repo (--create creates a missing private repo); pull installs or refreshes templates from that repo; add --from-repo installs a single template file directly from a repo. --all on export/publish covers only your custom templates — built-ins are skipped. remote set stores a default remote in the [template_remote] config section (repo; ref defaults to main; path defaults to templates) so publish/pull/add can omit --repo; with no remote configured, those commands require an explicit --repo. Caution: templates are stored and published VERBATIM (prompt body + metadata) — point the remote at a PRIVATE repo unless the prompts are meant to be public. See the template capture workflow for the full flow.

Organization Registry

Organization mode is opt-in. Initialize a starter registry and verify the required Herdr provider (>=0.7.5) with:

gitmoot org init
gitmoot org brief --role owner [--json]
gitmoot org chart [--json]
gitmoot org status [--json]

The registry uses [org] enforce = "warn"|"block" and [org.roles."name"] entries with parent, scope, merge_rule, an optional cosmetic display_name, an optional model runtime pin, an optional per-role recycle_after duration override, and an optional pane Herdr binding. The binding resolves as a unique exact live pane label or a currently live literal pane id. Roles without a binding report unknown live presence, and event wakes for them are skipped with an observable log and increment the role's missed-wake counter rather than being inferred from a pane label. There is exactly one root named owner; accepted scopes are *, owner/*, and owner/repo. Malformed org configuration fails closed and loudly. brief records passive last-seen presence for its role and can render static context with provider state unknown during an outage; chart and status require a live compatible Herdr snapshot. When configured, brief --json and status --json include the role's pane binding. chart and status append a ⚠ flagged (N missed wakes) marker after the role reaches the positive [orchestrate].max_consecutive_missed_wakes threshold; their JSON rows expose missed_wakes, flagged, and flag_reason. The default threshold is 0 (disabled). A missed-wake row more than 24 hours old is omitted from this flag calculation; its stored consecutive counter remains unchanged for the next real delivery attempt. status --json also exposes active_jobs, the live queued-plus-running job count attributed to the role through ActingOrgRole (#1057); it is distinct from daily or historical job counts. Escalations are recorded with gitmoot org escalate and resolved with gitmoot org escalate resolve; correlation beyond the optional --note link remains phase 2 work.

For Claude-runtime jobs attributed with ActingOrgRole, an explicit provider weekly-quota rejection marks that role unavailable until the provider's stated reset time. org status prints ⚠ UNAVAILABLE, reason=quota, and the UTC reset instant in the role detail; org chart appends the same warning, and their JSON rows expose provider_state: "unavailable", unavailable_reason, and unavailable_until. New operator dispatches to the role are refused and already-queued jobs for it stay held. The incident sends one best-effort direct wake to the role's configured parent, then clears at the reset instant or on that role's first subsequent successful Claude-runtime job, whichever happens first. Success on another runtime cannot clear the Claude wall. If Claude supplies no parseable reset, Gitmoot uses the existing bounded 15-minute quota fallback. Codex and Kimi quota-message detection are not part of this phase.

The read-only Org page consumes GET /api/org for the store-backed role tree, health strip, typed escalations, and current signal feed, plus GET /api/org/role/{name} for one role's identity, presence, recycle history, and today's job counts. These endpoints open SQLite read-only and never contact Herdr. Responses are cached for at most 15 seconds; data_as_of is the newest persisted source timestamp, not the request time. detection_enabled is true only when blocked_role_wake_after is positive and at least one org event rule is enabled; otherwise detection_hint explains why an empty signal feed is not evidence that every role is healthy. The enabled blocked-role evaluator also persists its latest Herdr snapshot for these endpoints. Only observations from the last five minutes are rendered as blocked, working, or idle; stale, missing, done, and unknown observations render as never-seen. An active provider quota incident renders separately as unavailable, including its reason and reset boundary in presence_detail; it is never collapsed into never-seen.

Session lifecycle (phase 3): [org] recycle_after = "24h" (a duration, per-role overridable) marks a role recycle-overdue after it has been idle that long, shown read-only in the recycle column of org status (off | fresh | eligible | overdue). [org] recycle_enforce = "off" | "warn" | "block" (default off) then refuses (block) or advises (warn) new --org-role dispatches from a role past its recycle_after, until it hands off and recycles; journaling a handoff note is never blocked. recycle_enforce needs a configured recycle_after to take effect. Both are binary-first — deploy the binary before any config sets them. When recycle_enforce is not off, an overdue refusal or warning also emits a repeating (once per recycle_after) org.recycle_overdue event through the org event sink; route it to a wake with org events rule add --on recycle-overdue --wake <role>. Notification delivery is best-effort — reliable from a foreground agent ask, but a short-lived --background/orchestrate dispatch may exit before the wake fires.

Agent ask, run, review, implement, and orchestrate accept --org-role <name>. The role is validated and touched before dispatch, then stored as acting_org_role in the job payload for provenance, and its scope is enforced at enqueue. Capability booleans are not part of this phase.

External-coordinator workflow groups

Pass --workflow <label> to agent ask, agent run, agent review, agent implement, orchestrate, or job open. Labels are global lowercase slugs up to 64 characters. One / may separate a namespace and campaign; both sides use lowercase letters, digits, and single hyphens without leading/trailing hyphens. Orchestration children and continuations inherit the label.

Require workflow labels

require_workflow defaults to true. In the default auto mode, an unlabeled fresh agent dispatch is bucketed as adhoc/<agent>-<yyyy-mm-dd> and receives a workflow_autolabeled event; it is never rejected. Set [workflow] require_workflow = false to opt a repository out. To reject unlabeled dispatches, ensure the applicable global or repository policy explicitly sets require_workflow = true, then set require_workflow_mode = "strict" and pass --workflow <namespace>/<campaign>. Mode-only legacy configurations remain in auto. Both settings can be overridden in [repos."owner/repo"]. GitHub comment dispatches always take the auto-label path in either mode so acknowledgement ordering stays unchanged; engine PR reactions inherit their initiating dispatch's label instead. gitmoot doctor always reports unlabeled-job drift as advisory diagnostics (including session-open and task-recover rows that bypass enforcement), while the overview shows that item only for repositories where the policy is enabled. gitmoot repo add --agents-md scaffolds the team discipline into AGENTS.md.

With require_workflow = false, dispatch and enqueue remain byte-identical; doctor drift diagnostics remain always-on advisory, and the overview item remains policy-gated.

Organization registry and scoped dispatch

The optional [org] registry is enabled by any [org.roles."name"] section. Roles have parent, scope, advisory merge_rule (owner, self, or none), an optional model runtime pin, an optional per-role recycle_after duration override, and an optional Herdr pane used by live presence and event-rule wakes; exactly one parent-less role is required. Scope entries are *, owner/*, or exact owner/name, and child scope must be a subset of its parent.

Use gitmoot org validate to validate the registry against the live Herdr snapshot and event-rule store. It fails when a role has no live pane, has no enabled wake route, or a labeled live pane is not claimed by any role; each failure includes category counts and a reason. Use gitmoot org show to view the configured roles. When enabled, fresh local agent ask, agent run, agent review, agent implement, orchestrate, and task run dispatches require --org-role <role> (or GITMOOT_ORG_ROLE) and reject out-of-scope repositories at enqueue. enforce = "block" is the default; "warn" allows the job and records an org_scope_violation event. Merge rules are advisory in this phase.

gitmoot org seat add <name> --pane <label> [--home DIR] claims the one live Herdr pane with that exact label, writes or repairs the role's pane binding, and installs addressed reply, blocked, and escalation routes with stable IDs org-seat-<name>-<kind>. Duplicate labels hard-fail instead of choosing a pane. New child seats inherit the owner role's scope and parent; an empty registry must add owner first. Re-running the command repairs missing owned pieces without duplicating routes. It finishes by running the same reality validation as org validate, so success includes a green live-pane and route verdict rather than only confirming that config parsed.

gitmoot org seat rm <name> [--home DIR] resolves the role's live pane and checks every distinct Git checkout reported by that pane's cwd and foreground_cwd. It refuses a dirty checkout or a branch whose HEAD is not merged into the locally known origin/HEAD (falling back to origin/main); unreadable branch state also fails closed. A safe removal deletes the role and all of its wake routes, closes the pane, and then runs the same reality validation. Roles that still parent another role cannot be removed.

The three provisioned routes are enabled, addressed, and have an empty match filter. Remove one by its stable ID with org events rule rm to quiet that kind; this is destructive and re-running seat add recreates it. There is currently no non-destructive event-rule disable verb.

gitmoot org recycle <role> --kind <kind> --handoff "<note>" [--pane <id>] [--json] [--home <dir>] journals a typed handoff in the role-lifecycle workflow org/<role>, builds the successor's boot prompt from org brief plus that handoff, and starts the requested Herdr agent kind in --pane or the role's configured pane. A pane binding and non-empty handoff are required. For safety, recycle does not kill or send exit keys to the old agent: the pane must already be at its interactive shell prompt. The Herdr start wait is bounded to 30 seconds; a failed start leaves the durable handoff note available for recovery. When a role configures model, recycle passes --model <value> to the successor only for the verified Herdr kinds codex, claude, and kimi; other accepted --kind values silently ignore the pin without an error or warning.

gitmoot org escalate --to <role> --workflow <label> [--org-role <from-role>] [--repo <owner/repo>] "<question>" writes a workflow journal note. The acting role is --org-role when given, otherwise GITMOOT_ORG_ROLE; it must be configured. An ancestor target preserves the upward escalation behavior; a descendant target records a downward ask. Both directions use the same typed note schema [org:escalate to=<to> from=<from> wf=<workflow>] <question> and set the from-role as author; --json prints the stored question fields. The same role is invalid. Peer questions are refused by a safe command-level default because Gitmoot has no configurable peer-question policy. This formalizes the earlier ad-hoc practice of typing organization questions into notes or panes; there is no code-level marker to migrate. The note and a pending wake outbox row commit atomically. With an opt-in reply rule, a daemon tick wakes the addressed role through its configured Herdr pane.

gitmoot org escalate resolve <escalation-note-id> [--by <role>] [--note <answer-note-id>] [--home <dir>] appends a typed resolution marker to the same workflow journal. --by defaults to the escalation's target role, and --note optionally links the workflow note containing the answer. The resolution marker is addressed to the escalation's parsed asker and atomically records a pending reply wake-outbox row, so an opt-in reply rule wakes the asker. A legacy typed escalation with no identifiable asker still resolves, prints a warning, and records no invented target. Resolved escalations are omitted from org dashboard projections while the original journal entry remains intact.

Event-rule wakes are separately opt-in:

gitmoot org events rule add --on attention --match owner/repo --wake maintainer
gitmoot org events rule add --on blocked --repo tendwire --wake maintainer
gitmoot org events rule add --on pane_input_pending --wake maintainer
gitmoot org events rule add --on reply --wake maintainer
gitmoot org events rule add --on reply --wake operator --scope observer
gitmoot org events rule list
gitmoot org events rule set-scope --home /alternate/home <rule-id> observer
gitmoot org events rule rm --home /alternate/home <rule-id>

--on accepts escalation, attention, guard, job-terminal, blocked, recycle-overdue, pane_input_pending, or reply. pane_input_pending matches the org.input_pending event emitted when Herdr continuously reports input_pending: true for a role's pane longer than [orchestrate].blocked_role_wake_after; it re-nudges at most once per that interval while the dialog remains pending. The pending signal takes precedence over the pane's last idle or working activity status. reply, blocked, and escalation wakes use the durable wake outbox. reply matches workflow notes and kind=chat messages addressed to the same role as --wake; non-triggering chat back-links such as job_result are excluded. Reply rows commit atomically with their source note or chat message. Blocked and escalation rows are persisted synchronously by the event sink after the source transition; an insert failure is logged but cannot roll back the emitting job. The daemon coalesces a rolling five-second window per event kind and role. Reply prompts carry N new items, oldest id X; blocked and escalation events retain their redacted event detail. Different event kinds never share a coalescing key, and a later tick flushes a quiet tail without another event. Rules default to --scope addressed: when an event names a target role, only the matching addressed rule receives it. During rule evaluation, --scope observer exempts a rule from that addressee gate. Events without a target role keep matching both scopes exactly as before. Durable-outbox claim authorization is scope-blind: among enabled, filter-matching rules for the event's own kind, wake-role equality with the addressed target is the only routing condition. An observer-scoped rule is therefore delivered when its wake role equals the target; when it differs and no other target-role rule authorizes the batch, the batch remains pending. set-scope changes an existing rule between addressed and observer. Upgrades preserve the existing global view by promoting non-reply rules with an empty match filter to observer; reply rules remain addressed because reply already carries a target role. gitmoot doctor warns when an event kind with a production target-role writer has no enabled observer rule, including when the rule set is empty. Filtered non-reply rules remain addressed after upgrade and must be promoted manually with set-scope when observer delivery is intended. Every outbox row retains a queryable pending, attempted, delivered, stalled, failed, or delivery_unknown state, so never-attempted is not confused with success and outstanding rows contribute to daemon tick health. --match is a case-insensitive substring matched against the event repo or job id; empty matches all. --repo is a discoverable alias for the same substring filter; pass only one of --match or --repo. The wake role must exist and set pane = "<herdr-pane>"; Gitmoot resolves that value as an exact pane label first and otherwise treats it as a literal pane id. Delivery is verified with Herdr's agent_prompted versus agent_prompt_stalled result. attention, guard, job-terminal, recycle-overdue, and pane_input_pending wakes remain best-effort; zero rules leaves the feature off.

gitmoot orchestrate planner "Coordinate the dashboard wave." --repo owner/repo --workflow fable/dashboard-redesign
gitmoot job list --workflow fable/dashboard-redesign
gitmoot workflow list
gitmoot workflow show fable/dashboard-redesign --limit 100
gitmoot workflow describe fable/dashboard-redesign "Coordinate and ship the dashboard redesign."
gitmoot workflow note fable/dashboard-redesign "Implementation started." --author operator --status active
gitmoot workflow close fable/dashboard-redesign --reason "Shipped and verified."

List/show include state counts, notes, first/last activity, and best-effort token totals. workflow show keeps the newest 100 entries by default and displays that window chronologically; use --limit 0 for the complete timeline or a larger --limit N for a wider window. If entries are omitted, text mode reports the shown and total counts with that guidance on stderr, and JSON includes "truncated": true. Notes store bodies and authors verbatim. The read-only web dashboard also renders labels as Galaxy hubs and provides a Workflows index plus mission log at /workflows/<label>. active means queued/running; recent means no work is live but activity occurred within 30 minutes; failed/blocked workflows with an unacknowledged failure and 30 minutes to 24 hours of silence are stalled; everything else is settled. A done or settled status immediately projects as settled regardless of note recency, unless queued/running work makes it active. The optional --pane, --session, and --workdir note flags persist the latest coordinator handoff. Inside Herdr, omitted identity flags are filled from the current pane: its label, full agent session UUID, and working directory. Explicit flags always win; --no-auto disables detection. Missing Herdr state, command failures, timeouts, and invalid output are ignored so the note still succeeds, and author is not inferred. Only a full UUID is eligible for the dashboard resume command. If coordinator author metadata is empty, the newest note author is used. Each workflow has a stable description and live status. Description is auto-seeded from a referenced local issue title, else the first note sentence, else the label campaign; override it with workflow describe. Legacy workflow note --summary remains a description alias and mirrors the retained summary field for older clients. workflow note --status is the manual status control and accepts only active, blocked, ready_to_merge, done, settled, or parked (plus an explicit empty value to unset it). Put free-text detail in the note body. Legacy status strings remain readable but cannot be written anew. Each metadata field is limited to 300 bytes.

workflow close <label> [--reason "..."] refuses queued/running work, appends a typed [workflow:close] note, and sets status to done atomically. Repeating close on done or settled is an idempotent success without another close note. A later note without explicit --status writes a preceding [auto:workflow:reopened] receipt and returns the workflow to active; explicit status remains authoritative.

The daemon conservatively auto-settles a workflow only when it references at least one PR, every referenced PR is locally known as merged or closed, no job is queued or running, its status is not blocked/parked/done/settled (deliberate human-set states are never auto-settled), and the latest human note or job update has been quiet for [workflow].auto_settle_after (default 24h; set "0" to disable). Daemon receipts do not extend the quiet period. Auto-settle appends an [auto:workflow:settled] note, sets status to settled, never deletes data, and any later note revives the workflow. Two edges are reversible-by-note rather than auto-revived: a task paused at awaiting_human (still shown in the dashboard Attention section regardless of workflow status), and a PR reopened after auto-settle — post a workflow note to revive it.

Linked PR transitions add structured [auto:pr:...] notes as author daemon and advance status at open, checks-green/ready-to-merge, and merged or closed-without-merging. The workflow/PR/transition key deduplicates poll replays, and automatic updates never overwrite description. --remember stages low-trust memory in the shared pool by default; --agent NAME selects a registered agent's private pool. A single repo is inferred, otherwise --repo is required. The note and observation are atomic, and prefilter rejection writes neither. JSON returns note bytes verbatim; plain-text show output strips terminal escape sequences, maps control characters to spaces except tabs, and caps each field.

Goals

Print the standard Gitmoot goal prompt template:

gitmoot goal template

Import a goal file into local Gitmoot state:

gitmoot goal import --file GOAL-feature.md --repo owner/repo

Start a task in its dedicated branch worktree and inspect task state:

gitmoot task run task-001 --repo owner/repo --owner lead --base main
gitmoot task list --repo owner/repo
gitmoot task list --repo owner/repo --state implementing --json
gitmoot task dismiss task-001 --reason "abandoned experiment"
gitmoot task resume-work task-001 --reason "review requires another fix pass"
gitmoot task resume-work task-001 --reason "withdraw pending merge" --override-pending-human-decision
gitmoot task events task-001 --json

task run stores the deterministic task worktree path under $GITMOOT_HOME/worktrees/<owner>--<repo>/<task-id>/ and leaves the registered checkout on its current branch.

task dismiss is an explicit terminal action for stale implementing or blocked tasks. It refuses states owned by planning, PR/review/merge, or human resume machinery, and refuses while a matching job or worktree process remains live. It preserves branch and worktree, releases the branch lock best-effort, and records task_dismissed_manual; an already-dismissed task is an exit-0 no-op (changed:false in JSON). task events <id> lists the append-only trail, including daemon task_dismissed_auto, opt-in task_dismissed_planned_ttl, closed-unmerged pr_closed_unmerged, terminal top-level implement triage (task_blocked_terminal_no_pr or task_blocked_job_failed), and explicit recovery events.

task resume-work is an explicit coordinator-only return to development from reviewing, ready_to_merge, or awaiting_human_merge. It requires --reason, refuses while a matching job or worktree process is live, preserves the branch lock, moves the task to implementing, and records task_resume_work_manual. The awaiting_human_merge state also requires --override-pending-human-decision, acknowledging that Gitmoot cannot observe a human who may be about to merge. Daemon advancement and autonomous retries do not invoke this command. Repeated manual use can still recreate the uncapped review/fix pattern tracked by #1142; the distinct event makes that activity measurable rather than invisible.

Recover a dead implement

If an implementer dies mid-work — its process exits after editing the task worktree but before it commits, pushes, and opens a PR — the changes are left uncommitted in the worktree. A retry of that same implement job re-delivers into its recorded task worktree when both the worktree process probe and runtime-owner lease prove the prior attempt is dead and the bounded retry budget remains. The retry prompt tells the agent to review and preserve the uncommitted work; the normal finalizer still owns commit, push, and PR creation. A live worktree, a dirty registered/other checkout, wrong-head state, or an exhausted retry budget keeps the existing block/failure path.

A new task run (or agent implement) still refuses a dirty worktree with no active job rather than silently discarding the work, and points you at task recover:

gitmoot task recover task-001 --owner lead
gitmoot task recover task-001 --owner lead --repo owner/repo --skip-native-review-fanout --json

--owner <agent> is required for preserved branch/worktree recovery and names a registered implement-capable agent attributed as the recovery lead. A dismissed task with no branch returns directly to planned, so that path does not require --owner. --repo owner/repo is optional and falls back to the task's stored repo, so it is only needed when the task carries none. --skip-native-review-fanout persists that flag before the PR is opened, and --json prints the machine-readable recovery result.

task recover commits the full worktree state (git add -A, including untracked non-ignored files), pushes the task branch, and opens or adopts the task's PR — the finalize steps the dead implementer never reached. When the worktree is already clean it recovers the commit already ahead of the base, and refuses when there is nothing ahead to recover.

task recover is also the only task-level recovery from dismissed. Preserved branch/worktree artifacts move through implementing to pr_open; a branchless task returns to planned with guidance to use task run. Ordinary allocation and workflow advancement cannot resurrect the task. Retrying one of its jobs restores it explicitly and records task_recovered_job_retry.

If an existing task worktree has fallen off the resolved base lineage, Gitmoot re-cuts it only when it is clean. When it also has uncommitted changes, task run/agent implement preserve the worktree, move the task to blocked, and record stale_worktree_dirty_blocked; manually salvage, commit, stash, or clean the changes before retrying.

The daemon reads a bounded oldest-first stale window and processes up to 20 qualifying implementing/blocked tasks per repo poll. [workflow].stale_task_ttl = "168h" is the default and "0" disables the leg. updated_at is a conservative activity proxy. Live jobs, same-repo open-PR branches, branches still present on origin, and remote-check uncertainty all prevent automatic dismissal; successful transitions record task_dismissed_auto.

Delegation worktrees use the separate default-on [workflow].delegation_worktree_ttl = "72h"; "0" disables that pass. Only final job owners older than the TTL are force-reclaimed. Blocked, queued, and running owners remain pinned and are reported by gitmoot doctor.

[workflow].planned_ttl = "720h" is a separate repository opt-in for old never-started plans. It is disabled by default; unset, empty, "0", and invalid values all mean off because automatic dismissal can destroy human planning context that goal-file re-import cannot reconstruct. When enabled it uses the same live-job, same-repo open-PR, remote-branch, and remote-uncertainty skips and records task_dismissed_planned_ttl. Task allocation claims planned -> implementing atomically at the write boundary, so a concurrent TTL dismissal cannot be resurrected by task run; explicit recovery is required.

A clean closed-unmerged PR moves pr_open, reviewing, or changes_requested to blocked with pr_closed_unmerged; ambiguous PR state remains conservative. After advancement and delegation handling, a terminal top-level implement job with no attached PR and no live successor checks whether the task branch already has an open PR. An implemented success with that binding returns the task to pr_open and records task_terminal_pushed_to_open_pr; without an open branch PR it blocks with task_blocked_terminal_no_pr, whose reason names the recoverable branch and recorded head SHA when available. Other terminal outcomes remain task_blocked_job_failed. Delegation children and queued fixes, retries, continuations, or pending advancement are not reclassified.

Two refusals guard recovery (and the task run / agent implement restart that points to it):

  • Dirty worktree without an active job — a restart refuses when the task worktree has uncommitted changes and no in-flight job. Inspect it, then run task recover to commit/push/open the PR, or clean/stash the worktree before retrying.
  • Live process still in the worktreetask recover refuses while a live process is still inside the task worktree. Wait for it to exit, or stop the orphaned implementer, before recovering.

PR Comments

Use GitHub PR comments as the public audit trail:

/gitmoot help
/gitmoot status
/gitmoot <agent> review [instructions]
/gitmoot <agent> implement [instructions]
/gitmoot ask <agent> [question]
/gitmoot retry <job-id>
/gitmoot cancel <job-id>
/gitmoot merge
/gitmoot resume <job-id> retry|continue|abort|answer [instructions]
@<agent> ask|review|implement [instructions]

A bare @<agent> <action> … mention on a PR comment (or, with the daemon's --watch-issues flag, an issue comment) is treated as the same command as the /gitmoot <agent> <action> form (#389). /gitmoot resume <jobID> retry|continue|abort|answer resumes a delegation tree paused by escalate_human or an ask-gate human_questions pause — see the result contract for the pause/resume semantics. See also the PR comment workflow.

Jobs And Locks

gitmoot job list --repo owner/repo # add --json for machine-readable rows
gitmoot job show <job-id> # add --json for the full job + operational detail
gitmoot job watch <job-id>
gitmoot job watch <job-id> --transcript [--log-path <path>] [--runtime codex|claude|kimi|kimi-cli|shell]
gitmoot job transcript <job-id> --export md|jsonl [--output <path>] [--log-path <path>] [--runtime codex|claude|kimi|kimi-cli|shell]
gitmoot job transcript --all [--state succeeded,failed] [--since 720h] --export jsonl [--output <path>]
gitmoot job events <job-id>
gitmoot job run <job-id>
gitmoot job retry <job-id>
gitmoot job gates <job-id> # list resumable gates; add --json
gitmoot job gates clear <job-id> --need "<text>"|--all # satisfy gate(s); auto-resume on last
gitmoot job cancel <job-id> # one queued|running|blocked job
gitmoot job cancel --state blocked [--older-than 7d] [--repo owner/repo] [--agent name] [--yes]
gitmoot job kill <root-job-id>
gitmoot lock list --repo owner/repo
gitmoot lock show owner/repo <branch>

Terminal background ask and review jobs that ran in a throwaway read-only worktree preserve a bounded git status --short plus git diff HEAD snapshot before Gitmoot removes that worktree. job show prints the captured diff and job list adds a compact DIFF: badge. Their JSON forms expose read_only_worktree_diff, read_only_worktree_diff_truncated, and read_only_worktree_diff_error; the same durable fields are present under job show --json's payload. Capture is capped at 4 MiB, and an oversized snapshot ends with an explicit omitted-byte marker instead of being silently cut. Git subprocesses and the wait for each index-file copy share a 10-second context: on expiry Gitmoot kills the subprocess or stops waiting for the copy. The operating system still owns cancellation of an in-progress filesystem syscall, so this is a bounded-wait guarantee rather than a promise that kernel I/O itself is cancelled. Failures are recorded and never prevent worktree removal.

When standard output is an interactive terminal (and NO_COLOR is unset), the transcript renders styled: agent turns get blank-line spacing and keep their line breaks plus lightweight heading/list/inline-code treatment. Tool calls use type-specific icons; shell output previews its last five lines while read/search output previews its first 10-15 lines, with exact omitted-line counts. Tool and turn durations render dim, cancelled tools render yellow, completed machinery and usage render dim, and failed tool results render red. Piped or redirected output always uses the plain byte-stable format.

Every transcript opens with an orientation header — job action, agent, runtime/model (per-job override first, then the agent default), workflow label, and the redacted, length-capped prompt — so a pane or saved transcript is self-describing.

job watch --transcript follows a cockpit tee log from offset zero and renders redacted, bounded human-readable runtime output until the job settles, then drains the file to EOF. It is incompatible with --json. Without --log-path, Gitmoot derives the job-mode path under <home>/logs/jobs/; if that file is not available, it prints transcript unavailable; showing job events and uses the normal event watcher. --log-path and --runtime are primarily cockpit wiring flags, but remain usable for diagnosis. When --runtime is omitted, the job's runtime override wins over the registered agent runtime.

Fidelity follows each runtime's actual output contract: Codex JSONL renders live; Kimi stream-json is turn-buffered and kimi-code 0.19.2 reports no usage; Claude emits only its final JSON envelope, so its transcript remains quiet until completion; shell output passes through as redacted raw lines. Usage is labeled latest reported usage because resumed Codex counts can be session-cumulative. Malformed or unknown lines degrade individually to redacted capped raw output without stopping later lines.

job transcript <job-id> --export md remains the deterministic, ANSI-free Markdown snapshot. --export jsonl emits schema-versioned, self-contained trajectory rows for every normalized event. Bulk export requires the explicit --all guard; --state and --since filter the created-time-then-id ordered stream. Bulk mode skips pre-retention or GC-missing logs and reports counts on stderr, while explicit single-job absence is an error. --output <path> uses a mode-0600 temporary file plus atomic rename. Oversized runtime lines become marked truncated raw steps instead of aborting the export.

JSONL export redacts every text-bearing event field with Gitmoot's best-effort credential masker and has no raw bypass. The source log remains unredacted and mode 0600; best-effort export masking is not a vault.

Verified Codex command/file-change events and Kimi function tool calls/results render as typed compact lines; unrecognized shapes keep the generic/raw fail-open path. Render-time redaction is a per-line best-effort defense in depth: a secret split across physical lines may be only partially masked, and the raw cockpit log plus the external tail -F fallback remain unredacted.

Resumable gates (make blocked + needs actionable)

When a stage returns blocked with a needs list (e.g. needs: ["Maps API key"]), gitmoot persists each need as a gate attached to the blocked job. gitmoot job gates <job-id> lists them (open / satisfied); clearing a gate marks the blocker resolved:

gitmoot job gates clear <job-id> --need "Maps API key" # satisfy one need
gitmoot job gates clear <job-id> --all # satisfy every open gate

When the last gate is cleared, the blocked stage auto-re-runs via the same RetryJob machinery gitmoot job retry uses (re-queued, then dispatched by the daemon; downstream stages follow the normal delegation DAG) — resume happens on clear, no polling. A session job (externally driven) and a stage whose tree is paused awaiting a human (escalate_human / ask-gate) are never auto-resumed even with all gates cleared — a resource gate must not bypass the human's gitmoot resume decision; the command reports not resumed: … with the reason. A blocked job with no needs records no gates and is byte-identical to before.

Session jobs (record "here"-method work)

The "here" method — importing an agent's prompt into your calling session with gitmoot agent prompt <agent> — does the real work in your session but creates no gitmoot job, so the dashboard / job list / event stream never reflect it. Session jobs record that work as a first-class tracked job without gitmoot spawning a runtime — a clock-in / clock-out pair (plus a one-shot recorder):

# Clock in: create a RUNNING, externally-driven job (no dispatch); prints its id.
gitmoot job open --agent <name> --repo owner/repo --type ask|review|implement \
[--title "..."] [--task <id>] [--pr <n>] [--head-sha <sha>] \
[--workflow <label>] [--json]

# Clock out: apply the result and move the job to its terminal state.
gitmoot job close <id> --decision approved|changes_requested|blocked|implemented|failed|skipped \
[--summary "..."] [--pr <n>] [--head-sha <sha>] \
[--branch <name>] [--json]

# One-shot post-hoc: create an already-terminal job (open + close in one).
gitmoot job record --agent <name> --repo owner/repo --type ask|review|implement \
--decision <decision> [--title "..."] [--summary "..."] \
[--task <id>] [--pr <n>] [--head-sha <sha>] \
[--branch <name>] [--json]

An externally_driven job is created directly in running (it never queues, so the daemon never claims or Delivers it — no runtime subprocess, no runtime-session or checkout lock) and the stuck-running reaper skips it, so a session may hold it open for as long as the work takes. close reuses the exact result path an engine-run job uses: --decision maps to the same terminal state (approved/changes_requested/implemented/skipped -> succeeded, blocked -> blocked, failed -> failed) and emits the same finished/failed/blocked event, so a recorded job is indistinguishable from an engine-run one in the dashboard and events. A job can be closed once (it must be a running session job); an orphaned open job stays running (reaper-exempt) until you job close --decision failed or job cancel <id> it. A session job is never engine-executed, so job retry refuses it (retrying would re-queue it for a real runtime with an empty payload) — recover one by opening a fresh session job instead. The agent and repo must exist.

For an in-session PR review, clock in before reading the diff and bind the display row to its exact head and workflow journal:

gitmoot job open --agent <name> --repo owner/repo --type review \
--pr <n> --head-sha <sha> --workflow <label>
gitmoot workflow note <label> "reviewed tests and error paths"
# Post the verdict, then:
gitmoot job close <id> --decision approved|changes_requested|blocked \
--summary "..."

For a running externally-driven review, job list and job show derive review_status: in_progress|stalled from the newest workflow note, falling back to the job's creation time; the signal becomes stale after 20 minutes. head_sha is also exposed in text and JSON. Every such session status is explicitly review_status_grade: reported and review_status_authority: non_authoritative (text lists use REVIEW (reported; non-authoritative): ...). Workflow notes are caller assertions, not system-observed reviewer attribution. These fields are display-only: they never satisfy, block, or otherwise feed the merge gate, and this feature never writes tasks.state.

When an externally-driven review closes, its payload retains review_status_grade: reported; job list --json and job show --json continue to expose that field after the running liveness status ends. The grade is never observed or verified because both the reviewed head and verdict came from caller-supplied flags. This durable reported record is not merge-admissible evidence, and no merge gate consumes it.

For a running engine-dispatched review with an isolated worktree, job list also samples the verified daemon's descendant process tree twice. A descendant whose cwd is that worktree (or a directory below it) yields review_status: in_progress; a conclusive absence in both samples yields review_status: stalled. A runtime root remains visible while the agent is idle between tool commands, so the check does not depend on a transient tool child. These statuses carry review_status_grade: observed and review_status_authority: non_authoritative. If the daemon identity or process table cannot be verified, the status is omitted rather than guessed. The daemon-valued jobs.runner_pid, job timestamps, and event age do not decide this status. This is observation only: it does not cancel, retry, reclaim, or feed the merge gate.

Make in-chat / "here" work show on the dashboard. The one-step default is gitmoot agent prompt <agent-or-template> --record: it opens the session job as you import the prompt and prints a header naming the job id (see the agent prompt section above). It works for a registered agent (repo defaults to its scope) and for a bare template id with an explicit --repo (the template id is recorded as the identity, #673). Apply the prompt, do the work, then clock out with gitmoot job close <id> --decision …. That is all it takes for otherwise-invisible current-chat ("here") work to appear in job list, the dashboard, and the event stream — no daemon, no runtime, no PR comment. Use plain agent prompt (no --record) only when you just want to read a prompt without tracking it.

For a queued or blocked job, gitmoot job list appends a WHY: column and gitmoot job show prints a why_stuck: (and, when a lease applies, a next_retry_at:) line explaining what the job is waiting on — e.g. waiting on runtime session lock runtime:codex:<ref> (held by job <id>), blocked: awaiting human, auth failing: …, throttled: …, or retrying: … (#552). The reason is derived from the most authoritative existing signal (the latest reason-bearing job events entry plus the owning resource lock's lease); a healthy job's output is unchanged.

For a terminal (succeeded, failed, blocked, or cancelled) job whose recorded worktree still has a locally observable process, job list reports LIVE PROCESS: worktree still has an active process and job show prints the same detail as process_active: worktree still has an active process; their JSON forms carry process_active: true. The badge is omitted when no live process is observed, when local process liveness is unavailable, or while the job is still non-terminal.

For an implement job, job show and job list --json expose Gitmoot's own post-agent delivery verdict as delivery_status: delivered means the job's result decision was implemented and either a completion marker is paired with a persisted non-zero pull request, or the job has no advance events at all and already carries a persisted non-zero pull request; pending means delivery is in flight or scheduled for retry, and blocked means the latest delivery attempt blocked. A completed advancement alone (e.g. an implemented result that produced no PR, or a non-implemented decision) does NOT count as delivered — the field is omitted in that case, never a false delivered. The field is derived at read time from the existing advance_* job events and pull-request payload; it is omitted for non-implement jobs and legacy/unknown states. This field, not the agent's free-text result summary, is authoritative about commit/push/PR delivery because Gitmoot performs that step after the agent turn ends.

For a running job dispatched through a PID-aware runtime runner, the payload records runtime_pid plus a process-start identity. job list --json and job show --json derive runtime_process_active: true means that exact process is alive, false means it is confirmably gone (including PID reuse), and an omitted field means unknown because no PID/identity was recorded or process inspection is unavailable. This is direct per-job ground truth and is separate from the terminal worktree-scanning process_active badge above.

Operational blockers auto-retry (#532): a delivery failure classified as runtime_auth or runtime_quota does not fail the job terminally — the daemon re-queues it as deferred with a bounded retry budget and a hold until the earliest retry time. gitmoot job show --json carries the blocker_class and attempt count, and over the [events] stream a job.deferred follows the job.failed (making it non-terminal; see the event stream). A job that "failed then reappeared as queued" is the deferral working, not a bug.

When a runtime session ends without producing a gitmoot_result envelope — the CLI process crashed, exited non-zero, was signal-killed, or completed but never emitted a valid envelope even after repair attempts — the job records failure diagnostics (#806): a phase marker (launched = died before any stdout, streaming = died mid-output, result-parse = every delivery completed but no valid envelope was found), the process exit_code or terminating signal, a redacted stderr tail (hard-capped at 2 KB; redaction runs over the full text with the same token-redaction rules as job comments before the tail is cut, so a secret can never leak partially), and the runtime session id when one is known. gitmoot job show prints a failure_diagnostics: block, job show --json carries payload.failure_diagnostics, and gitmoot report bug includes a "Failure diagnostics" section. Successful jobs never store one, and a retried job clears the previous run's crash report.

Jobs stuck in running are backstopped too: a running job with no lease progress past the staleness window (default 30m) is assumed orphaned by a dead worker and recovered/re-queued. The window is tunable via the GITMOOT_STALE_RUNNING_AFTER environment variable; the smallest honored value is 1m — below-1m, malformed, or non-positive values are rejected (with a one-time warning) in favor of the 30m default rather than clamped (#560).

gitmoot job kill <root-job-id> is the operator kill switch for a runaway delegation tree: it terminates the tree identified by its root job id gracefully. In-flight jobs finish normally; the coordinator's next continuation is routed through the graceful finalize path (synthesize what completed → stop) and the daemon stops starting queued children of that root. See the termination bounds for how it relates to the other delegation backstops.

gitmoot job cancel <job-id> is the single-job abandon verb. It dismisses a queued, running, or blocked job (a blocked job is one paused awaiting a human — an operator permission gate or an unrecoverable blocker — so dismissing it is the same abandon intent as cancelling a queued/running one; #631). Cancel is a single-row transition: it does not propagate to a delegation tree, touch task state, or set the killed flag — abandoning a whole tree is gitmoot job kill. Cancelling also releases any resource locks the cancelled job still owned — including a stranded runtime:<rt>:<session> lock left behind when a foreground gitmoot agent ask was killed — so the next ask on that agent does not wait out the lock TTL before it can run. Dismissal is reversible: gitmoot job retry accepts a cancelled job, so a mistakenly dismissed one can be resurrected.

gitmoot job cancel --state blocked is the bulk form for clearing a backlog of blocked jobs. Only blocked is accepted for --state (queued/running jobs have single-job cancel; terminal jobs have retry). Narrow the selection with --older-than (a Go duration like 168h, or a convenience <N>d days suffix like 7d; age is measured from when each job became blocked), --repo owner/repo (matches the job's payload repo), and --agent name. The bulk form is a dry-run by default — it prints the matching jobs (id, agent, repo, age) and exits without cancelling anything; pass --yes to actually cancel the selection. Each selected job is dismissed through the same per-job job cancel path, so its locks are released too. <id> and --state are mutually exclusive, and --older-than/--repo/--agent require --state. gitmoot doctor warns when blocked jobs older than 30d have piled up and prints this exact command.

To automate the sweep, set [orchestrate].blocked_ttl to a positive Go duration (e.g. blocked_ttl = "168h"): the daemon's housekeeping tick then dismisses any blocked job whose blocked-transition timestamp is older than the TTL, through the same CancelJob abandon path (recording a distinct blocked_ttl_expired job event so a TTL auto-expiry is distinguishable from a manual cancel). It is off by default — an empty or 0s value disables it (a negative value is rejected), because a blocked job is a human-awaiting decision that is never auto-discarded unless you opt in. This is distinct from [orchestrate].escalation_ttl, which auto-finalizes a whole paused delegation tree and is on by default (24h); blocked_ttl dismisses a single blocked job and is off by default.

Native task auto-merge is enabled by default only behind an exact-head approved review and green SHA-scoped commit statuses/check-runs. A miss parks the task as awaiting_human_merge, records an org escalation, and wakes jarvis. Set [repos."owner/repo".merge_gate] auto_merge = false as an explicit kill-switch; that deliberate hold does not escalate. Pipeline allow_auto_merge is independent.

Merge-gate retries are automatic while the daemon is running. Retryable states, such as a busy base-branch merge queue or a GitHub branch update in progress, are retried on the next daemon poll tick. The default poll interval is 30s unless the daemon was started with a different --poll. When an external system owns the merge decision, set GITMOOT_DISABLE_NATIVE_MERGE_GATE=1 (also true/yes/on; #545): Gitmoot then abstains from its native merge gate — fail-closed, it never merges gatelessly; the external gate makes the call.

Interactive Prompts

Pending interactive prompts (dashboard form questions, ask-gate questions) can be answered from the CLI:

gitmoot interactive list [--state pending|resolved|all] [--json]
gitmoot interactive show <id> --json
gitmoot interactive answer <id> <value> [--source source]
gitmoot interactive clear <id> [<id>...] | --resolved | --all

Result Checks

After a daemon-run job's gitmoot_result is parsed, Gitmoot runs a set of deterministic, LLM-free binary checks over the parsed result — a contract-hygiene audit that catches results that are technically valid but vague or missing evidence. Each check is a yes/no question with an explanation:

  • implement — a result whose decision is implemented must list its changes_made and its tests_run. When the engine owns a job worktree, it also persists payload.result_observation from the worktree diff and fails implement-changes-observed if a claim names a path absent from the diff, a claim names no file path, or the diff contains a path no claim mentions.
  • review — a changes_requested review must carry findings (evidence).
  • ask — the answer (summary/artifact_body) must be non-empty and actionable.
  • blocked (any action) — a blocked result must list actionable needs.
  • coordinator finalize — a finalize continuation must produce a substantive reconciliation summary.

The mode is set in config.toml and is warn by default:

[workflow]
result_checks = "warn" # off | warn | block (default: warn)
  • warn (default) — failing checks are recorded as a result_checks_failed job event (visible in gitmoot job events <id> and gitmoot job show <id>) and attached to the job detail (job show --json payload.result_checks and the web dashboard), but the job still finishes on its own decision.
  • block — a failing check additionally fails the job through the same terminal path a malformed result takes (opt-in, for strict workflows).
  • off — the audit emits no check, event, or failure record. The worktree observation is still persisted when available; recording evidence is independent of whether a gate refuses it.

A result that passes every applicable check records nothing, so the audit is quiet on healthy jobs. Failed checks are also stored durably for later SkillOpt consumption as structured feedback; there is no SkillOpt behavior change today.

SkillOpt Exchange

gitmoot skillopt review create --template <id> --repo owner/repo --run <run-id>
gitmoot skillopt review item add --run <run-id> --item <item-id> --baseline baseline.md --candidate candidate.md [--title text]
gitmoot skillopt review create --template <id> --repo owner/repo --run <run-id> --mode explore --exploration-level high --options 4
gitmoot skillopt review item add --run <run-id> --item <item-id> --option a=option-a.md --option b=option-b.md [...]
gitmoot skillopt review status --run <run-id>
gitmoot skillopt export --run <run-id> [--output training.json]
gitmoot skillopt import --file candidate.json [--artifact-dir artifacts]
gitmoot skillopt candidate list [--template id]
gitmoot skillopt candidate show <version-id>
gitmoot skillopt candidate promote <version-id>
gitmoot skillopt candidate reject <version-id> [--reason text]
gitmoot skillopt ab <agent> "<prompt>" [--challenger <versionId>] [--pick a|b] [--seed N] [--judge] [--judge-only] [--home path]
gitmoot skillopt pairwise import <packet-dir> [--packet path] [--secret-map path] [--picks path] [--reviewer name] [--json]
gitmoot skillopt rubric induce --template <id> [--out <dir>] [--holdout 0.2] [--min-events N] [--home path] [--json]
gitmoot skillopt feedback markdown export --run <run-id> --output .gitmoot/evals/<run-id>
gitmoot skillopt feedback markdown import --packet .gitmoot/evals/<run-id> [--reviewer name]
gitmoot skillopt feedback github publish --run <run-id> [--repo owner/repo] [--pr <number>]
gitmoot skillopt feedback github sync --run <run-id> [--repo owner/repo] (--issue <number>|--pr <number>)
gitmoot skillopt train start --template <id> --repo owner/repo --request <text> --items-file items.yml [--workspace-repo owner/workspace] [--preview-repo owner/previews] [--preview-mode none|optional|required] [--preview-renderer none|vue-vite] [--preview-publisher none|github-pages] [--preview-route-template template] [--create-repos] [--yes]
gitmoot skillopt train status --session <id>
gitmoot skillopt train run [--config path | --session <id>] [--plain]
gitmoot skillopt train continue --session <id> [--generator-type skillopt-generator | --generator-agent name] [--skillopt-bin path] [--dry-run] [--promote version|--reject version --reason text] [--start-next]
gitmoot skillopt train recover --session <id> [--out-root path] [--generation [--abort | --advance-state]] [--json]
gitmoot skillopt train stop --session <id> --reason <text>
gitmoot skillopt judge-report [--template <id>] [--home <path>]
gitmoot skillopt judge agreement [--template <id>] [--home <path>] [--json]
gitmoot skillopt judge promote --template <id> --task-kind <kind> --file <pkg.json> [--home <path>] [--yes] [--json]

On a real terminal, skillopt train run opens an interactive view of one session (resolved from --session or the newest session of a --config): a phase bar plus a single keypress per step — enter advances the current phase (the long generate/optimizer steps run in a detached background process so q leaves the run going), p/x promote or reject a candidate, n starts the next iteration. Review-blocked phases show the GitHub issue link to continue from the browser. --plain, a piped stdin, or GITMOOT_NO_TUI/TERM=dumb print a one-shot status snapshot instead. train status/continue print a continue_from_github: line at review-blocked phases. train start --create-repos (or the prompt in the train init form) creates a missing target/workspace/review repo on GitHub.

Use skillopt train for the product workflow. It pins the template version, tracks sessions and iterations, validates item diversity, generates temporary agent options, publishes review packets, syncs feedback, hands off to the external optimizer, imports pending candidates, publishes candidate review context, and starts follow-up iterations only after a promoted/rejected/abandoned decision. Use skillopt review, feedback, export, import, and candidate directly only for advanced debugging, custom research runs, or recovering one step of a train session.

Option generation in skillopt train continue is durable and idempotent on resume. Each review item's artifacts, item row, and options commit in a single transaction the moment that item finishes, so an interrupted generation phase keeps every item that already completed instead of losing the whole batch. Re-running skillopt train continue regenerates only the incomplete items: completed items are skipped, no duplicate options are written, and finished work is never rewritten. If an item has some but not all of its options persisted, resume hard-errors with item <id> has partial generated options; inspect or clear review options before continuing so you can inspect or clear that item before retrying.

skillopt train recover repairs the optimizer phase of a session by default — it re-imports or repairs the optimizer candidate package and classifies the iteration as already_completed_candidate, already_completed_no_candidate, optimizer_active, or corrupted_unrecoverable:

gitmoot skillopt train recover --session <id>
gitmoot skillopt train recover --session <id> --out-root .gitmoot/skillopt/<run-id>
gitmoot skillopt train recover --session <id> --json

--out-root overrides the optimizer output directory (it defaults to the session's persisted optimizer path), and --json prints the recovery result as JSON.

Pass --generation to recover the generation phase instead:

gitmoot skillopt train recover --session <id> --generation
gitmoot skillopt train recover --session <id> --generation --advance-state
gitmoot skillopt train recover --session <id> --generation --abort

This reclaims a generation lock stranded by a crashed/killed train continue (whose deferred lock release never ran) and salvages the persisted per-item options. Reclamation is liveness-gated: the lock is released only when its owner PID is provably dead AND it was held on this same host. A live owner is refused (skillopt train generation is already running) so you stop the running process first; a cross-host owner requires the lock TTL to expire. The recover process re-acquires the lock for itself so the salvage is crash-safe. Salvage is import-only — it reports expected_items, recovered_items, and missing_items and classifies the run as generation_complete, generation_incomplete, or generation_active. The iteration advances to options_generated only with --advance-state and only when every expected item is recovered (regenerating missing items remains train continue's job). --abort reclaims the lock and leaves the phase at items_ready, keeping persisted items. A stale generation lock also surfaces in train status as a stale active lock, separate from the true current phase.

skillopt review create starts a review run for a template and target repo. Use the default A/B shape for validation, or pass --mode explore|refine|distill with --options N for ranked exploration. skillopt review item add stores saved baseline/candidate outputs as artifact-backed A/B review items, or repeated --option label=path artifacts for ranked N-way items. skillopt review status reports whether the run has items, complete artifacts, imported feedback for every item, ranking stability, pairwise preference count, and a recommended next mode. Recommendations are advisory; Gitmoot never changes mode, imports a candidate, or promotes a template automatically.

skillopt export writes a JSON training package with the template snapshot, eval run, review items, artifact manifests, feedback events when present, and evaluator config. Use gitmoot-skillopt optimize --training-package training.json --artifact-root ~/.gitmoot/evals/blobs --out-root .gitmoot/skillopt/<run-id> --candidate-output candidate.json --dry-run first to validate the contract without model calls. Before real model-backed optimization, check gitmoot-skillopt --version and gitmoot-skillopt optimize --help, or install it with pipx install https://github.com/jerryfane/gitmoot-skillopt/releases/download/v0.4.2/gitmoot_skillopt-0.4.2-py3-none-any.whl. Verify required model/backend environment variables for the installed optimizer version. skillopt import validates a candidate package and stores the candidate template as a pending version; it never promotes the candidate automatically. If the candidate package includes new artifact manifest entries, pass --artifact-dir so Gitmoot can verify relative paths and SHA256 hashes before storing blobs. skillopt candidate show displays candidate metadata, eval report JSON, preference summary, and a content diff against the base/current version. skillopt candidate promote makes a pending candidate current, while skillopt candidate reject records an auditable rejection and prevents that version from being selected by @latest.

skillopt pairwise import <packet-dir> ingests a blinded paired-review packet produced by the gitmoot-skillopt fork (the pairwise-review.json packet plus its secret map and the reviewer's picks), de-blinds it, and stores the pairwise-preference feedback events — the import path for Mode B's paired-review evidence. The daemon can also import review-issue feedback automatically when started with --watch-skillopt-reviews.

At the manual promote/reject gate, Gitmoot records every judge↔human outcome into a local store — all four directions: agree_accept, agree_reject, judge_accept_human_reject (judge accepts, human rejects — a false positive), and judge_reject_human_accept (judge rejects, human accepts — a false negative). Each outcome is tagged with the judge prompt version, evaluator id, and prompt hash that produced the score, so later analysis can compare judges by prompt revision. This capture is measurement only: it never changes the judge, overrides a decision, or bumps the result contract.

skillopt judge-report reads those captured outcomes and reports how well the LLM judge is calibrated against human verdicts. It prints a confusion matrix (the four direction buckets), the agreement rate and Cohen's κ, calibration buckets (judge soft-score versus the human decision), and per-dimension disagreement. Pass --template <id> to scope the report to one template, and --home <path> to read from a non-default Gitmoot home. It is read-only.

skillopt judge agreement is the judge↔human agreement measurement harness (#344). It joins the stored A/B judge verdicts (skillopt ab --judge / jury rows) against the human ranked/pairwise feedback on the same comparison: each skillopt ab invocation stamps a shared per-comparison token on all of its rows, so repeated A/Bs of one challenger stay separate observations (older tokenless rows are excluded and counted as unmeasurable, never pooled; internal ties within one comparison are skipped and counted). It reports Cohen's κ as the headline metric (raw agreement overstates judge quality because it does not correct for chance), the raw agreement rate, per-human-source and per-juror-family breakdowns, an assignment-corrected position-bias audit over judge rows that carry the recorded raw a/b pick (P(pick=a) stratified by the champion's presented position and reported alongside P(option A = champion); undefined when a fixed --seed pinned the champion to one position), and a summary of the candidate-level judge outcomes above. Small samples get a loud warning — sample size is the limiter. --json emits the machine-readable report. It is read-only.

skillopt rubric induce is the offline, deterministic rubric-induction tool (#344/#347, AutoLibra-style — 2505.02820). It reads the human feedback already captured for a template (the useful_traits / rejected_traits / required_improvements on ranked feedback events, across all of that template's runs) and induces a criterion-separated rubric from it, then freezes it as reviewed static JSON. The pipeline is fully offline — no LLM calls, so it is reproducible and testable: (1) ground each trait string into an aspect {text, sign +/-, source_event_id}; (2) cluster aspects by normalized token-overlap (Jaccard, greedy single-linkage, stable ordering) into up to six metrics, each {name, definition, positive_examples, negative_examples, source_event_ids}; (3) meta-evaluate on a held-out split — coverage (fraction of held-out aspects a metric matches) and redundancy (max inter-metric similarity, lower is better); (4) write rubric.json (the frozen rubric, {version, template, metrics[...]}), report.json, and a human-readable report.txt under --out (default <home>/skillopt/rubrics/<template>). Flags: --template <id> (required), --holdout 0.2 (fraction reserved for coverage; 0 keeps in-sample), --min-events N (minimum usable feedback events, hard floor 3), --home <path>, and --json (emits the report plus the artifact paths). It errors with an actionable message and a non-zero exit when there are fewer than three usable feedback events or fewer than two separable metric clusters. The token-overlap clusterer is the deterministic v1; swapping in AutoLibra's LLM thematic clustering is a clearly-marked extension point that changes only the clustering step, leaving grounding, the held-out meta-eval, and the emitted contract identical.

The tool is read-only over the store and human-gated: it only writes files and never injects anywhere. To adopt an induced rubric, a human reviews rubric.json and maps its metrics onto gitmoot-skillopt's evaluator_config['rubric'] — one dimension per metric, using the metric name as the dimension key and its definition (plus the positive/negative examples) as the description, with a weight the reviewer chooses. Because the judge's _compose_evaluator_rubric already merges arbitrary evaluator_config['rubric'] dimensions and _normalize_dimension_list accepts arbitrary names, adopting an induced rubric needs zero judge-code change and no result-contract bump.

skillopt judge promote closes the judge-prompt optimization loop: it applies an accepted judge-prompt variant (from the judge-prompt optimizer's gitmoot-skillopt-judge-candidate package) into a template, so the next skill-opt run judges with the improved prompt. Select the variant with --task-kind <kind> (use _global for the all-items pass). It previews by default — printing the template id, task kind, the baseline→best agreement delta, and a truncated prompt preview, and writing nothing — and requires --yes to apply. It refuses (hard error) any variant whose accepted is not true or whose best_prompt is empty, and any task kind missing from the package. On apply it writes the prompt into the template's evaluation metadata (judge_prompt_templates keyed by task kind, merging so other task kinds are preserved, plus a bumped judge_prompt_version) and records a skillopt_judge_outcomes audit row (human_decision=promoted, the old→new version, and the agreement delta in reason). --json emits the machine-readable preview/apply summary.

The Markdown feedback collector writes blind A/B review packets with index.md, per-item Markdown files, editable feedback.yml, and hidden assignment metadata that Gitmoot uses to validate the full response and import de-blinded canonical feedback events. Open index.md, review every file in items/*.md, set reviewer, edit feedback.yml with exactly one of a, b, tie, neither, or skip for every item, and leave .assignments.json untouched. Ranked packets use the same files, but feedback.yml contains ordered rankings plus optional useful_traits, rejected_traits, and reasoning. After feedback exists, packet summaries hide outcome-bearing phase details so later blind reviewers do not see the current winner before responding.

The GitHub feedback collector publishes the same blind A/B review packet to a new issue by default, or to an existing PR when --pr <number> is provided. Repository resolution uses --repo, then the eval run target repo, then the template source repo, then optional [feedback].repo = "owner/reviews" in Gitmoot config. Reviewers can reply with full YAML or run-scoped short-form lines such as run_id: run-1 followed by item-001: b - More concrete.. github sync imports valid comments into canonical feedback events and ignores unrelated comments safely. Ranked GitHub comments can use item-001 ranking: C > A > D > B plus trait notes. Use the ranked workflow for exploration/refinement and return to A/B validation for final promotion decisions on fresh items.

Agent Memory

Agent persistent memory is off by default and enrolled per agent ([agents.<name>].memory = true), with optional [memory] knobs (disabled, default_enroll, token_budget, max_entries, and the distill-at-terminal knobs distill_at_terminal, distill_successes, distill_max_per_job, distill_all_jobs, plus the default-off groom LLM knobs groom_split_llm, groom_split_llm_runtime, groom_split_llm_model, and groom_split_llm_max_per_run, and the default-off insight-harvest knobs harvest_enabled, harvest_runtime, harvest_model, harvest_effort, harvest_max_per_job, and harvest_max_jobs_per_sweep. Daemon-consumed knobs are hot-read with no restart; default_enroll is read by each manual agent start. See Agent Persistent Memory for the full model. The inspection commands are read-only except for recall's best-effort usage counter bump; ingest and confirm write behind a human gate:

gitmoot memory list [--pending|--confirmed] [--agent NAME] [--repo owner/repo] [--json]
gitmoot memory recall "<query>" [--repo owner/repo] [--agent NAME|--shared] [--limit N] [--expand] [--json]
gitmoot memory replay [--agent NAME] [--repo owner/repo] [--limit N] [--json]
gitmoot memory eval --fixtures fixtures.json [--k N] [--json]
gitmoot memory vault export [--out DIR] [--agent NAME] [--force] [--json]
gitmoot memory vault import <DIR> [--dry-run|--yes] [--json]
gitmoot memory ingest <path|dir> --agent NAME [--shared] [--repo owner/repo] [--tier repo|general] [--dry-run] [--json]
gitmoot memory ingest sweep [--json]
gitmoot memory observations [--agent NAME] [--provenance-prefix P] [--json]
gitmoot memory confirm <obs-id>... | --provenance-prefix P [--agent NAME] [--to-shared] [--yes] [--json]
gitmoot memory retire --provenance-prefix P [--agent NAME] [--dry-run] [--yes] [--json]
gitmoot memory promote --to-shared <id>... [--json]
gitmoot memory links backfill [--dry-run] [--json]
gitmoot memory links list <id> [--json]
gitmoot memory log [--key K] [--agent A] [--repo R] [--kind k1,k2] [--since 168h] [--limit N] [--json]
gitmoot memory log --id <memory-id> [--json]
gitmoot memory log backfill [--dry-run] [--json]
gitmoot memory groom --propose [--out PLAN.json] [--json]
gitmoot memory groom --yes --plan PLAN.json [--json]
gitmoot memory groom --split [--dry-run] [--json]
gitmoot memory groom --split-revert [--dry-run] [--parent N]... [--since RFC3339] [--json]
gitmoot memory clusters [--json]
gitmoot memory clusters recompute --propose [--out PLAN.json] [--json]
gitmoot memory clusters recompute --apply [--plan PLAN.json] [--json]
gitmoot memory cluster rename <cluster-id> <label>

memory list shows confirmed memories and/or pending observations. memory recall runs the same FTS5/BM25 confirmed-memory retrieval used for prompt injection and prints the matching facts in injection bullet format. Without --agent, recall searches all agent owner pools plus the shared pool; pass --agent NAME to inspect that agent's private pool plus shared, or --shared to inspect only shared facts. Private matches outrank shared matches on equal BM25 scores, and a floor guard keeps a private match visible when shared rows would otherwise fill the limit. Without --repo, recall searches every repo and general-scope facts. --repo owner/repo narrows repo-scoped facts to that repo while still including general-scope facts. --expand follows one hop of persisted memory links from direct matches, appending visible linked facts after all direct matches and marking their bullets with [linked]. --json returns raw rows for scripts, including author_ref for shared facts that preserve a different author and linked_from when a row came from link expansion. Prompt injection applies the same link expansion automatically for enrolled agents, within the entry limit and token budget, and non-empty memory blocks include a footer pointing the agent at gitmoot memory recall "<query>" --agent <agent-name> for on-demand search. Successful live delivery increments injection telemetry only for facts inside the rendered token-budget cut; preview/replay/eval reads never increment it. Successful recalls increment direct-hit telemetry only, excluding linked expansion. Both writes are best-effort. Brain fact and Knowledge JSON expose injectedCount, lastInjectedAt, recalledCount, and lastRecalledAt. Semantic or embedding search is future work; current retrieval stays SQLite FTS5 plus persisted links. memory replay re-renders recent real jobs' prompts with and without the injected learnings block and reports the token/entry delta. memory eval computes recall/precision@K of retrieval over a labeled {agent, repo, instructions, expected_keys} fixtures file.

memory vault export renders confirmed memory as a disposable, Obsidian-compatible vault view: one Markdown note per confirmed memory (sorted-key YAML frontmatter, the content verbatim, and a ## Links section of FTS co-occurrence plus persisted [[wikilinks]]), a per-owner index note, and a manifest.json staleness anchor. The vault is a view, not a replica: the SQLite store stays the only source of truth, so it is regenerated from scratch on every export, safe to delete, and deterministic: the same store yields byte-identical files (no exported_at; stable id-derived filenames). The export is read-only and atomic (temp dir then rename over --out, default a vault/ directory under the home's evals area); --agent narrows it to a single agent owner plus shared facts authored by that agent. Shared notes include an author: frontmatter line when author_ref is set, so graph views still attribute moved facts to the real author. Because the export replaces --out wholesale, it refuses to overwrite a non-empty directory that is not itself a prior gitmoot vault (one with a manifest.json), so an accidental --out ~/my-obsidian-vault can never delete your own notes; pass --force to override.

memory vault import <DIR> is the human curation gate: export a vault, edit it in any editor, then import diffs the folder against a fresh export and applies only on confirmation. It regenerates a fresh export first and aborts as stale if the store moved since the vault was written (manifest snapshot_hash mismatch). An edited note updates its source memory's content via an optimistic CAS on updated_at (exact-row, never key-based; resyncs FTS); a deleted note retires its memory (additive retired_at/retired_reason + FTS removal — kept for audit, never hard-deleted, and excluded from injection and future exports); a new .md file (no memory_id) stages a pending observation (provenance=vault-import:<file>, trust normal) behind the confirmation gate. Frontmatter identity edits (key/scope/owner) are out of scope — detected, warned, and skipped. --dry-run is the default (prints the diff, writes nothing); --yes applies edits, retirements, and new observations in one transaction. If any note fails to parse (e.g. broken YAML frontmatter), --yes refuses to apply so a malformed note is never misread as a deletion. A vault produced by export --agent NAME stays importable even when other owners have memories. The <DIR> positional may sit before or after the flags.

memory ingest stages arbitrary Markdown as observations: it walks *.md, strips leading YAML frontmatter, chunks a file only when its body exceeds ~512 estimated tokens (on ## headings, sub-splitting any still-oversized section on paragraph/line boundaries so no chunk exceeds the budget), PreFilters every chunk (per-reason rejection counts in the summary), dedups by exact content within the same scope+repo visibility domain (identical text under a second repo still stages), and inserts survivors with provenance = ingest:<relpath> and trust_mark = low. --tier defaults to repo; general is only chosen explicitly. --shared stages observations in the shared pool while recording --agent NAME as the authoring identity. Chunk keys are stable: slug(file)-slug(heading), with an ordinal suffix (-2, -3) only when a file/heading pair repeats within one sweep; the content hash participates only in dedup, never in the key, so an edited note re-sweeps onto the same key and updates its confirmed fact in place instead of spawning a hash-suffixed sibling. By default observations stay pending. If [memory].ingest_auto_confirm = true, memory ingest, memory ingest sweep, and chat remember immediately confirm the staged observation into the authoring agent's private pool only. They never auto-confirm into shared; shared stays explicit through confirm --to-shared or promote --to-shared. Auto-confirmed key-matched updates are supersede-preserving: the prior edition is archived as a superseded_by row (out of FTS, out of the vault, links unchanged on the live row) before the live row is overwritten; manual paths (vault import CAS edits, memory confirm --yes) keep plain overwrite semantics. memory ingest sweep reads every configured [[memory.ingest]] source from the current config at run time and runs the same ingest logic in-process for each one. --json reports per-source path, agent, repo, tier, inserted, confirmed, skipped_retired, deduped, rejected, and error, plus aggregate totals. One bad source does not stop the rest; it exits non-zero only when the config is invalid or every source fails. With no sources it exits zero with a skipped note. memory observations lists pending observations, flagging which keys are already confirmed. memory confirm is the human-gated promotion: by id or --provenance-prefix, it copies observations into confirmed memory (idempotently), and without --yes only prints the plan. --to-shared confirms selected observations into the shared pool while preserving the observation author. memory promote --to-shared <id>... moves active confirmed facts into shared, refuses retired or superseded rows, preserves existing links, and stamps author_ref from the previous owner when needed. memory retire --provenance-prefix P is the blast-radius undo for a collector batch. It selects active confirmed rows whose provenance starts with P, scoped optionally by --agent NAME, and is a dry run unless --yes is passed. Applying the plan sets retired_at and retired_reason and removes the rows from FTS in the same transaction. Retired keys are not resurrected by ingest or collectors on re-ingest; only explicit human-controlled confirmation paths may revive a retired key. Ingested Markdown is an indirect-prompt-injection vector, so default installs keep it inert at trust_mark = low until a human confirms it; nothing reads trust_mark for a decision yet.

Confirming a fact also records up to three deterministic persisted links from that confirmed row to active related confirmed memories. Links live in memory_links with BM25-derived scores and do not rewrite fact content. Link candidates use the same private-plus-shared visibility as prompt injection, so private facts can link to shared facts and shared facts can link back through their author pool. memory links backfill runs the same pass over the active confirmed pool in id order; --dry-run reports what would be created, and repeat runs create nothing new. memory links list <id> shows one fact's outgoing persisted links. Vault export merges these persisted links with content-derived links and dedupes by target in each note's ## Links section.

memory log is the append-only brain changelog. Its filtered feed is newest-first; --id returns one fact's complete biography oldest-first. memory log backfill idempotently synthesizes historical creation, retirement, and supersession receipts, with --dry-run available before writing.

memory groom --split [--dry-run] automatically partitions qualifying bricks at deterministic byte-offset story seams into exact-substring children. List items, Why, and How to apply sub-fields are not seams; length alone never cuts, status/changelog content is excluded, and segments below 200 trimmed bytes merge into a neighbor. The split supersedes and de-indexes the parent, carries its cluster membership to the children, and gives each rendered child (split from: <parent-key>) context in one CAS-guarded transaction. With [memory].groom_split_llm = true, over-threshold bricks left intact by the deterministic pass are offered to fresh one-shot runtime sessions. The host enumerates blank-line and strong-seam boundaries outside lists and fenced code; the model returns strict JSON choosing only those ids or keeping the brick. Gitmoot verifies exact echoed lines and runs selected offsets through the same runt merge, substantive-child, byte-coverage, store re-check, and CAS path. It never accepts model-written content. Runtime defaults to codex, empty model means runtime default, max calls defaults to 5, calls time out after 90 seconds, and content over 8192 bytes is skipped without truncation. Split and no-split verdicts cache by trimmed-content SHA-256; --json reports model, decision, cut ids, cache status, and fail-closed fallback reasons per considered brick. memory groom --split-revert [--dry-run] [--parent N]... [--since RFC3339] restores all active split parents by default. It retires, never deletes, children only when their id-ordered content still reconstructs the original parent, then restores parent FTS and the lowest-id child's current cluster. Changed groups skip whole and repeat runs are no-ops. memory groom keeps all other curation as a propose → review → apply round-trip. --propose reads active confirmed memory, computes the current vault snapshot_hash, runs deterministic detectors (status/changelog/ToC snapshots — short notes need a strong STATUS:/… & deployed marker; bare to-do lists; exact duplicates scoped to the same owner/repo/scope; over-long or strong-seam multi-story bricks are flagged when not already split; facts at least 90 days old with zero injection and recall usage are listed in a separate never_used_flags owner-review section and are never auto-retired; seam-poor long prose remains flag-only; legacy-key rekeys that migrate pre-stable-key rows ending in an 8-hex hash suffix, keeping the newest edition under the stable key and retiring older siblings with reason rekey: superseded edition; cross-pool stale shared editions, where a strictly newer private fact matches a shared fact in the same repo and scope by stable-key equality, or by a strong BM25 top-match that also shares a memory_links edge, proposing promote-the-private-and-retire-the-shared with reason cross-pool: superseded by promoted edition), and writes a reviewable plan artifact — it touches nothing in the store. --yes --plan recomputes the snapshot_hash, aborts as stale if the store changed since the proposal, then applies the whole plan in one transaction: retirements (reason groom:<detector>), rekey groups (FTS key column re-synced in the same transaction), and cross-pool promote-and-retire pairs. Content is never edited, and applying is idempotent (already-retired ids skip; a group whose rows changed state skips whole). A ready-to-register nightly proposal pipeline lives under docs/examples/memory-groom-nightly.

memory clusters groups confirmed facts into emergent communities over the fact-similarity graph (the same bm25 + id-tiebreak signal the vault [[links]] use), retiring the dashboard's old fixed key-prefix "category" hubs. The community detection is id-ordered label propagation with lowest-label tie-breaks, a pure function of the graph, so the same store yields byte-identical clusters, labels, medoids, and ids. A top-level cluster splits automatically at 20 facts when a second pass over its internal graph yields at least two children of four or more facts. An existing split remains above 12 parent facts while every child stays at least four; otherwise it dissolves. Depth is capped at two levels. Labels are up to three distinctive terms (cluster term frequency weighted against corpus document frequency), anchored to the cluster medoid; facts with no neighbors fall into the reserved cluster 0 unclustered. recompute is a human-gated propose → apply round-trip: --propose writes a plan with a staleness anchor over each active fact's (id, updated_at) and explicit planned splits or dissolves; --apply --plan re-checks the anchor, aborts as stale on drift, then rewrites the whole clustering in one transaction (a bare --apply is allowed only on first run, when nothing exists to protect). Confirming a new fact best-effort attaches it to the nearest neighbor's leaf cluster; memory cluster rename sets an owner label override that wins over the computed label and survives while that parent or child identity persists. The Knowledge payload adds optional child parent_id values and renders a repo → cluster → subcluster → fact hierarchy. Parent hubs are view-only aggregates.

Pipelines

A pipeline (#681) runs a declared DAG of shell stages — a fixed, repeatable multi-step flow — on demand or on an interval schedule. Each stage is an ordinary queued job run through the shell runtime; a scan-based advancer folds each stage's gitmoot_result decision and enqueues the stages whose needs have all succeeded. Pipelines reuse the job queue, the result contract, and the heartbeat scheduling idiom (durable next_due, overlap guard, missed-ticks-coalesce), and are off by default.

Define a pipeline in a YAML file, then register and run it:

name: nightly-sync # required, name-safe token (letters, digits, - _)
repo: owner/repo # optional to register; REQUIRED to run
env_file: /root/.config/nightly-sync/env # optional operator-owned 0600 secret file
env: # optional inline NON-secret defaults
OUTPUT_DIR: /srv/nightly-sync
schedule: # optional interval schedule (no cron in v1)
interval: 24h
jitter: 15m
trigger: # optional generated Activepieces event source
kind: email
connection: gmail-imap # default
mailbox: INBOX # default
map:
subject: subject
sender: from_address
stages: # the DAG, keyed by unique id and wired by needs
- id: source
cmd: "curl -sf https://example.com/data > data.json"
env_keys: [SOURCE_API_TOKEN]
- id: score
cmd: "python score.py data.json"
isolate: true # optional shell-only detached read-only worktree
needs: [source] # runs only after source SUCCEEDS
- id: triage # #757: an AGENT stage (exactly one of cmd|agent)
agent: reply-triager # an existing managed agent, run as a read-only leaf
action: ask # ask (default) | review — no implement
prompt: "Triage the scored data; block if a human is needed."
needs: [score] # upstream results are prepended to the prompt
- id: deploy
cmd: "rclone copy out/ r2:bucket"
needs: [triage]
timeout: 30m # optional per-stage job timeout
retry: 2 # optional; re-attempt a FAILED stage up to N times
gitmoot pipeline add nightly-sync.yaml --enable # validate + store; omit --enable to add disabled
gitmoot pipeline install-defaults # install built-in memory pipelines, skipping existing names
gitmoot pipeline list [--json]
gitmoot pipeline show nightly-sync [--json] # registry view for a name
gitmoot pipeline bind-trigger nightly-sync # create/re-sync owned AP flow
gitmoot pipeline run nightly-sync [--payload key=value ...] [--payload-json '<obj>']
gitmoot pipeline watch <run-id> [--timeout 10m] [--poll 5s] [--json]
gitmoot pipeline show <run-id> [--json] # run funnel for a "prun-…" id
gitmoot pipeline expose --schema schema.json <name>
gitmoot pipeline serve [--addr 127.0.0.1:8792] [--allow-remote]
gitmoot pipeline resume <run-id> [--from <stage>]
gitmoot pipeline cancel <run-id>
gitmoot pipeline enable|disable nightly-sync
gitmoot pipeline remove nightly-sync

Service API and offline proof

pipeline expose opts a shell-only, template-free pipeline into a bounded flat input schema and prints its base64url bearer token once; only the SHA-256 digest is stored. pipeline serve is a separate authenticated listener, loopback-only by default. Valid typed inputs reach stages only as reserved GITMOOT_INPUT_* environment variables in fail-closed detached worktrees. Stages declaring env_keys, network access, or extra read/write authority are rejected. Admission atomically applies rate/concurrency limits and creates an unpredictable 128-bit run id.

Successful shell stages can deliver files from out/. Gitmoot collects them before disposing the detached worktree, stores them as artifacts/<stage-id>/..., and fails finalization if the run exceeds 64 MiB. After success, an authenticated status GET finalizes the frozen pipeline bundle with those files, proof.json, and verification.json; artifact proof nodes commit each file's size and SHA-256 digest. gitmoot proof --verify <service-run-id> checks the persisted run/stage/job/result-hash and artifact relationships offline; it does not rerun commands, query CI, or promote reported tests.

The public /receipts/<run-id> page shows artifact names, sizes, and digests, but its sanitized bundle omits artifact bytes. The authenticated service bundle is the only download containing them. Token rotation revokes the old bearer credential; disabling blocks new POSTs but does not revoke reads or polling of accepted runs. Both bundles include the frozen #941 spec with full shell command bodies and referenced environment-variable names, so never inline a secret literal in cmd. Public capability receipt URLs remain public after token rotation.

An enabled trigger.kind: email pipeline auto-binds. If Activepieces is down, registration succeeds with a pending binding; bind-trigger retries it and recreates an owned flow deleted in Activepieces. Map output names are lowercase identifier keys up to 64 bytes; selectors are subject, from_address, text, message_id, and date. Mapped flows require @gitmoot/piece-gitmoot 0.1.4+. Create the default IMAP connection with gitmoot activepieces connect gmail; --with-smtp is optional.

pipeline add validates the whole spec at add time and stores the raw YAML verbatim plus a content hash; each run snapshots the hash and executes its snapshot, so editing the file later never mutates an in-flight run. It also auto-creates one hidden shell runner agent (pipeline-<name>-runner) that owns the shell stage jobs; it is hidden from agent list and disposed by pipeline remove. A stage may instead set agent + prompt to run a named managed agent on its own runtime. Four agent-stage kinds: ask/review (#757, read-only leaves); implement (#768, action: implement + write: true; mutates the repo, with only implemented promising a PR and waiting for its stamp; other configured successes settle immediately; never auto-merges); orchestrate (#758, orchestrate: true; a sub-tree coordinator that fans out owned children and folds the synthesis); and gate (#768, gate: pr_merged + source:, no agent; a jobless waiter that folds when the source implement stage's PR merges). A read-only stage's needs result summaries are prepended to its prompt, and a repo-bound read-only agent stage runs in its own detached read-only worktree so same-repo stages parallelize without touching the live checkout. A non-service shell stage can opt in with isolate: true; it then runs in a disposable detached read-only committed-tip worktree. Default false preserves the shared checkout. Allocation failure records readonly_worktree_skipped and falls back to that checkout; success adds GITMOOT_CHECKOUT=<live-checkout> to the shell environment. This removes checkout-lock serialization, and each isolated stage also takes a job-scoped shell runtime-session key (runtime:shell:job:<hash(job)>) rather than the command-hash key, so same-repo stages run concurrently even when they share the identical command (#1034). The field is rejected on agent/gate stages, while service shell stages retain unconditional fail-closed isolation. For shell-stage API credentials, set an absolute pipeline env_file and list exact names or globs in each stage's env_keys. The file must be a regular, operator-owned 0600 file outside Gitmoot state and managed checkouts; inline env is for non-secret defaults. Missing/reserved keys and env_keys on an agent/gate stage are rejected at add time. A stage with no list receives no injected values. Values are read fresh at delivery for restart-free rotation; the job payload stores only the path and expanded names, never file values. A stage selector may also resolve to a shared keychain key the pipeline was granted (see the gitmoot key registry below): the pipeline's own env_file key always wins over a same-named grant, resolution is deny-by-default (GITMOOT_* internals > own env_file > granted shared > inline default > fail closed), and a grant revoked between enqueue and delivery fails closed. A granted key registered with mode proxied (and configured with gitmoot key configure) is never delivered as a value: the stage receives a per-job placeholder plus a GITMOOT_PROXY_<KEY>_URL loopback lease pinned to the configured HTTPS origin/base path, and Gitmoot re-checks the grant and reloads the real value on every request. Produce stages may additionally declare reads: — read-only Landlock input roots symmetric to writes: that narrow the readable filesystem to an allowlist and can never expose the Gitmoot home, the keychain, or the pipeline env_file.

Keychain registry (gitmoot key)

gitmoot key path [--json]
gitmoot key add <NAME> --mode injected|proxied [--json]
gitmoot key configure <NAME> --upstream <https-url> --auth bearer|header:<HeaderName> [--json]
gitmoot key list [--json]
gitmoot key show <NAME> [--json]
gitmoot key grant <NAME> --pipeline <pipeline> [--json]
gitmoot key revoke <NAME> --pipeline <pipeline> [--json]
gitmoot key rm <NAME> [--force] [--json]

The keychain is one operator-owned 0600 env file (default ~/.config/gitmoot/keychain.env, overridable via [credentials] keychain_path); key path prints its location and status. The CLI registers names and metadata only — there is deliberately no value flag anywhere, and secret values never enter SQLite, argv, logs, or --json output. key add requires the name to already exist non-empty in the keychain file; key grant requires the pipeline and key to exist and refuses unconfigured proxied keys; key rm refuses while grants exist unless --force (which removes metadata and grants but never touches the file). Grants are deny-by-default and audited by name: job payloads record {stage, name, source, mode} rows only. See Runtime Ambient Credential Hygiene for the full custody model, including the honest limits of proxied delivery.

Every agent stage receives a non-empty trigger payload as bounded, dynamically fenced UNTRUSTED external data; shell stages receive exact GITMOOT_TRIGGER_<UPPERCASE_KEY> environment entries. The full payload is retained in the SQLite run row and normal job data. Triggered mutating stages additionally require top-level allow_triggered_writes: true.

pipeline install-defaults installs the built-in memory-ingest-sweep and memory-groom-propose pipelines. The daemon also runs this installer at startup. It is idempotent: an existing pipeline with either name is skipped without overwriting user-edited YAML, enabled state, or schedule. Empty memory pipeline config installs manual-only definitions. Configure sources with [[memory.ingest]] and intervals with [memory.pipelines], or run them on demand with gitmoot pipeline run memory-ingest-sweep and gitmoot pipeline run memory-groom-propose. The installed ingest sweep has a fixed two-stage shape that calls gitmoot memory ingest sweep --json, then summarizes the totals. It reads [[memory.ingest]] at run time, so config edits apply on the next manual or scheduled run without reinstalling defaults. The installed groom pipeline has a fixed split -> propose -> summarize shape: only the lossless split auto-applies, while the generated retirement/rekey/ cross-pool plan remains owner-gated.

A stage signals its outcome by printing a gitmoot_result blob to stdout; the advancer folds by the decision, never the job's exit state (changes_requested is a succeeded job but folds as a stage failure by default):

  • a decision in the stage's success_decisions (default approved/implemented/skipped) -> succeeded, dependents enqueue;
  • blocked → the stage blocks, its needs persist, the run parks blocked (downstream never enqueues, zero compute while parked);
  • failed / any other decision / a cancelled job / no gitmoot_result → the stage fails (retried if budget remains), else the run parks failed.

skipped means the stage itself had no work and advances by default with a [skipped: no work] summary marker. An explicit success_decisions list is strict: omitting skipped makes it fail. A pr_merged gate whose terminal succeeded source opened no PR parks blocked because there is nothing to wait for.

pipeline run prints only the run id (script-stable). Repeat --payload key=value or provide one --payload-json string object to use the same validated trigger input seam as the bridge; the forms are mutually exclusive. It ignores the enabled flag but still needs a repo and refuses to start while a run is active. pipeline show <run-id> renders the text funnel (source OK -> score BLOCKED (needs: R2 token) -> deploy SKIPPED); a failed run also prints the exact gitmoot report bug --job <stage-job> command (never auto-filed). pipeline resume re-runs a parked run from its halted stage (or --from) plus its transitive dependents while never re-running a succeeded stage. A pipeline stage is a leaf: a stage result carrying delegations[] never spawns children. See Pipelines for the full workflow.

pipeline watch <run-id> blocks until terminal state, printing each stage state change once. It exits 0 for succeeded, 1 for failed/blocked/cancelled, and 2 with still running when the timeout expires. --json emits the final pipeline show --json summary without transition lines.

Native Chat (agent threads)

gitmoot chat (#534, V1 local-only) is a durable, repo-aware conversation ledger where registered agents and the human talk in threads, @-tag each other, and (explicitly) promote a message into a real job. It lives in local SQLite — zero network, zero entmoot dependency. The core rule: a message is a row (free); a job is compute (explicit). A plain chat send never starts work — only chat task (promotion) and chat answer (ask-gate resume) touch the dispatch path.

gitmoot chat create <name> --repo owner/repo [--topic "title"] [--json]
gitmoot chat list [--repo owner/repo] [--all] [--json] # open threads; --all includes archived
gitmoot chat show <thread> [--repo owner/repo] [--limit N] [--json]
gitmoot chat send <thread> "message" [--as agent] [--repo owner/repo] [--ref kind:value ...] [--json]
gitmoot chat remember <thread> <message-seq> [--repo owner/repo] [--tier repo|general] [--agent NAME] [--json]
gitmoot chat inbox <agent> [--unread] [--json]
gitmoot chat task <thread> "@agent message" [--action ask|review|implement] [--repo owner/repo] [--json]
gitmoot chat answer <thread> "<question-id>: answer text" [--repo owner/repo] [--json]
gitmoot chat close|reopen <thread> [--repo owner/repo] [--json]
gitmoot chat rename <thread> "new name" [--repo owner/repo] [--json]
  • create<name> is slugified to a topic-path-safe handle ([a-z0-9-], no +/#//; unique per repo). --repo is required; --topic sets the human display title. The slug is the stable handle — a later rename changes only the title.
  • send — appends a chat message. @agent mentions land in a registered agent's unread inbox; an unknown mention is recorded for audit with a stderr warning and never fails the send. --as <agent> authors as a registered agent (default: the human); --ref kind:value attaches structured refs.
  • remember: captures exactly one existing message by sequence as a memory observation with deterministic provenance chat:<thread-id>#<seq>. It stores the message body verbatim, applies the memory PreFilter, and dedups by content hash in the target scope/repo. It does not scan for natural-language prefixes or bulk-mine a thread. --agent defaults to lead; if [memory].ingest_auto_confirm = true, the observation is confirmed into that agent's private pool only.
  • inbox — an agent's mentions, newest first; --unread restricts to unread.
  • task — the one promotion verb. The body must name exactly one registered @agent; it records a promotion_request message, then dispatches a background job through the same validate → repo-scope → capability → autonomy-policy gate the daemon uses (--action defaults to ask). The message is back-linked (promoted_job_id), and the terminal result is appended into the thread as a job_result message (non-promotable, reply_to the promotion). An identical (thread, body) promotion within 60 s is refused (anti-ping-pong).
  • answer — the local answer channel for the ask-gate (#445). When a job pauses at awaiting_human, the engine auto-links a job-<hash> thread and posts the questions as a system message with a {kind:job} ref; chat answer routes the answer onto the existing resume path and enqueues the coordinator continuation that carries it.
  • close/reopen — archive (audit-preserving) / restore a thread.

Message kinds are a fixed vocabulary: chat, promotion_request, job_result (never promotable), and system. Every row carries an origin stamped with a generated stable per-DB home_id (never the literal self) and a versioned canonical envelope — schema discipline that keeps a future cross-machine bridge additive without changing any V1 behavior. See Chat for the full workflow.

Routing Telemetry (Advisory)

Gitmoot records lightweight execution-grounded routing telemetry: one additive row per job at its terminal transition capturing which combination actually ran and how it turned out — repo, action, phase, runtime, model, agent, resolved template_id + commit, terminal job_state, result decision + approval flag, a coarse tests-run count, duration_ms, and input/output tokens (best-effort). Capture is always on, additive, and fail-safe: it writes only to the new routing_telemetry table and a telemetry error can never fail a job.

v1 is advisory only — nothing reads this back to change routing, and no automatic model/runtime override happens anywhere. It is a local feedback loop you inspect, not a global benchmark.

Inspect observed performance (read-only), grouped by (action, runtime, model, template):

gitmoot router summary [--repo owner/repo] [--action ask|review|implement] [--since 30d] [--json]

It reports per-group count, success rate, approval rate, median duration, and summed tokens, always labeled "local observed performance, not a benchmark". --since accepts a Go duration or an <N>d days suffix.

Optionally inject a bounded (≤12-line) observed-performance table into a coordinator's prompt. It is off by default; with it off, coordinator prompt assembly is byte-identical and no telemetry query runs during a job:

[router]
context_enabled = true # inject the advisory table into top-level coordinator prompts (default false)

The injected block carries the same "not a benchmark" disclaimer, is added only to top-level (coordinator) jobs, and never forces a route — routing stays advisory.

V1.5 — auto-respond, chat wait, and moot

V1.5 (#534) adds the agent-to-agent layer. Both additions are off by default and keep anti-ping-pong structural: only a kind=chat message with a resolved mention triggers work, and every back-linked reply is a non-triggering kind=job_result.

Auto-respond sweep — an opt-in daemon-tick sweep that lets an enrolled agent answer an @mention without a human running chat task, enqueueing one bounded read-only ask per unread mention through the same dispatch gate as chat task. It is a no-op (zero chat-table queries per tick) unless both the global switch and a per-agent opt-in are set: [chat] auto_respond = true and [agents.<name>] chat_autorespond = true.

[chat] knobDefaultMeaning
auto_respondfalseGlobal kill switch; false overrides every per-agent opt-in.
auto_respond_cap4HARD cap on auto-responses per (thread, agent). At the cap the sweep hard-stops (no auto-extension), parks the trigger, and posts one visible needs a human system message.
auto_respond_cooldown2mMinimum spacing per (thread, agent); a trigger inside the window is deferred (left unread to re-fire), never dropped.

chat wait — a blocking read verb for moot turn-taking: it polls until the thread has a message with seq > --since-seq, then prints the new messages plus a last-seq: N line (feed N back as the next --since-seq). On a capped moot thread it returns immediately with the wrap-up line instead of spinning to the timeout.

gitmoot chat wait <thread> [--since-seq N] [--timeout 90s] [--repo owner/repo] [--json]

gitmoot moot — convene N registered agents as seats in one bounded brainstorm. Each seat is one background read-only ask job through the same validate → repo-scope → capability → policy gate as chat task; seats converse via chat send / chat wait, so the cost is exactly one job per seat regardless of message count. Messages are rows (free).

gitmoot moot <name> "topic" --agents a,b,c --repo owner/repo [--max-messages N] [--home ...] [--json]

Every seat must be registered, repo-scoped, and carry the ask capability, or the moot is rejected before any thread or seat is created. The moot HARD-STOPS at its agent-message cap (no auto-extension): at the cap chat send --as is refused, one visible MOOT CAP REACHED overrun system message is posted, and each seat wraps up by returning its partial conclusions (what it knows / is unsure of / would ask next) as its gitmoot_result, which arrive via the job_result back-link path (the cap never blocks those). Human sends and seat conclusions are never gated by the cap.

[chat] knobDefaultMeaning
moot_max_seats6Max agents one moot may convene; a larger roster is rejected.
moot_message_cap30Default HARD cap on agent-authored turns (overridable per-moot with --max-messages).

[chat] is optional: with no [chat] section every knob resolves to its default, auto_respond stays off, and the daemon tick is byte-identical.