← All docs

satelle doctor — is satelle ready to govern this repository?

satelle doctor is the one diagnostic surface. It answers a single question for the current repository — and, with --all, for every registered one.

It composes the checks that already own their rules rather than adding rules of its own. That is deliberate: satelle init, satelle doctor, and the engagement precondition must never form three different opinions about the same repo, and the only way to guarantee that is for them to run the same code. A rule that lived only in doctor could not be changed without a binary release, which is exactly what the constitution forbids.

What it checks

| Area | Owner | | --- | --- | | Every binding's command, transport, timeout, and ${VAR} resolution | the agents layer check | | Machine-wide profile references — missing profile, reference cycle, role conflict | profile resolution | | Workflow node/edge allocations, and lifecycle-hook allocations | the agents layer check | | Reviewer permission ceilings | the agents layer check | | Workflow / skill / principle / task structure | the authored-substrate contracts | | Cross-workflow consistency — ambiguous applies_to, unresolved skills | the workflow consistency check | | Required binaries — each isolated binding's executable is on PATH | doctor | | Harness hook scaffolding vs this binary's canonical wrappers | the scaffold check |

Two configuration layers, deliberately kept apart

Doctor names both, because confusing them is the mistake it exists to prevent:

  • Repo workflow POLICY.satelle/: workflows, gates, lifecycle hooks, and which logical agent runs each step. What this repo enforces. Never machine-wide.
  • Machine-wide EXECUTION~/.satelle/agents.toml: reusable provider profiles (command, transport, model, effort, tools). How a command runs on this machine, shared by every repo that explicitly references one.

For each binding, doctor prints the effective value and its source:

GRANT [reviewer] role=reviewer interface=command backend=isolated:claude read-only
  source: command = "claude -p …" (profile:claude-opus)
  source: effort = "low" (repo)
  source: model = "opus" (profile:claude-opus)
  source: tools = "Read,Grep,Glob" (embedded)

The four sources are repo (inline in .satelle/workflows/agents.toml), profile:<name> (an explicitly referenced machine-wide profile), global-role:<name> (the catalog's opt-in [roles] default), and embedded (satelle's compiled fallback).

Secrets

Environment values are never printed, in any mode, including --json. Doctor lists each binding's env key names with whether the value resolved:

Environment keys (names only — values are never printed):
  [planner] ANTHROPIC_AUTH_TOKEN (resolved), ANTHROPIC_BASE_URL (resolved)

An unresolved ${VAR} is a finding that names the key, never its contents.

Severity

| | Meaning | | --- | --- | | FAIL | an error — the repo is not ready, and engagement will refuse for the same reason, with the same identifier | | WARN | advisory — printed, never blocking. Used where the underlying test is a heuristic and a false positive would strand a working repo | | INFO | context you asked for (a live probe that answered) |

Every finding carries a stable identifier (binary.missing, agents.profile.broken, hook.alloc.unresolved, …). The identifier is the contract: the same defect reports the same id whether you meet it in doctor, in satelle init, or in an engagement refusal — so it can be scripted against.

Exit codes

| Code | Meaning | | --- | --- | | 0 | healthy — warnings are allowed and still printed | | 1 | one or more error findings, in any checked repository | | 2 | doctor itself could not run (bad flag, unreadable global config) |

--all — every registered repository

satelle doctor --all

Each registered repo is checked independently. An unreadable, uninitialised, or pathological repo becomes its own one-finding report rather than aborting the sweep — a diagnostic that stops at the first bad repo is useless to an operator with several. Output ends with N healthy, M unhealthy, and the exit code is the worst result across all of them.

satelle service status prints the same healthy/unhealthy tally for registered repos, so an unhealthy repo is visible from the service surface rather than looking identical to a ready one.

--live — opt-in provider probes

Ordinary satelle doctor performs no paid and no network model call at all.

--live additionally starts each isolated binding's CLI:

  • a command-transport binding is asked for its --version;
  • an ACP binding is taken through a single initialize handshake.

Neither opens a session nor sends a prompt, so neither is a paid model call — but both spawn provider processes and may consume provider authentication and rate budget. That is the side effect to be aware of before wiring --live into anything automated.

Every probe is bounded by --timeout (default 20s) and its process group is killed and reaped on the deadline or on cancellation, so a probe never leaves a provider process behind. Authentication is diagnosed only where the provider says so — an unexplained failure is reported as a spawn failure, not guessed at as an auth problem.

Live findings are advisory: a provider that is down does not make a well-configured repository unhealthy.

Machine-readable output

satelle doctor --all --json
{
  "repos": [{"repo": "…", "ok": false, "findings": [
    {"id": "binary.missing", "severity": "error", "title": "Missing executable",
     "detail": "…", "remediation": "…", "artifact": "reviewer"}
  ]}],
  "summary": {"healthy": 2, "unhealthy": 1},
  "exit_code": 1
}

Ids and severities are stable; env values never appear.

See also: satelle help global-agents (the machine-wide profile catalog), satelle help workflows (what a workflow governs), and satelle help agent-dispatch (how a dispatched step runs).


Mirrored from satelle’s built-in help. Read it in the binary with satelle help doctor, or see the canonical source in the satelle repo.