Skip to content

feat: agent-facing --timeout flag and expected-duration schema hints #129

Description

@rshade

Overview

Add a uniform --timeout flag auto-mounted on every ax-go command, mapping to
context.WithTimeout on the command context, plus optional per-command
expected-duration metadata surfaced in __schema. A timeout maps to the
existing deterministic exit code for deadlines (exit 3, network/timeout).

User Story

As an agent that plans retries and aborts around time bounds, I want a uniform
--timeout on every command and an expected-duration hint in __schema, So
that I can bound a call, get a deterministic timeout error when it overruns, and
size my own deadlines before I even invoke.

Problem Statement

Every deadline in ax-go today is internal (telemetry shutdown, Loki flush, MCP
transport shutdown) — none is exposed to the operator. Agents plan retries and
aborts around time bounds, so the absence of a uniform, machine-discoverable
timeout is a gap. A per-command expected-duration hint lets an agent choose a
sane deadline without trial-and-error. This is the CLI expression of the context
discipline ax-go already enforces internally.

Proposed Solution

Auto-mount a persistent --timeout duration flag next to the other primitives.
When set, wrap the command context with context.WithTimeout in the persistent
pre-run so cancellation propagates to all downstream work. When the deadline
fires, the resulting context.DeadlineExceeded maps to exit 3 through the
existing ErrorExitCode path (contract/error.go already maps
context.DeadlineExceeded → ExitNetwork). Separately, let authors declare an
optional expected-duration per command, reflected into __schema as additive
metadata.

Technical Approach

  • Add FlagTimeout = "timeout" to internal/cli/cli.go, mounted as a duration
    flag in prepareCommand (execute.go).
  • In wrapPersistentPreRun (execute.go), when --timeout > 0, derive a child
    context via context.WithTimeout and set it on the command; ensure the cancel
    func is invoked (defer/cleanup) so no context leaks.
  • Confirm the deadline → exit-3 mapping via contract.ErrorExitCode and add a
    test pinning it; surface a structured timeout envelope on stderr.
  • Expected-duration: an author declaration (via cmd.Annotations + a helper,
    parallel to F2's side-effect declaration) reflected through
    internal/schema/schema.go into an additive CommandSchema field.

Files Likely Affected

  • internal/cli/cli.go - FlagTimeout constant + duration-flag mount helper
  • execute.go - mount --timeout; apply context.WithTimeout in wrapPersistentPreRun; ensure cancel is called
  • contract/error.go - reuse existing DeadlineExceeded → ExitNetwork mapping; ensure a structured timeout envelope path
  • internal/schema/schema.go + schema/schema.go - additive expected-duration field on the command schema
  • README.md, examples/integration/ - document --timeout and demonstrate an expected-duration hint

Acceptance Criteria

  • --timeout is auto-mounted on every ax-go CLI and appears in __schema
  • A command exceeding --timeout terminates with exit 3 and a structured error envelope on stderr
  • The derived context cancel func is always invoked (no context leak; passes -race)
  • Authors can declare an expected duration; it surfaces in __schema as additive metadata
  • Tests pass with adequate coverage (per AGENTS.md testing-first discipline)
  • Documentation updated (README + integration example)

Out of Scope

  • Per-command default timeouts (a command-author policy layer) — only a uniform operator-supplied flag plus a hint here.
  • Retry/backoff orchestration — the envelope already carries retryable/retry_after_seconds (ax.Error recovery/remediation fields (amend ADR-0002) #27); F9 only bounds the call.

Technical Notes

Additive (Constitution XI): a new auto-mounted flag and a new optional schema
field GAIN surface only. The timeout→exit-3 mapping is the already-fixed
contract (contract/error.go maps context.DeadlineExceeded → ExitNetwork), so
no exit-code contract changes. Expected-duration metadata lives in the
import-isolated schema/contract packages. context.Context discipline: the
derived context must be the command context first parameter and its cancel must
be deferred. Routes through Spec Kit. Output stays byte-identical deterministic
(the timeout envelope carries no wall-clock timestamp in its deterministic
fields).

Testing Strategy

  • Unit tests: --timeout triggers cancellation and exit 3; unset leaves behavior unchanged
  • -race test confirming no context/cancel leak
  • Golden-file tests: --timeout in __schema and expected-duration metadata reflection
  • Exit-code mapping test pinning DeadlineExceeded → 3

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort/mediumMedium effort - half day to 1 dayenhancementNew feature or requestroadmap/futureFuture milestones - Future Vision

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions