Releases: valbaudo/awf
Release list
awf v0.7.0
A passed gate is transparent to its generate: subtree. The accepted attempt's
typed outputs now leave the gate, so a generate → evaluate → repair loop can bind
its result straight to workflow outputs: — closing the gap that forced gated
extraction to round-trip through output_artifact or be rebuilt outside AWF.
Added
- Gate typed-output forwarding.
{{ step.<id>.<field> }}for a producer in a
gate'sgenerate:block now resolves to the gate's accepted attempt from
anywhere a normal step reference works: workflowoutputs:, a downstream
step'srun:/input:/with:, anif:condition, and the
map → gate → reducetyped fan-in (a gate-nested body producer contributes its
accepted attempt's typed outputs toaggregate.json, alongside its files). This
generalizes the prior files-only forwarding to typed outputs. The evaluator's
verdict stays gate-internal — read it with{{ evaluate.<field> }}from inside
the gate only.examples/awf-llm-gate-extract/workflow-typed.yamldemonstrates
a gated extraction bound directly to workflowoutputs:.
Changed
AWF5003restated. It now covers the two cases that remain rejected: a gate
evaluator step referenced from outside the gate, and a gate nested inside a
mapbody referenced directly from outside the map (its accepted attempts fan
in throughreduce:). A gate-nested-in-map producer referenced from outside the
map now reportsAWF5003rather thanAWF5002. No previously valid workflow
changes behavior — this is a pure widening: workflows that were rejected for
referencing a passed gate'sgenerate:output now validate.
Fixed
- Gate integrity is enforced at every layer. The generate-only rule (only
generate:producers forward; the evaluator's verdict stays internal) holds
identically in validation (ir.blockingScope), scalar resolution
(stepRuntimePath), and artifact resolution (passedGateArtifactRuntimePath),
so the scalar and file reference rules cannot drift apart. No IR field, event,
or fold arm was added: the definition digest is unchanged for every existing
workflow, and resume is unaffected.
awf v0.6.0
Ownership of the host↔container boundary. Artifacts a step produces can now be
materialized onto the host by name, agent CLIs stay runnable and stay logged in
under the native sandbox, and the documentation stops describing an MCP input
channel that never existed.
Added
awf outputs --dest <dir>. Materializes a committed step'soutput_files
onto the host, written by theawfprocess itself and therefore owned by the
invoking user — no root-owned writeback, nochownpass. Each declared
container path is mirrored under<dir>with its leading separator stripped,
confined byos.Root: a..component or a symlink that would escape the
destination is refused, while an absolute container path is contained rather
than rejected. Every destination is resolved before the first byte is written,
so a colliding or unusable path fails the command without leaving a partial
tree.
Fixed
- Agent token refresh survives the native sandbox. Credential directories
were re-bound read-only, so an OAuth refresh written during a run was
discarded when the sandbox tore down its mount namespace — a silent logout
days later. The per-agent directories (~/.claude,$CODEX_HOMEor
~/.codex,~/.factory,~/.config/goose) are now writable for agent
invocations; the bare~/.configcatch-all stays read-only, so a step never
gains write access to the wider XDG tree. - Agent CLIs are executable under the native sandbox. Read+execute is now
granted for~/.local(where~/.local/binentries and the~/.local/lib
or~/.local/sharetargets they symlink to live) and/opt(where node and
npm global prefixes land, including the real binary behind a
/usr/local/binshim). Without them a step died with an opaque exit126
or127instead of a named error. - The ignored-image warning names the remedy.
--backend nativeon a
workflow that declares a container image now points at--backend auto, the
default that routes such a workflow to Docker, instead of only stating that
the image was ignored.
Security
- The writable credential grant refuses any candidate that is the home
directory or an ancestor of it.$CODEX_HOMEis environment-derived, and a
broad value would otherwise have been re-bound read-write over the sandbox's
$HOMEtmpfs, restoring the real home —~/.sshincluded — inside the
sandbox. - The writable credential and tool-prefix grants apply only to an agent adapter
invoking its own CLI. A workflowrun:step receives neither, and keeps
exactly the read-only credential baseline it had before.
Changed
- MCP is documented as what it is. The format reference advertised
with: { mcp_servers: ... }as an input channel, and the runtime design
claimed the Claude adapter mappedmcp://refs to a harness config. No
adapter ever accepted such a key — each validateswith:against a strict
allowlist and rejects an unknown key at run start — and no--mcp-configwas
ever emitted. The claims are removed rather than the rejection. In their place
the reference documents the arrangement that already works: run an
HTTP-transport MCP server as a digest-pinned Compose service and pass the
agent its URL, inheriting pinning, health-gated readiness, reconstruction from
the recipe on resume, and teardown with the run.with:remains opaque and
adapter-validated; no format field was added or removed. - Compose bind-mount ownership is documented.
awfnever bind-mounts a host
directory, so root-owned files on the host can only originate in an author's
own Compose file. The manual now records the fixes at that source —user:on
the service, a rootless engine, or an idmapped mount — and why those are
preferable to:Uor--userns-remap. - The sanctioned agent-CLI install prefixes are documented under
--backend native, with the exit-126failure they explain.
awf v0.5.2
A documentation-only patch release adding a clean-room Claude Code subscription
readiness path. There are no runtime or workflow-format changes.
Added
- Claude Code gated readiness example. A public workflow invokes the host
Claude Code CLI as a black box from one AWF agent step, validates its typed
output, and passes it through an independent deterministic gate. Generator
and evaluator retries plus the gate attempt limit are each set to one, so AWF
performs no orchestration-level reruns. The accompanying guide documents
privateclaude setup-tokenhandoff, the explicit native backend, the
macOS-safe absolute state directory, gate-scoped output inspection, cleanup,
and troubleshooting.
awf v0.5.1
A patch release that clears resolvable dependency advisories. No functional
changes to the tool.
Security
- Dependency bumps clearing 11 of 20 Dependabot advisories, including the
sole critical. Go:containerd/v22.1.5 → 2.1.9 (5 advisories) and
in-toto-golang0.9.0 → 0.11.0 (1). UI dev tooling:vite5 → 6.4.3 and
vitest2 → 3.2.6 (5, including the criticalvitestUI-server file-read;
these packages are build-time only and never ship in theawfbinary). The
nine remaining advisories are all in the Docker/Moby client chain and are
either unfixed upstream or blocked on the Docker v29 /moby/mobymigration
thatdocker/composehas not yet adopted; SECURITY.md tracks
each one and why it is still present.
awf v0.5.0
A minor release adding the Contract-v1 machine-contract stability policy and
richer live visibility for the openai/codex-live agent adapter.
Added
- Machine-contract stability policy (COMPATIBILITY.md):
theawf run/awf outputsinterface, the exit codes, and the workflow format
plus a published JSON Schema (schema/workflow.v1.schema.json) are now a
versioned Contract v1, decoupled from the binary's0.xversion. Adds a
consolidated output-binding-rules section toawf-workflow(5)and a
COMPATIBILITYsection toawf(1). - Live event visibility for
openai/codex-live. The adapter now maps its
app-server events onto the shared display vocabulary instead of the raw
[kind] {json}fallback, and fills the previously-empty durable per-event
journal record: assistant answers stream as prose, reasoning renders dimmed,
shell commands surface as tool call/result lines (command, elided output,
line/byte counts, exit status), and a failed turn surfaces a live error. A
streamed answer that also arrives as a completed item no longer prints twice.
Fixed
openai/codex-livemisread every successful app-server response as an
error. A nil*RPCErrorwas copied into theerror-typed response field,
producing a non-nil (typed-nil) interface, so each successful JSON-RPC call
returned a bogus"<nil>"error andturn/startcould nil-panic — the
adapter could not complete a turn. Success responses now carry a true nil
error.
awf v0.4.1
A patch release fixing a native-backend timeout deadlock.
Fixed
- Native-backend timeout deadlock (leaked agent process). On the native
backend a step's real workload runs as a grandchild (undersh -c, and the
sandbox trampoline). A timeout SIGKILLed only the direct child, so the
grandchild survived holding the step's stdout/stderr pipe write-ends open — the
reader goroutines never saw EOF, no step outcome was ever produced, and the
whole run deadlocked, with the agent process orphaned and still holding its
network connections. The native backend now runs each step in its own process
group and kills the whole group on timeout (with aWaitDelaybackstop),
reaping the workload and surfacing the timeout as a retryable failure that
feeds the existing retry /recovery: continuepath — matching the Docker
backend's existing process-tree reaping.
awf v0.4.0
A workflow-format release adding input-parameterizable agent roles: a reusable
role's model/system_prompt/top-level-string with: may reference
{{ input.* }}, resolved against the owning module's run input at step
execution, so one --input steers a whole fleet and forwards across a call:
boundary into a child workflow's own role. The workflow-format version
(version: 1) is unchanged.
Added
- Input-parameterizable agent roles. A role's
model/system_prompt/top-level
stringwith:values may reference{{ input.* }}, resolved against the owning
module's run input at step execution — so oneawf run --input model=…steers a
whole fleet, and a child workflow's own role reads a model forwarded via
call: input:. Guarded by AWF1067 (input.*only; no nested templates).
Behavior change: a rolewith:value that previously carried a literal
non-input{{ … }}(e.g.{{ run.id }}) — which used to pass validation
and reach the adapter as literal text — now fails validation with AWF1067.
awf v0.3.0
A workflow-format release. It renames several author-facing keys (migrate per
the BREAKING list below), adds container-less run: steps and native stall
detection with retry-as-continue, and tightens validation. The workflow-format
version (version: 1) is unchanged, but existing workflows must apply the key
migrations to keep validating.
BREAKING (v0.3.0 format)
reduce's quorumover:is renamed tofield:. The per-branch boolean
field aquorumcounts is now declared asreduce: { quorum: <k>, field: <field> }.
Migration: renameover:tofield:underreduce.quorumonly — amap's
ownover:(the fan-out worklist) is unrelated and unchanged.- The top-level workflow
input:schema is renamed toinput_schema:.
Migration: rename the workflow's top-levelinput:key toinput_schema:—
{{ input.<field> }}template references and acallstep's owninput:
(the instance binding passed to a subworkflow) are unrelated and unchanged. prune'skeep: top(<k>)is nowkeep: <k>. The function-call-shaped
wrapper is removed since top-k was always the only mode. Migration: replace
keep: top(<k>)with the plain integerkeep: <k>.- A signal
where:clause now requires the{{ }}envelope, and correlates
against the payload via asignal.<field>root. The old bare-identifier
form (an implicit substitute-into-string-then-parse against the payload,
with a string-quoting hazard) is removed. Migration: wrap the clause in
{{ }}and prefix payload fields withsignal.— e.g.
where: 'candidate_id == "{{ hyp.id }}"'becomes
where: "{{ signal.candidate_id == hyp.id }}". Every other root (input.*,
step.*,run.*, anas:binding, …) resolves against the surrounding
engine scope exactly as inif/loop/gateconditions, and the value is
compared as typed data — no more quoting workaround for strings.
Added
output_artifactis no longer containerless-only. Any agent step that
declaresoutput_schema— container-backed or containerless — may now also
declareoutput_artifact: <name>to publish its typed output as a
content-addressed artifact. The requires-output_schemaand
mutually-exclusive-with-output_filesrules (AWF3014) are unchanged.map'sover:accepts a literal YAML sequence. In addition to a
{{ }}expression evaluated at runtime,over:may now be an author-fixed
literal sequence (e.g.over: [a, b, c]) — a static, digest-pinned
parameter sweep known before the run, as opposed to a runtime-sized
worklist.- Container-less
run:steps (bare shell). Arun:code step may now omit
container:; it executes host-side under the native sandbox (auto-selected
when the workflow declares no image), so a hello-world needs no image or
digest procurement. A barerun:is rejected under--backend docker
(AWF1065) — declare acontainer:or run native. map'sconcurrency:is optional (default 1, serial). Omitting it runs
the map serially; an explicit value<= 0is now rejected (AWF1012).- Native stall detection + retry-as-continue. An idle watchdog cancels an
agent step that goes silent longer thantimeout.idle(distinct from the
wall-clocktimeout), turning a wedged agent into a retryable failure. The
openai/codexlive adapter forwards codex's reasoning-summary heartbeat and
gets a generous default idle (~300s); every other adapter is opt-in, and
AWF3016warns whenidle:is set on an adapter that surfaces no liveness
signal. On a stall, a persistent-session step resumes the same conversation
thread —retry: { recovery: continue }, the default for session adapters —
instead of restarting from scratch;recovery: restartis the escape hatch
(AWF1064rejects any other value at validation time). awf validatestrictly rejects unknown workflow/step keys (AWF1062). A
stray or typo'd key anywhere in a workflow document — previously silently
tolerated — is now a hard validation error.awf validatestrictly rejects bare-integer durations (AWF1063). A
timeoutorretry.initial/retry.maxvalue must be a quoted duration
string (e.g."300s", not300) — a bare integer previously parsed as
nanoseconds with no error, causing the step to time out instantly.
Changed
- Breaking (native backend): run workdirs moved from
work/towork/<run-id>/
to isolate concurrent native runs. A native run started before this change
cannot be resumed after upgrading (its workdir path moved). Docker runs are
unaffected (already run-id namespaced). Start affected native runs fresh.
awf v0.2.0
New workflow-format capabilities for typed-output artifacts, conditional-branch
optionality, and nested output addressing. Backward-compatible — existing
workflows are unaffected and the workflow-format version (version: 1) is
unchanged.
Added
output_artifact:on containerless agent steps. A containerlessawf/llm
step can declareoutput_artifact: <name>to publish its validated
output_schemaobject as a first-class, content-addressed artifact
step.<id>.files.<name>. The dispatcher serializes the typed output to
canonical JSON (byte-identical to the step's own typed-output blob — one blob,
two references), so it flows through the existing artifact machinery: a
deterministic gate evaluator reads it viainput_files, and it forwards out of
a gate to a workflow-leveloutput_files:alias. Validated byAWF3014
(containerless-only; requiresoutput_schema; mutually exclusive with
output_files).reactis excluded in this release.if-branch optionality. A reference from outside anifto a step whose
branch was not taken now resolves to a distinct ABSENT sentinel (AWF4006)
instead of failing the run. Inoutputs:— and symmetrically in a workflow
output_files:alias — the bound key is simply omitted; arequired
output_schemafield still fails on omission. Omission composes across a
sub-workflowcall: a child that omits an optional output makes the parent's
binding omit too. Referencing an absent step anywhere else (arun:
substitution, a gateuntil, another step's input) remains an author error.first_of:output selection. A workflow output may bind
first_of: [ <ref>, <ref>, ... ]to select the first present (non-absent)
reference — the "read whicheverifbranch ran" pattern — as a structured
directive, without adding an operator to the templating language.awf outputs --stepreads nested runtime paths.--stepnow accepts a
step's full runtime address verbatim (e.g.
gate[0].attempt-2.generate.<id>,map[0].item-3.<id>,
loop[0].body.iter-3.<id>), honoring the documented promise. The caller names
the instance; a path that names no committed step (including a step under a
non-takenifbranch, or a path missing its suffix) is a read failure
(exit 1), not a usage error.
Changed
AWF3012warning wording. The conditional-scope-output validation warning
now describes the new omit / required-fail semantics — an output bound to a
non-takenifbranch is omitted (and only required schema fields then fail),
rather than claimingawf outputswill error.
awf v0.1.3
Fixes concurrent anthropic/claude-code runs on the native backend that failed
when 2+ ran in parallel. No workflow-format or CLI changes. (Supersedes the v0.1.2
tag, whose release build failed a formatting check and published nothing.)
Fixed
- Concurrent native Claude runs no longer contend on claude-code's version lock.
0.1.1 gave each run its ownCLAUDE_CONFIG_DIR, but claude-code keeps a per-version
single-instance lock at$XDG_STATE_HOME/claude/locks/<version>.lock— outside the
config dir. On thenativebackend every run inherits the shared host$HOME(and
therefore$XDG_STATE_HOME), so two or more concurrent runs of the same claude version
contended on the same lock and the loser exited non-zero. Bothanthropic/claude-code
andanthropic/claude-code-sessionnow also relocateXDG_STATE_HOMEand
XDG_CACHE_HOMEto per-run directories alongsideCLAUDE_CONFIG_DIR, isolating the
lock and cache per run.HOMEandXDG_DATA_HOMEare intentionally left shared
(claude's versioned binary lives underXDG_DATA_HOMEand must stay resolvable).