Skip to content

Latest commit

 

History

History
299 lines (247 loc) · 16.1 KB

File metadata and controls

299 lines (247 loc) · 16.1 KB

AVM CLI Protocol

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.

1. Output modes

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.

2. Exit codes

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.

3. Success output (JSON mode)

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 [] or null. Existing stores generally emit []; missing/uninitialized backing directories may surface Go's nil slice as null. 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.goAgent, Identity, Instructions, CapabilityRef, RuntimePref, AgentSummary, AgentDetail, RuntimeMappingSummary, FieldMappingSummary
  • preferences.goPreferences, ProfilePreference, ProjectPreference, RunIntent, RunResolution, ResolvedRunRequest, UseResult, PreferencesView, ResolutionExplanation, ProfileListItem
  • capability.goCapabilityID, CapabilityKind, CapabilitySource, CapabilityRecord, GlobalCapability, CapabilityCandidate
  • run.goRunRequest, RunPreview, RunResult, RunRecord, BoundarySummary, MappingStatus, DriftPolicy, DiffEntry, Warning
  • package.goPackageManifest, PackageSummary, PackageDetail, InstallRequest/Result, ExportRequest/Result, ConflictResolution
  • requests.goImportCapabilityRequest/Result, BootstrapCapabilitiesRequest/Result, SkippedCapability
  • diagnostics.goDoctorReport, StatusReport, RuntimeCheck, CheckResult
  • system.goInitResult, SetupResult, SetupRuntimeResult, UninstallResult

4. Error envelope (JSON mode)

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.

5. Error codes

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 from details.runtimes.
  • DRIFT_DETECTED: present details.entries (each is {path, field, reason}), prompt for keep or discard.
  • DRIFT_MERGE_UNSUPPORTED: tell the user merge is not yet implemented, then prompt for keep or discard.
  • MISSING_INPUT: surface details.hint to the user as guidance for missing input.
  • All other codes: render code + message to the user; no automatic retry.

6. Common request patterns

Agent CRUD

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)

Daily profile defaults

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.

Run flow (UI two-step)

# 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.

Package install flow

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}

Capability resolve flow (UI: render IDs an Agent already references)

# 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

Runtime picker flow (UI: pick a runtime when creating an Agent)

# Non-diagnostic runtime listing. Same per-runtime payload Doctor uses
# (RuntimeCheck), without home/PATH/shell-integration noise.
avm runtime list --json                         # []RuntimeCheck

First-run setup flow

# 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.

Capability discover / import flow

# 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: [...] }

7. Stability guarantees

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.

8. Where the contract is enforced

  • JSON schemas: implicit, derived from Go struct tags in internal/app/model/.
  • Error code constants: internal/app/service/errors.go Code*.
  • 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.