Packages

Elixir/OTP runtime for supervised coding-agent sessions with ACP, Subagents, Workflows, and replayable evidence.

Current section

Files

Jump to
pixir CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to Pixir are recorded here. Issue and PR references
(`#N`) point to the private development repository; this public mirror
carries curated history, so those references may not resolve here. Pixir is a developer preview;
the CLI/ACP runtime is the public surface, and there is no stable Elixir library
API yet. Versions follow [Semantic Versioning](https://semver.org/) with the
caveat that pre-1.0 minor versions may still change behavior.
## [Unreleased]
## [0.1.13] - 2026-08-02
### Added
- New `:dangling_tool_call` error kind in the provider family of the `Pixir.Tool`
error taxonomy (#462). It classifies the Responses API's in-band rejection of a
request whose input carries a `function_call` with no matching
`function_call_output` — delivered as `invalid_request_error` over HTTP 200 with
`param: "input"` — and carries the rejected `call_id` in `details`. The
structural gate comes first, and only once it passes is the `call_id` parsed
out of the message; prose alone never classifies. The kind is
never retryable at the provider layer; the bounded recovery that consumes it
lives in the Turn.
- Delegate admission fails closed on short caller horizons (#433): a declared
`--timeout-ms` shorter than the plan's critical path is rejected with a
structured error and a `suggested_timeout_ms`, the dry-run carries the same
verdict as a non-mutating `would_reject` advisory, and
`--allow-short-horizon` remains the explicit break-glass. Envelope schema
revision 6.
- Dry-run plans carry an advisory `plan_warnings[]` (#443): when a declared
workflow timeout caps a step's own declared budget, the warning names the
offending step (`step_index`/`step_id`/`json_pointer`/`path`), the declared
and capping timeouts, and the `effective_step_timeout_ms` the step will
actually receive. Advisory only — it never changes `would_reject`, an
admission verdict, or an exit code. Envelope schema revision 7.
- Workflow steps can opt in to scheduling against an unverified dependency
(#436): `allow_unverified_depends_on` names, per step, the specific upstream
dependencies the step may start against when their checkpoint exists but has
not passed verification — for lanes that audit on partial results. The step's
own checkpoint then confesses it: `verification.unverified_dependencies` plus
a `"ran_against_unverified_dependencies"` known limitation. The default
remains verified-only; the opt-in is per step and per named dependency,
never global.
- A child cancelled by its parent now gets a terminal `cancelled_by_parent`
event appended to its own Log (#444), so the child's record ends with an
honest terminal instead of trailing off mid-Turn.
- ACP `session/prompt` results carry machine-readable turn-failure facts
(#465): `_meta.pixir.turn_failure` is attached exactly when a `turn_failed`
event was observed during the prompt — bounded, type-guarded fields
(`terminal_status`, `error_kind`), never the message or details, which
already travel as chat content. The chat rendering of ADR 0009 §5 (content +
`stopReason:"end_turn"`) is unchanged; the ADR is amended in place.
- Monitor: a child Session id resolves to its owning parent run (#438), child
write evidence and policy denials fold into the mutation basis (#439), owner
residency is reported separately from owner brokenness in liveness (#440),
keyed workspace sets widen to a bounded 2..8 sources (#442), and child
activity arriving after the parent run went terminal is reported instead of
dropped (#447).
### Changed
- Delegate child entries carry a `warm_start` lineage projection (#435), on
`subagents` children and workflow steps alike: `warm_started`,
`seed_session_id`, `fork_root_session_id`, `replay_event_count`, `strategy`,
and `boundary_marker_kind`. The key is unconditional — a cold child reports
the absence of a seed rather than omitting it, so a consumer can tell a cold
run from an unreported one. `tasks[]` entries and workflow steps accept an
optional `seed_session_id` spec key to request the warm start. The delegate
envelope's additive schema revision is now `9`; the
`pixir.delegate.envelope.v1` family name is unchanged.
- A bounded-write denial is recoverable feedback instead of an unconditional
Turn kill (#446). The first denial in a Turn no longer halts the tool loop:
the remaining calls of that provider response still run, and the structured
denial — denied tool, requested and normalized path, matched rule, policy
identity, `next_actions` — is delivered to the model as that call's tool
output on the next round-trip, so a worker that probes a path can write inside
the allowlist, fall back to read-only work, or finish with an honest report
instead of costing a coordinator a full adjudication and a `pixir resume`. The
**second** denial in the same Turn is turn-fatal, unchanged in shape:
`turn_failed` with the terminal tool-error record and the same exit code. N = 2
is fixed — no flag, no policy key, no environment variable. The strike is keyed
on the denial kind, so allowlist misses, workspace-root targets,
child-broadening refusals, outside-workspace bash tokens, and denials surfaced
through `apply_virtual_diff` all count the same; `bash_disabled` remains
non-terminal and never strikes. The counter is Turn-scoped and resets on every
Turn including a resumed one, so prior denials in a Session's Log are never
recounted.
- A bash command whose path reaches outside the workspace now raises the
`write_policy_denied` kind rather than `outside_workspace` **when a bounded
write policy is in force** (#446). It is a boundary probe like any other, so it
strikes, is turn-fatal on the second strike, and enters the confession — the
three surfaces now classify one set of events instead of three. The rule that
refused the command survives unchanged in `matched_rule: "outside_workspace"`,
and the plain workspace-confinement error raised outside any bounded-write run
keeps the `outside_workspace` kind, since it is not a policy denial at all.
- Every denial, recoverable or fatal, is still a `permission_decision` Log Event
with `gate: "write_policy"` and its policy identity intact — recoverability does
not reduce the audit record. Identity now survives the confinement and root
denials too (`path_outside_workspace`, `workspace_root_not_writable`,
`symlink_path_component`, `path_not_inspectable`), which are raised before the
allowlist is consulted and previously carried no `policy_id`/`policy_hash`/
`policy_version` for the coordinator to reconcile against.
- Delegate envelopes and workflow checkpoints gained a mandatory `write_denials`
confession (#446). It appears at the envelope top level and on each child entry,
and in the `workflow_checkpoint.v1` checkpoint payload, present even when there
were no denials — and even when the run produced no children at all — so its
absence is a schema violation rather than an ambiguous silence. Every
bounded-write checkpoint carries it, including held, virtual-overlay, and
apply steps that never spawn a subagent and so confess an empty one. Each entry
reports the tool, the target it aimed at — the requested path, or the denied
command, or the tool name for the two denials that aim at no path at all
(`child_policy_override_unsupported`, `unsupported_mutating_tool`). The target
is always the aim and never where a refusal stopped: a write to
`link/deep/out.txt` refused at the `link` component confesses the full
requested path, with the failing component reported beside it in its own
`symlink_component` / `uninspectable_component` key, so the coordinator can
tell *which* write under `link/` was refused. Each entry also reports the
matched rule, the policy identity, and a
`disposition` of `recovered`, `fatal`, or `unresolved`.
`unresolved` is the honest reading of a denial whose **own** Turn never reached
a terminal event in the Log — a crash mid-Turn, an interrupt, a partial Log, a
fold of a running Session: Pixir does not infer recovery from missing evidence.
A Turn ends at its terminal event or at the `user_message` that opens the next
Turn, whichever comes first, so an interrupted or supervisor-killed Turn — which
records neither `assistant_message` nor `turn_failed` — has its denial frozen as
`unresolved` instead of being closed by the *next* Turn's terminal event on a
resumed Session. A Log that exists but cannot be read now confesses
`status: "unavailable"` with an `error` and **no** `count`, at the child level
and contagiously on the aggregate, so a coordinator summing counts can never
add a zero for evidence it never saw; a Session that simply never wrote a Log
is unaffected and still folds to `count: 0`. A
completed run with a non-empty `write_denials` is a signal to check whether the
worker's scope was drawn correctly, not a defect in the worker. `bash_disabled`
denials are excluded from the confession for the same reason they never strike:
the shell being off is a property of the mode, not a scope drawn too small.
They remain in the Log. The confession is scoped to terminal envelopes;
dry-run plans, detached start acknowledgements, and `status`/`attach` liveness
snapshots omit it rather than shipping a misleadingly empty one, as
`docs/cli-contract.md` now records. The delegate envelope's additive schema
revision is now `8`; the `pixir.delegate.envelope.v1` family name is unchanged.
- The bounded write policy's verify allowlist is operator-declared (#445): a
policy's `bash` map accepts `verify_prefixes` alongside `verify`, so a writer
in a pnpm, Python, Go, or Rust workspace can verify its own work instead of
structurally finishing blind. A prefix is one or two literal tokens matched by
token (`pnpm typecheck` never admits `pnpm typechecker`), and declaring an
allowlist replaces the built-in `mix format` / `mix compile` default rather
than extending it. Every existing per-entry filter still applies unchanged —
no shell metacharacters, no parent-directory tokens, at most 8 entries,
workspace confinement at authorization time — and rejections now report the
allowlist actually in force so the operator can tell which one applied.
Omitting `verify_prefixes` leaves behavior and the durable policy hash
byte-identical, and `mix test` stays rejected under the default. On `resume`,
the restored and requested allowlists intersect by tokenwise coverage — a
prefix survives only when the durable side covers it, so `mix` against `mix
format` keeps `mix format` — and the shell is disabled when the `verify`
intersection is empty. The new `docs/bounded-write-policy.md` documents the
config shape.
- The delegate envelope's schema revision derives from an append-only,
feature-keyed registry (#461): two parallel envelope PRs now collide as a
textual merge conflict instead of silently shipping several features under
one revision number, and a parity pinning test guards the registry's
contiguity, uniqueness, and the wave-elected 7/8/9 history. Consumers see no
shape change — `schema_version` stays an additive integer. Every additive
envelope change appends exactly one registry entry and one CHANGELOG line
naming its feature; the convention is stated in `docs/cli-contract.md`.
- Monitor: each run-card copy slot names the fact it reports (#441), so a
card's copy can no longer read as a claim about a different lifecycle stage.
### Fixed
- A terminal record can no longer out-crash the Turn it reports (#470). The
three terminal finishers recorded `turn_failed` (and partial assistant text)
with a bare call that exited the Turn Task when the Session was already gone
— dying in the very path built to report the failure. Terminal writes now
degrade with a bounded-class log line, the ORIGINAL error survives as the
Turn's return value, and a record stalled past its call timeout classifies
as Session unavailability instead of escaping as a crash.
- Cancelling a Turn mid-stream no longer poisons the Session (#462). A function
call the provider had committed on the wire but Pixir had not yet persisted
died with the killed Turn Task, and every later prompt on that Session was
rejected in-band by the Responses API — `invalid_request_error` over HTTP 200,
`param: "input"`, "No tool output found for function call `<id>`" — for an id
that appeared in no Log event, so the Session was unusable for good. Two
layers close it. **Drain to persistence:** each function call is now handed to
the Session process the instant its output item completes on the wire, from
inside the streaming path rather than after the stream returns, so a cancel
landing mid-stream still finds the call in the hands of a process that
survives the kill; the interrupt drains it to the Log as a `tool_call`, which
the existing orphan reconciliation closes with its usual `orphan_tool_call` /
`interrupt` result. Both transports inherit this — the seam is in the shared
event reducer, not in a transport. The streaming runner outlives the Turn it
belongs to on the default idle-watchdog topology, so declarations carry the
identity of the Turn that made them — a monotonic generation issued at Turn
start — rather than merely asserting that some Turn is running. A declaration
whose generation is no longer the running one is drained on the spot: both when
it lands with no Turn at all and when it lands while a *successor* Turn is
already alive, where attributing it to that successor would have let the
successor drop it at its own clean end. A Turn start drains whatever it finds
instead of clearing it. A declaration that cannot be handed over — a
Session that is gone or stalled — is logged and swallowed rather than killing
the stream, and is never reported as a retryable transport error.
**Build-time recovery:** a request rejected
for a dangling call id absent from the Log is healed by synthesizing a
cancelled output for exactly that id and retrying, which also repairs Sessions
already poisoned by older binaries. Classification is gated structurally first
— an `invalid_request_error` (or `bad_request`) on `param: "input"` — and only
then is the rejected id parsed out of the message; prose alone never
classifies, but a message rewording does silently disable the layer, so the
parse is not incidental. Recovery is bounded once per id, by a total budget per
Turn, and by a Session-lifetime ceiling counted off the synthesized markers
already in the Log; it refuses to fire for an id the Log already carries (that
remains the orphan reconciliation's job); and a second rejection surfaces the original
provider error unchanged. Every synthesis appends durable Log evidence — a
`tool_call` marked `synthesized` plus an `orphan_tool_call` result asserting
the call did **not** run — before the retry is issued, so the Log never
pretends a tool executed. A recovered Turn's envelope and exit code are those
of a normal Turn. This is the first TURN-LEVEL recovery retry in Pixir: the
first retry triggered by a structured `:dangling_tool_call` error that
*modifies the rebuilt request*, appending synthesized evidence before
re-sending it. Transport- and status-level retries of transient provider
failures already existed via `Pixir.Provider.attempt/5`, which re-sends the
request unchanged; ADR 0036 is amended to record this new class against that
ADR's own retry conditions.
- Monitor: a launch handoff no longer ends `serve` (#437). The handoff is
auxiliary to serving, so a reader that races the FIFO writer, closes it early,
or never attaches now emits one `launch_degraded` frame and re-arms a fresh
private directory, FIFO, and bounded writer — announcing the newly valid
`fifo_path` in a new readiness frame — instead of exiting 1 with
`{:writer_exit, 74}` while the listener and projection were healthy. Re-arm is
bounded (16) and its exhaustion is a terminal `launch_surface_exhausted` frame,
after which the monitor keeps serving without a FIFO. A successful handoff also
re-arms, so a second browser session needs no restart; every handoff still
issues its own fresh, one-use, TTL-bounded capability, and no capability byte
reaches any frame, stdout, or stderr.
- Monitor: darwin mode reports its launch outcome and accepts re-entry (#437).
The serving stdout frame now carries a bounded `launch` object distinguishing
`succeeded`, `failed`, and `not_attempted` (including `unsupported_platform`
off macOS), so a launcher failure is observable instead of fatal. `SIGUSR2`
mints a fresh one-use capability and relaunches without restarting the monitor,
replacing the previous one-shot token with no recovery path. SIGUSR1 is
deliberately avoided: ERTS reserves it for its crash-dump handler.
## [0.1.12] - 2026-07-21
The contract-honesty cut: 11 pull requests since 0.1.11. A checked-in CLI
contract turns the real builders into pinned public evidence and catches three
product bugs in the process; delegate specification admission now fails closed
across every known surface; and Open Responses mode is proven live against a
local open endpoint.
### Added
- The checked-in CLI contract (#205, PR #423) records four surfaces and 87
fields in `docs/cli-contract.md`, with 85 assertions pinned against the real
builders. Those pins caught and fixed three product bugs in the same PR:
`--json resume` no longer leaks streamed text to stdout before its envelope;
pre-session failures in `--json` mode return an envelope instead of a bare
human line on stderr; and `run_turn` no longer double-subscribes on a second
in-process route and double-delivers output.
- The first live `mix pixir.smoke.open_responses` run against a local Ollama
endpoint (#208, PR #424) proves Open Responses endpoint compatibility. A new
`termination` evidence field tolerates and confesses a missing `data: [DONE]`
sentinel while `done` remains strict; conformance probe v2 communicates its
sentinel and pins its digest at compile time; and the honest claim level is
`endpoint_compatibility_observed`.
### Changed
- Delegate specification admission fails closed (#422/#425, PR #426; #427,
PR #429; #428, PR #430): unknown Workflow step keys are rejected with a
nested `json_pointer` and a `depend_on` hint; root `steps` requires the
Workflow strategy; and the `workflow` shell is a closed, accessor-backed
object that rejects non-maps and the wrong strategy while making root and
nested steps mutually exclusive. Workflow step `limits` accepts only the
virtual-overlay limit keys with non-negative integer values and is rejected
outright on non-overlay steps. The root `limits` knob bag accepts only
`timeout_ms`, `delegate_timeout_ms`, `child_timeout_ms`, and
`wait_horizon_ms`; the runner's nested-shell merge uses the same accessor;
and attached-Workflow help now describes the admitted surface truthfully.
### Fixed
- Monitor hardening drains SSE streams and makes the API fail closed during
shutdown (#405, PR #411). Rescue diagnostics serve fixed reasons rather than
`Exception.message`, with a source-level boundary test (#414, PR #420). A
dead bootstrap catch is replaced by an honest init-failure net, and the Runs
list stale banner gains its Retry control with per-view copy (#416/#417,
PR #421).
- Two diagnostics and metadata boundary fixes from the 0.1.11 mirror review
harden the release without widening its claims.
## [0.1.11] - 2026-07-17
The Monitor cut: 42 pull requests since 0.1.10. Pixir Monitor — a read-only,
loopback-only web console over the append-only Log — goes from first
implementation to daily driver in one release, hardened by a maturity
gauntlet, a degradation suite, and browser evidence that is now mandatory in
CI. The provider gains the Open Responses conformance arc and truncation
honesty for successful output, and the post-0.1.10 security tranche lands
session-id and read-set confinement. The Monitor lives in `monitor/` as a
sibling escript project and ships in the repository, not in the Hex package;
Pixir core does not depend on Phoenix.
### Security
- Session-id and read-set confinement hardening (#323). `Pixir.SessionId` is
the single 1..235-byte UTF-8 session-id grammar, and untrusted ids are
validated before the Log, writer-lease, Registry, recovery-command,
process, CLI, ACP, Delegate, Workflow, and Resource boundaries. Pixir-owned
Session Log and writer-lease paths get a structured `lstat` preflight that
refuses existing or dangling symlinks; evidence, doctor, and GC consumers
are hardened; and leases are released on both failed `Session.init/1` and
post-start rejection. Competing virtual-overlay `read_set` grammars are
replaced by one shared structural classifier used by specification and
execution callers, and lexical Workspace confinement is fixed for trusted
root `/`. Documented boundary: this is a deterministic confinement
tripwire, not a descriptor-relative POSIX sandbox — `openat`-style
no-follow, hard-link, mount, and same-UID check/reopen race closure are
explicitly not claimed, and Session Resource payload-path hardening below
the validated session-id boundary is deferred.
- Responses and Anthropic body construction is hardened so hostile structs,
improper lists, authority collisions, non-UTF-8 terms, and build or encode
failures return redacted structured errors before credential assembly or
transport (#317).
- Monitor 503 diagnostics no longer include the absolute workspace root;
the error detail carries the workspace basename instead, and the full path
stays observable only in the local server log (#369).
### Added
- **Pixir Monitor v1** (#324, ADR 0038): a local web presenter over the
frozen `pixir.presenter.run.v1` contract for Workflow runs and Pixir
Delegate fan-outs. The append-only Log remains canonical and the presenter
stores no independent run state: authoritative HTTP snapshots plus bounded
SSE invalidation hints, with disposable browser state that refetches after
gaps, reconnects, or invalidation anomalies. The server is read-only and
loopback-only with exact Host/Origin and Fetch Metadata checks, a one-use
in-memory bootstrap token, strict CSP/Trusted Types, local assets only,
and no mutation routes; model-authored hostile content renders literally.
Surfaces: Runs inventory, Workflow Run Detail, Subagent Fan-out Detail,
and Unit Inspector, keeping execution, dependency gates, advisory
verdicts, liveness, and evidence source as independent dimensions, retry
and resume attempts in one logical-unit lineage, and usage derived only
from durable `provider_usage` events. A v1.1 operator repair pass (#329)
followed: honest gate/advisory distributions, correct pagination totals, a
truthful parent-Log-only attention lower bound, and actual-edge dependency
captions.
- Monitor daily-driver train (#331, #332, #333, #334, #346, #347): launch
against an explicit workspace resolved at invocation time; bounded search
by title, Run id, or parent-observed child Session with the query in the
route and the searched domain confessed; recency ordering over a frozen
temporal schema with evidence-derived durations and confessed
completeness; Follow as a refetch policy represented in the route, with
refetch convergence later proven in a real browser (identity loss vs. a
missing logical Unit are distinguished); list-scope activity honesty —
the Runs list stopped offering liveness it cannot evidence, so list rows
carry only "unobserved"/"not applicable", grouping is "Needs
attention"/"Recent" over the parent Log only, and an "Active" state is
pinned out by regression; and daily triage works at 390px narrow
viewports.
- Monitor structure contracts, frozen then implemented: semantic-zoom v1
(#336) — a bounded 100-unit Workflow zoom with wave:bucket clusters,
six-plus-overflow windows, arc counts, and an exact edge ledger (#348) —
and workspace-set v1 (#339) — a two-workspace read-only Workspace
Overview keyed by operator-chosen workspace keys (#349). Large Delegate
fan-outs group without inventing topology (#338), and the Overview stays
useful when one workspace is stale or unavailable, with per-source
degradation states and Retry (#340).
- Monitor honesty doctrine, adjudicated mid-release (#360, #403): the
Builder normalizes out-of-vocabulary enums to `unknown` and unparseable
timestamps to `null`, confessing every normalization
(`unknown_enum:<field>:<raw>`, `malformed_timestamp:<field>:<raw>`);
advisory classification stays the sole verdict authority; list and detail
fail-close with the same execution vocabulary. The same sweep caught a
real bug: a malformed event timestamp could win the lexicographic maximum
into `source.last_durable_at`, so derived date-times now consider only
parseable values and confess exclusions. The launch page reaches a
terminal honest state in three categories (capability absent, capability
rejected, generic failure) announced via a `role="status"` live region
(#399), and the darwin launcher hands the launch capability to osascript
via the environment, with the child actually reaped (#386).
- Monitor evidence and maturity gates: a 500-unit Workflow performance gate
within DOM and render budgets, with scale growth measured against the
100-unit baseline (#337); the maturity-gauntlet evidence package across
security, accessibility, and envelope frontiers (#341), adjudicated
operator-side with all three frontiers remaining Experimental (#360);
scale cells exercised at magnitude — 512 logs, 50-row pages, 32 KiB
fields (#397); a real 300-second SSE stream rotation — the page held open
past the server's stream-lifetime timer, the stream required to end, and
a second authenticated stream plus authoritative refetch required to
follow — driven end to end (#400); four hostile-text vector classes
(script, entities, RTL override, near-cap field) sealed per evidence
source, eight source-qualified instances across both sources, plus
keyboard deep-links into paginated cluster members (#402); and a named
main landmark whose accessible-name gap fails closed (#390) with the
variant-B redesign, a vertical per-source registry, across the Overview
and zoom cluster views (#391, #392).
- Monitor test tiers: a real built-escript CDP browser harness drives the
suites above; the default browser suites are mandatory in CI with an
action-pinned Chrome and Node 22 (the missing gate was Node's global
WebSocket, not Chrome) and a fail-loud toolchain assertion instead of
silent skips — confessed exception: the five-minute lifecycle suite
stays out of CI by design, opt-in via `PIXIR_MONITOR_LIFECYCLE=1`
(#401); and a first tier executes the Presenter's frozen JavaScript seam
in `node:vm`, fail-closed (#404).
- Monitor integration: a secure Codex sidecar handoff over a FIFO (#357),
and detail projections restored in the built escript: schema loading had
assumed a source layout (`:code.priv_dir/1`) that fails inside the
escript, so the vendored Presenter schema is now embedded at compile
time (#358).
- Open Responses conformance arc (#317, #318, #319, #320): an opaque,
fail-closed, request-scoped Responses backend profile snapshots provider,
model, capabilities, backend policy, and defaults once per request;
request-scoped routing and auth enforce exact URL/transport capabilities
with ephemeral policies for existing ChatGPT credentials, `bearer_env`,
and `none` (strict token68, HTTPS or literal-loopback HTTP only, never
stored or projected); open mode stays HTTP/SSE-only with `store: false`,
a strict WHATWG SSE decoder, and 24 pinned event roots validated before
any reducer effect. Conformance evidence is deterministic: a checked-in
69-schema validator generated from a pinned OpenAPI commit with no
runtime schema dependency or network, a 14,235-mutation differential
corpus against an independent JSON Schema Draft 2020-12 oracle, and an
offline smoke task. The default ChatGPT/Codex wire — request bytes,
ordered headers, WebSocket/SSE policy — keeps exact regression coverage.
- Provider-neutral output-truncation evidence (#268, ADR 0039): a tri-state
contract for successful OpenAI and Anthropic calls persists durable
call-level evidence through Turn, Event, and Log, and surfaces bounded,
deduplicated projections in CLI, ACP, diagnostics, replay, Subagents, and
Delegate. Provider text is preserved exactly; successful truncation is
distinguished from input overflow, error partials, and local channel
bounds; tool execution stays gated on complete validated calls.
### Fixed
- Exact-cap error-body truncation provenance (#306): the binary-only error
accumulator is replaced by an opaque capture that records whether any
byte was actually discarded, so `err_body_truncated` is true byte-drop
evidence rather than retained-length inference. At exactly 16,384
received bytes the marker stays absent; a later nonempty byte makes it
true and sticky for that capture; each retry starts fresh. OpenAI
Responses, Anthropic Messages, and model-catalog refresh share the
contract with retry and classification policy unchanged.
### Changed
- The public skills surface is pruned to the delegation trio plus
diagnostics (#344): `pixir-audited-executor` folds into
`pixir-delegate-codex` as its audited single-run mode, and the
fork-maintenance skill family leaves the public mirror (the sync script
gains fail-closed verify checks for the excluded paths).
## [0.1.10] - 2026-07-11
Hardening cut: the resume-sandbox security arc (#311) plus the five majors
from the public-mirror review adjudication wave, and explicit model-catalog
discovery.
### Security
- Resuming a delegated writer no longer discards its sandbox (#311). Spawn
records a durable `permission_posture` event in the child Log before its
first Turn (`permission_mode`, write-policy metadata with an integrity
hash, `workspace_mode`), and `pixir resume` cold-folds that evidence and
rehydrates the posture: the same `write_policy` gate that ran before the
interruption denies out-of-policy writes after the resume. Resume flags
may restrict but never widen (`read_only` wins, `bash: disabled` wins,
a requested `write_policy` narrows to the intersection of the durable and
requested allow sets); the no-widen matrix is pinned.
- Every new root session records a durable posture marker at sequence 0
(`lineage=root`) at creation, through the single seam all product
surfaces share (CLI one-shot, ACP `session/new`, delegate runner). A
failed posture append stops the Session rather than delivering an
unmarked one. The delegate runner records the run's real ceiling
(read-only, or auto bounded by the spec's write policy), never an
unbounded default.
- Fail closed on missing evidence: a history containing a successful
mutating tool call with no readable, unambiguous posture refuses to
resume write-capable (`resume_policy_unavailable`, with `next_actions`);
mutation-free legacy logs still resume read-only. Unbounded `auto`
without a write policy is restorable only when the root marker itself
declared it.
- Legacy escape hatch, explicit and durable: `pixir resume
--assume-legacy-root --legacy-root-reason <text>` recovers a pre-0.1.10
write-capable root Log only when posture evidence is missing (unreadable
or malformed shapes stay closed), only into read-only, ask, or
policy-bounded auto (never unbounded `auto`), and records an
`operator_attested_legacy_root` attestation so later plain resumes
restore the attested ceiling without the flag.
- The trust rules survived five adversarial review rounds (seven confirmed
majors from reviewers of different model families, two of them against
intermediate versions of this fix): a root marker is trusted only at
root position in physical append order (the first event that is not a
`session_fork`, resolved from raw NDJSON line order rather than
seq-sorted fold order, so neither a forged id nor a forged seq can move
the boundary), only with source `root_session_start`, and never when it
carries spawn fields. An unbounded `auto` ceiling with no write policy is
refused for any non-root lineage even when the write evidence is deleted,
so a bounded child cannot be surgically elevated. A forked root stays resumable, and a partially
edited child Log does not elevate; the exact attack shapes are pinned as
regressions. Accepted residue, documented: a same-UID rewrite of the
entire session NDJSON can fabricate the trusted shape (local filesystem
threat model, unchanged by this arc).
- ACP `session/load` and reattach restore the same posture, and the
fail-closed paths stop the live Session (no lingering writer lease).
Provider folds and `reconstruct` treat the posture marker as evidence
only: it never reaches the model context and never fabricates a phantom
child.
### Added
- `pixir models` and `pixir models refresh [--json]` (#288): the model
catalog is operator-configurable and discovery is explicit, never
implicit network. Refresh is per-provider fail-closed: OpenAI only with
an API-key credential, Anthropic only with `ANTHROPIC_API_KEY`;
subscription auth is confessed as unsupported for the models endpoint
rather than guessed. The refresh path is the first config writer: atomic
tmp+rename preserving foreign keys, owning exactly `models`,
`anthropic_models`, and `models_refreshed_at`; if every provider skips
or fails, `config.json` stays byte-identical.
`config.json["anthropic_models"]` now overrides the static Anthropic
list (mirroring the OpenAI pattern), doctor gains an informational
`model_catalog` check with a 30-day stale hint, and the built-in catalog
gains `gpt-5.6-sol` / `gpt-5.6`.
### Fixed
- Replay skill-view pairing no longer fabricates orphan outputs when
unrelated tool calls are pending (a hole in the 0.1.9 pairing fix): both
provider folds pair a skill activation iff exactly one pending candidate
matches the activation name and a skills main-file path; zero or several
candidates keep the conservative refusal, and an unrelated `tool_result`
queues without disturbing a deferred activation. Provider parity is
pinned with mirrored regressions; the original incident fixture is
unchanged and green.
- Two crash classes in virtual/workflow execution: symlink resolution
crashed on `Path.join([])` when a symlink was the final path segment;
and virtual-step/apply-step tasks now run under a supervisor
(`Task.Supervisor.async_nolink`) with an explicit `{:exit, reason}`
clause, so an abnormal task exit returns a structured step failure
instead of crashing the workflow (and an interrupted workflow no longer
leaves a write-capable apply task running unsupervised). Also:
unbounded-glob validation reports the original list position, and apply
write-set validation runs once instead of twice.
- Completed delegate children keep their retry lineage across a cold
Manager restore: the completed terminal event records the same terminal
fields as every other terminal path (`retry_history`, `virtual_diff_ref`,
`elapsed_ms`), and recovery guidance no longer treats a read-only role as
write-capable.
- Virtual-overlay and workflow hardening from the public-mirror review of
the 0.1.9 release diff: `run_virtual_commands` sanitizes the echoed
command line like any other model-channel output; the canonical
whole-workspace `read_set` spellings are rejected (robust normalized
rejection of every equivalent spelling is tracked as follow-up hardening);
an `apply` step serializes against
readers with overlapping footprints in either argument order; and
workflow validation errors emit real 0-based JSON pointers (the emitter
interpolated 1-based indices).
### Changed
- New sessions carry the posture marker at sequence 0, so every subsequent
event seq shifts by one; consumers that pin history shapes should expect
the posture `subagent_event` first. Compaction excludes the posture
marker from durable summaries.
- System-prompt construction reuses the per-Turn rendered skills index
snapshot for cache-control providers instead of re-rendering it on every
loop iteration (other providers keep their own render; public signatures
unchanged; the cache-key timing design pass remains open).
## [0.1.9] - 2026-07-10
### Added
- Orchestrator ergonomics (#205 items 1 and 3): the doctor JSON envelope
carries an explicit delegation decision — `proceed: "true" | "judge" |
"block"` with `judge_checks` listing the non-passing check ids (populated
for `block` too; severity lives in `proceed`, detail in `checks[]`) — and
the `read` tool gains optional line-based `offset`/`limit` with
self-teaching continuation: sliced or truncated results end with
`[truncated: showing lines X-Y of Z; continue with offset=N]` plus
`lines_total`/`lines_returned`/`offset_effective`/`next_offset` metadata,
an oversized single line advances past itself with a caveat, and default
small-file reads stay byte-identical.
- Virtual delegate children (#284 F3): `subagents.workspace_mode:
"virtual_overlay"` runs each child as a real model conversation against a
bounded in-memory import of `subagents.read_set` — zero physical snapshots,
zero real writes. The child's only mutation surface is the new
`run_virtual_commands` tool (commands only; `read_set`/`limits` come from
operator context, never model arguments), the Executor confines real reads
to the imported read set and denies bash/write/nested orchestration for
virtual children, and each child's proposed changes return as
`children[].virtual_diff` with a bounded provenance ref in the durable
`subagent_event` (envelope schema revision 4, additive). A child that
finishes without producing an artifact fails honestly
(`virtual_diff_missing`); oversize artifacts fail with the ref preserved.
Transport retries for virtual children are gated by child-Log evidence
(ADR 0036): any durable model/tool output blocks the retry, and a child
that dies after producing a valid artifact keeps it on the failed result.
Applying an artifact remains a separate explicit operation
(`apply_virtual_diff`); `workflow_apply_from_compatible: false` is
confessed on the affordance.
- `pixir gc` (#221 phase 2): `--json` builds an effect-free reclamation plan
for terminal isolated subagent snapshots in the current workspace;
`--apply` executes it. Fail-closed classification (reclaimable only when
every parent-log reference reconstructs terminal — the status vocabulary
derives from `Pixir.Subagents.terminal?/1`, `closed` included; running,
detached, and unreferenced dirs are skipped with reasons; corrupt parent
logs block planning), every `*.ndjson` under any `.pixir/sessions` path preserved
byte-intact at its original path, per-dir apply errors recorded without
aborting, and `reclaimable_bytes` reported net of preserved logs.
- Guided resume for transport-dead delegate children (#285): when a child
dies on a terminal transport error and was not auto-retried, the envelope
child carries `recovery.kind: "resume_suggested"` with a reason naming the
error kind, the ready-made resume/diagnose commands, and — for
write-capable children — notes that the session log is the source of
truth, applied writes should be inspected before resuming, and a stale
writer lease fails closed by design. Transport classification reuses the
#278/#280 retryable vocabularies; when public results collapse the reason,
the runner reads the latest `turn_failed` from the child Log.
- Native Subagents rehearsal (#204): `spawn_agent {"validate_only": true}`
runs the exact validation a real spawn runs (shared pure core in the
Manager) with zero side effects and returns an allowlist plan projection —
effective knobs and limits, the workspace fidelity contract, effective
permission, and an explicit `limitations` list confessing what validation
cannot prove. Non-mutating only on exact boolean `true` (read-only and ask
postures rehearse without prompting; malformed values fail closed), the
Turn-level dry-run returns the same normalized plan, and virtual children
may rehearse spawns while real spawns remain denied there.
- A worked example of the propose→review→apply cycle at
`docs/examples/propose-review-apply.md` — written into the repository by
the exact workflow it documents (#284 F4), including the honest limits it
demonstrated in production: review verdicts are advisory (checkpoints gate
order and failure, never verdict content) and truncated diffs are
review-only evidence.
- Workflow apply step (#284 F2): a step may declare `"apply_from":
"<producer_step_id>"` to explicitly apply the `virtual_diff` a
`virtual_overlay` step produced — the propose-review-apply DAG. Validated at
plan time (producer must exist, be virtual, and be in `depends_on`; knobs
and spawning fields rejected; an explicit non-empty `write_set` is required
and bounds the artifact paths BEFORE the engine runs), executed without
spawning a subagent, and evidenced by the engine result stored verbatim in
the completed-step record; a non-applied outcome fails the step and holds
dependents.
- ACP exposes `reasoning_effort` as a session config option (#289): a third
select alongside mode and model (`default|low|medium|high|xhigh`), sticky
per session, with prompt-time precedence `_meta.reasoning_effort` >
session option > config. The unset state renders honestly as `default`
(both providers omit effort from the request body when none is set), and
selecting `default` suppresses a configured effort rather than inventing a
value. The requested effort is recorded as durable `subagent_event`
evidence (operator-side truth); providers do not echo an effective effort
back, so no such proof is claimed.
- Virtual Diff Apply (#284 F1, ADR 0030): `Pixir.VirtualDiffApply` plans and
applies `virtual_diff` artifacts through a staged two-phase commit —
all add/modify content is written to temp files before any target mutates
(a staging failure, the dominant class, leaves every target byte-identical),
the rename/delete commit phase has a narrow residual window whose rollback
is total and non-raising, and the failed result confesses recovery
(`recovery.rolled_back`, structured `restore_failures` kinds) instead of
claiming filesystem transactionality it cannot have. Hash-checked
preconditions, canonical workspace confinement with symlink resolution,
per-file authorization through the write-policy seam, evidence paths
protected at the Executor exactly like `write`/`edit`, and the ADR result
shape as durable evidence (contents stripped). The model-visible
`apply_virtual_diff` tool defaults `dry_run` to true and only counts as
mutating when `dry_run` is explicitly false, so plans stay available in
read-only mode. Apply is never automatic: overlays keep producing
`apply.status: "not_applied"` until this operation is explicitly invoked.
- Bounded-write workers can self-verify (#240, v1 conservative): the write
policy's `bash` key accepts `{"verify": [commands]}` alongside `"disabled"`,
where each operator-declared command must be a literal `mix format` or
`mix compile` invocation (no shell metacharacters, no parent-directory
tokens, at most 8 entries). Authorization is exact match against the
declared list after leading/trailing-whitespace trimming on both sides
(no substring or prefix matching), after the existing confinement and
read-only safe-list checks; denials now confess how many verify commands the policy declares.
The effective list travels with the policy (normalize/metadata/rehydrate)
and bounded_write dry-run plans report the per-child count. `mix test`
entries are rejected with their own message: test execution stays with the
orchestrator until this v1 is dogfooded.
- A no-network regression gauntlet for the Anthropic arc (#274): eight
pins with canned transports — six end-to-end through `Turn.run`, two at
the provider seam (`Anthropic.stream`) — covering the pa1
request body (system blocks + `cache_control` + fenced late context),
registry routing and its stub fallback, verbatim thinking replay with the
foreign-dialect guard, the tool loop (including the pa1 fence riding the
latest tool_result group — the P3 latest-user-message contract), durable
cache evidence on `provider_usage`, compaction interplay at the Turn level,
the error taxonomy (rate limit, overflow, overloaded, bounded `err_body`),
and the fail-closed web_search/hosted-tools rejections.
### Fixed
- Every `bounded_write` workflow spec containing an `apply_from` step was
rejected by the CLI validation gate — in dry-run AND real runs — because
the rehearsal ran without the write policy the gate itself had just
normalized (#291). The F2 apply-step runtime was unreachable through the
CLI until this fix; the policy is now threaded into the shared rehearsal
path and the acceptance is pinned end-to-end through the CLI contract.
- ACP prompts now resolve at a single total-order synchronization point
(#267): a `session/cancel` racing a Turn's terminal status could flip the
reply's `stopReason` under parallel load because the cancel flag was
snapshotted in one server call while the reply was written in another.
Cancel-wins-ties semantics (ADR 0009 §5) are preserved exactly and both
orders plus the raced tie are pinned through an injectable resolution seam.
- Replay no longer orphans a `skill_view` tool call whose canonical
`skill_activation` was recorded between the call and its result (#204):
the activation is deferred past the matching `function_call_output`
(byte-exact, only when exactly one unresolved matching call exists —
ambiguous histories fall back to ordinary orphan repair), true orphans
keep the synthetic repair without losing the activation, and the Anthropic
history folding mirrors the rule so `tool_use`/`tool_result` adjacency
cannot drift across providers.
- Workflow steps gained the #239-class gate (#282): in a bounded_write
workflow, a writer-posture step whose agent resolves to a read-only
`sandbox_mode` is rejected fail-closed at plan time with step-level
location details; a read-only `subagents.role` at the spec level is
correctly ignored by the workflow strategy.
- In-band provider overload errors are retryable again (#278): when the
Responses stream delivers an `error`/`response.failed` event over HTTP 200
with a transient type or code (`server_is_overloaded`,
`service_unavailable_error`, `server_error`, `overloaded`, rate-limit
codes), the classifier stamps `retryable: true` in the error details —
mirroring the Anthropic 529 precedent — and BOTH retry layers read that
classification: the provider's turn-level retry and the delegate's
read-only-child auto-retry (which keeps honoring the legacy
`type: "server_error"` shape for events recorded before this change).
- A `bounded_write` delegate spec with a read-only role (e.g. `explorer`) is
now rejected fail-closed as `invalid_spec` at validation — dry-run and real
runs identically (#239). The role's `sandbox_mode: "read-only"` used to
silently override the spec mode: the delegation rehearsed clean, ran, and
wrote nothing while the envelope looked successful. The rejection names the
conflicting role and offers the two honest exits (write-capable role, or
`read_only` mode).
- The pa1 prompt contract is now actually wired into the Anthropic request
path (#272): when the provider registry reports `prompt_cache:
:cache_control`, Turn threads the neutral pa1 ingredients (`prompt_mode`,
`skills_index`, `agent_instructions`, `previous_turn_boundary_seq`) and the
Anthropic provider assembles the body through `Prompt.build/1` — layer0 and
skills-index system blocks with their `cache_control` breakpoints, and the
previously-dropped `developer_context` (plus subagent role instructions)
fenced into the latest user message as pa1 late context. The planned
contract (version, breakpoints, layer0_hash) rides `provider_metadata`
under `"prompt_contract"` as durable evidence. The OpenAI request shape and
the legacy Anthropic path (no `prompt_mode`) are byte-unchanged.
- Error-body capture on non-2xx provider responses is bounded (#268): both
transports retain at most 16 KiB through a shared helper, and a capped
capture is confessed as `err_body_truncated: true` in the classified error
details (key absent when nothing was dropped).
## [0.1.8]
### Added
- **Anthropic (Claude) as a second provider**, built evidence-first behind the
existing provider seam (epic #243, design in ADR 0037):
- Messages API transport core with streaming SSE and a fail-closed error
taxonomy (#249).
- `pa1` prompt contract: a frozen, cache-maximal prefix with a deliberate
`cache_control` breakpoint planner (#254). The planner is unit-tested but
not yet wired into the live request path — the integration is tracked in
#272, found by this release's audit.
- Provider-owned usage as durable evidence: an explicit cache map
(`usage_summary.cache` with `creation_tokens` / `read_tokens`) on
`provider_usage` events, `pa1` family identity stamped alongside, costs
reconciled from Logs rather than estimates (#258).
- Thinking replay: reasoning events are dialect-labeled at record time and
re-injected verbatim next to their `tool_use` blocks on the next turn,
guarded by model identity so a dialect never replays into the wrong
provider (#262).
- Tool-use mapping: Pixir tools project to Anthropic `tools` specs and the
session history folds to Messages with correctly grouped `tool_result`
blocks (#261).
- Provider registry: `claude-*` model ids route to the Anthropic provider,
auth checks are provider-scoped, and `pixir doctor` reports honestly per
provider — `ANTHROPIC_API_KEY` guidance and data-retention notes only where
they apply (#265).
- Threaded the opt-in hosted `web_search` knob through effective config, CLI parsing,
Turn request assembly, delegate validation, spawn-agent stripping, and Anthropic
fail-closed rejection so Provider-hosted search remains default-off and explicit
(#255).
- Delegate/CLI attachments channel (#250, ADR 0021): a `tasks[]` entry may be an
object `{"task": ..., "attachments": [...]}` and one-shot/resume take a
repeatable `--attach <path>`; each local path becomes a durable Session
Resource in the child, dry-run plans confess a per-child `attachment_count`,
unknown task-object keys reject fail-closed, and the model-facing
`spawn_agent` tool strips caller-authored attachments (operator knob, not a
model capability).
- Workflow steps accept per-step `model`, `reasoning_effort`, and `attachments`
(#270): validated at plan time, threaded through the runtime-owned opts
channel only, and reported honestly in plans (`attachment_count`, omission
over null); `virtual_overlay` steps reject the knobs, and the model-facing
`run_workflow` tool strips them from step objects so a workflow-spawning
model cannot smuggle provider knobs to children.
### Fixed
- Provider `output_items` order is preserved through Turn recording (ADR 0007):
interleaved reasoning/message/tool items no longer regroup by type (#246).
- Workflow dry-run now validates the DAG through the runner's own path (#263):
cycles, unknown dependencies, and duplicate step ids are rejected identically
in attached, async, and dry-run modes — rehearsal parity by construction, not
by a second copy of the rules.
- Subagent identity is runtime-owned (#234): child ids are always generated and
task-position `index` arrives via the runtime opts channel, so caller-authored
`id`/`index` args are ignored and can no longer influence workspace paths or
forge position evidence in durable logs.
- Attachment URI handling is centralized and hardened (ADR 0021): case-variant
`FILE://` schemes normalize, `file:` without `//` and URIs carrying query or
fragment parts are rejected with specific reasons, and paths percent-encode
correctly on the way in.
## [0.1.7]
### Added
- Delegate subagent children now carry durable `children[].index` task-position
evidence across attached envelopes, async snapshots, tree projection, and retry
lineage (#227). The delegate envelope `schema_version` is now `2` to signal the
additive `children[].index` / `children_order` keys; the
`pixir.delegate.envelope.v1` family name is unchanged (reserved for breaking
shape changes).
- Delegate specs are validated fail-closed (#223): unknown top-level or
`subagents` keys are rejected as structured `invalid_spec` with `field`,
`json_pointer`, `path`, and `next_actions`, in dry-run and real runs alike,
so a typo can no longer pass the rehearsal silently.
- New delegate spec provider knobs mirroring ACP `session/prompt` `_meta`
(#223): `subagents.model` and `subagents.reasoning_effort`
(`low|medium|high|xhigh`) thread to every child's provider calls with
spec > config > default resolution; effective values are evidenced by child
`provider_usage` events, not echoed in the envelope. The model-facing
`spawn_agent` tool strips caller-authored `model`/`reasoning_effort` args:
provider knobs are operator decisions, not a capability a spawning model
grants its children.
### Fixed
- Delegate `--dry-run` now mirrors the runner's task normalization exactly:
malformed or blank `tasks[]` entries are rejected as `invalid_spec` instead of
silently planned, and a list-valued `tasks` field owns validation (even when
empty) before any legacy `task` fallback, matching real-run branch precedence.
- The model-facing `spawn_agent` tool strips caller-authored `index` args so
task-position evidence in durable `subagent_event` data cannot be forged.
- Workspace confinement no longer misreads leading POSIX environment
assignments as path arguments (#188): `TMPDIR=/tmp mix test` and
`PREFIX=/usr/local ./configure` pass, while literal outside paths,
redirection targets, and non-leading `NAME=VALUE` arguments keep failing
closed; the window resets after `;`, `&&`, `||`, and `|`. The residual
runtime-expansion vector (`VAR=/outside cmd $VAR`) is documented in
SECURITY.md: confinement is a defense-in-depth tripwire, not a sandbox.
## [0.1.6]
### Added
- Published the proc-pressure kernel-pressure evidence bundle (23 files,
gate-hardened) at `docs/benchmarks/scale/`, and the matching /scale
kernel-tax section: marginal threads/processes/RSS/kernel-CPU/involuntary
context switches per additional worker under two labeled machine
conditions, per-provider-call normalization from durable usage evidence,
the spawn-tax microbench, and the completion audits with the negative
result (199/205 loaded) published as the headline. The /scale hero line
and the N=8 transport-table cause cell were corrected to claims the
published bundles can back.
### Fixed
- `pixir delegate --help` (and the main help) now document `--timeout-ms` on
the attached form; the flag was always accepted there but only listed under
`delegate start`, so contract-of-record readers concluded it was invalid
(#204).
### Added
- Subagents-strategy delegate result envelopes now give every non-completed
child the same ready-made recovery commands the one-shot contract ships:
conditional `children[].resume_command` and `children[].diagnose_command`,
including children reported `running` at the collection horizon (their
writer lease fails closed if anything is genuinely alive). Start/lifecycle
snapshots stay terminal-only, and workflow-strategy children keep their
step-based evidence without these commands. Without `--json`, per-child
resume hints print on stderr, matching the one-shot mode contract (#204).
### Changed
- A `bash` call denied because the bounded write policy disables the shell now
surfaces as `kind: "bash_disabled"` ("shell is disabled by the bounded write
policy") instead of the misleading `write_policy_denied` ("write denied"),
with shell-free `next_actions` (`use_native_read_tools`,
`use_edit_or_write_within_allowed_globs`). The denial keeps exit code 3 but
is no longer terminal for the child's Turn: the model can adapt with native
tools instead of dying on a read-only command. Write-allowlist denials keep
`write_policy_denied` and remain terminal (#218).
### Added
- Delegate spec transport knob: `subagents.transport` (or top-level
`transport`) accepts `auto` | `websocket` | `http_sse`; invalid values fail
`--dry-run` with a structured error and the effective value is surfaced as
`limits.transport` (#205).
- Manager-level bounded auto-retry for read-only subagent children killed by
mid-stream websocket drops or provider-declared-retryable server errors,
with jittered re-queueing, an observable `retrying` lifecycle event, and a
retry confession on delegate envelopes (`children[].retry_attempts`,
`retry_max_attempts`, `current_attempt_index`, `retry_history`; present only
when a retry happened) (#205).
- `usage_summary.model` is now populated on provider usage events, and
subagent session ids are aliased in `pixir tree` projections (#216).
- Isolated subagent workspace snapshots accept extra exclusion directory names
(directory basenames, byte-exact, matched at any depth) via
`config :pixir, :subagents, snapshot_excluded_dir_names: [...]` or the
`:excluded_dir_names` snapshot option; built-in defaults (`.git`, `.pixir`,
`_build`, ...) always stay in effect, invalid names and a non-keyword
`:subagents` application env fail closed with structured errors, and the
effective list is confessed as `excluded_dir_names` in snapshot metadata and
runtime failure envelopes (never on validation failures, where no effective
policy ran) (#221).
## [0.1.5]
### Fixed
- Clean CLI exits now release Session writer leases across one-shot, `resume`,
and attached delegate (parent and children); stale leases from crashed runs
remain fail-closed behind `--force-release-writer-lease`. Headless
orchestrators no longer need the force flag on the happy path.
- `Subagents.Manager` survives late timeouts or cancels racing an
already-terminated child Session; the recorded timeout/cancel evidence is the
honest record either way.
### Added
- `pixir-delegate` skill family for orchestrating agents
(`.agents/skills/pixir-delegate*`): a host-neutral delegation judgment core,
real blind-run demonstrations, deterministic `fanout.sh`/`steer.sh` scripts,
and variants for Claude Code, Codex root agents, and Pixir-native
orchestration. Discoverable by Claude Code via `.claude/skills/`.
- Site route `/scale` with measured scale evidence (up to 64 concurrent
workers) and bounded public claims.
- Delegate CLI contract documentation for expensive orchestrators
(`docs/examples/delegate-cli-live/README.md`): a per-subcommand exit-code table,
the read/scope vs write-policy denial distinction (`outside_workspace` vs
`write_policy_denied`), and honest caveats for `next_actions` and
`observed_applied_writes`.
- `SECURITY.md` with private vulnerability reporting and a clear
tripwire-not-a-sandbox disclaimer, and `CONTRIBUTING.md` describing the real
workflow and invariants.
- Partial-write observability: bounded-write delegate envelopes now report
`observed_applied_writes` from child Session Logs as an at-least lower bound,
without changing existing `writes_applied_to` / `contract_status` fields.
### Changed
- Elixir requirement moved to `~> 1.20`; CI runs on Elixir 1.20.2 / OTP 29.
- Delegate error locations now carry machine-readable metadata (`json_pointer`,
`path`, `step_index`) alongside the human-facing 1-based field label.
### Fixed
- `mix check` no longer hangs after the test phase. The test step nested a BEAM
node under `cmd env MIX_ENV=test mix ...` that did not exit cleanly on teardown;
it now shells out with `MIX_ENV` as an environment variable and returns cleanly.
- Bash tool workspace confinement now rejects path escapes beyond `..` — absolute
out-of-workspace paths, `$HOME`/`~`, and symlink prefixes resolving outside the
workspace — before crossing the host boundary, surfaced honestly as
`outside_workspace` rather than a write-policy denial.
## [0.1.4] and earlier
See the release notes at
`docs/release-notes/open-beta-developer-preview.md` for the 0.1.0–0.1.4 developer
preview history (runtime truth and fanout honesty, ACP Registry readiness, and
runtime diagnostics).