Help topics¶
The concept pages of the terminal, word for word: ag help TOPIC prints the same text. They are short on purpose; the guides they point to have the detail.
ag help workflowag help policyag help leasesag help identitiesag help revisionsag help exit-codesag help jsonag help config
workflow¶
Workflow: from an edit to a landed change
A change goes through four independent checks. None implies another:
Mergeable is there one unambiguous result? ag preview PROPOSAL
Validated did trusted checks pass on THAT tree? ag validate run PROPOSAL ...
Approved did enough eligible people approve? ag proposal review PROPOSAL --approve
Integrated was the stream updated atomically? ag integrate PROPOSAL
The everyday loop
ag init --namespace me/project once: creates .ag/ and the stream "main"
ag snapshot -m "message" record the working directory (private until proposed)
ag propose -t "title" publish the snapshot and open a proposal
ag integrate PROPOSAL move the stream; policy decides whether it may
Any unique prefix (6 or more characters) of a proposal id works: `ag integrate prop_8f3a1c`.
When integrate says "Blocked" (exit code 3)
The stream was not changed. The message lists what is missing; provide it and run the SAME command again, it
resumes the same operation:
missing validation ag validate run PROPOSAL --check NAME --key ISSUER -- COMMAND
missing approval ag proposal review PROPOSAL --approve (by someone eligible)
missing provenance ag provenance attach @WORKSPACE --agent NAME
lease token ag integrate PROPOSAL --fencing-token N
When it says "Conflict" or "Denied" (exit code 4)
The stream was not changed and waiting will not help:
conflict ag sync, resolve the marked files, ag snapshot, ag proposal update PROPOSAL
denied the policy forbids it for this actor or change; see `ag help policy`
Working in parallel
ag workspace create NAME --dir ../NAME an isolated directory that starts at the stream head
ag snapshot -m MSG --workspace NAME
ag propose @NAME -t TITLE
ag sync --workspace NAME bring the stream's new head into the workspace
Where am I? `ag status` shows the workspace, your proposals, your leases, your task and the remote.
Is everything healthy? `ag doctor`. What exactly happened? `ag trace`, `ag op show OPERATION`.
More: docs/getting-started.md and docs/tutorials/first-agent-safe-repo.md
policy¶
Policy: what must be true before a change lands
A policy is a JSON document per stream. It is versioned and append-only: `ag policy set FILE` stores a new
version, `ag policy history` lists them, `ag policy show` prints the one in force. Unknown fields and
policies that can never be satisfied are rejected when you set them.
ag policy example > policy.json a document to start from
ag policy set policy.json needs an admin of the current version
ag policy show
You do not have to write the first one: four presets are built in (solo, team, agents, strict).
ag policy preset list who each is for
ag policy preset apply agents store one (or `ag init --preset team`); `--ci-key ci` also requires a signed check
ag policy explain the policy in force in plain language, read from the document itself
The fields
integrators / admins / allowed_authors who may integrate, change policy, author proposals
trusted_issuers identities AND public keys whose attestations count
required_validations checks that must have passed on the exact candidate tree
approvals min, min_for_agents, approvers, forbid_self_approval
protected_paths globs that need extra approvals (min_approvals, approvers)
agent_autonomy auto_integrate, allowed_paths, max_files: the envelope
require_provenance_for_agents agent changes need a valid signed provenance record
max_changed_files deny integrations that touch more files
With no policy stored: any non-agent may integrate; agents cannot integrate on their own and need one human
approval.
What the verdicts mean
Blocked (exit 3) something you can provide is missing: validation, approval, provenance, lease token
Denied (exit 4) the policy forbids it: actor not allowed, a trusted FAIL on this tree, too many files
Validation is bound to the tree: an attestation counts only if it is signed by a trusted issuer, names this
exact candidate tree, and was made under this policy version. Changing the policy, or the stream moving,
means validating again. Approvals are bound to the proposal head: pushing a new head resets them.
`--json` output of `ag integrate` carries stable blocker codes: missing_validation, insufficient_approvals,
changes_requested, missing_provenance, lease, failed_validation, actor_not_allowed, author_not_allowed,
too_many_changed_files.
`ag explain [PROPOSAL]` puts every reason in words and lists the commands that fix it.
Command help: `ag policy --help`. More: docs/guides/policy.md and docs/tutorials/policy-cookbook.md
leases¶
Leases: claiming a part of the tree
A lease claims a path scope (a glob such as `src/auth/**`) for an identity, for a limited time.
ag lease acquire 'src/auth/**' --ttl 30m soft: a signal, never blocks anyone
ag lease acquire 'src/auth/**' --ttl 30m --exclusive enforced at integration; prints a fencing token
ag lease extend LEASE --ttl 30m the token stays the same
ag lease release LEASE
ag lease list [--all]
Soft leases are advisory. Acquiring one over someone else's reports the overlap as a warning.
Exclusive leases are refused while another principal holds an overlapping exclusive lease, and they are
enforced when a change lands: while the lease is active, a change that touches its scope can only be
integrated if its author is the holder AND the integration presents the lease's current fencing token:
ag integrate PROPOSAL --fencing-token 7
Fencing tokens come from one strictly increasing counter. If a holder stalls, its lease expires and someone
else acquires the scope with a higher token; when the stalled holder wakes up, its old token no longer passes.
Expiry never deletes changes that were already proposed: it only stops a stale holder from landing them into
a scope that is now someone else's.
`ag integrate` reports a lease problem as a blocker (exit 3): "exclusive lease on 'src/**' held by ...
(held_by_other / stale_or_missing_token)". Look with `ag lease list`, wait or ask the holder, then pass the
current token.
Finishing an agent run (`ag agent finish RUN`) releases that run's leases; `ag agent reap` abandons runs with
no recent heartbeat and releases theirs.
`ag predict` shows overlapping work before it collides; `ag preview PROPOSAL` gives the definitive merge answer.
More: docs/guides/agents.md
identities¶
Identities: who is acting
An identity is `kind:name`. The kind is `user`, `agent`, `service` or `system`:
user:alice a person
agent:refactor-bot a registered agent (see `ag agent register`)
agent:refactor-bot#run_7f3a... one run of that agent (`ag agent start` prints it)
service:ci a trusted validation service
Rules that compare people (self-approval, lease ownership) compare the principal, the part before `#`, so an
agent cannot approve its own work by starting a new run.
Which identity `ag` uses, first match wins
1. --as IDENTITY
2. the AG_IDENTITY environment variable
3. the `identity` setting: ag config set identity user:alice
4. user:$USER
`ag config get identity` shows the result and where it came from; `ag status` and `ag doctor` show it too.
In local mode an identity is a label, not authentication: anyone who can run `ag` in the repository can claim
any identity. Against clients you do not control, use `ag-server`, which authenticates every request and
enforces policy there (docs/architecture/security.md).
Agents: `ag agent register NAME`, then `ag agent start NAME` prints the identity to use for the run:
export AG_IDENTITY='agent:NAME#run_...'
More: docs/concepts.md and docs/guides/agents.md
revisions¶
Revisions: naming a point in history
Wherever a command takes a revision you can use:
@ the head of the default workspace
@NAME the head of workspace NAME
main a stream name (its current head)
prop_8f3a1c a proposal id (its proposed head); a unique prefix of 6+ characters is enough
e55836a9d643 a changeset id, or a unique prefix of 6+ hex characters
optionally followed by `~N` (the N-th first-parent ancestor) and `^N` (the N-th parent):
main~2 two changesets before the head of main
prop_8f3a1c^2 the second parent of the proposed head (for a merge)
`REV:PATH` names a file at a revision:
ag cat main:src/lib.rs
ag cat @:README.md the workspace head
ag blame main:src/lib.rs
Without a revision, most commands use the head of the default workspace, or the default stream when the
workspace has no snapshots yet.
If a name cannot be resolved, `ag stream list`, `ag proposal list` and `ag log` show what exists.
More: docs/concepts.md
exit-codes¶
Exit codes: what a script or an agent can rely on
0 success
1 error: bad input, a missing object, I/O; the message is on stderr (with --json, as one JSON line)
2 usage error: unknown command or option, a missing argument (printed by the argument parser)
3 blocked: something you can provide is missing (an approval, a validation, a lease token);
provide it and run the same command again. The stream was not changed.
4 refused: a conflict, a policy denial, or an invalid request. Waiting will not help;
the stream was not changed.
Which commands use 3 and 4
ag integrate, ag op resume, ag remote integrate 0 integrated, 3 blocked, 4 conflict / denied / invalid
ag remote prepare 0 nothing blocks it, 3 blocked, 4 conflict or problem
ag preview 0 clean, 4 conflict or problem
ag sync, ag pull 0 up to date, 4 the merge left conflict markers
ag validate run 0 the check passed, 4 it failed (the failure is recorded)
ag queue run 0 everything integrated, 3 something is blocked or failed
ag predict 0 no overlapping work, 3 overlaps found
Commands that find a problem with the repository itself exit 1: ag verify, ag audit, ag recover (errors),
ag backup (unhealthy copy), ag doctor (a check failed).
Code 4 is the verdict on a change, not a general "conflict" or "not found" status: asking for something that does
not exist ("not found: proposal 'x'", "no stream 'x'") or creating something that already exists ("already
exists: stream 'main'") is an ordinary error and exits 1. A script that must tell those apart reads
`error.message` of the JSON error; the exit code stays 1 so that scripts written against it keep working.
Branch on the code, not on message text. With --json, `ag integrate` also returns `outcome.status`
and stable `blockers[].code` values (see `ag help policy`).
Output goes to stdout; errors, hints and diagnostics go to stderr. `--quiet` removes confirmations and hints,
never errors.
More: docs/reference/cli.md
json¶
Machine-readable output: --json
Every command that reports something accepts the global `--json` flag (before or after the command):
ag --json log
ag integrate PROPOSAL --json
Standard output then holds exactly one JSON document and nothing else; text, hints and diagnostics go to
standard error. Errors become one JSON line on standard error:
{"error":{"message":"not found: proposal 'x'","hint":"list proposals with `ag proposal list` ...","exit_code":1}}
Commands that print file contents or a template are the exceptions, because their output is the data: `ag cat`,
`ag remote cat`, `ag policy example`, `ag completions`, `ag man`. `ag help` prints text.
A command that never ends is the other exception to "one document": `ag remote events --follow --json` prints one
compact JSON object per line (NDJSON), so that a script can read it line by line.
Stability: the shapes are version 1 (`ag version --json` prints `json_schema`). Within a version, keys are only
ever added, never removed, renamed or retyped, so ignore keys you do not know. The shapes are checked by golden
tests; the list of commands and their shapes is docs/reference/cli-json.md.
Typical use in a script:
id=$(ag --json propose -t "Fix typo" | jq -r .id)
ag integrate "$id" --json; rc=$? # branch on the exit code, see `ag help exit-codes`
config¶
Configuration: files and environment
`ag config list` shows every effective setting and where it came from.
Files
.ag/config.json the repository: namespace, default stream, storage backends (set by `ag init`)
.ag/remotes.json remotes (managed with `ag remote add|list|remove`); never holds a secret
.ag/credentials/NAME a remote's token stored with --token-stdin (mode 0600)
.ag/keys/ agent and issuer private keys (mode 0600)
.agignore files the working directory scan skips (one glob per line)
user file personal defaults: `ag config path` prints where; keys: identity, color
ag config set identity user:alice store your default identity
ag config set color never
ag config unset color
Precedence, highest first
identity --as, AG_IDENTITY, the user file, user:$USER
colour --color WHEN, the user file (when not auto), NO_COLOR, CLICOLOR_FORCE, TERM=dumb /
CLICOLOR=0, then whether the output is a terminal
repository -C DIR / --repo DIR, else the nearest parent directory that contains .ag/
credential the variable named by the remote's token_env, the remote's token file,
AG_TOKEN_<REMOTE>, AG_TOKEN, then the login saved by `ag login` (credentials.json beside the user file)
Environment variables
AG_IDENTITY the acting identity
AG_CONFIG path of the user file
AG_KEY_DIR where private keys live instead of .ag/keys
AG_RUN the agent run a server credential acts for
AG_TOKEN credential for remotes; AG_TOKEN_<REMOTE> for one remote (upper-case, non-letters as _)
NO_COLOR, CLICOLOR, CLICOLOR_FORCE, TERM colour (https://no-color.org)
Storage backends are configured in `ag init --storage FILE` (PostgreSQL URL and S3 credentials are read from
the environment variables that file names; see docs/operations/postgres-and-s3.md).
More: docs/reference/configuration.md