For the complete documentation index, see llms.txt. This page is also available as Markdown.

CLI

Every dotagent subcommand, what it does, and a minimal example.

dotagent <COMMAND> [ARGS] [FLAGS]

Run dotagent --help for the cheat-sheet, dotagent <command> --help for per-command flags. Every subcommand documented below mirrors the clap-generated help.

Command
Purpose

run

Run a single schedule of an agent in the foreground.

tick

One-shot dispatch pass (what the daemon does on each cycle).

daemon

Long-lived adaptive scheduler. Invoked by launchd / systemd.

api

Raw JSONL bridge to the daemon's local Unix socket.

status

Textual health dashboard.

daily-summary

Send the end-of-day health summary.

bootstrap

Mark every schedule's window as ok (one-shot, post-install). Not implemented yet.

install

Generate + write the daemon unit file (launchd / systemd).

uninstall

Remove the daemon unit file.

doctor

Validate manifests, resolve plugin references, warn on drift.

plugin list

List discovered plugins.

plugin invoke

Invoke a plugin manually for debugging. Not implemented yet.

audit verify

Verify the audit log's hash chain and report how far back the guarantee reaches.

logs

Tail the daemon-captured stdout/stderr for one or all agents.

inspect

Dump heartbeat + manifest hash + schedule state for one agent.

reload

Send SIGHUP to the running daemon.

run-now

Force-run an agent immediately, ignoring schedule windows.

mcp

Serve every discovered agent as an MCP tool over stdio.

memory

Read and curate the long-term memory workspace.

completions

Print a shell completion script (bash / zsh / fish / elvish / powershell).


run

Run a single schedule of an agent in the foreground. Useful for development — no daemon involvement, no on_* plugin hooks, no notifiers fire.

Arg/flag
Meaning

NAME

The agent's agent.name (not the directory name).

--schedule ID

Which schedule to use — picks the args and the slug from this schedule.

--dry-run

Inject AGENT_DRY_RUN=true. The script can decide what to do (typically: skip side effects). dotagent skips writing the heartbeat.

What it does: discovers the manifest, injects the env vars, spawns the command, captures stdout/stderr, prints stdout to your terminal, and exits with the agent's exit code. Notifiers and sinks do NOT fire — use run-now for that.

Example:


tick

One-shot dispatch pass. Same logic the daemon loop runs once, but without sleeping or installing signal handlers. Used by the daemon internally; exposed so you can debug "what would dotagent do RIGHT NOW".

Flag
Meaning

--dry-run

Don't actually spawn anything. Prints scanned N; would dispatch M; next event: ts.

--verbose

(Reserved — currently a no-op.)

Example:

When debugging "is my agent gonna fire?", run this. It tells you which schedules dotagent considers due.


daemon

The long-lived adaptive scheduler. You don't invoke this directly — launchd / systemd does, via the unit file dotagent install writes.

If you run it manually (e.g., for development), it stays in the foreground until you Ctrl+C (SIGINT) or kill it (SIGTERM). It:

  • Discovers manifests + plugins

  • Writes state/daemon.pid

  • Initializes structured JSON logging into logs/daemon/dotagent.log

  • Initializes OTel export if [telemetry] is configured

  • Loops forever: tick → sleep until next event → wake on SIGHUP

  • Cleans up the PID file on exit (Drop guard)

The daemon process responds to:

Signal
Effect

SIGHUP

Wake immediately, re-read manifests + plugins on the next tick.

SIGTERM

Graceful shutdown. Audit log gets a DaemonStopped entry.

SIGINT

Same as SIGTERM.

See guides/daemon-lifecycle.md for how to install / start / stop / reload the daemon end-to-end.


api

Bridge raw JSON Lines between stdin/stdout and the daemon's local Unix socket. The command does not parse, render, or persist messages, so it can be used as a backend for scripts or a TUI.

By default it connects to $DOTAGENT_HOME/api.sock (normally ~/.config/dotagent/api.sock). --socket overrides the path for tests or an explicit endpoint. Every input frame is forwarded with its original bytes and line ending; every socket frame is forwarded to stdout the same way.

When stdin reaches EOF, the client half-closes its request side and continues reading responses/events until the daemon closes the socket. Diagnostics go to stderr. There is no prompt or TUI rendering.

Example:

See Local Client API for the wire contract.


status

Textual health dashboard. Read-only — never writes to audit, never dispatches. Takes no flags.

Output:

One row per (agent, schedule) pair — inbox-triage above declares two schedules and gets two rows, each with its own health. The counter at the top counts rows, not agents: 2/5 there is five schedules across four agents.

