cli

package
v0.0.0-...-89e6720 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: Apache-2.0 Imports: 43 Imported by: 0

Documentation

Overview

Package cli assembles the command-line surface. Slice 1.6 shipped `awf validate <path>`; slice 2.5 added `awf run`; slice 2.6 added `awf resume`; Phase 3 added `signal` / `pause` / `cancel`; slice 4.5 added `--backend {auto,native,docker,fake}` on `awf run` + log-driven backend selection on `awf resume`. Later phases add `inspect` / `trace` / `ls` (Phase 6). The entry point is Runner.Run, not init() or a package-level CLI framework, so tests drive the full surface with bytes.Buffer for IO and an int return for the exit code — no real os.Exit ever called from package code.

Runner holds the production-vs-test seams: Backend (test injection; production leaves it nil and the run/resume subcommands construct via the package-private newBackend helper when Backend is nil — see cli/backend.go for the kind switch) and IDGen.

Index

Constants

View Source
const (
	ExitOK        = 0 // success: validation produced zero error-severity diagnostics, OR run terminated ok
	ExitInvalid   = 1 // `awf validate` produced ≥1 error-severity diagnostic (also returned by `awf run` if its pre-run validation finds errors)
	ExitUsage     = 2 // usage: bad args/flags, unreadable file, loader-stage failure (parse/path-escape/missing-compose), or a precondition refusal (run-id collision, digest/pin/runtime drift, terminal, capability)
	ExitRunFailed = 1 // `awf run` completed but the run terminated as retryable_failure / permanent_failure
	ExitInfra     = 3 // environment/setup failure AWF owns: blob store, backend/daemon client, container create/restore, run-dir/log I/O, live-home, run-lock
)

Exit codes are part of the CLI contract — downstream tooling (CI, IDE extensions, scripts) will branch on them. Locked by tests.

Note ExitInvalid and ExitRunFailed share the numeric value 1 — both mean "the input was structurally fine but the operation didn't succeed." We keep them as separate Go constants so each subcommand can name its own failure mode for readability.

The 2-vs-3 split is "whose artifact failed". Anything about the user-supplied workflow file or arguments — bad flags, an unreadable/parse-failing file, a path escape, a missing compose, or a precondition the user fixes by editing args (run-id collision, digest mismatch, pin/runtime drift, terminal refusal, capability mismatch) — is ExitUsage (2). Anything about the environment AWF itself owns and sets up — the blob store, the backend/daemon client, container create/restore, the run-dir/log I/O, the live-home, or the run-lock — is ExitInfra (3). 2 = "your input is wrong"; 3 = "my environment is broken".

Variables

View Source
var ErrRunLockHeld = runlock.ErrHeld

ErrRunLockHeld is returned by acquireRunLock when a live process holds the lock.

Functions

func Run

func Run(args []string, stdout, stderr io.Writer) int

Run is the top-level CLI entry point — constructs the production Runner and delegates. cmd/awf wraps the returned int with os.Exit.

