For agents
Every command takes --json. With --json, or when stdout or stdin is not a terminal, the CLI prints no colour, no spinner and no prompt, so an agent never blocks on stdin. Human warnings go to stderr; in JSON mode they travel inside the data instead and stderr stays quiet.
The envelope
Section titled “The envelope”stdout carries exactly one JSON object per command (except watch --json --follow, below):
{"v":1,"ok":true,"data":{"intent":{"id":"i269d1c348e","status":"open"}}}{"v":1,"ok":false,"error":{"code":"not_logged_in","message":"not logged in; run 'wayglass login --worker <url>' or set WAYGLASS_URL and WAYGLASS_KEY"}}v changes only for breaking changes. data is present only when ok is true, error only when it is false. Branch on error.code, not on the message.
Exit codes
Section titled “Exit codes”| Exit | Meaning |
|---|---|
0 |
OK. After propose: the change is approved, or waiting for a reviewer agent |
1 |
Error |
2 |
Usage error (bad flag, missing argument, placeholder reason) |
3 |
Blocked by policy (or the decision was rejected) |
4 |
A person must act |
propose exits with 3 or 4 after printing a successful envelope (ok: true); the exit code carries the routing, the envelope carries the details. approve and reject exit 0 whenever the verdict was recorded.
Error codes
Section titled “Error codes”Codes the CLI sets itself:
| Code | Meaning |
|---|---|
usage |
Bad arguments or flags (exit 2) |
not_logged_in |
No Worker URL or key |
not_a_repo |
Not inside a git repository |
not_initialized |
No .wayglass/config.yaml; run wayglass init or set WAYGLASS_PROJECT |
no_intent |
No intent given and none current (exit 2) |
not_pushed |
HEAD is not what the intent last pushed; run wayglass push |
no_token |
The Worker did not mint a fork token and WAYGLASS_FORK_TOKEN is unset |
no_why |
why found no note and no recorded decision |
policy_invalid |
The local .wayglass/policy.yaml does not parse |
stack_unsupported |
declare --on reached a Worker that does not stack intents |
land_conflict |
The approved change conflicts with the trunk; nothing was pushed |
repropose_required |
Merged onto the trunk, the change touches files the decision did not cover; nothing was pushed |
trunk_moved |
The trunk moved during the land; nothing landed, run land again |
commit_missing |
The approved commit is not in this checkout |
wrong_worker |
A session URL points at a Worker you are not logged in to |
no_session |
session show found no forked session in this checkout |
canceled |
Interrupted |
Errors from the Worker keep the Worker’s code, for example unauthorized, forbidden, not_found, conflict and bad_request.
Environment variables
Section titled “Environment variables”| Variable | Effect |
|---|---|
WAYGLASS_URL, WAYGLASS_KEY |
Worker URL and key; override the stored login |
WAYGLASS_AGENT |
Your name in the audit log (default user@host). Give each agent its own |
WAYGLASS_PROJECT |
Project name; overrides .wayglass/config.yaml, or stands in for it |
WAYGLASS_INTENT |
Intent for push, propose, land when no argument is given |
WAYGLASS_FORK_TOKEN |
Fallback fork token, used only if the Worker cannot mint one |
WAYGLASS_CONFIG_DIR |
Where credentials.yaml lives (default ~/.config/wayglass) |
A loop for an agent
Section titled “A loop for an agent”export WAYGLASS_AGENT=claude-code-1
# 1. Say what you will do and why. Read data.overlaps before you start.wayglass intent declare --json \ --goal "Add rate limiting to login" \ --reason "Brute-force attempts in the audit log" \ --path "web/login.ts" --path "worker/src/limits/**"
# 2. Work, commit with the trailer, push. Read data.overlaps[].replan.git commit -am "feat(login): rate-limit by IP
Brute-force attempts in the audit log.
Wayglass-Intent: i269d1c348e"wayglass push --json
# 3. Ask policy.wayglass propose --jsoncase $? in 0) ;; # approved, or pending_agent: check data.status 3) exit 3 ;; # blocked: change the code, do not retry as is 4) exit 4 ;; # a person must act: stop and report data.nextesac
# 4. Land once data.status is "approved".wayglass land --jsonRules worth following:
- Declare every path you expect to touch. Files outside the declared paths come back in
proposeasundeclared_paths, and other agents’ overlap checks could not see them coming. - On exit 4, stop. Report
data.decision.id,data.decision.reasonanddata.nextto whoever started you. Do not try to approve your own change; the Worker refuses it. - When
proposereturnsstatus: "pending_agent", the change waits for a reviewer agent. It is not approved yet, andlandrefuses until it is. - After
land,data.verifiedis true when the Worker confirmed the trunk holds exactly the approved change.
Reviewer agents
Section titled “Reviewer agents”agent_review decisions wait for an agent other than the author. A reviewer agent finds them with:
WAYGLASS_AGENT=claude-reviewer wayglass review --for agent --json{"v":1,"ok":true,"data":{ "project": "acme-web", "sample": false, "queue": [ { "decision": { "id": "d4b1e2c3a09", "intent_id": "i269d1c348e", "commit": "9f3c2a1...", "tags": {"areas": ["web"], "data_classes": [], "risk": "low", "jurisdiction": "us", "...": "..."}, "matched_rule": "rules[5]", "action": "agent_review", "status": "pending_agent", "reason": "Low risk; second agent checks intent vs diff.", "approvals_needed": 1, "approvals": [], "files": [{"path": "web/login.ts", "added": 30, "deleted": 4}], "created_at": "2026-10-13T18:20:11Z" }, "intent": { "id": "i269d1c348e", "agent": "claude-code-1", "goal": "Add rate limiting to login", "reason": "Brute-force attempts in the audit log", "declared_paths": ["web/login.ts"], "actual_paths": ["web/login.ts"], "fork": "acme-web--i269d1c348e", "status": "proposed", "jurisdiction": "us", "created_at": "2026-10-13T18:02:40Z" } } ]}}The reviewer reads the diff (the intent’s fork, or git show <commit> from a clone), checks it against the intent’s goal and reason, and decides with a reason that says what it checked:
wayglass approve i269d1c348e --json --reason "Diff matches the goal; limiter keyed by IP, tests cover the lockout window"wayglass reject i269d1c348e --json --reason "Also changes session expiry, which the intent does not mention"review --json without --for agent lists the person’s queue: human_approve, multi_approve and quarantine decisions still waiting. If data.warnings is present, the Worker could not show everything, and an empty queue does not mean nothing waits.
Watching as an agent
Section titled “Watching as an agent”wayglass watch --json prints one snapshot of the project: counters, intents, overlaps, decisions and recent audit events. Add --follow to keep polling and print one envelope per line (NDJSON) whenever something changes, until interrupted. Poll errors become error envelopes on their own line, and polling continues with backoff. --interval sets the poll interval (default 2s).
Tokens
Section titled “Tokens”The fork token from intent declare --json (data.token.value) is the only token an agent ever sees, and only if it asks for JSON. It is a write token for that intent’s fork and nothing else, valid for 15 minutes. You do not need to keep it: push gets a fresh one from the Worker each time. If you do use it with plain git, pass it as a header through the environment, never in the remote URL or on disk:
GIT_CONFIG_COUNT=1 \GIT_CONFIG_KEY_0=http.extraHeader \GIT_CONFIG_VALUE_0="Authorization: Bearer $TOKEN" \git fetch "$FORK_REMOTE"