# Changelog All notable changes to this project will be documented in this file. ## [Unreleased] - **BREAKING — removed the deprecated `--task-id` / `--requirement-id` flags.** `task` and `requirement` are ordinary subject kinds now, so there is no task/requirement-specific CLI or MCP surface. Use `--subject-id` for every kind on `queue enqueue`, `workflow run`, `workflow prompt render`, and the `workflow list` filter (accepts a qualified `task:TASK-001` / `requirement:REQ-042` / `blog:BLOG-001` or a bare `TASK-001`); the `agent approve-hook` escalation context moves from `--task-id` to `--subject-id`. The matching `animus mcp serve` tool inputs (`animus.queue.enqueue`, `animus.workflow.run`, `.execute`, `.run-multiple`, `.list`) drop `task_id` / `requirement_id` for `subject_id`. `--title` and `--adhoc` are unchanged. The flags were already deprecated in v0.7 (TASK-247); callers that shell the CLI or MCP should pass `--subject-id` / `subject_id`. Note: because requirement runs no longer have a privileged kind-specific default, a requirement dispatched via `--subject-id requirement:REQ-001` without an explicit workflow ref uses the project default workflow (the old `--requirement-id` flag defaulted to `animus.requirement/plan`) — name the workflow explicitly, e.g. `animus workflow run animus.requirement/plan --subject-id requirement:REQ-001`. ## [0.7.0-rc.1] - 2026-07-03 **v0.7.0-rc.1 — Composable, multi-tenant platform (release candidate).** Cut from `release/0.7` (off v0.6.33 + the actor foundation). Portal upgrades to this RC for staged verification before promoting to `v0.7.0` final. - **Multi-kind plugins** — a plugin advertises `plugin_kinds` and satisfies many roles; discovery + preflight index by `serves_kind`. Protocol at `animus-protocol` v0.7.0-rc.2 (single-source git-dep; the in-tree `animus-plugin-protocol` copy was removed). Unblocks one Node process serving subject + config + queue + journal + chat. - **Cross-role host-sharing** — a process-global `ResidentHostRegistry` keyed by `(binary, mtime, spawn-context)`; `config_source` ⟷ `workflow_journal` collapse to one process. - **Security hardening** — `auto` secret key source prefers real key material (no locally-decryptable device-id default on servers); context-aware plugin signature policy + fail-closed org TOFU in non-TTY; discovery scope-filters BEFORE the `--manifest` probe (closes hostile-repo code-exec on the server-safe path). - **Execution environment as a plugin** — new `environment` PluginKind + `animus-environment-protocol` (`prepare`/`exec`/`exec_stream`/`teardown`); `EnvironmentClient`, `resolve_environment` `(subject_kind × harness)` routing, optional preflight role; the worktree materialization is thin-delegated to the env plugin behind a gated, zero-regression fallback (`ANIMUS_ENVIRONMENT_DELEGATE`). - Carries all v0.6.33 reliability fixes (plugin-handshake stderr capture + transient respawn retry) and the full per-user actor foundation (scoped config, owner-scoped schedules, isolated MCP). ## [0.6.11] - 2026-06-24 **v0.6.11 — `animus update` defers to avm.** - When the running binary is managed by [avm](https://github.com/launchapp-dev/avm) (the Animus Version Manager — it installs kernels under `~/.avm/versions/` and dispatches through an `animus` shim), `animus update` no longer self-replaces the binary (which would corrupt avm's version directory). It now prints the `avm install` / `avm use` commands and exits. Plain installs (`~/.local/bin`, `/usr/local/bin`, Cargo) are unaffected and still self-update. - `scripts/install.sh` and the docs now point multi-project users at avm. ## [0.6.10] - 2026-06-24 **v0.6.10 — first-party providers install without `--allow-shadow-builtin`.** - The reserved-provider guard (`enforce_provider_tool_policy`) no longer treats the legitimate first-party plugins as name squatters. A canonical `animus-provider-` (claude / codex / gemini / opencode / oai / oai-agent / oai-runner) published by a **built-in trusted publisher** (`launchapp-dev`) is the rightful owner of the name and installs with **no flag**. Untrusted orgs — and `--path` / `--url` installs (owner unknown) — still require `--allow-shadow-builtin`. `enforce_manifest_name_matches_repo` and the org-trust gate still run, so this is not a security loosening. - Corrected the misleading "reserved in-tree backend" error wording. These names are reserved so an **untrusted** plugin cannot silently hijack the core provider dispatch path — the providers are **plugins, not compiled into the kernel** (the resolver only resolves to installed plugins; there is no fallback, so the reserved names are an anti-squat guard, not native backends). ## [0.6.9] - 2026-06-24 **v0.6.9 — the npm-style project experience + core providers driven properly.** - **`animus.toml` project manifest + `install` / `add` / `remove`.** A committed `animus.toml` (`[project]` kernel, `[plugins]`, `[packs]`; deps as a version string, `{ git, tag }`, or `{ path }`) declares intent; `animus install` resolves it into `.animus/plugins.lock` and installs the set. `--locked` reproduces the committed lock exactly (npm-ci) and fails on manifest↔lock drift. `animus add ` / `animus remove ` mutate the manifest + install/uninstall. `animus init` scaffolds `animus.toml`, `.env.example`, and a merge-safe project `.gitignore`. Onboarding becomes `git clone && animus install`; a Dockerfile is `animus install --locked`. - **Plugin lock = source of truth.** `.animus/plugins.lock` is authoritative; the `plugins.yaml` registry is a derived projection regenerated on every mutating op. - **`.env` → secrets sync.** `animus install` provisions secrets from `.env` into the device-encrypted store (idempotent; blank `KEY=` values are skipped, never stored empty; imports are audited) and warns about keys declared (uncommented) in `.env.example` but still unset. - **Core providers driven properly by default.** Curated provider defaults now use proper transports instead of stdout scraping: **gemini** + **opencode** drive their CLI over **ACP** (`animus-provider-{gemini,opencode}` v0.3.0, thin wrappers on the shared `animus-provider-acp` client), **codex** over **MCP** (`animus-provider-codex-mcp`). `claude` and `oai` stay native (native session + approval hook / direct API). `provider_tool` ids are unchanged, so kernel model→provider routing is identical — the transport is an internal detail. - **Fixed** restart-resume of the legacy `oai-runner` / `animus-oai-runner` aliases: they canonicalize to `oai-agent` (the agentic provider), not `oai`. ## [0.6.8] - 2026-06-22 **v0.6.8 — install Agent Skills from GitHub.** - `animus skill install ` imports & normalizes any GitHub-hosted Anthropic "Agent Skills" `SKILL.md` (the standard Claude Code / Hermes / OpenClaw share) into an Animus skill. Source forms: `OWNER/REPO[@ref]`, repo/`tree`/`blob` URLs, raw `SKILL.md` URLs. Format detection: `animus:` frontmatter → native passthrough; otherwise Anthropic semantics — `name`/`description` map over, the markdown BODY → `prompt.system`, `allowed-tools` → `tool_policy.allow`. Handles single skills and `skills//SKILL.md` trees (downloads sibling assets). Foreign names are slugified (path-traversal-safe); provenance recorded under the `github-import` source (origin + format), shown by `skill list`/`info`. ## [0.6.7] - 2026-06-22 **v0.6.7 — platform-aware plugin lock + resident config_source.** - **Platform-aware `plugins.lock`** (schema 1.0→2.0): each entry records integrity per target triple (`targets: {triple → {archive_sha256, signature_bundle_sha256, installed_binary_sha256}}`). Install fetches each release's `SHA256SUMS.txt` and records the tarball sha for EVERY published platform, so a lock generated on one machine drives a verified `--locked` install on another (macOS → linux container). `--locked` verifies the current platform's tarball before extract; `lock verify`/ `list` are target-aware; 1.0 locks migrate to empty targets (re-install to upgrade). - **Resident config_source host**: the daemon spawns the `config_source` plugin ONCE and reuses it (process-global cache, re-spawn only on ConnectionLost, reaped at shutdown) instead of forking it per `config/load` (~50-60 forks/min → ~0). A CacheToken short-circuit skips the pack-overlay merge + validate when the source config is unchanged. Eliminates the fork churn amplified by v0.6.5's config/write. - **Resident config_source host.** The daemon no longer spawns + reaps a fresh `config_source` plugin process on every `config/load` / `config/write` (~4-5 forks per scheduler-loop pass under v0.6.3's leak fix). It now keeps EXACTLY ONE warm host per project root, reused for the life of the process and reaped only on a death-like failure (re-spawn once via the typed `HostError`/`classify` API), an in-place plugin-binary upgrade (mtime change), or graceful daemon shutdown. A CacheToken short-circuit additionally skips the pack-overlay merge + validate compile when the source token AND the resolved pack registry are both unchanged; a `config/write` or any token/pack change invalidates it so a real config change always recompiles. Net live-process count drops versus v0.6.5; correctness is preserved (never serves a stale compiled config across a real change). ## [0.6.6] - 2026-06-22 **v0.6.6 — cap plugin tokio runtimes to stop PID/thread exhaustion.** - **Plugin thread cap.** Every Animus stdio plugin uses a bare `#[tokio::main]`, which sizes its worker pool to *all* CPU cores. With v0.6's resident-plugin fleet (config_source + subject backends + queue + workflow_runner + providers + transport) that is hundreds of threads on a many-core host, exhausting the PID/thread budget — new forks (including the provider CLI an agent phase spawns) fail with `EAGAIN` and the run hangs at the agent phase. The daemon now injects `TOKIO_WORKER_THREADS=2` into every spawned plugin (`PluginHost::spawn_with_options`, after `env_clear()`) and into the workflow-runner subprocess, honoring an operator override set on the daemon env. Plugins are I/O-bound stdio RPC servers, so a small pool is sufficient. Fixes agent phases stalling on PID-capped hosts (e.g. Railway). Follow-up (tracked): make `config_source` resident to remove the per-tick respawn churn, and switch the plugin scaffold to a `current_thread` runtime for new plugins. **v0.6.5 — config write-back + reproducible plugin pinning.** - **Config write-back** (`config/write`): the kernel can now push the full validated canonical config to the installed `config_source` plugin, which resolves persistence. New `ConfigSourceClient::write`, CLI `animus workflow config {set,agent-set,agent-remove,workflow-set,workflow-remove}`, and MCP `animus.workflow.config.{set,agent-set,agent-remove,workflow-set,workflow-remove}`. Entity verbs are kernel-side read-modify-write (load → mutate → validate → write); writes are gated on the source advertising `config_write` and always validated before persist. Contract in `animus-config-protocol` v0.1.21. - **`animus.lock`** (`.animus/plugins.lock`): per-plugin integrity pinning (name, resolved version, artifact sha256, cosign signature-bundle sha256). `plugin install`/`update`/`uninstall` maintain it; `plugin install --locked` reinstalls + verifies the exact pinned set; `plugin lock {list,verify}` inspects + drift-checks; daemon preflight runs a non-fatal lock drift check. **Config write-back — manage agents/workflows/config through Animus.** - New OPTIONAL `config_source` write path: the kernel ships the entire validated canonical `WorkflowConfig` to the installed writable `config_source` plugin via `config/write` (animus-protocol v0.1.21), which persists it however it wants. Coarse full-model write only — no granular per-entity wire methods. - A source advertises writability with the `config_write` manifest capability. A read-only source (the default `animus-config-yaml`) omits it and the kernel refuses the write up front with an actionable error naming the plugin — never a partial write. - New CLI verbs under `animus workflow config`: `set` (full model from `--file`/stdin), plus entity read-modify-write `agent-set` / `agent-remove` / `workflow-set` / `workflow-remove` (load the RAW source model, mutate one entity, validate, write the full model back — pack overlays are NOT baked in). Validation is mandatory before any write. The DEFINITION-management surface; does not collide with the runtime `animus agent` verbs. - Matching MCP tools: `animus.workflow.config.set`, `.agent-set`, `.agent-remove`, `.workflow-set`, `.workflow-remove`. ## [0.6.4] - 2026-06-22 **v0.6.4 — pluggable chat conversation store (per-user + shared history).** - New OPTIONAL kernel plugin role `conversation_store` (mirrors `config_source` / `subject_backend`): when a `conversation_store` plugin is installed the CLI routes chat persistence through it, else falls back to the in-tree `FileConversationStore`. Not a required preflight role — chat works with zero plugins installed. - `ConversationMeta` gains `owner: Option` and `visibility {private,shared}` (serde-defaulted, back-compatible with existing on-disk conversations). - `animus chat` gains `--as-user ` (new/send/list/get/delete/rename/export/ search) and `--visibility` (new). `list --as-user X` returns X's own + all shared; direct-id denials return uniform "not found" to avoid existence leaks. - Wire contract in `animus-plugin-protocol::conversation_store` (`conversation/{create,load_meta,save_meta,append_message,load_messages,list,delete}`); `seq` is client-authoritative. Reference backend: `launchapp-dev/animus-chat-postgres`. ## [0.6.3] - 2026-06-22 **v0.6.3 — fix the config_source plugin process/connection leak (fleet-wide).** - `orchestrator-config` `config_source_client::resolve_plugin_base` spawned a fresh `config_source` plugin process for every `config/load` and **never shut it down**: `spawn_with_options` sets `kill_on_drop(true)`, but the child lives inside shared state held by the host's background reader task, so dropping the `PluginHost` handle didn't drop (or kill) the child — and a persistent stdio plugin never sees stdin EOF. The daemon calls this on **every config reload**, so each reload leaked one process (and, for DB-backed sources like `animus-config-postgres`, an open Postgres connection pool) — exhausting the container `pids` cap / Postgres `max_connections` over time (observed: 51 lingering processes on the launchapp portal → `sorry, too many clients`). Fix: borrow the host for handshake + `config/load`, then **always `host.shutdown()`** on every exit path so the process is reaped. Affects every v0.6 daemon (the default `animus-config-yaml` source leaked the same way, without the Postgres amplifier). ## [0.6.2] - 2026-06-21 **v0.6.2 — bootable Docker image + current default plugin pins.** - **Docker image rebuilt for v0.6** (the published image had been stale since v0.4.4 and no longer built): builder bumped `rust:1.89` → `rust:1.96` to match `rust-toolchain.toml`; added `libdbus-1-dev`/`pkg-config` (builder) and `libdbus-1-3` (runtime) for the `libdbus-sys` dep behind `animus secret`; COPY the root-level `flavors/` + the RBAC doc embedded via `include_str!` (with a `.dockerignore` exception); install the full required-role plugin set via `install-defaults` (incl. the now-published `config_source`); run the foreground `daemon run` instead of the detaching `daemon start`. - **Refreshed curated default plugin pins:** `animus-workflow-runner-default` v0.4.5 → **v0.4.8** (brings the autonomous approval gate into default installs) and `animus-queue-default` v0.3.0 → **v0.3.3**. ## [0.6.1] - 2026-06-21 **v0.6.1 — protocol crates leave the CLI; config-yaml ships.** - **`protocol` + `animus-config-protocol` moved out of ao-cli into `launchapp-dev/animus-protocol`** (tag `v0.1.19`), per the rule that no protocol/wire-type crates live in the CLI. The workspace dropped from 12 to 10 members and now git-deps both crates; all `animus-protocol` deps (`protocol`, `animus-config-protocol`, `animus-provider-protocol`, `animus-session-backend`, `animus-subject-protocol`) are pinned to the same `v0.1.19` tag so transitive `animus-subject-protocol` resolves to one source. Bumping the session-backend pin (v0.1.13 → v0.1.19) pulled the typed `SessionRequest.mcp_servers` field and the `SessionEvent::Interaction*` variants; the kernel session drivers were updated to populate / handle them. - **`launchapp-dev/animus-config-yaml` published (v0.1.0).** v0.6.0 required a `config_source` plugin to boot but the reference plugin was never released, so a fresh `animus daemon start` failed preflight with no installable fix. v0.6.1 publishes it; `DEFAULT_CONFIG_SOURCE_PLUGINS` (already pinned to v0.1.0) and `default-install.json` now resolve. `daemon start --auto-install` / `plugin install-defaults` install it automatically. ## [0.6.0] - 2026-06-20 **v0.6 — config sourcing is a plugin, and approval is protocol-layer infrastructure.** The first minor: two load-bearing, intentionally breaking changes. - **config_source plugin role (the YAML-is-a-plugin thesis).** The kernel no longer parses `.animus/*.yaml` in its runtime load path. The base `WorkflowConfig` (and the agent-runtime config derived from it) is sourced **exclusively** by an installed `config_source` plugin; the daemon now *requires* one (`RequiredRole::ConfigSource` in `daemon_default`). The YAML parser still ships in `orchestrator-config` as a library that the reference plugin `launchapp-dev/animus-config-yaml` links. The kernel keeps the compiler (pack overlays + validate + state-machine derivation). New `animus-config-protocol` crate (`config/load|validate|changed`, `ConfigModel`, `CacheToken`). The config_source plugin is spawned with the daemon's full env forwarded so non-secret `${VAR}` interpolation still resolves. **BREAKING:** a daemon without a config_source plugin fails preflight — `animus plugin install launchapp-dev/animus-config-yaml` (or `animus plugin install-defaults`) before upgrading. - **Approval infrastructure + LLM auto-approve.** `ApprovalPolicy` gains an `llm` mode (plus `evaluator_model` / `evaluator_instructions`) alongside `ask` (manual) / `allow` (approve-everything) / `deny`. In `llm` mode a judge model reads the gated tool call and returns allow/deny; it also auto-answers `animus.agent.ask` questions (flat + structured) from context, recorded in the unified inbox. The judge runs as a one-shot provider session with no MCP (cannot recurse); any failure falls back to manual escalation (never silently allows). Provider plugins own per-tool-call interception (claude natively via `--permission-prompt-tool`; others route through `request_approval`). See `docs/architecture/RFC-v0.6-approval-protocol.md`. ## [0.5.23] - 2026-06-21 **`oai-runner` routes to the agentic multi-step provider; robust reasoning-model result capture.** `canonical_tool_alias` mapped `oai-runner` / `animus-oai-runner` to `oai` (the one-shot completion provider `animus-provider-oai`), and providers are keyed by name-derived `provider_tool`, so the multi-step agentic provider `animus-provider-oai-agent` (`provider_tool = "oai-agent"`, write-capable, MCP, agent loop) was unreachable. A one-shot completion provider cannot run a reasoning / tool-calling model — it emits tool calls and a prose-then-JSON answer the one-shot drops, so the result JSON is never captured. `oai-runner` now resolves to `oai-agent`; `oai-agent` is reserved; raw `tool: oai` still selects the one-shot provider for callers that want the simple single-turn path. Also adds `collect_balanced_json_objects` + `extract_result_payload` to `animus-runtime-shared` (balanced-brace scan honoring JSON string literals, last-object-by-kind) so a chatty model's prose-then-JSON / multi-line / tool-call output is captured reliably instead of dropped by the whole-line scan. **BREAKING:** workflows using `tool: oai-runner` now require `animus-provider-oai-agent` installed; switch to `tool: oai` for the one-shot path. ## [0.5.22] - 2026-06-19 **Queue-enqueue stores the derived subject kind (completes the 0.5.21 fix).** 0.5.21 derived the subject kind from the `:` id to *resolve* the subject at enqueue, but then stored the queue dispatch with `SubjectDispatch::for_task_with_metadata(...)` — coercing it back to `task`. The daemon leased that dispatch, re-resolved it via `task/get`, and the owning backend rejected it: the workflow died in ~150 ms at the first phase with `failed to resolve subject context for ':'`. This broke every queue-driven workflow for non-task subjects (e.g. a Postgres `song:SONG-001`), across the control surface and the HTTP/GraphQL transports that route through it. ### Fixed - **Control-routed `queue/enqueue` now stores the dispatch with the subject's real kind** via `SubjectDispatch::for_subject_with_metadata( subject_ref_from_qualified_id(&resolved_id), ...)`. Plugin-backed custom kinds lease + resolve via `/get`; `task`/`requirement` keep their canonical kinds; bare ids still default to `task`. Regression test: `enqueue_dispatch_preserves_custom_subject_kind`. ## [0.5.21] - 2026-06-17 **Queue-enqueue honors custom subject kinds.** Control-routed `queue/enqueue` built its `SubjectRef` with a hard-coded `SubjectRef::task(id)` when the in-tree task store missed, so a plugin-backed subject with a non-task kind (e.g. a Postgres `song:SONG-001`) was resolved through `task/get` and rejected by its owning backend ("is not a task subject"). No custom subject kind could be enqueued — the queue-driven workflow trigger silently failed for them. ### Fixed - **`queue/enqueue` derives the subject kind from the `:` id qualifier** (`song:SONG-001` → resolve via `song/get`). `task:`/`requirement:` still map to the canonical in-tree constructors; a bare id (no prefix) defaults to task for backward compatibility. New `subject_ref_from_qualified_id` helper in `ops_queue/control_routing.rs`, with a unit test. ## [0.5.20] - 2026-06-17 **Preflight requires any subject backend, not forced task/requirement kinds.** The daemon preflight required-role set hard-coded `subject_kind:task` and `subject_kind:requirement`, forcing every deployment to install those two specific backends even when a single custom subject backend (e.g. a Postgres `kind=song` backend) was the only subject store in use. This was a design flaw: the daemon should require *a* working subject backend, not dictate which kinds it serves. ### Changed - **`RequiredRole::AtLeastOneSubjectBackend` replaces the hard-coded `SubjectKind("task")` + `SubjectKind("requirement")` roles** in `PluginPreflightSpec::daemon_default()`. Preflight now passes when any `subject_backend` plugin is installed, regardless of the subject kinds it claims. Auto-install maps the new role to `animus-subject-default`. The `SubjectKind(_)` variant is retained for custom specs but is no longer part of the daemon default. The doctor `plugins` check mirrors the same posture. ## [0.5.19] - 2026-06-16 **Queue-enqueue plugin-subject fallback.** Control-routed `queue/enqueue` resolved the subject only through the in-tree task store, which has been empty since v0.4.12 (subjects now live in `subject_backend` plugins). Every enqueue of a plugin-backed subject failed with "task not found", silently breaking the queue-driven workflow trigger over the HTTP/GraphQL transports. ### Fixed - **`queue/enqueue` now engages the subject-plugin fallback**, mirroring the workflow-run path: try the in-tree task store first, then resolve via `subject_resolver().resolve_subject_context()` and dispatch with the project's `default_workflow_ref`. The subject *read* path got this fallback in an earlier fix (`bdc7b310`); the enqueue path was missed, so the queue trigger could not see any plugin-backed subject. Found via live transport E2E. ## [0.5.18] - 2026-06-16 **Device-encrypted secret backend.** A second `animus secret` backend that seals secrets in a `0600` file with a device-bound key — an alternative to the OS keychain for hosts where the keychain is awkward (a macOS binary whose signature changes re-prompts on every access) or absent (a headless Linux box with no session keyring). ### Added - **`device` secret backend.** Secrets live AEAD-sealed (ChaCha20-Poly1305) in `~/.animus//secrets/secrets.enc.v1`. A random master key seals the data and is itself wrapped under a configurable **key source**, so the wrapping key can rotate without re-encrypting every secret. All crypto is pure-Rust RustCrypto (`chacha20poly1305`, `argon2`, `hkdf`, `zeroize`), honoring the rust-only dependency policy. - **Four key sources** (`secrets.key_source`): `device-id` (`HKDF(machine-id + per-install salt)`, cross-platform, no prompt — the default), `user-key` (operator 32-byte key via `ANIMUS_SECRET_KEY` or `key_file`), `passphrase` (`Argon2id` over `ANIMUS_SECRET_PASSPHRASE`, env-driven and script-safe), and `auto`. - **`animus secret migrate --to [--remove-source]`.** Copies every secret between backends, verifying each value before optionally clearing the source. `--remove-source` reports any deletions that failed instead of claiming a clean clear. - **Global `secrets` config block** (`~/.animus/config.json`): `backend` (`auto`|`keyring`|`device`|`env`), `key_source`, `key_file`. `auto` keeps existing keyring installs on the keyring (never strands secrets) and uses the device store once one exists for the scope. ### Notes - The device store **defeats off-device theft** (a copied file / errant backup is undecryptable) and **raises the on-device bar**, but does **not** defend against a live attacker running as your user. See `docs/architecture/secret-backends.md` for the full threat model. - The OS-hardware key sources (Secure Enclave / DPAPI / TPM) are deferred: Secure Enclave re-introduces the keychain prompt this feature exists to avoid, DPAPI can't be built/tested off-Windows, and TPM's `tss-esapi` links a C library. `device-id` is the shipped cross-platform default. ## [0.5.17] - 2026-06-16 **Plugin release/update hardening.** Recommend the binary-shipping queue plugin, and warn loudly when a stale one is installed. ### Changed - **Recommended `animus-queue-default` pin bumped v0.3.0 → v0.3.3.** v0.3.0 shipped no release binaries (so `animus plugin install …@vX` and `plugin update` failed with "no release asset matched platform") and predated the dedup-safe enqueue + precise-wake fixes. v0.3.3 is the first release that ships platform binaries. ### Added - **Stale queue-plugin preflight warning.** The daemon surfaces a non-fatal warning at startup when an installed `queue` plugin is below the precise-wake floor (v0.3.2) — "lacks precise-wake … upgrade with `animus plugin update`" — instead of silently falling back to the heartbeat for reactive dispatch. Mirrors the existing workflow-runner under-pin warning. ## [0.5.16] - 2026-06-16 **Daemon queue-dispatch reliability.** The enqueue → lease → run → drain pipeline now works end to end — two compounding bugs that stranded queued work are fixed — plus foreign-skill log-spam suppression. ### Fixed - **Dispatch queue never drained — leases were starved.** The daemon eagerly spawned every installed `subject_backend` plugin at startup and held them alive, exhausting the plugin-process cap (50). The queue plugin's spawn-per-call lease was then refused, so enqueued work sat `pending` forever. Subject plugins now spawn **lazily** on first route to their kind (bounded LRU host cache with lease-pinning + per-plugin spawn locks), so a project only spins up the backends it actually uses. - **Completed queue entries were stranded as `assigned`.** The `queue/completion` request passed the real run `workflow_id`, which never matched the id the queue plugin synthesizes at lease time, so the plugin refused to prune the entry. The daemon now omits it (the unique `entry_id` + `workflow_ref` identify the entry), so dispatches drain `pending → assigned → completed → gone`. - **Daemon foreign-skill parse-warning spam.** The daemon no longer sprays `could not parse markdown skill` warnings for foreign agent-host skill dirs (`~/.claude`, `~/.codex`, `~/.cursor`, `~/.config/opencode`) on every tick, which could bloat `daemon.log` and fill the disk. ### Changed - **`animus mcp auth` auto-detects advertised OAuth scopes.** When neither `--scopes` nor config `scopes:` is set, the flow now requests the server's advertised `scopes_supported` from discovery metadata (RFC 8414) instead of requesting nothing. Scope precedence is now `--scopes` > config `scopes:` > advertised `scopes_supported` > none. Auto-detected scopes are clearly marked as such in the consent preview, `--dry-run` output, and the `--json` envelope (new `scopes_auto_detected` flag), and can be narrowed with `--scopes` or fully opted out with `--scopes none` (or config `scopes: [none]`) to force the prior no-scope server-default behavior. Scope resolution and the consent gate now run after discovery (a read-only public-metadata GET) so the preview shows the real resolved scopes before the browser opens. ([`docs/reference/mcp-oauth.md`](docs/reference/mcp-oauth.md)) ### Fixed - **`animus mcp auth` against servers that require a scope** (e.g. Robinhood's trading MCP, which advertises `["internal"]` and rejects an empty-scope authorize). Requesting the advertised scope set by default makes these servers authenticate without the user having to discover and pass `--scopes` by hand. ## [0.5.15] - 2026-06-12 **Hooks, conformance, and provider parity.** Harness hooks (kernel `animus-hook` spine + claude PreToolUse policy enforcement + author-controlled agent `hooks:`), SDK-conformant AskUserQuestion/permission-prompt handling with structured-question parity across all providers, plus observability and config-validate polish. ### Added - **Harness-hook activation (claude) + agent-level author-controlled hooks.** Building on the P1 hook spine (`animus-hook` binary + `protocol::hook_policy`), the kernel now activates a provider's native harness hooks so every tool call an autonomous agent makes is gated by an Animus policy before it runs. - `inject_harness_hooks` (in `animus-runtime-shared`) — for **claude** providers (gated on a new `harness_hook_config_vector(tool)` classifier; only the `Settings`/claude vector is generated this wave) and when `ANIMUS_DISABLE_HARNESS_HOOKS` is unset — writes a per-session `animus-policy.json` (compiled `protocol::HookPolicy`) and a minimal `animus-hooks.settings.json` (claude settings with ONLY a `hooks` block, every command pointing at the resolved `animus-hook` sibling binary), then appends `--settings ` to the launch args. Gate events (`PreToolUse`/`PermissionRequest`) carry `--policy`; observability events (`PostToolUse`/`Stop`/`SessionStart`/`SessionEnd`) do not. Only the per-session run dir is written — never `~/.claude` or any shared settings — and `--settings` is additive so the user's own hooks still run. `--include-hook-events` is intentionally NOT emitted (it is not a real claude flag and would error the session). - `compile_hook_policy` — compiles a resolved `AgentToolPolicy` (claude matcher syntax like `Bash(* --live*)` → tool glob + `command` regex) plus agent-authored guardrail rules into one `HookPolicy` with `default_decision = defer`. - Agent profiles gain an optional `hooks:` block (additive serde; old configs load unchanged), authored identically in the agent-runtime config and workflow-YAML agent definitions. `hooks.policy_rules` (author `HookPolicyRule`s) merge into the compiled policy; `hooks.observers` route extra harness events to `animus-hook` constrained to a named built-in `action` (`record`) — never an arbitrary shell command. **deny-wins**: the severity-ordered evaluator guarantees an author `allow` can never weaken a kernel/`tool_policy` `deny`. - `protocol::hook_policy` types (`HookPolicy`, `HookPolicyRule`, `InputMatcher`) now derive `PartialEq`/`Eq`. - New docs: `docs/reference/harness-hooks.md`; agent `hooks:` block in `docs/reference/workflow-yaml.md`; `ANIMUS_DISABLE_HARNESS_HOOKS` kill-switch in `docs/reference/configuration.md`. ### Fixed - `animus.agent.request_approval` now conforms to the Claude Agent SDK permission-prompt-tool contract (verified against the claude CLI v2.1.175 parser and ). The claude transport wires this tool as `--permission-prompt-tool`, and the CLI invokes it with `{ tool_name, input, tool_use_id }` for every gated tool call — previously the tool rejected that input shape (required `agent_id` / `action`) and answered with `{ decision, message?, source }`, which the CLI cannot parse, so every native approval round-trip failed. Now: - The input accepts the SDK shape additively (`input`, `tool_use_id`, `suggestions`); identity comes from the server pin and `action` is derived from `tool_name` when absent. - Responses emit the exact SDK payload in the result's single text block — `{ "behavior": "allow", "updatedInput": , "updatedPermissions"? }` or `{ "behavior": "deny", "message" }` — with the legacy `{ tool, result: { decision, source, … } }` envelope kept alongside (the CLI's non-strict schema strips unknown keys). - Native `AskUserQuestion` calls become structured Question records (`questions[]` on the interaction record, additive serde — old records load unchanged) that surface in the same inbox/notifier flow as `animus.agent.ask`; the answer builds the SDK `updatedInput` `{ questions: , answers: { :