🎉 Features
- Add Batuta to marketplace (#432)
- Complete Loop graph engineering and typed inputs (#427)
- Simplify the interface for everyday users (#440)
- Deliver the command palette operating surface (#441)
🐛 Bug Fixes
- Website font style
- Harden Loop runtime and graph execution (#438)
- Use candidate version in release dry-run
- Coalesce redundant ACP tool updates (#442)
- Resolve agent runtime recovery regressions (#447)
- Preserve run-agent session lifecycle (#446)
- Use migration timeout for tail replay
🔧 Miscellaneous Tasks
- (mise) Pin Go 1.26.6 (#433)
Release Notes
Features
Agents operate commands without the UI
Everything the palette does is reachable from the CLI, HTTP, UDS, and native tools, with the same reasons and the same gates. An agent supervising CompozyOS discovers a command, checks its contract, targets a client, invokes it, and follows the approval — never depending on a browser. (#441)
- Native tools:
compozy__cmd_palette_listreads the daemon-canonical catalog for the bound workspace, andcompozy__cmd_palette_invokeruns one command withid, optionalargs, and optionalclient. Availability, targeting, single-flight, and approval rules all still apply. - Every refusal is structured and carries the same text the UI row shows:
command_not_found,invalid_argumentsnaming the fields,no_attached_shell,multiple_clientslisting every attachment ID, andalready_running. - HTTP and UDS expose the catalog, clients, invocation, and stream under
/api/cmd-palette/*, plus rank signals, usage, pins, and personalization. Approvals are read and canceled through/api/tools/approvals/{id}. - Configuration parity is complete: bindings, aliases, pins, and personalization resets go through the same validated daemon paths Settings uses, and a change made by an agent reaches connected shells without a restart.
compozy approvals show|cancelis a new top-level verb for the tool-approval lifecycle behind any invocation.
compozy cmd-palette invoke session.new --client <attachment-id> -o json
compozy cmd-palette invoke <destructive-id> -o json # returns approval_pending + approval_id
compozy approvals show <approval-id> -o json # pending → terminal
compozy approvals cancel <approval-id>
Ask the agent when nothing matches
A query with no strong result no longer dead-ends. The palette offers one visually distinct Ask agent: '<query>' row; pressing Enter creates a session with the workspace's default agent and uses the query as the opening prompt. (#441)
- Nothing is sent before Enter. Typing never carries the query to a provider, and a rapid double Enter still creates exactly one session.
- A weak-but-real match keeps both the results and the fallback row; only a query below the served threshold is fallback-only.
- With no workspace default agent, Enter opens the agent picker first. If the session fails to start, the failure names its reason and the palette reopens with your query intact.
- Turn it off in Settings → Palette, or set
fallback_targets = []. Both report the same desired state, and the row disappears immediately.
[cmd_palette]
# The current runtime accepts "agent". Use [] to disable the fallback row.
fallback_targets = ["agent"]
Batuta in the Marketplace
Batuta v0.1.0-beta.3 ships as a curated community extension, so operators discover and install it from the Marketplace instead of going through the unverified GitHub-install consent flow. (#432)
- Its agent, skill, and Loop are resource-only and vendored for review, pinned to a deterministic archive digest.
- It installs from the
communitytier and still requires explicit enablement before it publishes its resources.
Correct one output, repair one lane
Operators can fix a settled node output without rewriting what actually happened, and can act on a single fan-out cell without disturbing its siblings. (#427)
- Amend output applies to a settled output while its run, node, or cell is parked, and appears only when the node declares an output shape to validate against. It shows the recorded original read-only beside the corrected value and takes a reason.
- Amendments are append-only: the recorded output is never rewritten, the corrected value becomes what resume and downstream reads see, and both stay visible in history and in a diff. Amending does not re-run consumers — pair it with Rerun from here.
- Run detail returns
amendments[]with bounded, redacted values, or a byte-size and content-hash summary for large data. No API reads an amendment's private output reference directly. - The control is available as
compozy loop node amend,POST /loop-runs/:id/nodes/:node/amend, andcompozy__loop_node_amend. --item(oritem_index) pauses, resumes, cancels, or kills one fan-out cell without touching the rest of the window.
compozy loop node amend --run-id <run-id> --node build --item 3 \
--payload '{"artifact":"dist/app-1.4.2.tgz"}' --reason "wrong tag captured"
Every domain opens inside the palette
The palette is not only a launcher — it browses. Sessions, Tasks, Loops, Jobs, Agents, Triggers, Marketplace, Bridges, Knowledge, Vault, Worktrees, Network channels, and Extensions each open as a view without leaving the overlay, and views stack so one selection can push the next. (#441)
- Four view kinds ship: list, detail, grid, and form. Lists carry domain chips with truthful counts and single-select semantics; a chip with zero matches names the filter and clears in one keystroke.
- State badges come from the shared status vocabulary and always pair a glyph with a label — never color alone.
- Selecting a row previews its metadata and sanitized text in a detail pane without stealing focus from the list, and the pane clears when the row disappears from another surface instead of showing stale content.
- Form views traverse typed fields in declared order, block an invalid submit on the first failing field, and discard values when you pop the view.
- Vault rows show names and metadata only. A secret value never enters a view, a preview, or a match highlight.
- A cold open shows a loading state, never a blank list dressed up as empty; an oversized list either scrolls virtually or states the exact
showing N of M. - Views stream patches, so a list already on screen updates in place as the runtime changes.
compozy cmd-palette list --source core -o json | grep palette.view.
# palette.view.sessions, .tasks, .loops, .jobs, .agents, .triggers,
# .marketplace, .bridges, .knowledge, .vault, .worktrees,
# .network-channels, .extensions
Extensions contribute commands and views
An extension can add its own commands and views to the palette from resources.cmd_palette, beside its tools. CompozyOS validates the contribution during extension build, extension validate, install, and development reload, and prefixes every local ID with the extension name — capture from the notes extension becomes ext.notes.capture. (#441)
- The action union is closed:
toolcalls a tool the same extension owns,viewopens one of its views,navigateopens a CompozyOS app, andurlopens an external link. Extensions cannot declare client operations. - A declarative view names a read-only tool as its source and returns the shared
v1view payload, which the daemon validates before rendering. A mutating, destructive, interactive, or open-world tool is rejected at validation time, so opening a view never starts an approval flow. - A programmable view sets
program: trueand is backed by the publicview.providersurface, with patch streaming for live updates. Start from the template withcompozy extension init notes --template view-provider-ts. - A command can ship a
default_shortcut. If that chord already belongs to something else, the default stays dormant and the conflict is visible in Settings instead of silently stealing the key. - Destructive extension commands must declare themselves and supply confirmation copy; the same approval gates apply to them as to core commands.
resources: {
cmd_palette: {
commands: [{
id: "capture",
title: "Capture note",
section: "Notes",
icon: "pencil",
action: { kind: "tool", tool: "capture_note" },
default_shortcut: "alt+shift+KeyN",
}],
views: [{
id: "recent",
title: "Recent notes",
kind: "list",
source: { tool: "list_recent" },
}],
},
},
Fan-out settles with an honest count
A fan-out can declare how it settles, and a partial result stays partial everywhere it is read instead of being rounded up to success or down to failure. (#427)
strategyacceptswait_all(the default),fail_fast,race, andbest_effort.best_effortrequires both a threshold — a percentage like66%or a count like{ count: 2 }— and an explicitmissing: acceptable.- A collect result is
succeeded,partial, orfailed, and its output carriestotal,succeeded,failed,canceled,coverage_rate, andpartial. - Live counts read through
nodes.<fan-out-id>.progress.*—total,succeeded,failed,canceled,running,pending,settled,success_rate,failure_rate— with the shortprogress.*form inside the fan-out body. Rates are0for an empty collection. - The run page separates lanes that succeeded, lanes that failed, lanes the strategy canceled, and lanes that never materialized because the window did not open them. Partiality is a run-level fact (
completion_state), so it readspartialin the outcome card, the run lists, and a diff. A wide fan-out reports aggregate counts instead of one row per lane. - The fan-out window has no daemon-wide ceiling; logical width stays bounded by each node's positive
max_fan_out. Write-time validation rejects a negativefan_out_width.
- id: inspect_files
class: control
kind: fan-out
collection: "{{ .nodes.changed.output.files }}"
bind_as: file
strategy:
kind: best_effort
threshold: 66%
missing: acceptable
Loop inputs that know what they point at
A Loop input can declare the kind of thing it accepts, and every surface that edits inputs now validates against the real workspace catalog before anything starts. A wrong agent name, a retired skill, or an unsupported model is caught at the field that caused it instead of failing deep inside a run. (#427, #438)
- New input types:
agent(an exact agent name),refwith a closedref.kindofskill,loop,worktree,session,workspace, orsecret, andruntime({ provider?, model?, reasoning? }, accepting exact custom model IDs). A string-like input may still declareenum, and those choices take precedence over a catalog picker. - Effective values resolve one field at a time: run input, then workspace config, then global config, then the definition default. The daemon validates the winning value immediately before a dry run or a run, including entity existence and runtime support.
- A failure starts no run, creates no task and no external action, and returns the same
input_validationpayload —{ loop, field, kind?, value?, origin, reason }— over HTTP, UDS, CLI, native tools, and the web form, which attaches the reason to that field. - The same typed controls appear wherever Loop inputs are edited: the run form, scheduled automation, event-trigger mappings, fork and amend flows, and human-request answers annotated with
x-compozy-kind. Every surface submits the exact stored identifier — a display label is never treated as a reference. - The run form reuses the canonical runtime selector instead of free-text provider, model, and reasoning fields. From the CLI, a runtime input also accepts the compact
provider/model@reasoningform, with-leaving provider or model unset. compozy loop runprompts in an interactive terminal only for supported required inputs still missing after defaults;--no-promptfails instead, and structured or non-interactive input never prompts, so scripts stay deterministic.- Secret inputs expose Vault reference names and metadata only — a secret value never enters a catalog or an error payload. Two native tools back the new pickers:
compozy__agent_listand metadata-onlycompozy__vault_list. params.runtimebinds a declaredtype: runtimeinput through an exact reference such as{{ .inputs.worker_runtime }}. Provider, model, and reasoning are validated at bind time, while literal runtime objects keep compile-time typo detection, and the resolved runtime recordsinputprovenance.- If a saved reference is no longer listed, the field keeps the exact value visible so the daemon returns a field-level error instead of silently replacing it.
inputs:
reviewer: { type: agent, default: code_reviewer }
release_token: { type: ref, ref: { kind: secret } }
worker_runtime:
{ type: runtime, default: { provider: codex, model: gpt-5.5-codex, reasoning: high } }
Loops can stop and ask you a question
Two new ways for a Loop to bring a person into a run: an ask node parks the run until someone answers a question, and a review block parks an action node until someone decides on the arguments it is about to run with. Both are answerable from the web run page, the CLI, HTTP, UDS, and native tools, so an agent supervising a Loop never depends on the web UI to unblock it. (#427)
compozy loop requestslists what is waiting,compozy loop requestreads one, andcompozy loop respondanswers it. Agents getcompozy__loop_requests,compozy__loop_request, andcompozy__loop_respond.- The run page's Needs you region presents one question at a time — a "Question 1 of N" header steps through several instead of stacking forms. The bounded redacted context preview and the node and generation that asked sit behind a closed Details disclosure.
- The answer form renders only what the daemon authorized. An ask generates its fields from the
expectschema, with enum values as choices and booleans as Yes or No; a review shows the proposed arguments with that node's own decision allowlist, so an unauthorized decision is absent rather than a disabled button. Onedit, the fields arrive pre-filled with the proposed arguments. - An answer that fails the shape comes back with the failure on the field that caused it and the request stays open. A request someone else already answered — or whose run has ended — shows the recorded outcome instead of a form.
- Each fan-out lane carries its own request, named by lane and answerable independently. The waits rail counts pending requests alongside timers and events.
# a review block on an action node
review:
when: inputs.environment == "production"
prompt: "Review the production release"
decisions: [approve, edit, reject, respond]
responders: { agents: deny }
on_reject: { route: repair_release }
compozy loop respond --run-id <run-id> --node publish --decision approve
Loops take one path and tell you why
A Loop graph can now pick exactly one forward path with a route control node, and a gate verdict can route the same way, instead of forcing authors to encode every choice as nested branches. Each decision is recorded durably, so an operator or an agent reads why a run went the way it did rather than inferring it. (#427)
- A
routenode checks its CEL conditions in declaration order and takes the first match, falling back to a mandatorydefault. Every destination must be a unique direct forward edge. - A broken condition fails closed with
predicate_evaluation_failed; it never falls through to the default. - Gate verdicts (
pass,fail,error,timeout,invalid_output) route tocontinue,revise,next_generation,escalate,halt, or an in-body forward target written as{ route: node_id }.approvalaccepts onlyescalateorhalt, so an object route cannot slip past a pending approval. - Run status carries
generations[].route_causes— the route node or gate, the selected forward node, the lane index, the cause, and the time. It is read from the durableroute_takenevent, so HTTP, CLI, native-tool status, and SSE replay agree.
Migration notes: the branch gate action is removed and is now rejected at authoring time.
- id: classify
class: control
kind: route
routes:
- { when: nodes.score.output.value >= 0.8, to: publish }
- { when: nodes.score.output.value >= 0.5, to: revise }
default: reject
Pin, rename, and bind any command
The palette learns your workspace. Pins float the commands you always want first, ranking signals push the ones you actually use, aliases give a command your own vocabulary, and any command can take a chord — including a system-wide one on the desktop app. (#441)
- Pins and recents are workspace-scoped and shared across every attached client, so a pin made in the desktop shell shows up in the browser tab.
personalization = falseturns ranking and recents off as one desired state. - An alias is 1–32 characters with no whitespace, unique in the workspace, and searchable alongside the command's title.
- Bindings, aliases, and pins are validated against the complete effective keymap. A conflict names the command that currently owns the chord or alias and stores nothing;
--overwritetransfers it as one atomic change. - The desktop shell registers global hotkeys —
meta+shift+Spacesummons CompozyOS with the palette open by default — and reports per-machine truth for each one: active, captured by another app, permission required (with a shortcut into macOS Accessibility settings), or unsupported. A browser tab shows the section disabled with the reason requires desktop shell rather than pretending. - Settings → Palette exposes the agent fallback and personalization; Settings → Layouts → Shortcuts owns the keymap, aliases, and the global section.
compozy cmd-palette pin palette.view.sessions --workspace acme
compozy cmd-palette alias set session.new new --workspace acme
compozy cmd-palette bind palette.view.tasks meta+shift+KeyY --workspace acme
compozy cmd-palette bind palette.summon.global meta+shift+Space --global
compozy cmd-palette personalization show --workspace acme -o json
[cmd_palette]
personalization = true
[cmd_palette.aliases]
"session.new" = "new"
[window_manager.global_shortcuts]
"palette.summon.global" = "meta+shift+Space"
Runtime speed is part of the runtime
speed joins provider, model, and reasoning as a first-class part of a Loop runtime selection, and reports the same value everywhere it is observed. (#438)
- Speed is accepted on Loop runtime inputs, per-node runtimes, Loop defaults, and
config.tomlLoop runtime defaults, and appears in resolved provenance across CLI, HTTP, UDS, native tools, SSE, and web inspection. - The web run form reuses the existing runtime selector's Fast control rather than introducing a parallel concept, and the run inspector shows resolved provenance read-only.
- CompozyOS reports whether speed was applied or is unsupported by the chosen provider instead of inventing support it cannot deliver.
Migration notes: the session creation profile moves to v3 as a hard cut, with no v2 branch.
runtimes:
worker: { provider: codex, model: gpt-5.4, reasoning: high, speed: fast }
judge: { provider: claude, model: opus, speed: normal }
# the compact CLI form; "-" leaves a field unset, so speed-only intent is -/-:speed=fast
compozy loop run --name release --input worker_runtime=codex/gpt-5.4@high:speed=fast
The command palette runs CompozyOS
⌘K (or ⌘⇧P) opens a palette the daemon owns. Every command — shell actions, window and desktop moves, domain views, settings, and extension contributions — is registered once in the runtime and projected to every surface, so the web app, the desktop shell, the CLI, HTTP, UDS, and native tools read the same catalog with the same availability truth. (#441)
- A row is available or it is not, and the daemon says why. A command that needs an attached shell reports
requires an attached shellin the row itself instead of failing after you press Enter. - Commands that need input collect it inline as typed arguments —
text,password,dropdown,checkbox— before anything runs. A destructive command carries its own confirmation title and confirm verb. - Execution is single-flight per command: a second invocation while one is still running returns
already_runninguntil the first reaches a terminal result, so a double Enter cannot run something twice. - A destructive command goes through the existing tool-approval path and returns
approval_pendingwith a stable approval ID.compozy approvals show|cancel <id>follows or ends it; approve runs exactly once, deny or cancel ends it with no effect. - A command that acts on a shell targets one attached client. With a single attachment it auto-selects; with several it asks for an explicit client and lists every attachment ID instead of guessing.
- ⌘K on a selected row opens a filterable action panel anchored to that row: the runnable action plus Pin, Set alias, and Set shortcut. Unavailable rows expose only those meta-actions and the daemon's reason.
- The catalog is live. Installing an extension, changing a binding, or pinning from another window updates open palettes without a reload.
compozy cmd-palette list --available=false -o json # every command with the daemon's own reason
compozy cmd-palette inspect session.new -o json # action, arguments, execution policy, risk
compozy cmd-palette clients -o json # the targeting source of truth
The interface speaks plain words at a legible size
Every end-user surface moved one step up the legibility ramp and one step toward ordinary language. Body text goes from 13.5px to 15px, item titles from 15 to 16, buttons and rows get real height, the radius ladder rebases on 8, and the canvas warms up — so the interface stops asking for a magnifying glass. (#440)
- Home's first run tells the truth. Instead of seven zones filled with zeros, a fresh install shows one heading and the three starts that actually exist. A machine with an agent already running is never told nothing has happened.
- Some labels now use the word people say, while the runtime keeps its canonical noun: the dock reads Connections (bridges) and Permissions (sandbox); Settings reads Remote access (gateway), Notifications (attention), Diagnostics (observability), and groups them under Personal. The old names stay searchable.
- An alias is a label and nothing more. Code, wire payloads, CLI verbs, config keys, and generated references keep the canonical name, and the canonical noun is always one step deeper in the UI.
- "Daemon" leaves the end-user surfaces for "CompozyOS" or "this machine" across gateway, sessions, onboarding, marketplace, automation, tasks, vault, loops, and settings. Sessions get a conversation glyph instead of a terminal one.
- Small caps labels become sentence case by default, with uppercase available as an explicit variant.
- Plain language never hides the machine: install, setup, and
config.tomlstill run through a terminal, and this release makes no claim of a no-terminal path.
compozy bridge list # the dock reads "Connections"
compozy gateway status # Settings reads "Remote access"
Time travel — compare, rerun, and fork a Loop run
Durable Loop history became something you can act on. diff reads what changed, rerun opens a new generation from a settled node in the same run, and fork starts a linked run from a historical generation without changing its source. (#427)
compozy loop diff --run-id <id> --generation 1 --against-generation 2compares two generations;--against-runcompares two runs of the same Loop and marks different pinned definitions. Large values return their byte size and SHA-256 content hash instead of an oversized inline payload.compozy loop rerun --from-node verifyre-runs the selected node and its transitive dependents while unrelated settled cells carry forward;--itemaddresses one fan-out lane. The new generation has originoperator_rerun.compozy loop fork --generation 2pins the source run's executed definition: generation 1 is a settledfork_seedbaseline and generation 2 executes the body with the source inputs plus any validated overrides. Lineage is two-way — the child carriesforked_from, the source listsforks.- In the web UI, Compare… on an Inspect generation row opens a deep-linkable comparison page whose node rows group by the same
changed / rerun / skipped / carried / verdictvocabulary the CLI prints, and Fork from here pre-fills the source run's declared inputs. Two identical generations render an explicit "nothing changed" state. - Pass
--request-idto retry a rerun or fork after a transport failure: the same key with identical inputs returns the committed result, and a changed request under a reused key returnstimetravel_key_reuse. - Agents need the
loops.timetravelcapability and getcompozy__loop_diff,compozy__loop_rerun, andcompozy__loop_fork. Diff is an ordinary workspace-scoped read. An agent cannot rerun its own executing run, but it may rerun its own terminal run.
compozy loop diff --run-id <run-id> --generation 1 --against-generation 2
compozy loop rerun --run-id <run-id> --from-node verify --reason "retry verification"
compozy loop fork --run-id <run-id> --generation 2 --input service=payments
Fixes
A burst of tool updates no longer drops the provider
An ACP provider can emit hundreds of state-equivalent updates for a single tool call. Those duplicates filled the active prompt's bounded event channel, stalled delivery, and disconnected an otherwise healthy provider. CompozyOS now keeps one canonical projection per tool call for the duration of the prompt. (#442, fixes #439)
- Only redundant nonterminal updates are suppressed. A new title, name, kind, input, or prechecked state still comes through, and terminal results and prompt completion keep their order.
- The projection is prompt-scoped and keyed by the current
tool_call_id; it is discarded when the prompt ends and never enters a session, workspace, or global store. - Public event shapes are unchanged — nothing about the session transcript contract moved.
- Verified against 1,100 identical in-progress updates followed by a terminal one: both the original prompt and a follow-up completed with a single call/result pair and no disconnect.
A finished Loop leaves nothing running
A completed or failed Loop run could leave live descendant work behind — including a next-generation source task sitting in ready with no task run attached. The terminal transaction now drains every open descendant, covering ready-only tasks and needs_attention runs, on both normal terminal settlement and coordinator execution failure. (#438)
- The public reproduction ends with zero open tasks: the run reports failed, its next-generation task is canceled, and no descendant survives.
A guarded history reference no longer crashes generation 1
A Loop that referenced previous.* defensively still failed on its first generation, because the history namespace did not exist yet when templates were evaluated. The complete shape of previous.* and the generation history namespaces is now defined before evaluation, so documented guarded references validate and generation 1 runs. (#438)
- History construction is topology-aware, so a node sees the namespaces its position actually implies.
- Template and materialization failures stay inside the node lifecycle instead of escalating into an opaque coordinator failure.
- Canonical compiler, linter, namespace, coordinator, and control-flow suites cover the behavior.
{{ if .previous.generation }}
The prior quality gate returned {{ .previous.verdicts.quality.outcome }}.
Blocking issues: {{ .previous.verdicts.quality.blocking_issues }}
{{ end }}
Fan-out filters actually filter
A filter on a fan-out node was accepted at authoring time but never applied, so batching and max_fan_out still saw the whole candidate list. Each filter is now evaluated against the raw candidate before batching and branch limits. (#438)
- The candidate
item, its originalindex, the fan-out alias, and outer fan-out aliases are all available during compilation and evaluation. - Original order and candidate indexes survive filtering.
- Zero matches is a valid zero-branch materialization, not an error.
- A predicate failure routes through the existing
on_eval_errorpolicy instead of being silently ignored.
- id: inspect_files
class: control
kind: fan-out
collection: "{{ .nodes.changed.output.files }}"
filter: "item.endsWith('.go')" # now decides what gets batched
batch_size: 1
max_fan_out: 8
Four runtime regressions around prompts, permissions, and settlement
A cluster of failures where the runtime reported the wrong thing about its own state. (#447)
- Prompts stop replaying history (fixes #399). Submitting a message sent the entire persisted chat transcript to the session prompt endpoint. Only the newest user message goes now, with its original message ID and retry idempotency preserved.
- Permissions follow the live agent (fixes #415). The observer carried a duplicate permission-mode resolver instead of the resource-backed agent catalog the daemon uses. Live snapshots are built from effective permissions and cached by runtime identity and revision, so a revision change can no longer leave stale permissions in place; a stopped session's fallback snapshot stays deliberately shallow.
- A committed result survives a failed publish (fixes #435). A pre-commit lease failure and a post-commit publication failure looked identical, so an already-settled run could be failed and settled a second time. A claimed run fails only when completion returned no committed run.
- A crashed process is reported as crashed (fixes #436). A non-zero exit code or a signal is now classified as a process failure and maps to the process-exited stop cause. A clean exit with code
0after a transport failure stays on the transport path and keepserror, soagent_crashedmeans the subprocess actually died.
Managed run-agent workers keep their lineage and let go
Two lifecycle bugs in Loop run-agent actions, both reproduced against v0.3.0-beta.18: a managed worker lost the trail back to the session that started it, and a worker could outlive the Loop cell it belonged to. (#446, fixes #444 and #445)
- A managed worker now records the nearest originating session as informational parent lineage — parent and root are readable from the session — without borrowing or hijacking that origin session.
- When a Loop cell settles successfully, the run-owned worker binding closes and durable terminal cleanup is enqueued in the same atomic step. Cancellation and terminal failure follow the same path, and cleanup cannot run twice.
- A retryable output failure keeps the same worker session active instead of orphaning it, so a retry reuses the worker and only terminal settlement ends it.
- No public API, schema, migration, or config key changed; existing Loop and session reads simply expose corrected stored state.
Recover a Loop-owned task run without losing its place
A Loop worker task run parked in needs_attention had no honest way back. Generic subprocess-health escalation swallowed Loop-owned crashes, and task run recover re-enqueued a run that no longer belonged to its Loop. (#447, fixes #437)
- A confirmed agent crash inside a Loop-owned task run stays out of the generic escalation path and projects into the Loop's own node control and event model as worker attention.
task run recovernow fails the parked source run, creates and links a child run, and rebinds it to the exact same Loop node and item with the next attempt and epoch — all atomically. Workspace, runtime selection, designation, worktree, network, capabilities, and metadata stay attached to that cell, and attention plus death-streak state is cleared.- Recovery diagnostics point where they should: a run that needs attention names
task run recover, while an active run names cancellation. - No schema, migration, or config key changed — recovery reuses the existing
wait_interventionattention flag and the existing Loop event vocabulary.
compozy task run recover <run-id> --reason "operator recovery" -o json
Removing the suggested Home folder no longer breaks the desktop
Onboarding seeded every daemon registration into the selectable project draft, including the internal operator-home registration that Global runs on. Removing the suggested Home folder deleted that registration and left a desktop where dock apps took focus but opened nothing. (#440)
- Onboarding now partitions project workspaces from the operator home, so that row can never be seeded, added, or deleted as a project.
- The fix is covered by the canonical onboarding suite, with three cases that fail against the previous behavior.
The daemon owns a managed worker's outcome
A managed Loop worker session could call complete or fail and race the daemon's own validated action result, so what a generation recorded depended on which side got there first. The daemon is now the single terminal authority for managed workers. (#438)
- A worker session may heartbeat while it holds the lease, but terminal settlement calls are denied by session lineage.
- A generation settles as succeeded only with schema-valid structured output; an invalid capture terminates as
invalid_outputinstead of passing. - The exact validated object round-trips through inline and content-addressed storage, including downstream template and CEL hydration, so a large output no longer loses required fields.
Contribution data is temporarily unavailable.
30 files published by the GitHub release workflow.
Browse downloads
- checksums.txt2.7 KB · 63 downloads
- checksums.txt.sigstore.json9.8 KB · 54 downloads
- compat.json70 B · 16 downloads
- compozy-0.3.0-beta.19.tar.gz184 MB · 4 downloads
- compozy-0.3.0-beta.19.tar.gz.sbom.json791 KB · 2 downloads
- compozy-0.3.0.beta.19-1.aarch64.rpm48 MB · 2 downloads
- compozy-0.3.0.beta.19-1.aarch64.rpm.sbom.json2.9 KB · 2 downloads
- compozy-0.3.0.beta.19-1.x86_64.rpm52 MB · 3 downloads
- compozy-0.3.0.beta.19-1.x86_64.rpm.sbom.json2.8 KB · 2 downloads
- CompozyOS-0.3.0-beta.19-linux-x64.AppImage212 MB · 5 downloads
- CompozyOS-0.3.0-beta.19-linux-x64.deb174 MB · 5 downloads
- CompozyOS-0.3.0-beta.19-mac-arm64.dmg203 MB · 13 downloads
- CompozyOS-0.3.0-beta.19-mac-arm64.zip203 MB · 5 downloads
- CompozyOS-0.3.0-beta.19-mac-x64.dmg213 MB · 3 downloads
- CompozyOS-0.3.0-beta.19-mac-x64.zip212 MB · 2 downloads
- compozy_0.3.0.beta.19_amd64.deb52 MB · 1 downloads
- compozy_0.3.0.beta.19_amd64.deb.sbom.json2.6 KB · 1 downloads
- compozy_0.3.0.beta.19_arm64.deb48 MB · 2 downloads
- compozy_0.3.0.beta.19_arm64.deb.sbom.json2.6 KB · 1 downloads
- compozy_darwin_arm64.tar.gz50 MB · 33 downloads
- compozy_darwin_arm64.tar.gz.sbom.json316 KB · 1 downloads
- compozy_darwin_x86_64.tar.gz52 MB · 1 downloads
- compozy_darwin_x86_64.tar.gz.sbom.json316 KB · 1 downloads
- compozy_linux_arm64.tar.gz48 MB · 3 downloads
- compozy_linux_arm64.tar.gz.sbom.json320 KB · 1 downloads
- compozy_linux_x86_64.tar.gz52 MB · 92 downloads
- compozy_linux_x86_64.tar.gz.sbom.json320 KB · 1 downloads
- compozy_windows_x86_64.zip52 MB · 7 downloads
- compozy_windows_x86_64.zip.sbom.json330 KB · 1 downloads
- install.sh8.0 KB · 5 downloads
- Source code (tar.gz)
- Source code (zip)