This document is the stable contract between the AVM Go CLI and any
external consumer (the TS/JS UI in ui/, shell scripts, CI). The Go CLI
is plumbing only — it never prompts. All input comes from flags or
stdin; all output goes to stdout (success) or stderr (errors, human mode).
Versioning rule: changing a field name in JSON output, removing an error code, or changing exit-code semantics is a breaking change. Adding new fields, codes, or commands is non-breaking.
The root persistent flag --json switches between two output modes.
Any subcommand inherits it.
| Mode | Default | Stream | Format |
|---|---|---|---|
| Human | yes | stdout | column-aligned text, ASCII only |
| JSON | --json |
stdout | indented JSON; errors envelope on failure |
Non-zero exit code still means "the command failed". JSON mode does not change that — it only changes how the failure is described.
| Code | Meaning |
|---|---|
0 |
Success |
| non-zero | Failure. Specific code is the runtime process exit code for avm run; for other commands it is 1. |
avm run [profile] propagates the runtime's own exit code so shell
scripts can branch on it (avm run x; echo $?). When profile is
omitted, the CLI resolves it from AVM preferences.
Every command emits the corresponding internal/app/model value as a
JSON object. Field names are snake_case. Empty optional fields are
omitted (omitempty).
Empty list payloads: when a command's success type is a slice (
[]AgentSummary,[]CapabilityRecord,[]RuntimeCheck, ...) and there are zero items, the JSON value may be either[]ornull. Existing stores generally emit[]; missing/uninitialized backing directories may surface Go's nil slice asnull. Consumers must handle both as an empty list.
| Command | Success payload |
|---|---|
avm list |
[]ProfileListItem |
avm use <profile> |
UseResult |
avm defaults |
PreferencesView |
avm defaults set <profile|runtime|drift> <value> |
PreferencesView |
avm defaults unset <profile|runtime|drift> |
PreferencesView |
avm defaults explain |
ResolutionExplanation |
avm agent create |
Agent |
avm agent list |
[]AgentSummary |
avm agent show <name> |
AgentDetail |
avm agent edit <name> |
Agent |
avm agent delete <name> |
literal null |
avm agent clone <name> --name <new> |
Agent |
avm agent rename <old> <new> |
Agent |
avm run [profile] --preview |
RunPreview |
avm run [profile] |
RunResult |
avm package list |
[]PackageSummary |
avm package show <name> |
PackageDetail |
avm package install <file> |
InstallResult |
avm package uninstall <name> |
literal null |
avm package export <agent> |
ExportResult |
avm package inspect <file> |
PackageDetail |
avm capability discover |
[]CapabilityCandidate |
avm capability list |
[]CapabilityRecord |
avm capability show <id> |
CapabilityRecord |
avm capability import |
ImportCapabilityResult |
avm capability bootstrap |
BootstrapCapabilitiesResult |
avm runtime list |
[]RuntimeCheck |
avm doctor |
DoctorReport |
avm status [agent] |
StatusReport |
avm init |
InitResult (currently human-only; JSON support TBD) |
avm setup |
SetupResult |
avm uninstall --yes |
UninstallResult (TBD as above) |
The model types live in internal/app/model/. JSON tags on the Go
structs are the source of truth for field names. See:
agent.go—Agent,Identity,Instructions,CapabilityRef,RuntimePref,AgentSummary,AgentDetail,RuntimeMappingSummary,FieldMappingSummarypreferences.go—Preferences,ProfilePreference,ProjectPreference,RunIntent,RunResolution,ResolvedRunRequest,UseResult,PreferencesView,ResolutionExplanation,ProfileListItemcapability.go—CapabilityID,CapabilityKind,CapabilitySource,CapabilityRecord,GlobalCapability,CapabilityCandidaterun.go—RunRequest,RunPreview,RunResult,RunRecord,BoundarySummary,MappingStatus,DriftPolicy,DiffEntry,Warningpackage.go—PackageManifest,PackageSummary,PackageDetail,InstallRequest/Result,ExportRequest/Result,ConflictResolutionrequests.go—ImportCapabilityRequest/Result,BootstrapCapabilitiesRequest/Result,SkippedCapabilitydiagnostics.go—DoctorReport,StatusReport,RuntimeCheck,CheckResultsystem.go—InitResult,SetupResult,SetupRuntimeResult,UninstallResult
On failure, JSON mode writes a single envelope to stdout and exits non-zero:
{
"error": {
"code": "AGENT_CONFLICT",
"message": "agent \"alpha\" already exists",
"details": {
"name": "alpha"
}
}
}| Field | Type | Meaning |
|---|---|---|
code |
string (constant) | machine-readable identifier; see §5 |
message |
string | human-readable, may include the offending value |
details |
object | omitted | structured context, code-specific shape |
Human mode prints avm: <message> to stderr instead.
| Code | Used by | details shape |
Meaning |
|---|---|---|---|
AGENT_CONFLICT |
agent create, agent clone, agent rename, package install |
{"name": string} |
An Agent with that name already exists; caller must pick a different name or pass --on-conflict overwrite. |
AGENT_NOT_FOUND |
agent show/edit/delete/clone/rename, run, package export, package uninstall |
{"name": string} |
The named Agent does not exist. |
AGENT_INVALID_NAME |
agent create/edit/clone/rename (when name is non-empty but malformed) |
{"name": string} |
Name failed regex ^[a-z][a-z0-9-]{0,62}$. |
RUNTIME_NOT_FOUND |
run, agent show mapping render |
{"runtime": string} |
Runtime name not registered in the driver registry. |
RUNTIME_AMBIGUOUS |
run |
{"agent": string, "runtimes": [string], "suggested_command": string} |
Agent has multiple runtimes and neither --runtime nor preferences selected one. UI: prompt the user to pick one of details.runtimes and re-issue with --runtime, or persist it with avm use. |
RUNTIME_MISSING |
run |
{"agent": string, "suggested_command": string} |
Agent has no runtimes configured at all and AVM cannot infer one. Caller can pass --runtime or persist one with avm use. |
RUNTIME_BINARY_MISSING |
run (launch spec) |
{"runtime": string, "agent": string} |
Driver reported the runtime binary is not installed or not on PATH. UI: link the user to Doctor for that runtime. |
RUNTIME_PLAN_FAILURE |
run, agent show |
{"runtime": string, "agent": string} |
Driver Plan, Boundary, or LaunchSpec failed before a valid process launch. Missing binaries use RUNTIME_BINARY_MISSING; spawn/wait failures use RUNTIME_PROCESS_FAILURE. |
RUNTIME_PROCESS_FAILURE |
run (spawn/wait) |
{"runtime": string, "agent": string} |
The runtime process failed to spawn or exit cleanly after a valid launch spec (not an exit-code failure; runtime exit codes propagate via RunResult.exit_code). |
DRIFT_DETECTED |
run |
{"agent": string, "runtime": string, "entries": [DiffEntry], "suggested_command": string} |
Managed config drifted from AVM Agent definition and --drift was unset. UI: present entries to user, prompt for keep or discard, re-issue with --drift=<choice>, or persist a choice with avm defaults set drift .... |
DRIFT_MERGE_UNSUPPORTED |
run |
{"agent": string, "runtime": string, "entries": [DiffEntry], "suggested_command": string} |
--drift merge was requested while drift exists, but merge-back into the Agent is not yet implemented. UI: prompt for keep or discard and re-issue with that policy. |
PACKAGE_NOT_FOUND |
reserved for future installed-package registry | {"name": string} |
Named installed package does not exist. |
PACKAGE_INVALID_MANIFEST |
package install/inspect |
{"file": string} or {"path": string} |
Manifest could not be parsed or required fields are missing. |
PACKAGE_CHECKSUM_MISMATCH |
reserved | {"file": string, "want": string, "got": string} |
Capability blob checksum did not match manifest. |
CAPABILITY_NOT_FOUND |
package export, capability import |
{"id": string} for export; {"runtime": string, "kind": string, "name": string} for import |
The referenced capability ID / (runtime,kind,name) is unknown. |
CAPABILITY_CONFLICT |
capability import |
{"kind": string, "name": string, "existing_id": string, "existing_checksum": string} |
A different-content capability with the same (kind,name) already lives in capstore. UI: present existing record and prompt for `--on-conflict skip |
MISSING_INPUT |
many; common for missing --name, profile, --runtime, --yes, --shell |
{"field": string, "hint": string, "suggested_command"?: string} |
A required input was absent. UI: surface hint and re-issue with the field set. agent create/edit raise this with field="runtime" when an Agent would have zero runtime preferences. |
VALIDATION |
several; e.g. unknown --on-conflict value |
varies | Generic validation failure not fitting a narrower code. |
IO_FAILURE |
many | varies | Underlying filesystem / zip / network IO failed. |
INTERNAL_ERROR |
catch-all | varies | An error the CLI/service does not recognise; treat as bug. |
UI policy hints:
AGENT_CONFLICT: prompt rename / overwrite / cancel; re-issue with--on-conflict.RUNTIME_AMBIGUOUS: select fromdetails.runtimes.DRIFT_DETECTED: presentdetails.entries(each is{path, field, reason}), prompt forkeepordiscard.DRIFT_MERGE_UNSUPPORTED: tell the user merge is not yet implemented, then prompt forkeepordiscard.MISSING_INPUT: surfacedetails.hintto the user as guidance for missing input.- All other codes: render
code+messageto the user; no automatic retry.
avm agent create --name alpha --runtime codex # required: --name + at least one --runtime
avm agent create ... --on-conflict overwrite # opt into overwriting an existing agent
avm agent edit alpha --description "new desc" --skill cap_x --skill cap_y
# any list flag (--skill/--mcp/--runtime) replaces
# the whole list. Absent flags keep existing values.
avm agent show alpha --json # read current state for diff/edit flows
avm agent delete alpha --yes # --yes is required (no implicit confirm)
avm list --json # []ProfileListItem
avm use mbti --runtime claude-code --drift discard --json
→ UseResult { active_profile, scope, resolution, next }
avm defaults --json # PreferencesView
avm defaults set runtime claude-code --profile mbti
avm defaults unset drift --profile mbti
avm defaults explain --json # ResolutionExplanation
Preferences live under $AVM_HOME/preferences.yaml. Project-scoped
preferences are keyed by canonical project path inside that same file;
the CLI does not write AVM state into the current project directory.
# Step 1: ask Preview
avm run [profile] --preview --json
→ success: render plan, including any drift entries
# Step 2: launch
avm run [profile] --runtime <r> --drift keep
→ stdout (JSON mode): RunResult; exit code = runtime exit
→ AGENT_NOT_FOUND / RUNTIME_AMBIGUOUS / DRIFT_DETECTED / DRIFT_MERGE_UNSUPPORTED
→ prompt + re-issue
If profile, --runtime, or --drift are omitted, avm run resolves
them from preferences and Agent runtime metadata. RunPreview includes
an optional resolution object so callers can show where each value
came from. Resolved RunRequest objects may include optional
project_path; this is additive and omitted when the caller project is
unknown. When drift exists, --drift keep preserves user-updated
managed files on disk for this run, while --drift discard reapplies
all planned managed files. --drift merge currently fails with
DRIFT_MERGE_UNSUPPORTED.
avm package inspect <file> --json # show what will be written
avm package install <file> # default fails on AGENT_CONFLICT
avm package install <file> --on-conflict {rename|skip|overwrite|cancel}
# Cheap capstore-only read. Does not call into runtime drivers, so it is
# safe for hot UI paths like "show me the names behind these IDs".
avm capability list --json # []CapabilityRecord
avm capability show <id> --json # CapabilityRecord
→ CAPABILITY_NOT_FOUND { "id": <id> } if unknown
# Non-diagnostic runtime listing. Same per-runtime payload Doctor uses
# (RuntimeCheck), without home/PATH/shell-integration noise.
avm runtime list --json # []RuntimeCheck
# Product-level onboarding command used by scripts/install.sh.
avm setup --json
→ SetupResult {
init,
runtimes: [
{ runtime, available, binary, version, imported, skipped, issues }
],
next_steps
}
# Limit capability import to one runtime, or skip it entirely.
avm setup --runtime codex
avm setup --no-capabilities
setup is idempotent. It calls init, probes runtimes, and bootstraps
runtime-global capabilities for available runtimes. Unavailable runtimes and
per-capability import failures are reported in issues / skipped; they do
not abort setup.
# 1. See every capability AVM can find — AVM-managed records plus
# runtime-global discoveries. Imported=true on a runtime-global
# candidate means "already in capstore, no need to import again".
avm capability discover --json
avm capability discover --runtime codex --kind skill
# 2. Import a single runtime-global capability into capstore.
avm capability import --runtime codex --kind skill --name hello --json
→ success: ImportCapabilityResult { id, created, replaced, source }
→ CAPABILITY_NOT_FOUND if the runtime doesn't expose this (kind,name)
→ CAPABILITY_CONFLICT if (kind,name) already exists with different content
→ re-issue with --on-conflict {skip|overwrite}
# 3. First-install bootstrap: import every runtime-global capability
# a runtime exposes. Per-item failures land in `skipped` and never
# abort the run.
avm capability bootstrap --runtime codex --json
→ BootstrapCapabilitiesResult { imported: [...], skipped: [...] }
| Surface | Stability |
|---|---|
| Command names + flag names | Stable. Removing or renaming = breaking. |
| Error codes (table in §5) | Stable. Removing or renaming = breaking. Adding new codes = non-breaking. |
JSON field names on internal/app/model types |
Stable. |
Error details shape per code |
Best-effort stable. Adding new keys = non-breaking; removing is breaking. |
| Exit codes | 0 = success, non-zero = failure. avm run propagates runtime exit code. |
| Human-mode output text | NOT stable. Scripts must use --json. |
- JSON schemas: implicit, derived from Go struct tags in
internal/app/model/. - Error code constants:
internal/app/service/errors.goCode*. - CLI error envelope wrapper:
internal/presentation/cli/root.go::renderError. - Test for JSON envelope (general):
internal/presentation/cli/agent_test.go::TestJSONError_Envelope. - Test for CAPABILITY_CONFLICT envelope:
internal/presentation/cli/capability_test.go::TestCapabilityImport_ConflictEnvelope.
If you change any of these, update this document in the same PR.