Rows are ordered most-urgent-first: failingdegradedstaleok. Within a bucket, discovery order.

Column
Meaning

AGENT/SCHEDULE

agent.name + the schedule's id.

STATE

One of the four health states.

LAST RUN

finished_at_iso from the heartbeat — when the run ended, success or not. never if there is no heartbeat yet.

REASON

Why that state, see below.

Agents with monitor = false in their manifest are excluded (those are typically one-shot/manual examples).

Reading the REASON column

The state comes from the heartbeat and from the window file for the window currently due — the same file the daemon writes retries against. The reason tells you which of the two decided:

State
Reason
What produced it

ok

ok

The due window succeeded on the first try.

ok

no window today · last success ok

Nothing is due — a weekday-only schedule on a Sunday, an hour outside hours — and the last run succeeded.

degraded

recovered after N attempts

The window did succeed, but burned N failed attempts first.

failing

N attempts, will retry

The window passed without success; the retry budget is not spent yet.

failing

gave up after N attempts

Retry budget exhausted — the window is marked given_up.

failing

window due Nmin ago, no attempt

The window is overdue and no window file exists — the daemon never dispatched. Usually means the daemon is not running.

stale

window missed Nmin ago (stale)

Older than stale_after_minutes; retrying is no longer useful.

stale

never ran

No heartbeat and no window due — the schedule has never fired.

The count is singular-aware: 1 attempt, 2 attempts.

N in recovered after N attempts counts failed attempts, not dispatches. The window file's attempts counter is bumped after every dispatch including the one that succeeded, so a first-try success lands on disk as attempts: 1 and still reports ok. See Health states.

For stale, an interval schedule is judged against the first window it missed after its last success, not the rolling window dispatch uses — so a schedule broken for weeks reports the full age.

Live subprocesses

When the daemon is running and has children alive, status appends a second table read from the supervisor snapshot (state/supervisor.json). It is omitted entirely when the snapshot is missing or empty.

KIND is one of agent, persistent, preflight, sink, notify, skill, plugin_info, plugin_validate. Rows sort by deadline pressure, highest first, and are flagged ⚠️ at ≥80% of the deadline and 🔴 at ≥100%.

A persistent row never gets a warning icon: its clock is the idle window before recycling, not a run running late, so reaching it is the pool working as designed. Those rows mark the clock idle, and their AGE is time since the last answer. See Lifecycle.

Agents launched by dotagent mcp or run-now run in that process, not under the daemon, so they never appear here.

The snapshot carries two fields this table deliberately does not show: pgid and command. They are identity proof for the next daemon's boot orphan reap, not information an operator reads at a glance. Full schema in paths.md.


daily-summary

Send the end-of-day health summary. The daemon delivers it once a day at [daily_summary].time (default 22:45 local) and schedules its own wake-up for that moment. grace_minutes (default 30) is the tail that still counts as on-time when the wake-up could not happen at all — laptop closed, machine off. Both are configurable; see [daily_summary].

Flag
Meaning

--dry-run

Print the message and its destinations to stdout instead of delivering it.

Typing the command delivers even when enabled = false. That flag governs the daemon's scheduled delivery; someone who ran this asked for this one.

Destinations come from [[daily_summary.notifiers]], which takes the same entries a manifest's [[notifiers]] takes. With none configured the summary goes to the desktop driver — the only one that needs no address and sends nothing off the machine.

Output — the same classification status renders, folded into a message short enough for a phone. Healthy schedules collapse into the count; only the unhealthy ones are named, and empty buckets are dropped:

An all-green day is three lines: the header, N/N ok, and nothing else.

--dry-run appends where it would have gone:

Each delivery is audited as plugin_invoked with plugin: "notifier:<driver>", on failure as well as on success — a summary that reached nobody is answerable from dotagent audit instead of from silence.


bootstrap

Not yet implemented. Calling it returns bootstrap — not yet implemented.

Intent: mark every schedule's "current window" as ok in one shot, so a fresh install doesn't trigger a flood of failing notifications for schedules that haven't had a chance to run yet.

Workaround for now: run the daemon for one full cycle of each schedule and it'll fill in.


install

Generate and write the daemon unit file. One unit per system — not one per agent.

Arg/flag
Meaning

--all

(Accepted for backwards compat — no-op, prints a notice.)

NAME

(Accepted for backwards compat — no-op, prints a notice.)

--all and NAME are accepted but ignored — dotagent now uses one daemon unit (run.avelino.dotagent) that manages every discovered manifest internally.

What gets written:

  • macOS: ~/Library/LaunchAgents/run.avelino.dotagent.plist

    • RunAtLoad=true, KeepAlive=true, ThrottleInterval=10

  • Linux: ~/.config/systemd/user/run.avelino.dotagent.service

    • Restart=always, RestartSec=10

The unit points at the currently-running dotagent binary (resolved via std::env::current_exe). If you move the binary, re-run install.

After writing the file, the command prints the platform-specific "Next step" line to actually load the unit:

See guides/daemon-lifecycle.md.


uninstall

Remove the daemon unit file. Idempotent (no error if nothing to remove).

Same flag caveats as install — both are no-ops.

Does NOT stop the daemon if it's running — launchctl bootout / systemctl --user disable --now first if you want a clean stop.


doctor

Validate every discovered manifest, resolve plugin references, and warn on inconsistencies. Always safe to run — read-only.

What it checks:

  • Manifest TOML is parseable.

  • agent.name and [run].command are non-empty.

  • Schedule ids are unique within each manifest.

  • Every plugin referenced by [[preflight]] / [[on_success]] / [[on_failure]] / [[notifiers]] driver = "plugin" resolves to a binary on $PATH / the discovery dirs.

  • Each manifest has a [security] section. (If absent, emits a warning — schema-only in v0; see security/threat-model.md.)

  • Compares each manifest's sha256 against state/known_manifests.json. Mismatch → "manifest drift since last daemon run" warning.

Exit code: 0 if 0 errors, non-zero otherwise. Warnings do not trigger a non-zero exit.

Example:


doctor also reports inbound Telegram when [telegram] is present in config.toml — whether the ingress is on, and the two ways it can be half-configured:

It stays silent when the section is absent, since the ingress is off by default. A dispatcher_agent that does not resolve is a warning: every accepted message would fail after passing the allowlist.

It also prints where long-term memory lives:

A path set in [memory] workspace that holds no outl workspace is a warning — the default path is scaffolded automatically, a configured one is not.

And how many skills it found, across every search root:

A SKILL.md that fails to parse is a warning, not an error — nothing stops running, an assistant just answers without a procedure it should have had. Two skill names that sanitize to the same tool name are also warned about: only the first is callable, and a skill you wrote and cannot call is otherwise a mystery.

Commands get the same treatment, with one extra check. They carry two derived names — the MCP tool and the Telegram menu entry — and Telegram's [a-z0-9_]{1,32} has no hyphens, so two commands can be distinct in the catalog and collide in the menu:

The files are named, not just the commands: two names differing only by - versus _ are near-identical on screen, and the useful question is which one to rename. See Commands.

Manifests that fail to parse are listed with and counted as errors. They no longer abort the scan — the healthy agents are still reported below them.


plugin list

List every plugin referenced by any discovered manifest, with its resolved path + advertised version + kinds.

Output (tab-separated):

If a plugin is referenced but not on $PATH, you'll see (not found: ...). Run dotagent doctor for a friendlier report.


plugin invoke

Not yet implemented. Currently returns plugin invoke <name> — not yet implemented.

Intent: run a plugin manually with a JSON payload. Until this lands, invoke directly:

See reference/plugin-protocol.md for the payload shape.


audit verify

Verify the hash chain in $DOTAGENT_HOME/audit.log and print what verification could actually establish.

The daemon runs the same check at boot, but it only asks yes/no. This asks how far back, which is the question a rotated log makes real: a history the operator pruned must not read the same as one somebody beheaded.

Verdicts:

Output
Meaning
Exit

chain intact from GENESIS

Every entry still on disk was checked, back to the first line ever written

0

chain intact since <ts>

Every entry checked links, and the oldest is a seam naming where the rest went. The text says whether that segment is still on disk (re-run with --full) or gone (retention — or evidence removed; the chain cannot tell those apart)

0

unexplained truncation

The oldest entry links to a hash nothing accounts for: no GENESIS, no seam. The head of the log was removed

1

chain broken at position N

A link mismatch, or a line nobody can parse, inside the data that is present. Names the segment and both hashes

1

Without --full the walk stops at the first seam, which is what the daemon does at boot — the live file is the only one that changes, so it is the cheap check worth running every time. --full is the one to run when you actually want the guarantee to reach GENESIS.

What the chain does and does not prove (short version: it catches partial edits, never a total rewrite) is in security/threat-model.md.


logs

Tail the daemon-captured stdout/stderr.

Arg/flag
Meaning

NAME (optional)

Tail one agent's logs. Omit to tail every agent at once (each chunk is prefixed by tail with ==> path <==).

-n LINES

Print the last N lines (default 50).

--follow / -f

tail -F-style follow. Survives rotation.

--schedule ID

(Reserved — currently unused.)