Slice 4.5: Backend is intentionally left nil; the run / resume subcommands construct it on-demand via newBackend (chosen by --backend flag on run; read from the log's run.started.Backend on resume). This defers docker-client construction to the moment we know the user actually wants Docker — important because a docker.New failure (Docker daemon not running) shouldn't crash `awf validate` or `awf help`.

Types

type ErrContainerRequired

type ErrContainerRequired struct {
	Ref string
}

ErrContainerRequired is returned at run start (and resume) when an agent step omits `container:` but its resolved adapter is NOT Containerless (a CLI-wrapping adapter like claude/droid/goose needs a container to exec in). Permanent: re-running won't fix a missing container. This is where the missing-container check for non-containerless adapters lives, because the structural validator (ir/) is registry-free and cannot know an adapter's Containerless capability.

func (*ErrContainerRequired) Error

func (e *ErrContainerRequired) Error() string

type ErrPersistentSessionContinuesTarget

type ErrPersistentSessionContinuesTarget struct {
	StepID   string
	TargetID string
	Ref      string
}

ErrPersistentSessionContinuesTarget is returned when a `continues:` edge targets a live/persistent adapter. Live transcripts stay provider-owned in this slice, so engine-threaded conversation context cannot be reconstructed from that predecessor without a separate design.

func (*ErrPersistentSessionContinuesTarget) Error

type ErrPersistentSessionGateEvaluate

type ErrPersistentSessionGateEvaluate struct {
	StepID string
	Ref    string
}

func (*ErrPersistentSessionGateEvaluate) Error

type ErrRuntimeDrift

type ErrRuntimeDrift struct {
	Ref       string
	Container string
	Recorded  string
	Current   string
}

ErrRuntimeDrift is the hard-error returned when a resumed run's agent runtime version no longer matches what was persisted in run.started. Mirrors the spec §8 pinning invariant (the existing definition-digest hard-error class); resume cannot adapt to a changed binary.

Phase 5 slice 5.1: scoped per (Ref, Container) pair, since different containers may have different `claude` binaries on PATH (decision 5).

func (*ErrRuntimeDrift) Error

func (e *ErrRuntimeDrift) Error() string

type ErrThreadedRequired

type ErrThreadedRequired struct {
	StepID string // the step that declares continues:
	Ref    string // its uses: ref (the non-threaded adapter)
}

ErrThreadedRequired is returned at run start (and resume) when an agent step declares `continues:` (engine-owned conversation threading) but its resolved adapter does NOT report Caps.Threaded — i.e. the adapter cannot prepend an engine-supplied AgentInvocation.Thread, so threading would silently no-op. Permanent: re-running won't make a non-threaded adapter understand a thread. This lives in cli/ (not ir/) because the structural validator is registry- free and cannot know an adapter's Threaded capability — the same constraint that puts the Containerless guard (ErrContainerRequired) here.

func (*ErrThreadedRequired) Error

func (e *ErrThreadedRequired) Error() string

type Runner

type Runner struct {
	// Backend is the container.Backend the run subcommand passes to
	// engine.LocalDispatcher. Production: left nil — the run/resume
	// subcommands construct on-demand via newBackend (cli/backend.go),
	// choosing a concrete fake/docker/native backend from the --backend flag
	// after resolving auto. Tests: inject any container.Backend (typically
	// a pre-programmed *container.Fake) to short-circuit construction
	// via (*Runner).resolveBackend.
	Backend container.Backend
	// Resolver is the agent.Adapter registry the CLI uses to look up `uses:`
	// refs at run-start and resume. Test-injection point:
	// tests assign a *agent.Registry populated with agent/fake adapters and
	// drive workflows that exercise the version-pinning + drift-check paths.
	//
	// Production leaves this nil. cli/run.go and cli/resume.go then build a
	// fresh *agent.Registry for the current invocation from --agent-env/default
	// env allowlists plus workflow env: declarations, and pass that registry
	// down without caching it here. This matters for long-lived Runner values:
	// imported workflow roles and env allowlists must not leak across runs.
	//
	// Workflows that don't reference any `uses:` step (every Phase 2-4
	// fixture) run unaffected — they hit zero Lookup calls regardless of
	// what Resolver is.
	Resolver agent.Resolver
	// AgentEnv is the env-var allowlist the CLI uses to construct the
	// production *agent.Registry (when Resolver is nil). Slice 5.3
	// test-injection point: tests assign []string{"ANTHROPIC_API_KEY"} to
	// drive cli/agent_registry.go's buildAgentRegistry without the
	// os.Setenv dance that --agent-env-via-flag would require.
	//
	// When Resolver is non-nil, AgentEnv is ignored (the test has injected
	// a fully-constructed Registry, bypassing buildAgentRegistry).
	// Production: cli/run.go reads --agent-env, splits to []string, and
	// passes here BEFORE calling buildAgentRegistry.
	AgentEnv []string
	// AgentEventTap is the io.Writer agent.AgentEvent live-tap lines are written
	// to during dispatch. Test-injection point: tests assign a bytes.Buffer to
	// capture the rendered lines, or io.Discard to silence. Production wiring
	// in cli/execute.go (Task 11) defaults to stderr.
	//
	// nil disables the live tap entirely (the dispatcher's writeAgentEventTap
	// becomes a no-op). Workflows without any `uses:` step (Phase 2-4
	// fixtures) are unaffected — the tap never fires.
	AgentEventTap io.Writer
	// IDGen mints run ids. Production: clock.CryptoIDGen{}. Tests: a
	// seeded *clock.Fake so run dir paths are reproducible.
	IDGen clock.IDGen
	// BrokerOptions are passed to signal.NewBroker when the run/resume
	// subcommands construct a broker. Slice 3.5 test-injection hook: tests
	// pass signal.WithPollInterval(time.Millisecond) for fast polling; the
	// production cli.Run constructor leaves this nil (defaults to 100ms).
	BrokerOptions []signal.BrokerOption
	// Stdin is the reader `awf run --input-file -` consumes for stdin-supplied
	// run input (S7). Tests inject a strings.Reader; production leaves it nil and
	// the run subcommand falls back to os.Stdin (see (*Runner).stdin). Read ONLY
	// on the `--input-file -` path — nil everywhere else.
	Stdin io.Reader
}

Runner holds the dependencies that vary between production and tests. The zero value is unusable — callers (production: cli.Run; tests: direct construction) populate every field.

func (*Runner) Run

func (r *Runner) Run(args []string, stdout, stderr io.Writer) int

Run dispatches a single CLI invocation using this Runner's dependencies.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL