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