Reads from ~/.config/dotagent/logs/agents/<name>/<name>.log plus any rolled <name>.log.YYYY-MM-DD files (skips .gztail can't follow compressed files).

Examples:

For structured logs (the daemon's own tracing output) read ~/.config/dotagent/logs/daemon/dotagent.log directly:

See guides/observability.md for the log schema.


inspect

Dump heartbeat + manifest hash + schedule state for one agent.

Output:

Use this when "is the heartbeat fresh?" / "did the last run succeed?" is your question.


reload

Send SIGHUP to the running daemon. The daemon picks up new manifests and plugin changes on its next tick.

Reads ~/.config/dotagent/state/daemon.pid and sends SIGHUP. Fails if:

  • The PID file is missing (daemon not running).

  • The PID exists but the process is gone (stale pidfile).

If you swapped the dotagent binary itself, SIGHUP isn't enough — restart the daemon via launchctl/systemctl. See guides/daemon-lifecycle.md.


run-now

Force-run an agent immediately, ignoring schedule windows. Unlike run, this DOES fire preflight, sinks, and notifiers — it's a single-shot version of what the daemon would do.

Arg/flag
Meaning

NAME

The agent's agent.name.

--schedule ID

Which schedule's args to use. If omitted, uses the first schedule declared.

--json

Emit one machine-parseable JSON line instead of the human report.

Example:

A preflight abort names the plugin that stopped it instead of a run result:

Colour follows the TTY and honours NO_COLOR. Truncated output says so explicitly rather than trailing off.

Use this to:

  • Trigger an agent after fixing a problem (don't wait for the next window).

  • Manually exercise the full plugin chain (preflight → spawn → sink → notify).


mcp

Serve every discovered agent as an MCP tool over stdio. JSON-RPC 2.0, one object per line.

Takes no flags — the catalog comes from the manifests already on disk. Point any MCP client at it:

Example — list the catalog by hand:

The catalog is not only agents: skill-* tools carry the procedures found under ~/.config/dotagent/skills/ and ~/.claude/skills/, command-get and command-list resolve the commands under ~/.config/dotagent/commands/, and the five memory-* tools appear when [memory] is on. See Skills and Commands.

Agents run in this process, like run-now — not through the daemon — so the subprocess tree does not appear in dotagent status. State is keyed off the trigger-mcp slug so an on-demand run never overwrites the scheduled history of the same agent.

stdout carries protocol only; logging goes to stderr, so RUST_LOG=debug dotagent mcp stays safe to run under a client.

Full protocol reference: MCP server.


memory

Read and curate the long-term memory workspace — the same store dotagent mcp exposes to agents, reachable from a shell and without a running daemon. That last part is the point: consolidating a day's raw facts into durable ones is a job an agent should be able to do on a schedule, and it can only do that if the verbs exist outside the daemon process.

recall ranks by shared words first and recency second; an empty query lists the most recent facts. --topic asks the graph instead — every fact linked to that subject, which is the better question when you know the subject.

Example — find a fact, then correct it:

Every line leads with the fact's id, because the follow-up move takes it. The ×2 suffix means the fact was stated more than once — restating a fact reinforces the one already stored rather than filing a copy.

supersede keeps the old fact readable in its journal and stops recalling it; forget deletes outright. Correcting something that changed wants the first; removing something that should never have been stored wants the second.

stats reports what the store holds:

--json emits one JSON object per line, provenance included, for scripting.

Full reference: Memory.


completions

Print a shell completion script. The script wires dynamic completion of agent names: pressing <TAB> after dotagent run, inspect, run-now, logs, install, or uninstall runs dotagent _list-agents (a hidden helper) and lists every manifest currently on disk — no stale baked-in list to refresh.

Arg
Values

SHELL

bash · zsh · fish · elvish · powershell

Dynamic agent-name completion is wired for bash, zsh, and fish. elvish / powershell get subcommand + flag completion only.

Install:

Example:


Exit codes

Code
Meaning

0

Success.

1

Generic failure (manifest invalid, plugin not found, etc.).

124

dotagent run only — the agent timed out (SIGTERM + SIGKILL).

Anything else

dotagent run only — the agent's exit code is passed through.


Environment variables

dotagent reads a small set of env vars for configuration overrides (DOTAGENT_HOME, DOTAGENT_ROOT, DOTAGENT_PLUGIN_PATH, RUST_LOG, OTEL_EXPORTER_OTLP_HEADERS). See env-vars.md for the complete list.

dotagent INJECTS env vars into the agent subprocess (AGENT_NAME, AGENT_TMPDIR, etc.). Same doc.


Last updated

Was this helpful?