All notable changes to this project will be documented in this file.
v0.6.11 — animus update defers to avm.
- When the running binary is managed by avm
(the Animus Version Manager — it installs kernels under
~/.avm/versions/and dispatches through ananimusshim),animus updateno longer self-replaces the binary (which would corrupt avm's version directory). It now prints theavm install/avm usecommands and exits. Plain installs (~/.local/bin,/usr/local/bin, Cargo) are unaffected and still self-update. scripts/install.shand the docs now point multi-project users at avm.
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 canonicalanimus-provider-<reserved>(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/--urlinstalls (owner unknown) — still require--allow-shadow-builtin.enforce_manifest_name_matches_repoand 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).
v0.6.9 — the npm-style project experience + core providers driven properly.
animus.tomlproject manifest +install/add/remove. A committedanimus.toml([project]kernel,[plugins],[packs]; deps as a version string,{ git, tag }, or{ path }) declares intent;animus installresolves it into.animus/plugins.lockand installs the set.--lockedreproduces the committed lock exactly (npm-ci) and fails on manifest↔lock drift.animus add <spec>/animus remove <name>mutate the manifest + install/uninstall.animus initscaffoldsanimus.toml,.env.example, and a merge-safe project.gitignore. Onboarding becomesgit clone && animus install; a Dockerfile isanimus install --locked.- Plugin lock = source of truth.
.animus/plugins.lockis authoritative; theplugins.yamlregistry is a derived projection regenerated on every mutating op. .env→ secrets sync.animus installprovisions secrets from.envinto the device-encrypted store (idempotent; blankKEY=values are skipped, never stored empty; imports are audited) and warns about keys declared (uncommented) in.env.examplebut 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 sharedanimus-provider-acpclient), codex over MCP (animus-provider-codex-mcp).claudeandoaistay native (native session + approval hook / direct API).provider_toolids 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-runneraliases: they canonicalize tooai-agent(the agentic provider), notoai.
v0.6.8 — install Agent Skills from GitHub.
animus skill install <github>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/blobURLs, rawSKILL.mdURLs. Format detection:animus:frontmatter → native passthrough; otherwise Anthropic semantics —name/descriptionmap over, the markdown BODY →prompt.system,allowed-tools→tool_policy.allow. Handles single skills andskills/<name>/SKILL.mdtrees (downloads sibling assets). Foreign names are slugified (path-traversal-safe); provenance recorded under thegithub-importsource (origin + format), shown byskill list/info.
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'sSHA256SUMS.txtand records the tarball sha for EVERY published platform, so a lock generated on one machine drives a verified--lockedinstall on another (macOS → linux container).--lockedverifies the current platform's tarball before extract;lock verify/listare target-aware; 1.0 locks migrate to empty targets (re-install to upgrade). -
Resident config_source host: the daemon spawns the
config_sourceplugin ONCE and reuses it (process-global cache, re-spawn only on ConnectionLost, reaped at shutdown) instead of forking it perconfig/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_sourceplugin process on everyconfig/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 typedHostError/classifyAPI), 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; aconfig/writeor 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).
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 withEAGAINand the run hangs at the agent phase. The daemon now injectsTOKIO_WORKER_THREADS=2into every spawned plugin (PluginHost::spawn_with_options, afterenv_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): makeconfig_sourceresident to remove the per-tick respawn churn, and switch the plugin scaffold to acurrent_threadruntime 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 installedconfig_sourceplugin, which resolves persistence. NewConfigSourceClient::write, CLIanimus workflow config {set,agent-set,agent-remove,workflow-set,workflow-remove}, and MCPanimus.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 advertisingconfig_writeand always validated before persist. Contract inanimus-config-protocolv0.1.21. animus.lock(.animus/plugins.lock): per-plugin integrity pinning (name, resolved version, artifact sha256, cosign signature-bundle sha256).plugin install/update/uninstallmaintain it;plugin install --lockedreinstalls + 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_sourcewrite path: the kernel ships the entire validated canonicalWorkflowConfigto the installed writableconfig_sourceplugin viaconfig/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_writemanifest capability. A read-only source (the defaultanimus-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-writeagent-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 runtimeanimus agentverbs. - Matching MCP tools:
animus.workflow.config.set,.agent-set,.agent-remove,.workflow-set,.workflow-remove.
v0.6.4 — pluggable chat conversation store (per-user + shared history).
- New OPTIONAL kernel plugin role
conversation_store(mirrorsconfig_source/subject_backend): when aconversation_storeplugin is installed the CLI routes chat persistence through it, else falls back to the in-treeFileConversationStore. Not a required preflight role — chat works with zero plugins installed. ConversationMetagainsowner: Option<String>andvisibility {private,shared}(serde-defaulted, back-compatible with existing on-disk conversations).animus chatgains--as-user <id>(new/send/list/get/delete/rename/export/ search) and--visibility(new).list --as-user Xreturns 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});seqis client-authoritative. Reference backend:launchapp-dev/animus-chat-postgres.
v0.6.3 — fix the config_source plugin process/connection leak (fleet-wide).
orchestrator-configconfig_source_client::resolve_plugin_basespawned a freshconfig_sourceplugin process for everyconfig/loadand never shut it down:spawn_with_optionssetskill_on_drop(true), but the child lives inside shared state held by the host's background reader task, so dropping thePluginHosthandle 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 likeanimus-config-postgres, an open Postgres connection pool) — exhausting the containerpidscap / Postgresmax_connectionsover time (observed: 51 lingering processes on the launchapp portal →sorry, too many clients). Fix: borrow the host for handshake +config/load, then alwayshost.shutdown()on every exit path so the process is reaped. Affects every v0.6 daemon (the defaultanimus-config-yamlsource leaked the same way, without the Postgres amplifier).
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.96to matchrust-toolchain.toml; addedlibdbus-1-dev/pkg-config(builder) andlibdbus-1-3(runtime) for thelibdbus-sysdep behindanimus secret; COPY the root-levelflavors/+ the RBAC doc embedded viainclude_str!(with a.dockerignoreexception); install the full required-role plugin set viainstall-defaults(incl. the now-publishedconfig_source); run the foregrounddaemon runinstead of the detachingdaemon start. - Refreshed curated default plugin pins:
animus-workflow-runner-defaultv0.4.5 → v0.4.8 (brings the autonomous approval gate into default installs) andanimus-queue-defaultv0.3.0 → v0.3.3.
v0.6.1 — protocol crates leave the CLI; config-yaml ships.
protocol+animus-config-protocolmoved out of ao-cli intolaunchapp-dev/animus-protocol(tagv0.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; allanimus-protocoldeps (protocol,animus-config-protocol,animus-provider-protocol,animus-session-backend,animus-subject-protocol) are pinned to the samev0.1.19tag so transitiveanimus-subject-protocolresolves to one source. Bumping the session-backend pin (v0.1.13 → v0.1.19) pulled the typedSessionRequest.mcp_serversfield and theSessionEvent::Interaction*variants; the kernel session drivers were updated to populate / handle them.launchapp-dev/animus-config-yamlpublished (v0.1.0). v0.6.0 required aconfig_sourceplugin to boot but the reference plugin was never released, so a freshanimus daemon startfailed preflight with no installable fix. v0.6.1 publishes it;DEFAULT_CONFIG_SOURCE_PLUGINS(already pinned to v0.1.0) anddefault-install.jsonnow resolve.daemon start --auto-install/plugin install-defaultsinstall it automatically.
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/*.yamlin its runtime load path. The baseWorkflowConfig(and the agent-runtime config derived from it) is sourced exclusively by an installedconfig_sourceplugin; the daemon now requires one (RequiredRole::ConfigSourceindaemon_default). The YAML parser still ships inorchestrator-configas a library that the reference pluginlaunchapp-dev/animus-config-yamllinks. The kernel keeps the compiler (pack overlays + validate + state-machine derivation). Newanimus-config-protocolcrate (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(oranimus plugin install-defaults) before upgrading. - Approval infrastructure + LLM auto-approve.
ApprovalPolicygains anllmmode (plusevaluator_model/evaluator_instructions) alongsideask(manual) /allow(approve-everything) /deny. Inllmmode a judge model reads the gated tool call and returns allow/deny; it also auto-answersanimus.agent.askquestions (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 throughrequest_approval). Seedocs/architecture/RFC-v0.6-approval-protocol.md.
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.
Queue-enqueue stores the derived subject kind (completes the 0.5.21 fix).
0.5.21 derived the subject kind from the <kind>:<native> 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 '<kind>:<id>'. 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.
- Control-routed
queue/enqueuenow stores the dispatch with the subject's real kind viaSubjectDispatch::for_subject_with_metadata( subject_ref_from_qualified_id(&resolved_id), ...). Plugin-backed custom kinds lease + resolve via<kind>/get;task/requirementkeep their canonical kinds; bare ids still default totask. Regression test:enqueue_dispatch_preserves_custom_subject_kind.
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.
queue/enqueuederives the subject kind from the<kind>:<native>id qualifier (song:SONG-001→ resolve viasong/get).task:/requirement:still map to the canonical in-tree constructors; a bare id (no prefix) defaults to task for backward compatibility. Newsubject_ref_from_qualified_idhelper inops_queue/control_routing.rs, with a unit test.
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.
RequiredRole::AtLeastOneSubjectBackendreplaces the hard-codedSubjectKind("task")+SubjectKind("requirement")roles inPluginPreflightSpec::daemon_default(). Preflight now passes when anysubject_backendplugin is installed, regardless of the subject kinds it claims. Auto-install maps the new role toanimus-subject-default. TheSubjectKind(_)variant is retained for custom specs but is no longer part of the daemon default. The doctorpluginscheck mirrors the same posture.
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.
queue/enqueuenow engages the subject-plugin fallback, mirroring the workflow-run path: try the in-tree task store first, then resolve viasubject_resolver().resolve_subject_context()and dispatch with the project'sdefault_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.
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).
devicesecret backend. Secrets live AEAD-sealed (ChaCha20-Poly1305) in~/.animus/<scope>/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 viaANIMUS_SECRET_KEYorkey_file),passphrase(Argon2idoverANIMUS_SECRET_PASSPHRASE, env-driven and script-safe), andauto. animus secret migrate --to <device|keyring> [--remove-source]. Copies every secret between backends, verifying each value before optionally clearing the source.--remove-sourcereports any deletions that failed instead of claiming a clean clear.- Global
secretsconfig block (~/.animus/config.json):backend(auto|keyring|device|env),key_source,key_file.autokeeps existing keyring installs on the keyring (never strands secrets) and uses the device store once one exists for the scope.
- 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.mdfor 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-esapilinks a C library.device-idis the shipped cross-platform default.
Plugin release/update hardening. Recommend the binary-shipping queue plugin, and warn loudly when a stale one is installed.
- Recommended
animus-queue-defaultpin bumped v0.3.0 → v0.3.3. v0.3.0 shipped no release binaries (soanimus plugin install …@vXandplugin updatefailed 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.
- Stale queue-plugin preflight warning. The daemon surfaces a non-fatal
warning at startup when an installed
queueplugin is below the precise-wake floor (v0.3.2) — "lacks precise-wake … upgrade withanimus plugin update" — instead of silently falling back to the heartbeat for reactive dispatch. Mirrors the existing workflow-runner under-pin warning.
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.
- Dispatch queue never drained — leases were starved. The daemon eagerly
spawned every installed
subject_backendplugin 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 satpendingforever. 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. Thequeue/completionrequest passed the real runworkflow_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 uniqueentry_id+workflow_refidentify the entry), so dispatches drainpending → assigned → completed → gone. - Daemon foreign-skill parse-warning spam. The daemon no longer sprays
could not parse markdown skillwarnings for foreign agent-host skill dirs (~/.claude,~/.codex,~/.cursor,~/.config/opencode) on every tick, which could bloatdaemon.logand fill the disk.
animus mcp authauto-detects advertised OAuth scopes. When neither--scopesnor configscopes:is set, the flow now requests the server's advertisedscopes_supportedfrom discovery metadata (RFC 8414) instead of requesting nothing. Scope precedence is now--scopes> configscopes:> advertisedscopes_supported> none. Auto-detected scopes are clearly marked as such in the consent preview,--dry-runoutput, and the--jsonenvelope (newscopes_auto_detectedflag), and can be narrowed with--scopesor fully opted out with--scopes none(or configscopes: [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)
animus mcp authagainst 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--scopesby hand.
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.
- Harness-hook activation (claude) + agent-level author-controlled hooks.
Building on the P1 hook spine (
animus-hookbinary +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(inanimus-runtime-shared) — for claude providers (gated on a newharness_hook_config_vector(tool)classifier; only theSettings/claude vector is generated this wave) and whenANIMUS_DISABLE_HARNESS_HOOKSis unset — writes a per-sessionanimus-policy.json(compiledprotocol::HookPolicy) and a minimalanimus-hooks.settings.json(claude settings with ONLY ahooksblock, every command pointing at the resolvedanimus-hooksibling binary), then appends--settings <path>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~/.claudeor any shared settings — and--settingsis additive so the user's own hooks still run.--include-hook-eventsis intentionally NOT emitted (it is not a real claude flag and would error the session).compile_hook_policy— compiles a resolvedAgentToolPolicy(claude matcher syntax likeBash(* --live*)→ tool glob +commandregex) plus agent-authored guardrail rules into oneHookPolicywithdefault_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(authorHookPolicyRules) merge into the compiled policy;hooks.observersroute extra harness events toanimus-hookconstrained to a named built-inaction(record) — never an arbitrary shell command. deny-wins: the severity-ordered evaluator guarantees an authorallowcan never weaken a kernel/tool_policydeny. protocol::hook_policytypes (HookPolicy,HookPolicyRule,InputMatcher) now derivePartialEq/Eq.- New docs:
docs/reference/harness-hooks.md; agenthooks:block indocs/reference/workflow-yaml.md;ANIMUS_DISABLE_HARNESS_HOOKSkill-switch indocs/reference/configuration.md.
animus.agent.request_approvalnow conforms to the Claude Agent SDK permission-prompt-tool contract (verified against the claude CLI v2.1.175 parser and https://code.claude.com/docs/en/agent-sdk/user-input). 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 (requiredagent_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 andactionis derived fromtool_namewhen absent. - Responses emit the exact SDK payload in the result's single text block —
{ "behavior": "allow", "updatedInput": <original or operator-modified input>, "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
AskUserQuestioncalls become structured Question records (questions[]on the interaction record, additive serde — old records load unchanged) that surface in the same inbox/notifier flow asanimus.agent.ask; the answer builds the SDKupdatedInput{ questions: <original array>, answers: { <question text>: <label | [labels] | free text> }, response? }. animus agent interactions answergains repeatable--select "<question|header|index>=<label[,label...]>"for structured questions (bare--textmaps to the single question's answer or the freeformresponse), plus--remember(echo localSettings-destination suggestions asupdatedPermissions) and--updated-input <json>(operator-modified tool input) on allowed approvals; the management-gatedanimus.interactions.answerMCP tool gains the same fields (answers,response,updated_input,updated_permissions,remember).showrenders structured questions readably.- Suspend mode on native prompt-tool calls answers
behavior: "deny"with the end-your-turn instruction (the CLI cannot park on a pending payload); the workflow pauses and resumes via session feedback carrying the per-question answers. Documented indocs/architecture/agent-interactions-protocol.md(suspend replaces the SDKdeferdecision; block-mode timeout deny vs the SDK's pending-indefinitely callback).
- The input accepts the SDK shape additively (
animus flavor currentnow counts project-scoped (animus plugin install --project) installs as satisfied. Previously its drift report scanned only the global plugin install dir, so a required plugin installed--projectshowed[missing]even though discovery,animus plugin list, andanimus plugin outdatedall saw it. The drift scan now unions the project install dir + project registry into the installed set.animus plugin listcollapses repeated "staleplugins.yamlentry" warnings (configured binaries whose path vanished) into a single summary line that names the prune remedy (animus plugin uninstall <name>), instead of printing one warning per entry and burying the shadowed-install notes below the table. Warnings from other discovery tiers still print per-line. The newanimus plugin list --verboseflag restores the full per-entry detail, and--jsonalways carries the completewarningsarray.
- Added a cross-crate drift guard (
orchestrator-daemon-runtime) asserting the stale-active-flavor → default → bundled-default scope-resolution ladder produces identical admit sets in bothorchestrator-plugin-host(PluginScope::load_for_project) and the daemon-runtime (resolve_scope_for_project) implementations — the two ladders are duplicated by a real crate-dependency constraint and can no longer silently drift, including their two embeddedflavors/default.tomlcopies.
- Budget-breach visibility and operability. When the daemon's housekeeping
sweep pauses a workflow for a declared
budget:cap, the breach cause is now visible without grepping logs:- Task annotation —
animus subject getshows the cause inline, e.g.paused by workflow wf-... — budget exceeded ($7.50 > $5.00 max_cost_usd)(phase caps readphase <id> budget exceeded (…)). The pause marker still clears onanimus workflow resume; the clear logic now matches thepaused by workflow <id>head by prefix, so both bare (pre-change) and enriched markers clear correctly and a genuine failureblocked_reasonis never touched. animus daemon health— gains abudget_enforcement: {enabled, last_sweep_at}line (persisted tobudget-enforcement.v1.jsoneach sweep) plus abreaches in last 24hrollup with the worst offender.animus status— a Budget section showing enforcement state and the active-breach count + worst offender, using a sharper resolution heuristic (apausebreach is active only while its workflow is stillPaused).
- Task annotation —
ANIMUS_DAEMON_DISABLE_BUDGET_ENFORCEMENT=1kill-switch (daemon restart required) skips the budget-enforcement housekeeping leg; the sweep still records its disabled status for the health/status surfaces. Documented underconfiguration.md#plugin-kill-switches.animus cost top --by provider— cross-run provider leaderboard, mirroring--by model.- Attribution-honesty hint: the grouped
cost summary --by/cost workflow --by/cost top --by model|providerviews now print a one-line note when theunknownbucket exceeds 20% of grouped cost (N% of spend lacks <model|provider> attribution; provider plugins must report model_id).
animus init --walkthroughnow offers an interactive flavor picker: when more than the bundleddefaultflavor is discoverable (flavors/*.tomlvialist_available_flavor_names_in, anchored at the init target root), it promptsFlavor [default]:and threads the choice into the walkthrough'splugin install-defaults --flavor <name>invocation soactive_flavorpersists. The prompt is TTY-guarded; non-interactive and--jsonruns keep thedefaultflavor. The bundleddefaultflavor is always offered alongside any on-disk custom flavors (a sole custom flavor still triggers the picker). The plan/apply envelopes now report the selectedflavor.animus subject batch-create/animus subject batch-updateCLI verbs, mirroring theanimus.subject.batch-*MCP tools. Both take a JSON items array via--file <json>(bare array or{ "items": [...] }wrapper), honor--on-error stop|continue, enforce the 100-item cap, and emit ananimus.cli.v1-wrappedanimus.cli.batch.result.v1envelope with per-item results. A batch where any item failed exits non-zero with the full payload attached under/error/detailsso scripts can detect partial failure.
- The stale-in-progress reconcile sweep now projects a task Cancelled
(not Blocked) when its terminal workflow died Cancelled in a crash
window, reusing
project_task_terminal_workflow_status. Failed/Escalated terminal workflows still project Blocked. animus daemon preflightnow emits a non-fatal WARNING when the installedworkflow_runnerplugin's manifest version is below the skill payload floor (v0.4.2) — such a runner silently ignores phase skills. The warning never fails preflight; it surfaces in the report JSON (warnings[]), on daemon-start (tracing::warn), and on stderr in the humandaemon preflightview, with ananimus plugin updatehint.- Bumped the curated
DEFAULT_WORKFLOW_RUNNER_PLUGINSauto-install pin (animus-workflow-runner-default) from v0.4.1 to v0.4.3 so fresh--auto-installinstalls already meet the new skill-payload floor (v0.4.2) and never warn against themselves.
- The
daemon observe --source <s> --followrejection error now echoes the user's kebab token (e.g.events) instead of the Rust enumDebugform (Events). - The bare
daemon observe --jsonmatrixis now keyed as objects ({verb, data_source, live, filters, when}) instead of positional arrays.
- Regenerated the CLI command-surface summary counts (28 top-level / 209 nested entries) and documented the counting basis.
- CLAUDE.md "CLI Reality Check" lists the full visible command-group set
(
chat/cost/auth/events/state/secret/approval/flavor), matching AGENTS.md. extending-animus.mdduplicate pack link split to distinct targets; skillpinknown-limitation, capabilities-as-feature-flags reframe, block-mode-not-crash-replayable selection criterion, and the participant-roster-is-advisory note added; scrubbed theself update --check-onlylineage phrase fromupdate --checkhelp.
The 9.5 campaign release: budgets that enforce, skills that supercharge any agent, a forensic chain that never dead-ends, and a smaller, honest surface. Driven by an 8-surface product-owner review (baseline 6.2/10, re-reviewed at 7.6 mid-campaign); every fix below was independently verified against the review findings. BREAKING items are marked.
-
Audited trust-on-first-use (TOFU) for plugin installs. The trusted-orgs store (
~/.animus/trusted-orgs.yaml,$ANIMUS_TRUSTED_ORGS) now records per-entry audit metadata:trusted_at(RFC3339),decided_by(interactive-prompt|yes|allow-org|built-in), andfirst_plugin(theowner/repowhose install first triggered the prompt). The loader still accepts the legacy bare-string format for back-compat; new grants always write the rich record. -
animus plugin revoke-trust <ORG>removes an org from the TOFU allowlist and records arevoked_attombstone so re-trusting re-prompts while the audit trail survives. The built-inlaunchapp-devanchor cannot be revoked (errors with an explanation). Emits atrust_org_revokedaudit event. -
animus plugin trust list(--json) shows current and revoked orgs with theirtrusted_at/revoked_attimestamps and how each grant was decided. -
Successful release-source installs now surface an
org_trustblock (org+trusted_at+decided_by) in the install JSON envelope and theplugin_installaudit line, soplugin install --jsoncan answer "when did we trust this org?". -
Active flavor persistence: a successful
animus plugin install-defaults --flavor <name>(oranimus flavor install <name>) now records the selected flavor project-locally in.animus/plugin-scope.yamlunder anactive_flavor:key. The daemon's flavor-only plugin scope resolver (resolve_flavor_pluginsinplugin_preflight_wiring.rs) and the CLI'sanimus plugin list/animus plugin scope showread that selection back viaorchestrator_core::flavor::active_flavor_id_in, so a non-default flavor's plugins are admitted by scoped discovery instead of being filtered out againstflavors/default.toml. Selectingdefaultagain clears the persisted key. An unknown persisted name (noflavors/<name>.tomlon disk) logs a warning and falls back to unrestricted (mode: all) scope — it never fail-closes discovery to an empty set. The selection merges into any existing scope file, preserving the operator'smode/allow/extras.animus flavor currentwith no--namenow probes the persisted active flavor (was alwaysdefault) and reports asourcefield (flag|persisted|default);animus plugin scope showaddsactive_flavor+active_flavor_source. Closes the codex-flagged TODO inbuild_install_defaults_targets. -
Cost attribution by provider and model. Per-phase cost records now carry a
provider(sourced from the phase session checkpoint) and amodel(already captured from run metadata events), so operators can answer "how much did claude vs gemini spend" and "which model drove the spike". New grouped breakdown views:animus cost summary --by provider|model,animus cost workflow <run-id> --by provider|model| phase, andanimus cost top --by model(a cross-run model leaderboard). Each grouped row reports total tokens, total USD, and the group's percentage of the grand total (token share when no USD is reported); phases without attribution fold into anunknownbucket. The grouped views attribute live workflows only — archivedhistoryrows lack per-phase detail and are excluded (cost workflow --byis rejected for archived runs; they land underunknownincost top --by model).cost trendsis intentionally left workflow-level (no provider/model split) since per-bucket attribution across history would require aHistorySummarystorage migration. New JSON envelope schemas:animus.cost.summary.breakdown.v1,animus.cost.workflow.breakdown.v1,animus.cost.top.models.v1. The additivePhaseCost.providerfield deserializes old cost-state files unchanged. -
animus status --failures <N>controls how many recent workflow failures the dashboard lists (default 3), applied to both the human and--jsonoutput. -
One observability entry point:
animus daemon observe. PO review found five log/event verbs (daemon logs,daemon events,daemon stream,logs tail,events tail) with no front-door — distinct data sources with no signpost for which answers which question.daemon observeis a routing front-door, not a new data path:--followdelegates todaemon stream --pretty;--source <logs|events|stream|workflow>routes to the specific existing handler;--since <dur>merges daemon events + logs chronologically across a recent window, labeling each line's source;--workflow <id>scopes surfaces that support filtering. Baredaemon observeprints a data-source matrix (verb | data source | live? | filters | when to use) plus the last ~20 merged lines.--jsonreturns{matrix, recent}(bare) or{lines}(window/source). Every branch reuses the existingevents/logs/streamreaders — zero duplicated data paths. -
Skill application is now VERIFIABLE end to end.
animus output phase-outputsgrew a human-readable per-phase view (verdict, reason, commit summary) with a Skills block showing requested vs applied (source scope + contribution kinds: prompt / tool_policy / mcp_servers / args / env / codex_config / model / timeout / capabilities) vs missing (not found — checkanimus skill list) skills;--jsonkeeps the raw envelope and now carries compact persisted skill records.PersistedPhaseOutputgained optionalrequested_skills/resolved_skills/applied_skillsfields (old outputs deserialize unchanged; skill-less outputs serialize byte-identically), populated via the newpersist_phase_output_with_metadataonce the out-of-treeanimus-workflow-runner-defaultpin bumps to pass itsPhaseExecutionMetadatathrough. Typo'd skill names also stopped being a silent no-op at authoring time: explicit workflow-YAMLskills:declarations (phases.<id>.skills,agents.<id>.skills) that do not resolve against the project's skill sources WARN (never error) on the compile stderr and in thewarningsarray ofanimus workflow config validate/compile; implicit builtin agent-profile defaults (pack names absent until the pack installs) are exempt, matching the runtime's explicit/implicit split. -
Pack dependencies are now real:
animus pack installresolves and installs the pack's declared non-optional[[dependencies]]after the requested pack (semver-aware skip of already-installed versions, recursion into deps of deps capped at depth 5 with cycle detection, same source-resolution path as the parent —ANIMUS_INIT_PACK_SOURCE_DIRoffline override, marketplace registry, or the pinneddefault-install.jsonGitHub release). Optional dependencies print as suggestions; a failing dependency never aborts the parent install and surfaces the manual install command. New flags:--no-depsskips dependency resolution,--dry-runprints the full dependency closure (and plugin requirements) without installing. -
New
[[requires_plugins]]pack-manifest section (repo, optionaltag, informationalrole,optional,reason): packs can declare the Animus plugins their workflows need.animus pack installchecks each entry against the installed-plugin registry; missing required plugins prompt for install in an interactive terminal (default yes), install non-interactively with--install-plugins, and otherwise print the exactanimus plugin install <repo>@<tag>commands — never silent.animus pack infonow reports both pack dependencies and required plugins with installed/missing status. Additive compat: packs WITHOUT the section keep loading on older animus versions; packs USING it require this version (pair withcompatibility.animus_core = ">=0.5.14"). -
First-class project-scoped plugin installation:
animus plugin install --project(and the same flag onuninstallandupdate) targets the project-local triple — binary<project>/.animus/plugins/, registry<project>/.animus/plugins.yaml, lockfile<project>/.animus/plugins.lock— with the identical sha256 / cosign / TOFU / fail-closed-lockfile pipeline as global installs.--projectconflicts with--plugin-dir. Project entries flow intoanimus plugin list(newSCOPEcolumn +scopeJSON field),plugin status(via discovery),plugin outdated(rows carryscope: global|project; the project registry is merged in),plugin update --project, andplugin lock verify, which now sweeps BOTH lockfile roots by default (global entries against the global dir, project entries against the project dir with a global-dir fallback for legacy entries). Discovery gains a project-registry tier (<project>/.animus/plugins.yaml) so official names outside the dir-scannedanimus-plugin-*/animus-provider-*prefixes (e.g.animus-subject-default,animus-queue-default) resolve when installed project-scoped. Discovery precedence fix: the project-local tier now also outranks the explicit registry yaml (which every global install writes), so a project install truly shadows a same-named global install; the hidden global binary is surfaced inplugin listas ashadowedentry (shadowed by project install).animus initand project-scoped installs write a.animus/.gitignorecoveringplugins/so binaries stay out of version control while the committable project lockfile pins the repo's plugin set. Seedocs/reference/configuration.md#project-vs-global-plugin-installs. -
animus plugin install-defaults --flavor <name>(default:default): the flavor manifest (flavors/<name>.toml, with a binary-bundled fallback fordefault) is now the source of truth for the install plan. Everything the manifest marksrequiredinstalls in one command — provider, subject backends (task + requirement), transport, workflow runner, and queue — covering every daemon-preflight role, so the canonical first run no longer needs a second--include-subjectspass. New--include-recommendedflag installs the manifest'srecommendedset (extra providers, web UI, GraphQL transport, more subject backends). Unknown flavor names error instead of silently falling back. -
Phase-level
skills:now actually reach workflow phase agents. The daemon resolves the union of each phase'sskills:list and the executing agent profile'sskills:at dispatch time — using the same scoped skill sources and trust rules as the ad-hoc--skillpath (project > user > installed > agent-host prompt-only, with agent-host structural fields stripped at load) — and ships the resolved definitions to the workflow runner as an additiveANIMUS_PHASE_SKILLS_JSONspawn-env payload (animus.phase-skills.v1; older runners ignore it, and a newer runner paired with an older daemon resolves locally with the same rules via the newanimus_runtime_shared::phase_skillsmodule). Activation gating (activation.tools/activation.models) is applied runner-side against the actually selected tool/model; applied skills inject prompt fragments, tool policy, launch args/env, and skill-declared MCP servers (joining the phase contract likephase_mcp_bindings; unknown server names warn and are skipped). Missing skill names are a loud dispatch-log warning plus amissingmetadata record — never a hard failure. Phase execution metadata now recordsrequested_skills,resolved_skills, andapplied_skillstruthfully, andanimus workflow prompt renderpreviews the skill-injected prompt. Runtime enforcement in dispatched workflows lands when the out-of-treeanimus-workflow-runner-defaultpin bumps to consume the payload. -
Skills now apply FULLY on the ad-hoc paths (
animus agent runandanimus chat send), not just their MCP servers + tool policy. The selected--skill's promptprefix/suffix/directiveswrap the outgoing prompt andprompt.systemrides the sessionsystem_prompt(an explicit--context-json system_promptcomes first);extra_argsandcodex_config_overridesare grafted onto the runtime contract'scli.launchblock — the same mechanism workflow phases use — and skillenvrides the session request env; the skill'smodelpreference andtimeout_secsapply when no explicit value is given. Precedence for every field: explicit CLI flags / context-json > skill > defaults; a caller-supplied--runtime-contract-jsondisables skill application entirely. Onanimus chat sendthe skill binds once per send invocation and applies per turn; launch-affecting skills force full-history replay instead of native session resume so launch flags reach every turn's provider process. New shared helpers inanimus-runtime-shared(apply_skill_prompt_to_body,merge_skill_system_prompt,skill_directives_section,inject_cli_extra_args_list,inject_codex_config_overrides_list) back both the workflow and ad-hoc paths.
animus flavor installis now manifest-driven: it delegates toanimus plugin install-defaults --flavor <name>instead of hard-coding--include-subjects --include-transports, and gains--include-recommended.animus flavor currentdrift and the install plan now share one required-set function (FlavorManifest::required_plugins), so they cannot disagree.flavors/default.toml:animus-subject-requirementsmoved fromsubjects.recommendedtosubjects.required— daemon preflight requires thesubject_kind:requirementrole, so it belongs in the required set.animus plugin install-defaultswithout flags now installs the flavor's required set (claude provider, subject-default, subject-requirements, transport-http, workflow-runner-default, queue-default) instead of all five providers; recommended providers install via--include-recommended. The legacy--include-subjects/--include-transportsflags keep working and add the recommended slice of those sections.- Daemon/
daemon preflightfailure output is composition-aware: when more than one required role is missing, the message also prints the single composed fix (animus plugin install-defaults --flavor default --yes) alongside the per-role install commands.
- Workflow lifecycle controls no longer leave the bound task in unexplained
ghost state.
animus workflow cancelnow syncs the task tocancelled(unless already terminal), matching the daemon-side execution-fact projection and the orphan reconciler, so a CLI-local cancel can no longer strand the task in-progress forever.animus workflow pauseannotates the task'sblocked_reasonwithpaused by workflow <id>(informational only — task status and thepausedflag are untouched) soanimus subject getexplains the stall, andanimus workflow resumeclears exactly that annotation without clobbering genuine failure reasons. Resetting a subject withanimus subject status ... --status readynow prints anunstuck: cleared ...line naming thepaused/blocked_*flags the transition cleared.
-
animus daemon healthnow leads its human output with a one-line verdict:healthy: true,healthy: true (paused)(paused runtime is a deliberate operator action, not a failure), orhealthy: false. The verdict isfalsewhen the daemon is down/crashed, a critical subsystem is failing, or any plugin reports unhealthy (including supervisor-disabled plugins); transitional degraded states staytrue. The same boolean is added additively as ahealthykey toanimus daemon health --jsonand theanimus.daemon.healthMCP tool on the live control-wire path (the offline snapshot already carried it). -
BREAKING:
animus daemon startnow always starts the daemon as a detached background process. Previously it silently fell through to the foregrounddaemon runpath unless--autonomouswas passed. The--autonomousflag remains accepted as a hidden, deprecated no-op (it now just names the default), so existingdaemon start --autonomousautomation keeps working unchanged. Useanimus daemon runfor a foreground (dev/debug) daemon.daemon startsuccess output now reports the daemon pid and the background log path, and starting while a daemon is already running is idempotent for all invocations (previously the flag-less form errored).animus daemon restartrestarts into detached mode, matching the new default. -
BREAKING:
animus daemon metricsis now a subcommand group. Bareanimus daemon metricskeeps the live observability display (counters, gauges, histograms;--watch / --interval-secs / --prettyunchanged), and the opt-in anonymous usage telemetry controls moved under it asanimus daemon metrics {status, enable, disable, flush, cleanup}.
animus daemon metricsno longer hard-errors when the daemon is offline. The telemetry subcommands already worked offline; the bare display did not. Now bareanimus daemon metricswith no daemon printsdaemon not running; live metrics unavailableplus the offline telemetry status summary and exits0(--jsonreturns{"daemon_running": false, "telemetry": {...}}). The hard "daemon required" behavior is retained only for--watch, where a live dashboard is meaningless without a running daemon.
- BREAKING: the
animus projectcommand group (list,active,get,create,set-active,rename,archive,remove). The project registry was a CLI-only facade with no daemon, workflow, or MCP consumers — the daemon routes purely via git-root + repo-scope. The underlyingProjectServiceApitrait, itsInMemoryServiceHub/FileServiceHubimpls, theproject_sharedhelper module, and theServiceHub::projects()method were deleted fromorchestrator-core. No aliases (model/runner removal precedent). - BREAKING: the
animus selfcommand group. Its only verb,self update, folded into the canonical top-levelanimus update:--checkreplaces--check-only, and--force/--prereleaseare folded in alongside the existing--yes/--channel. No aliases. - BREAKING: trimmed
animus gitto inspection-only. Kept:git repo list,git worktree list,git worktree prune. Removed:git repo {get,init,clone},git branches,git status,git commit,git push,git pull, andgit worktree {create,get,remove,pull,push,sync,sync-status}. Agent-driven merge/PR/commit behavior lives in the out-of-tree workflow runner plugin viapost_success.merge, not these in-tree handlers; there were never anyanimus.git.*MCP tools, so no MCP surface changed.git worktree prunekeeps itsprune_worktreesapproval gate. No aliases. - BREAKING: the top-level
animus metricscommand group. Its five telemetry verbs live underanimus daemon metricsnow; there are no aliases (v0.4 removal precedent). Themetricsvalue also left the telemetrycommand_grouptag enum —animus daemon metrics ...invocations record asdaemon. - BREAKING (alias retirement): the legacy CLI aliases added during
the v0.5.13 conventions convergence were removed with no replacement
aliases (matching the v0.4 no-alias precedent) —
pack inspect(usepack info),skill show(useskill info),flavor describe(useflavor info),output run(useoutput read),project load(useproject set-active), the hiddenanimus git confirm {request, respond, outcome}subcommand (useanimus approval), the-ishort flag onworkflow resume(use--id/--workflow-id), and the--forcevisible alias for--yesonworkflow prune/workflow delete.
Human-in-the-loop agents, MCP pass-down on every provider, an event-driven scheduler, and two deep bugfix waves. Agents can now ask questions and request approvals mid-run (blocking or suspend-and-resume), per-agent MCP servers reach all four providers on every run path, the daemon wakes on events instead of polling, and ~60 verified bugs were fixed across the workflow engine, config compiler, plugin host, and OAuth broker.
- Agent interactions (human-in-the-loop): blocking
animus.agent.askandanimus.agent.request_approvalMCP tools with per-agentapproval_policy(auto_allow/auto_denyglobs + default), ananimus agent interactionsinbox, management-gatedanimus.interactions.*tools (animus mcp serve --management), and notifier push on escalations. Workflow phases use suspend/resume: the tool suspends, the workflow pauses, and answering resumes the provider session with the decision as feedback. With protocol v0.1.13.5, claude enforces approvals natively via--permission-prompt-tool; codex/gemini/opencode get a prompt preamble. Design: docs/architecture/agent-interactions-protocol.md. - Per-agent MCP pass-down to the providers themselves: the resolved server
set rides the provider RPC as
extras.mcp_serversforanimus agent runandanimus chaton all four providers (protocol v0.1.13.4 transports), and on the workflow path via workflow-runner v0.4.3. Secret-bearing servers (all OAuth flows) are rewritten toanimus-mcp-proxystdio entries — tokens never reach CLI configs or argv. permission_mode: agent-profile field, phaseruntime:override (phase > profile), and--permission-modeonanimus agent run/animus chat send; forwarded to claude/codex/gemini native flags.animus mcp servegains--agent-id/--workflow-ididentity pins and--management(inbox tools agent-injected servers cannot access).- Event-driven scheduler:
daemon/nudgecontrol message (sent fire-and-forget by subject/queue write paths), cron-deadline wakes (schedules fire on time instead of on the next tick), and completion wakes.--interval-secsis now a fallback heartbeat, not the dispatch latency. animus daemon restart;animus plugin update --restart-daemonis now real (was a documented placeholder).workflow-failedandtask-blockednotifier events (once per transition).- Compile/validate warnings for declared-but-unenforced workflow YAML
(
UNENFORCED_RULESregistry), surfaced inworkflow config validateJSON. daemon health/statusreportruntime_paused(+ since-when) and per-plugin supervisor state (restart counts, disabled-until).animus pack uninstallandanimus skill uninstall(--dry-run,--json, reference guards).animus workflow prune(dry-run by default, terminal-status only) andanimus workflow delete --run-idfor run-history retention.- Bulk queue ops:
hold/release/droptake multiple ids or--all --yes; MCP queue tools gainsubject_ids[]. animus plugin outdated(installed vs recommended vs latest,--offline); registry fetch falls back to stale cache with an age warning, retries with backoff, and explains 429s.animus initreaches a runnable state: installs the recommended packs (interactive default-yes /--install-packs), suggestsanimus secretmigration for detected env-var API keys, and prints the next command.animus approval {request, respond, outcome}group (formerlygit confirm, which remains a hidden alias).animus.cli.v1envelope contract tests per command family and a documented CLI conventions section.
- Typed exit codes for
subject,cost,events, andplugin: invalid input exits 2, not-found 3, unavailable (missing plugin / network) 5 — previously all exit 1. Scripts matching on exit 1 for these cases must update. - Verb renames, aliases-first (old names still work, hidden):
pack info,skill info,flavor info,output read,project set-active. Workflow commands accept--workflow-idaliases;workflow prune --older-thanaccepts unit suffixes (30d,12h). animus plugin statusabsorbs provider health (aggregateprovider_plugins_healthy+ per-provider install state);animus doctorabsorbs orphaned-CLI-process detection (--fixprunes dead tracker entries only; live PIDs get a manual suggestion).- Provider/runner default-install pins: claude v0.2.7, codex v0.2.8,
gemini/opencode v0.2.6 (all on protocol v0.1.13.5), workflow-runner v0.4.3
(first cosign-signed runner release;
animus plugin installverifies it). animus daemon eventsis one-shot by default;--follow(bare,=true/false) opts into streaming. Previously follow defaulted on, so scripteddaemon events --limit Nnever exited.- Explicit queue entries (
animus queue enqueue) drain even whendaemon.auto_run_readyis false — the flag now gates only ready-task auto-dispatch. animus output artifacts/downloadread the scoped runtime artifacts root (~/.animus/<scope>/artifacts/) first, project-local legacy second.
animus modelcommand group (zero call sites) andanimus runnergroup + theanimus.runner.*MCP tools (absorbed as above).- The never-executed daemon git policy:
auto_merge,auto_pr,auto_commit_before_merge,auto_prune_worktreesconfig/flags/YAML keys, and the zero-callerGitProvider/BuiltinGitProvider. Merge/PR behavior lives in workflowpost_success.merge(workflow-runner plugin). Old pm-config.json files still load. - The no-op
idle_timeout_secsknob, the deadANIMUS_RUNNER_CONFIG_DIRandANIMUS_RUNNER_SCOPEenv vars, and the dead--runner-scopeflag. - Dead crate leftovers (
agent-runner,oai-runner), the orphanedrunner_helpers.rs, stale agent-runner doctor checks, and ~15 unused dependencies.
- Workflow execution: async
animus workflow runandworkflow resumenow spawn a real detached workflow_runner (previously they bootstrapped a Running record nothing executed; the reconciler zombie-cancelled it ~90s later and set the task Cancelled). Resumed workflows register a pid guard so the orphan reconciler leaves them alone; manually-claimed in-progress tasks are no longer re-blocked every tick (status_changed_at-aware reconciliation); in-progress tasks with no workflow self-heal to Ready. - Secrets:
workflow phases upsertround-trips unresolved sources — resolved env/keychain/${secret.*}values can no longer land in committable generated overlays; keychain-resolved values are redacted from parse diagnostics;${...}inside YAML comments is no longer interpolated. - Lifecycle engine: all ~20 remaining state-machine
.expect()panic sites fail gracefully (merge-conflict + phase-approve no longer panics the daemon); custom state machines are validated for executor-required transitions; the duplicate-workflow guard actually works on the file hub. - Triggers/schedules: file-watcher events survive failed spawns (baseline no longer advances on dispatch failure); fresh schedules fire via a horizon-bounded catch-up instead of requiring a tick inside the exact cron minute; the first file appearing under a watch dispatches.
- Concurrency: agent memory/message appends, OAuth token refresh, chat sends (per-conversation), and plugin lockfile saves are cross-process locked; SQLite multi-entity mutations are transactional; checkpoint numbering is collision-safe.
- OAuth:
invalid_grantevicts the cached refresh chain and retries the env seed in the same call; re-minted seeds bypass stale caches (value-hash fingerprints); corrupt keychain credentials degrade to unauthenticated instead of brickinganimus mcp auth status/logout. - Plugin host: a request racing reader teardown can no longer hang a daemon
dispatch task forever; subject routing has a response deadline; broken
flavors/default.tomlfails closed loudly and consistently acrossplugin list,daemon preflight(exit 2,--auto-installwithheld), and daemon startup. - Repository scope: clones on unmounted volumes no longer get their state adopted by same-origin siblings (mount-aware reclaim heuristic + fast-path ownership validation) — ends silent cross-clone workflow.db sharing.
- Observability:
daemon events --followanddaemon streamno longer lose records across log rotation (dev/ino cursor drains the rotated file first);chat list/searchskip stray directories instead of failing; a chat stream ending without a terminal event is an error, not a silently-successful empty turn. inittemplate prompt no longer hangs on non-TTY stdin (and detects EOF).- Stale help text (hardcoded version markers, internal jargon) and misleading
config docs:
.animus/config.jsonis self-update config only; daemon runtime settings live in the scopeddaemon/pm-config.json. Scheduler timing model and previously undocumentedANIMUS_*env vars documented. - Pack/skill uninstall tests pin HOME (no more real
~/.animuspollution or parallel flakes).
Agents reach Animus + project MCP servers, author skills, and tune thinking effort — plus MCP OAuth. This release makes the agent surface genuinely usable: an ad-hoc agent now gets the MCP servers its profile/skill declares, can create skills, and OAuth-protected MCP servers work through a local proxy.
- Per-agent MCP wiring —
animus chatandanimus agent runnow give an agent the MCP servers its profile/skill declares (a trading agent gets trading servers, a marketing agent gets marketing servers), resolved by name against the projectmcp_serversmap. Flags:--agent,--skill,--mcp-server <name>(add more),--no-animus-mcp(drop the built-in). Materializes the per-agent set into the cwd.mcp.jsonthe CLI tool reads, with secrets stripped. OAuth servers route throughanimus-mcp-proxy. - MCP OAuth —
animus mcp auth/auth-status/auth-logout+animus-mcp-proxy(stdio bridge), built on rmcp 1.7's auth engine with OS-keychain token storage. Discovery + DCR + auth_code/PKCE + loopback callback. Least-privilege scope default + preview/confirm before the browser +--dry-run/--yes. animus.skill.create/animus.skill.updateMCP tools — agents author project-scoped skills to.animus/skills/<name>.yaml(validated, overwrite- guarded, round-trip-verified). Joins the existingskill.list/get/search.--reasoning-effort low|medium|highonanimus agent run+chat send(and areasoning_effortagent/phase config field) — codex-c model_reasoning_effort, claude--effort.animus chat search <query>— grep conversation transcripts across the scope (case-insensitive,--limit,--case-sensitive).
- Chat
tool_resultstreams the full tool output + correct tool name. - MCP built-in tool count published in the docs synced to 78.
- codex / opencode / gemini providers stream tool/command activity correctly
against current CLI versions; codex/claude pick up
--reasoning-effort.
Chat streaming fidelity + the full provider matrix. animus chat send --stream --json now streams everything the agent does — and all four
provider tools (claude / codex / opencode / gemini) were brought up to
current-CLI compatibility so their tool/command activity streams too.
- chat
tool_resultstreams the tool output. Previously a tool result emitted onlysuccess: bool, dropping the actual content. It now carries the provider's full output (command stdout, file contents, structured JSON) so a consumer sees what each tool returned, not just that it ran. - chat
tool_resultreports the human tool name. When a provider put a tool_use id (e.g.toolu_…) in the result's name field, the stream now correlates it back to the precedingtool_call's name.
The provider plugins evolved past their parsers as the upstream CLIs
changed. All three are released at v0.2.4 on the stable v0.1.13.2
protocol lineage:
- codex v0.2.4 — codex emits sandbox shell runs as
command_executionstream items (notfunction_call); these were dropped. Now mapped totool_call+tool_result, so codex streams its commands. - opencode v0.2.4 — opencode v1.2 wraps frames as
{type, part}with tool details underpart.state; the parser read the old flat keys and producedunknown_tool. Now parses the part shape intotool_call+tool_result(old format still handled). - gemini v0.2.4 — gemini CLI refused headless runs in an "untrusted"
directory (exit 55). The provider now sets
GEMINI_CLI_TRUST_WORKSPACE=trueon spawn soanimus chat --tool geminiworks without interactive pre-trust.
All four providers verified end-to-end through animus chat against official
signed builds.
Animus Chat (CLI-first). Multi-turn conversations through the CLI, streamed as JSON, built entirely on the existing provider plugins (claude/codex/gemini) — no new plugin, no new API key.
animus chat new|send|get|list— durable multi-turn conversations.send --stream --jsonemits oneChatStreamEventJSON object per line to stdout (turn_started / text_delta / tool_call / tool_result / metadata / turn_completed) — the surface to build chat apps on.animus cost conversation <id>— per-conversation token + USD rollup, folding each turn's usage (matches the existing workflow cost aggregator).- Provider-owned continuity — each turn is strictly resume XOR replay,
never both: with a live provider
session_idthe prompt is just the new message (the CLI tool's native session carries history); with no session Animus replays its stored transcript. Single full-history retry on a stale-session error. No double-counted context. - Conversation store at
~/.animus/<repo-scope>/chat/<id>/(meta.jsoncontinuity pointer + append-onlymessages.jsonl), path-traversal-guarded. User message persists before the provider call (crash safety); the provider's nativesession_idis captured per turn. - Reuses
SessionBackendResolver(theanimus agent runpath), so the wrapped CLI tool reaches Animus's MCP server viamcp_endpoint— the model can callanimus.*tools mid-conversation.
- Direct-API
chat_providerplugins (own the turn structure, intercept tool use) and the OpenAI-compat HTTP surface are deferred to v0.5.11. The turn loop is factored behind aTurnProducerseam so that path is additive. See docs/architecture/animus-chat.md.
The performance + scoping wave. Five parallel agents cut the
compound cost that multiplies across Tauri polling, agent automation,
and daemon ticks. Headline: animus daemon status 3052ms → ~800ms,
animus status 11.1s → 3.1s, warm 30-plugin discovery 3000ms → ~4ms.
animus plugin cache clear|list— inspect/wipe the sha256-keyed on-disk manifest cache (~/.animus/cache/manifests/<sha>.json). Cache invalidation is automatic since install/upgrade rewrites the binary sha.ANIMUS_DISABLE_MANIFEST_CACHE=1kill-switch.animus plugin scope show|set|reset— per-project plugin scoping via.animus/plugin-scope.yaml. Three modes (all,flavor-only,allowlist);flavor-onlyis the default when a flavor manifest exists. Discovery, preflight, and the plugin-status registry iterate the scoped set instead of every binary in~/.animus/plugins/.--no-cacheglobal flag +ANIMUS_DISABLE_{CI,WORKFLOW}_CACHE=1/ANIMUS_CI_CACHE_TTL_SECSenv knobs for the new hot-path caches.ANIMUS_DISABLE_PROJECT_ROOT_CACHE=1kill-switch for the new in-processresolve_project_rootcache.
- sha256-keyed plugin manifest cache + parallel cold probes — warm
30-plugin discovery drops from ~3s to ~4ms; cold-cache probes run
min(8, num_cpus)-parallel instead of serial. Fixes the root cause of slowdaemon status/plugin list/initon many-plugin installs. - daemon status fast-path —
load_daemon_status_snapshot_fastskips the SQLite queued-task count + provider scan thatload_daemon_health_snapshotdoes.animus daemon statusno longer pays ~3.3s of health-snapshot cost. - MCP daemon tools route in-process —
animus.daemon.status/health/agentsno longercurrent_exe()-spawn a CLI subprocess per call, killing the Tauri 3-call refresh penalty (~3× full CLI startup per refresh). - single-socket ControlClient —
try_connect+call_rawreuse one AF_UNIX connect instead of two. - provider-healthy check is a directory walk —
provider_plugins_healthy_forwalks~/.animus/plugins/animus-provider-*instead of running full plugin discovery (the original v0.5.8.1 fix, now alongside the cache). - CI status cache (60s TTL, per-repo) skips the
gh run listsubprocess —animus status~8s faster. workflow YAML compile cache keyed by source mtime+sha. 1s in-process daemon health cache for multi-call flows. - CLI per-invocation cuts —
resolve_project_rootin-process cache,gh --versionmemoized, update-check end grace 750ms → 50ms, end-of-run metrics flush 2s → 200ms, keychain installer short-circuit.
This wave is the observability + reliability + secrets + state release. Eight parallel agents shipped one cohesive set:
- Project secrets via OS keychain:
animus secret set/get/list/rm/import-env/export-envwith manifest-filtered injection into plugin processes and${VAR}workflow YAML fallback. - RBAC small core: Principal + policy.rbac config + chokepoint #1 +
peer-cred +
animus auth whoami+ global--as <principal>. - Observability:
animus events tail+animus plugin status. - Hot reload: notify-based watcher + ArcSwap snapshot for
.animus/workflows.yaml+animus workflow config reload. - Doctor --fix: stale daemon pid, orphan worktrees, zombie phase sessions, merged agent branches.
- State export/import: versioned
animus.state.export.v1tar.zst with MANIFEST.json + safe-by-default import. - Workflow YAML diagnostics: rustc-style file:line:col + did-you-mean for parse errors.
- Self-update:
animus updatetop-level alias with atomic binary swap. - Plugin bulk-update:
animus plugin update --all/--kind/--namewith diff preview and semver-aware "ahead of pin" detection. - Workflow YAML quality-of-life:
worktree: true|falseboolean shorthand alongside the string short-form and long-form map. - Plugin rename verb + multi-kind subject backend collision check +
name_overrideround-trip through discovery.
animus plugin rename <PLUGIN_NAME> --to <NEW_KIND>— post-install rename of a plugin'sinstalled_kindinplugins.lock. Reuses the v0.5.7 install pipeline's collision check, auto-increment behavior, and invalid-character validation; only the lockfile'sinstalled_kindslot changes (the on-disk binary, manifest, andnative_kindstay put).--forceauto-increments past a collision (requirement->requirement-2) instead of failing. Emits ananimus.plugin.rename.v1envelope.Principaltype +policy.rbacconfig (small core). Introducesorchestrator_core::principal::Principal(User / Daemon / ServiceAccount) and aRbacModeenum (single-userdefault,enforceopt-in). Single-user installs are bit-identical — the permission hook is a no-op unlessenforceis set.~/.animus/principals.yamlauto-bootstrap. On first run the daemon writes a one-principal default file mapping the current OS user to anadmin-roledlocalprincipal. Existing files are never overwritten (collision guard from the design doc).- Chokepoint #1: control-dispatch permission hook. Every
control RPC routes through
check_principal_can. Under enforce, a hardcoded role table allowsadmin: *andviewer: *.read; everything else fails closed withpermission_denied. - Peer-cred resolution on the Unix socket (
cfg(unix)). The accept loop callsgetpeereid(BSD/macOS) orSO_PEERCRED(Linux) and resolves the peer UID to an OS username and a declaredPrincipalEntry. Honors the design doc §"Identity sources" resolution chain. animus auth whoami+ global--as <principal>flag. The CLI surfaces the effective principal and propagates--ashonor-system via a$/setPrincipalnotification at every control connection. Warns loudly on use.- Audit-log shim.
AuditActor::Principal { id, kind }carries typed principal fields alongside the legacyactorstring — additive; existing readers parse unchanged. - v0.6 deferrals: per-tenant scope migration, per-principal secret
routing, plugin-host trust changes, and chokepoints #2-#4. See
docs/architecture/multi-tenant-rbac-v0.5.5.md.
- Multi-kind subject backend collision detection — the install
pipeline's
rename_eligible_native_kindpreviously returned only the FIRST exactsubject_kind:*capability, so a single binary declaring bothtaskandrequirementslipped past collision checks on the secondary kind. v0.5.8 introducesall_rename_eligible_native_kindsand a secondary-kind collision pass incompute_kind_assignment. A multi-kind backend now refuses to install when ANY of its declared exact kinds collides with an existing install — the lockfile records oneinstalled_kindper plugin, so a secondary collision cannot be auto-incremented and must be surfaced. ClosesTODO(codex-p2 round-4 v0.5.7)inops_plugin.rs. --name <NAME>install override round-trips through discovery —plugins.yamlgains aname_overridefield that records the install-time--namevalue.PluginDiscovery::discover_configurednow usesname_overrideas the canonicalDiscoveredPlugin::namewhen set, so the daemon'sSubjectRouteralias map (keyed byplugin.namefrom discovery) finds the lockfile entry (also keyed under the override) instead of dropping the alias. Without this fix, a plugin installed as--name task-archivewould re-register under its manifest name on next daemon start and lose its rename. ClosesTODO(codex-p2 round-4 v0.5.7)insubject_dispatch.rs.
- docs(workflow-yaml): document
schedules,triggers, anddaemontop-level blocks — features have shipped but had no authoring documentation. Added per-type config tables forfile_watcher,webhook,github_webhook, andplugintriggers, runtime semantics for cron schedules (missed runs not replayed;run_countvsmissed_count), and the fullDaemonConfigfield surface. Cross-linked fromdocs/reference/configuration.md.
-
worktree:block on workflows and phases. Workflow YAML now acceptsworktree: { mode: auto|required|skip, cleanup, base_ref }at the workflow level and (with short-formworktree: skipsupport) at the per-phase level.mode: requiredfails-fast if a worktree can't be created;mode: skipkeeps the phase in the project root; phase-level values always override the workflow-level default. Replaces the previously implicit, always-on worktree creation in the workflow runner. -
secrets:block +${secret.<name>}interpolation. Workflow YAML now declares logical secret names mapped to process env vars:secrets: linear_token: env: LINEAR_API_TOKEN required: true mcp_servers: linear: env: { LINEAR_API_TOKEN: "${secret.linear_token}" }
Resolution happens at config-compile time. Required-but-unset env vars and references to undeclared keys fail the compile with the YAML file path and 1-based line number. The compiled
workflow-config.v2.jsoncontains the resolved string — plugins consume the same scalar shape they always did. -
Sensitive-interpolation lint. When a workflow YAML interpolates
${VAR}whose name matchesTOKEN|KEY|SECRET|PASSWORD(case-insensitive) outside thesecrets:block or a*_env:declaration field, the compiler emits a warning to stderr suggesting the author move it undersecrets:. The lint is a warning, not an error — trusted workflows may have legitimate uses. -
Trigger plugin authoring story. New
animus plugin scaffold trigger <name>subcommand emits a minimal, offline, self-contained Cargo starter for a customtrigger_backendplugin (Cargo.toml, plugin.toml, src/main.rs, README.md, .gitignore) pinned againstlaunchapp-dev/animus-protocolv0.5.5 by default. Unlikeanimus plugin new, the scaffolder works without network access and ships a workinginitialize+trigger/watchtrigger/ack+health/checkskeleton.
-
examples/triggers/fswatch/— reference filesystem-watch trigger plugin that watches a glob list and emitstrigger/eventnotifications on file modification. Compiles standalone; not a workspace member. -
docs/guides/authoring-trigger-plugins.md— walks the trigger protocol surface, daemon supervisor lifecycle, manifest format, scaffold usage, fswatch walkthrough, and debugging.
- Trigger plugins now receive workflow YAML
configblocks. The daemon'sTriggerSupervisorwas previously sendingTriggerWatchParams::default()for every trigger plugin, so thetriggers[].configblock authors wrote in workflow YAML never reached the plugin. The supervisor now forwards every enabledtype: plugintrigger asTriggerWatchParams.config.triggers[]; each plugin filters the list for entries it understands.
- Kernel-level OAuth broker for HTTP-transport MCP servers. HTTP MCP
entries (
mcp_servers.<name>withtransport: http+url:) can now carry anoauth:block declaring one of three flows (client_credentials,refresh_token,manual_bearer). The daemon resolves a bearer token at runtime-contract assembly time, caches it under~/.animus/<repo-scope>/mcp-oauth-cache/<server>.json(0600 perms on Unix, 60s skew margin), and injectsAuthorization: Bearer <token>into the/mcp/additional_servers/<name>/headersmap alongsideurlandtransport. Credentials are read from env vars named via*_envpointers; tokens never appear in YAML or logs. Validation rejectsoauth:on stdio transports and surfaces actionable errors when required env vars ortoken_urlare missing. See docs/reference/workflow-yaml.md for the full shape.
-
feat(workflow): basic eval framework for phase quality gates (YAML + runner library; runtime enforcement deferred). Phase definitions can declare anevals:block listingcommandandllm_judgechecks. The runner library (animus_runtime_shared::evals) executes them, scores againstpass_threshold, and routes failures torework(re-execute the phase, up tomax_reworks) orblock(pause for manual approval). Each check emits ananimus.eval.v1record (check_id, kind, passed, duration_ms, exit_code, output_excerpt) ready for the workflow decision log.Runtime status: the YAML/config/validator/runner ship in this release. The workflow-runner call site that invokes
run_evalsbetween phase output and phase advance lives inlaunchapp-dev/animus-workflow-runner-default(out-of-tree per the v0.5.1 fold-in) and is pending its next release. Until that pin bumps, configuredevals:blocks parse and validate but a phase still advances regardless of check results — author/test now, do not yet rely on it as a production trust gate. See docs/reference/workflow-yaml.md#evals-experimental--runtime-enforcement-deferred for the YAML surface.
- Workflow + phase budget caps in YAML. New
budget:block onWorkflowDefinitionand on rich phase entries declaresmax_tokens,max_cost_usd, andon_exceed: pause|fail|warn. Workflow caps subsume phase caps; phase rework resets the per-phase counter. Validated byvalidate_workflow_config. - Cost aggregator over existing
AgentRunEvent::Metadataevents.crates/orchestrator-cli/src/services/cost/folds vendor-reported cost + token counts into a per-(workflow_run_id, phase_id) rollup persisted to~/.animus/<scope>/cost-state.v1.json(schemaanimus.cost-state.v1). When the provider omitscost, a small per-model USD rate table estimates it (seedocs/reference/configuration.md#cost-governance-and-model-rates-v055). animus costCLI.summary [--since 24h],workflow <ID>,top [--by tokens|cost] [--limit N],trends [--window day|week|month]. All commands honor--json.- Budget-exceeded decision records. When a cap is crossed,
cap_check::check_capsproduces aBudgetExceededRecord(schemaanimus.budget-exceeded.v1) intended fordecisions.jsonl. The workflow runner hook that translates these into actual workflow pauses lands in thelaunchapp-dev/animus-workflow-runner-defaultplugin in a follow-up release; this commit ships the data, schemas, and read surface.
agent-runnersidecar deleted. The standalonecrates/agent-runner/binary (~8 kLOC) was removed entirely.animus agent {run, status, cancel, control}now talks directly to provider plugins viaorchestrator_plugin_host::session::SessionBackendResolver(the same surface the daemon and tests have used since v0.4.12). The Unix-domain socket bridge, shared-secret auth handshake, runner lockfile, and the in-process supervisor inorchestrator-core::services::runner_helperswere deleted at the same time. The publishedlaunchapp-dev/animus-protocol::animus-agent-runner-protocolcrate is bumped to v0.1.1 and marked deprecated.animus runner restart-statsremoved. It read daemon-event records for a sidecar that no longer exists.
animus runner healthnow reports one entry per discovered provider plugin (plugin_name,provider_tool,binary_path,installed) alongside daemon health. The rendered output addsprovider_plugins_healthy: bool;runner_connectedis still emitted on the wire asfalsefor back-compat.DaemonHealthgainsprovider_plugins_healthy: bool. The legacyrunner_connected: boolandrunner_pid: Option<u32>fields are kept for wire back-compat and now always serialize asfalse/None.animus agent controlreturnsunavailable(exit code 5) under the v0.5.3 provider-only model — there is no in-process pool to address; the CLI emits an actionable error rather than a silent success.
feat(cli): configurable auto-update viaanimus self update. Adds GitHub release polling on CLI startup with four modes (off/notify/prompt/auto), a configurable ISO-8601check_interval(defaultP1D), channel filtering (stable/prerelease), and an atomic same-directory binary swap. Env overrides:ANIMUS_AUTO_UPDATE_MODE(mode),ANIMUS_AUTO_UPDATE_DISABLE(short-circuit to off). The newanimus self updatecommand supports--check-only,--force,--prerelease, and--yes. The startup check is fire-and-forget; the subcommand it runs alongside is never blocked by network latency.feat(cli): opt-in anonymous metrics with first-run prompt (v0.5.3). Adds a hard-opt-in event-counter telemetry surface. First-run prompt onanimus initandanimus daemon start; persisted under.animus/config.json. Events are bounded enums (workflow_started,workflow_completed,plugin_installed,daemon_started,error_hit,cli_invoked,update_applied) with closed tag enums — no code, no file paths, no repo names, no prompts, no credentials reach the payload. Disabled by default if no TTY is available. Kill switch:ANIMUS_METRICS_DISABLE=1. New CLI surface:animus metrics {status, enable, disable, flush}. Default endpointhttps://metrics.animus.dev/v1/eventsis a placeholder pending product confirmation.feat(config): agent profiles now acceptsystem_prompt_fileto load the system prompt from an external UTF-8 file. The path resolves relative to the source YAML's parent directory (absolute paths supported). The contents are inlined verbatim intosystem_promptat config-compile time, so the compiledworkflow-config.v2.jsonstays self-contained and daemon runtime behavior is unchanged.system_promptandsystem_prompt_fileare mutually exclusive; missing files, non-UTF-8 contents, and files larger than 1 MiB fail compile with the resolved path in the error message.feat(config): workflow YAMLmcp_servers:entries can declare HTTP MCP servers viatransport: http+url:. Validation enforces exactly one transport per entry (http requiresurl:with anhttp:///https://scheme; stdio requirescommand:), rejects mixed shapes, and the URL flows through/mcp/additional_serversso agents reach remote MCPs without a bridge script. Env-var interpolation works onurl:andcommand:fields.
- Plugin kind translator:
subject_backendandproviderplugin collisions are now solved daemon-side instead of requiring each plugin to be parametric. Every install carries a user-facinginstalled_kindrecorded in.animus/plugins.lockthat may differ from the plugin-nativenative_kind. TheSubjectRouterrewrites outbound methods (<installed_kind>/<verb>→<native_kind>/<verb>) and inbound top-levelkindfields in responses, so plugins stay unmodified. Install pipeline auto-increments onsubject_kind:*/provider_tool:*collisions (task→task-2→task-3); explicit override via the newanimus plugin install --as-kind <KIND>flag. Preflight summarization consultsplugins.lockso renamed plugins report against theirinstalled_kind. Newanimus plugin doctorsubcommand shows every preflight role with its installed claimants and flags duplicates explicitly. See docs/architecture/plugin-kind-translator-v0.5.7.md for the architecture and the explicit deferral of nested-kind rewriting.
Follow-up hotfix to v0.4.18. The previous release wired the subject
plugin fallback into FileServiceHub and the CLI dispatch helpers,
but the parallel InMemoryServiceHub paths were still wired without
a plugin fallback, and plugin discovery only scanned the project-local
install dir — global installs under ~/.animus/plugins/ required a
manual $ANIMUS_PLUGIN_DIR override or a symlink to be picked up.
fix(services):InMemoryServiceHub::subject_resolver()andproject_adapter()now activate.with_plugin_fallback()when a project root is supplied via the newInMemoryServiceHub::with_project_root()builder, matching the productionFileServiceHubbehavior so plugin-backed subject resolution stays symmetric between the two hub variants. Three regression tests assert that the InMemoryServiceHub skips the plugin fallback when no project_root is set, engages it when one is supplied, and thatFileServiceHubcontinues to engage it.fix(plugin-host): plugin discovery now scans~/.animus/plugins/unconditionally instead of only when$ANIMUS_PLUGIN_DIRis set. This restores the canonical precedence chain: explicitplugins.yamlregistry entries first, then project-local<project_root>/.animus/plugins/, then the global install dir ($ANIMUS_PLUGIN_DIRoverride or~/.animus/plugins/default), then$ANIMUS_PLUGIN_PATH, then optionally$PATH. Duplicate plugin names dedupe to the first source that produced them, so project-local installs continue to override global ones deterministically. Four new discovery tests cover the global-only discovery, project-local-vs-global precedence, both-locations co-existence, and$ANIMUS_PLUGIN_DIRoverriding the default install location.
Hotfix release. Real-world reproduction in a v0.4.x project hit a
migration gap that wasn't covered by the v0.4.12-through-v0.4.16
audit waves: animus workflow run could not dispatch against any
task or requirement subject backed by a plugin (not by an in-tree
adapter — which v0.4.12 deleted).
fix(workflow-run): subject context now falls through to installedsubject_backendplugins on both sync + daemon dispatch paths.SubjectAdapterRegistry::resolve_subject_contextpreviously returned the in-treeBuiltinTaskSubjectAdapter's "task not found" error without consulting the configuredplugin_fallback, and the CLI dispatch helpers (resolve_workflow_run_dispatch{,_from_input}) calledhub.tasks().get()/hub.planning().get_requirement()directly without ever routing plugin-owned subjects through anysubject_backendplugin. Both paths now retry via the configuredSubjectFallback/hub.subject_resolver()chain so plugin-owned task and requirement subjects dispatch correctly.PluginSubjectFallbackprobes both canonical (animus.task) and bare (task) kind aliases across candidate plugins, canonical first to avoid nondeterministic shadowing under HashMap iteration.ensure_execution_cwdrecognizes plugin-resolved contexts via a newSUBJECT_ATTR_PLUGIN_RESOLVEDattribute marker, so in-tree tasks keep their managed worktrees while plugin-resolved tasks fall back toproject_root.- 9 new unit tests in
subject_adapter.rs::testscovering both the resolve + ensure_execution_cwd paths, including the marker semantics. - 4 codex review rounds; final clean. P1 catches: broad fallback
masked worktree errors,
task=Noneheuristic misclassified in-tree tasks after.take(), plugin probe missed the bare-kind alias, HashMap iteration order could shadow canonical plugin.
Ecosystem expansion — all reference packs and SDKs extracted to standalone
launchapp-dev/animus-* repos so agencies + community can fork
individually. Six communication-channel trigger plugins shipped to enable
end-to-end automation loops (inbound + outbound) for Slack, Discord,
WhatsApp, Telegram, Email, and SMS.
chore: extractpacks/*andsdk/*to standalone launchapp-dev repos. All 7 reference packs and both SDKs (TS + Python) are now their own public repos at launchapp-dev, all tagged v0.1.0:launchapp-dev/animus-plugin-sdk-tslaunchapp-dev/animus-plugin-sdk-pylaunchapp-dev/animus-pack-customer-supportlaunchapp-dev/animus-pack-marketing-outreachlaunchapp-dev/animus-pack-sales-pipelinelaunchapp-dev/animus-pack-engineering-backloglaunchapp-dev/animus-pack-recruiting-pipelinelaunchapp-dev/animus-pack-organization-meetingslaunchapp-dev/animus-pack-ecommerce-fulfillmentIn-treepacks/<name>/andsdk/<lang>/replaced with redirect READMEs + index files atpacks/README.mdandsdk/README.md. Stubs will be removed onceanimus pack install+ marketplace discovery are wired up.
- Communication channel trigger plugins under
launchapp-dev/. Each is a dual-role plugin:trigger_backendfor inbound events + custom RPC methods for outbound replies, so workflow phases can route fully end-to-end through a single plugin. All Node 20+ TS except Slack (Rust, existed; enhanced with outbound).animus-trigger-slackv0.2.0 — Socket Mode inbound +chat.postMessage/chat.postEphemeral/send_dmoutbound (Rust)animus-trigger-discordv0.1.0 — gateway intents (mentions + DMs) +send_channel_message/send_dm/send_embed(Node TS, discord.js)animus-trigger-whatsappv0.1.1 — Cloud API webhook (incl. signature validation) +send_text/send_template/send_media(Node TS)animus-trigger-telegramv0.1.0 — polling + webhook modes +send_message/send_photo/edit_message_text/answer_callback_query/set_chat_action(Node TS, grammy)animus-trigger-emailv0.1.1 — IMAP IDLE inbound (mailparser) + nodemailer SMTP outbound with correctly-threaded replies (In-Reply-To + References) (Node TS)animus-trigger-sms-twiliov0.1.0 — Twilio webhook withX-Twilio-Signaturevalidation +sms/send/sms/send_mms(Node TS)
fix(protocol-spec):trigger/eventwire shape was nested inspec.mdbut flat in the host runtime. The host'strigger_supervisor.rs:289deserializesnotification.paramsdirectly into the flatTriggerEventstruct (event_id,trigger_id,payload,action_hint,subject_id,subject_kind). The spec was showing{ id, event: {...} }which never matched any version of the host code. Spec.md §7.3 + §11.1 rewritten to match the deployed wire shape. Deeper drift flagged for v0.5 follow-up:animus-trigger-protocolcrate +animus-plugin-runtime::drive_trigger_streamin the launchapp-dev/animus-protocol repo still emit the old nested shape; existing Rust trigger plugins built ontrigger_backend_mainmay be silently dropped by the host today.
- Wire-shape mismatch caught + fixed in 2 of the comm plugins
(whatsapp v0.1.0 → v0.1.1, email v0.1.0 → v0.1.1) that initially followed
the stale spec. Other 3 Node plugins (discord, telegram, sms-twilio) and
the slack enhancement all verified against
trigger_supervisor.rs:289source-of-truth and shipped with flat shape from v0.1.0. - 2026-05-28
P3 housekeeping complete + vertical pack expansion (6 new reference packs) + the Python plugin SDK. The audit backlog is empty through P3. The workflow-engine framing now has 7 reference packs spanning engineering, customer support, marketing outreach, sales pipeline, recruiting, organization meetings, and ecommerce fulfillment.
feat(sdk-py): Python plugin SDK skeleton atsdk/python/animus_plugin_sdk/. NDJSON-RPC stdio wire, handshake,define_plugin(...), role Protocols, PEP 561py.typed, mypy strict + ruff clean. 1609 src + 544 test LOC. subject_backend role fully wired (same scope as the v0.4.14 TS SDK).feat(packs): 6 new vertical reference packs underpacks/: marketing-outreach (4-phase prospect triage), engineering-backlog (6-phase impl+codex-review+test+PR), sales-pipeline (5-phase BANT qualification), recruiting-pipeline (screening + debrief), organization-meetings (per-meeting + weekly rollup), ecommerce-fulfillment (process-order + handle-return).
fix(packs): namespace phase IDs to avoid cross-pack collisions in the daemon's globalphase_definitionsmap.flag_for_reviewwas used by 3 packs;enrichby 2. Renamedticket_*/prospect_*/lead_*.
chore(workflow-runner): rename binaryao-workflow-runner→animus-workflow-runnerwith 3 layers of back-compat (install-time symlink + Dockerfile symlink- daemon resolver fallback). Migration doc at
docs/migration/v0.4-to-v0.5.md.
- daemon resolver fallback). Migration doc at
chore: sweep staleao.*references from user-facing strings — 76 files. MCPao://project/*resource URIs still advertised for client back-compat.chore: strict clippy workspace-clean — 20 violations fixed. CI now runs clippy with-D warnings+cargo check --workspace --all-targets. Addedprotocol_drift_subjectCI gate alongside plugin-protocol.
feat(protocol): addTransportBackend+WebUivariants toPluginKind. The typedPluginKindenum inanimus-plugin-protocolpreviously only modeledprovider/subject_backend/task_backend/trigger_backend/log_storage_backend/custom, so first-partytransport_backend(consumed byops_web.rs) andweb_ui(legacypartition_transport_pluginspath) plugins landed inPluginKind::Other(...)for any caller using the typed helper. AddsPLUGIN_KIND_TRANSPORT_BACKEND/PLUGIN_KIND_WEB_UIconstants with matching enum variants, round-trip serde tests, and an updatedprotocol_driftallowlist. Wire shape is unchanged. Follow-up: bump the upstreamlaunchapp-dev/animus-protocolcrate to mirror the new variants and remove the allowlist entries.
chore(workflow-runner): rename binaryao-workflow-runner→animus-workflow-runnerwith back-compat (P3 housekeeping). Closes the v0.4.0 known-follow-up that left the legacyao-*bin name in place. The[[bin]]entry incrates/workflow-runner-v2/Cargo.tomlnow emitsanimus-workflow-runner;Dockerfile,scripts/install.sh, and.github/workflows/release.ymlall bundle the new name. Back-compat:- The daemon's runner resolver (
build_runner_command_from_dispatch.rs) probesanimus-workflow-runnerfirst and falls back toao-workflow-runner. Each name is resolved viacurrent_exesibling first, thenPATHsearch, so a daemon in~/.cargo/bin/still finds a runner that lives in~/.local/bin/. Atracing::warn!line nudges operators to reinstall when the legacy binary is selected. scripts/install.shcreates a back-compatao-workflow-runnersymlink alongside the new binary and also accepts already-published archives that ship the legacy filename (mappingao-workflow-runneronto the new name during extraction).Dockerfileadds the same symlink inside the image. v0.4.x daemon PIDs still spawning the legacy name keep working until they're restarted on the new lookup.- Migration steps live at
docs/migration/v0.4-to-v0.5.md.
- The daemon's runner resolver (
chore(lint): strict clippy now green workspace-wide (audit J4). Resolved every-D warningsviolationcargo animus-lint-strictsurfaced —unnecessary_filter_map,derivable_impls,single_char_pattern,while_let_on_iterator,chars_last_cmp,if_same_then_else,io_other_error,default_constructed_unit_structs,vec_init_then_push,manual_assert,useless_vec,useless_conversion,unwrap_or_else_on_some,nonminimal_bool,err_expect, plus six#[ignore]-without-reason entries that already had explanatory comments above them. The intentional std-Mutex-across-.awaitpatterns in the doctor / plugin-tools / e2e test modules get a scoped#[allow(clippy::await_holding_lock)]with a note: the contended resource is the process env, not the lock.
ci(workspace): gate clippy on-D warnings+ check the whole workspace in one cached job (audit J5). Adds-D warningstocargo clippyinrust-workspace-ci.ymlso any future regression fails the PR. Replaces the four-packagecargo-checkmatrix with a singlecargo check --workspace --all-targetsjob — one cache reuse instead of four, no manual sync againstdocs/guides/ci-cd.md.ci(drift): subject-protocol drift now gated alongside plugin-protocol (audit J6). Addsprotocol_drift_subjecttocrates/orchestrator-plugin-host/tests/protocol_drift.rsand exportsANIMUS_STANDALONE_SUBJECT_PATHfromprotocol-drift.ymlso any divergence between the in-treeanimus-subject-protocoland the standalonelaunchapp-dev/animus-protocol/animus-subject-protocolfails the build. The existing standalone-ahead entries are documented in the test'sexpected_driftallowlist for the next re-sync to drain.
Second audit-remediation release. Lands the 8 remaining P2 findings the external audit surfaced after v0.4.14 — MCP scoping, project-local plugin discovery in worktrees, logging scope unification, log_storage_backend actually dispatched, schedule/trigger budget split, skill path migration, and agent-host skill trust hardening. Plus docs-agent cleanup that accumulated across v0.4.13 → v0.4.15.
fix(mcp): per-project plugin registry cache (audit H1).AoMcpServerused to memoize a singlePluginRegistryfor the lifetime of the server. An MCPanimus.plugin.callagainst/repo/awould warm the cache, and a later call against/repo/bwould silently reuse/repo/a's discovered plugin set — running the wrong binary. The cache is now aHashMap<PathBuf, Arc<Mutex<PluginRegistry>>>keyed by the canonicalproject_root(canonicalize_lossy). When no override is supplied, the server'sdefault_project_rootis canonicalized and used as the key — there is no separate sentinel. Install/uninstall/marketplace-update all clear the entire cache so the next call rediscovers freshly mutated binaries.fix(mcp): plumb project_root + force_rewrite_lockfile through MCP install/uninstall (audit H2).animus.plugin.installandanimus.plugin.uninstallused to passproject_root: Nonetorun_plugin_install/run_plugin_uninstall, so the install-time lockfile + audit log silently fell through to~/.animus/plugins.lockinstead of the project-local.animus/plugins.lock. Both tools now accept an optionalproject_rootfield (defaulting to the server's configured root) and forward it.animus.plugin.installalso acceptsforce_rewrite_lockfile: boolto match the CLI's v0.4.14 G2 fail-closed escape hatch. Tool descriptions updated.fix(session): Carryproject_rootthroughSessionRequestso worktree-bound tasks find project-local provider plugins. When a task ran inside a managed worktree (~/.animus/<scope>/worktrees/...),spawn_session_processdiscovered provider plugins viaPath::new(cwd)and droppedproject_rootfrom the outgoingSessionRequest. Plugins installed at<project_root>/.animus/plugins/animus-provider-*therefore disappeared the moment a workflow switched into its worktree, breaking the plugin author guide's project-local-plugin promise. The supervisor now plumbsproject_rootthrough tospawn_session_process/build_session_request, the resolver scans<project_root>/.animus/plugins/instead of<cwd>/.animus/plugins/, and the field rides over the JSON-RPCagent/runpayload to the plugin. Negative-cased test inagent-runnerproves the fix.fix(logging): delegateLogger::for_project/for_run/logs_dirtoprotocol::scoped_state_rootfor canonical scope isolation.orchestrator-logginghad its own weaker scope resolver that scanned~/.animusfor directories starting with the raw basename and compared.project-rootto the raw input string with no canonicalization. Callers passing a relative path, a symlinked path, or a not-yet-created scope fell back to<project>/.animus/logs/, contradictingdocs/reference/data-layout.mdwhich puts logs at~/.animus/<repo-scope>/logs/events.jsonl. It also missed the G1 v0.4.14 hardening, so two clones of the same git origin could collide. Replaced the local resolver withprotocol::scoped_state_root(project_root)— same canonical-basename hashing, same-origin collision guard, same moved-clone reclaim path. Added regression tests covering noncanonical input, distinct same-origin clones, and end-to-endfor_project/for_runwrites landing under~/.animus/<scope>/logs/. (audit P2 cleanup)- daemon: actually dispatch
log_storage_backendplugin forlog_storage/store+log_storage/query(P2 audit follow-up). When alog_storage_backendplugin is installed the daemon now spawns + handshakes it at startup viaPluginSpawnOptions::for_manifest(matching subject/trigger/health), installs a process-globalLogStorageHandle, and forwards everyDaemonEventLog::appendrecord to the plugin as alog_storage/storerequest (theanimus-log-storage-protocolwire shape) while still writing the in-treedaemon-events.jsonl(tee policy — preserves existingdaemon events/ MCPdaemon.eventspoll readers). Thedaemon/logscontrol endpoint now issueslog_storage/queryagainst the plugin instead of reading the in-tree file, soanimus logs tailreflects the installed backend. Plugin failures degrade to atracing::warn!rather than killing the daemon. fix(scheduler): shared decrementing tick budget across schedule + trigger dispatch. Audit P2 finding:run_project_tickpreviously passed the SAME headroom value to the schedule hook and the trigger hook, letting each path spend the full pool independently. With pool cap N=5, schedule could commit 3 dispatches and trigger would then drain 5 webhook events —ProcessManagerrefused the over-budget spawns, leaving schedules markedlast_runand webhook events dropped without runners. Now both hooks share a&mut TickBudget(claim viatry_take, release on non-capacity spawn failure); failed schedules route through newproject_schedule_dispatch_missedwhich increments a newScheduleRunState.missed_countand leaveslast_rununtouched so the schedule re-fires on the next tick; webhook events are peeked from the queue and only popped after the spawn succeeds. (run_project_tick.rs,default_project_tick_driver.rs,trigger_dispatch.rs,execution_projection.rs,dispatch_support.rs,schedule_state.rs)fix(skill): project + user skill resolution honors the v0.4.animus/naming contract. The resolver andanimus skill install --pathpreviously read and wrote.ao/skills/even though every doc told users to drop SKILL.md under.animus/skills/. Result: skills placed at the documented location were silently undiscovered.project_markdown_skills_dir,user_markdown_skills_dir, and the matching YAML-definition dirs (project_skills_dir,user_skills_dir) incrates/orchestrator-config/src/skill_scoping.rsnow resolve under.animus/. The MCP tool description foranimus.skill.listand thecli_skill_lifecycleregression test both flipped to the new layout. Operators with files still under.ao/skills/OR.ao/config/skill_definitions/get a single one-shotwarning:line per process per legacy path pointing at the new location; runninganimus skill migrate-from-aomoves entries for both the markdown skills dir and the YAML skill-definitions dir, drops a.migrated-from-aomarker in each destination so the warning stops, and refuses to clobber non-empty targets. (audit I1)
fix(skill): agent-host skill stripper also clearsmodelandtimeout_secs.strip_structural_fields_for_agent_hostincrates/orchestrator-config/src/skill_scoping.rsenforces the trust boundary for SKILL.md files discovered under~/.claude/skills/,~/.codex/skills/, etc. The strip list previously coveredtool_policy,extra_args,env,mcp_servers,adapters,codex_config_overrides, andcapabilities, but leftmodelandtimeout_secsflowing through. A hostile SKILL.md could therefore force the runner onto a cheaper / less-capable model (or claim a model the workspace doesn't have access to) and monopolize the runner with an arbitrarily long timeout. Both fields are now stripped, and a new field-coverage test (agent_host_strip_covers_every_runtime_field) enumerates every serializableSkillDefinitionfield and fails the build if a new field is added without an explicit allowlist / strip-list classification.docs/architecture/skill-system.mdupdated to match. (audit I2)
Audit remediation release. External audit + parallel review across v0.3.2 → v0.4.13 surfaced 30+ findings (security, durability, JSON contract, control-protocol parity, release pipeline). This release lands ~20 of them across 14 merge commits + adds the TypeScript plugin SDK skeleton and the first non-coding reference pack (customer support).
feat(sdk-ts): TypeScript plugin SDK skeleton atsdk/typescript/animus-plugin-sdk/. Wire (NDJSON-RPC over stdio), handshake,definePlugin({ kind, impl, ... })entrypoint. subject_backend role fully wired (list/get/create/update/status/next); provider, trigger_backend, transport_backend, log_storage_backend roles export type-only interfaces and throw on dispatch (to prevent host routing to unwired plugins). 28 vitest tests.feat(sdk-ts): TypeScript types codegen from JSON Schemas. Newscripts/codegen.mjsgeneratessrc/types/{plugin,subject}-protocol.ts(32 types total) fromschemas/animus-{plugin,subject}-protocol/_all.jsonviajson-schema-to-typescript.pnpm run codegen:checkenforces no-drift. Enums render as"known" | (string & {})to preserve autocomplete + accept arbitrary strings for the RustOther(String)forward-compat case.feat(examples): hello-world TS plugin atexamples/plugin-hello-ts/. ~60-linesrc/index.ts, complete README install workflow, bash launcher, local-install script. The reference for what "plugin authoring in TypeScript" looks like end-to-end.feat(packs): first-party customer-support reference pack atpacks/customer-support/. 4-phase workflow (classify → draft_response → summarize_for_human → flag_for_review), 5 realistic sample tickets, full README + architecture + customizing docs, idempotent setup script. Proves the workflow-engine framing for a non-coding vertical and serves as the agency starter fork for support automation.
fix(cli): emit JSON envelope for clap argparse failures when--jsonis set.Cli::parse()exits the process directly on bad argv, bypassing every downstreamemit_cli_errorcall.mainnow pre-scans argv for--json, switches toCli::try_parse(), and on parse error emits ananimus.cli.v1invalid_input envelope witherror.details.stage = "parse"plus the raw clap text underdetails.raw. Non-JSON callers keep clap's pretty-printed help text unchanged. Exit code stays 2. (audit Fix 3)fix(cli):animus web serve --jsonreturns JSON envelope when no transport plugins installed.bail_with_install_helpwrote multi-line human help to stderr and calledstd::process::exit(2), never giving the JSON envelope path a chance to fire. Replaced withmissing_transport_plugins_error(json)which routes throughCliError(InvalidInput)— JSON callers get a single-line message pluserror.details.install_commandanderror.details.individual_plugins; humans still see the multi-line help. (audit Fix 1)fix(cli): MCP tool error payloads now read the stderr envelope. The productionbuild_tool_error_payloadandbatch_item_error_from_resultonly checkedstdout_json, so a properly-emittedanimus.cli.v1error envelope on stderr was silently dropped. A test-only helper (build_cli_error_payload) handled stderr correctly, so the test suite was green against the wrong helper. Production helpers now share apick_envelope_errorthat prefersstderr_json(canonical error channel perdocs/reference/json-envelope.md) overstdout_json. Added production-path regression tests. (audit Fix 2)fix(cli):workflow run --sync --jsonkeeps stderr silent. Phase/progress emitters inops_workflow::executeaccepted_jsonbut ignored it, spraying ANSI-colored progress to stderr in--jsonmode. Now gated onif json { return }so the JSON envelope on stdout is the entire user-facing surface. (audit Fix 4)fix(cli):init --walkthrough --jsonno longer prompts in TTY. Theinteractiveflag was computed from TTY detection alone, so a Guided walkthrough in a TTY would block onprompt_yes_noeven when--jsonwas set (silent hang for scripted callers).interactivenow requires!json, so the JSON envelope path is the entire surface in JSON mode. (audit Fix 5)fix(durability): propagate phase-checkpoint write failures in dispatch to preserve the crash-replay invariant. Three failure points inrun_workflow_phase_attempt(crates/workflow-runner-v2/src/phase_executor.rs) previously logged and continued: the pending-checkpoint write before runner dispatch, the post-dispatch flip toRunning, and the terminalCompleted/Failedmutation. Recovery scansRunningcheckpoints to shield in-flight phases across daemon restart; dispatching without a durable checkpoint risked silently losing the work AND double-dispatching side-effecting phases on a re-tick. All three now return explicit errors using typed sentinels:DispatchRetryableErrorfor the pre-runner case (no side-effecting work happened yet — operator triage can distinguish via thedispatch_retryevent discriminator) andTerminalCheckpointErrorfor the post-runner cases. The sentinels are matched by the agent retry/failover loop, which REFUSES to redispatch a phase on those errors even when the I/O message overlaps the transient-runner classifier (eg "connection timed out" on a network-storage fsync) — this prevents the agent loop from re-running a phase whose side effects have already executed. All four cases terminally fail the workflow phase so downstream daemon reconciliation surfaces the failure correctly and orphan recovery skips the run; automatic next-tick retry is left for a follow-up because it would require scheduler changes outside this PR. Added a per-thread fault-injection seam (phase_session::test_fault) with 5 regression tests covering each failure mode plus the typed-sentinel detection.fix(durability): persist failure now fails the phase instead of silently advancing.workflow_execute.rspreviously didlet _ = persist_phase_output(...)after a successful phase and then advanced workflow state — a persist failure would leave the workflow ahead of its durable completion marker. The resumed-agent path indaemon_run.rsalready treated this same failure as fatal. The normal-execution path now callsfail_current_phasewhen persistence fails (with apersist_failedphase_status discriminator so operators can distinguish from real phase failures), preventing the workflow from ever advancing past a phase whose completion oracle isn't on disk. Added a fault-injection seam (phase_output::test_fault) plus a behavioral test that the workflow'scurrent_phase_indexdoes not change when persist fails and the workflow ends inWorkflowStatus::Failed.fix(durability): graceful drain of subprocess workflow_events on shutdown AND normal-lifecycle process completion.SubprocessEventPipe::shutdownpreviously aborted the reader task unconditionally, which could drop the final batch of events the runner emitted right before exiting (writer flushed bytes into the socket buffer; reader had not yet consumed them when abort fired). The reader loop now responds to a shutdown notification by entering a bounded-wait drain pass (50 ms accept-queue probe + unbounded EOF read, capped overall by the 250 msSHUTDOWN_DRAIN_DEADLINE) so a runaway plugin still cannot stall daemon shutdown. The deadline path now keeps theJoinHandleborrowed via&mut taskacross the timeout so a leaked reader is actuallyabort()-ed on fallback (dropping aJoinHandleonly detaches the task).ProcessManager::check_runningnow takes and awaitsevent_pipe.shutdown()on the normal-completion, timeout, and probe-error paths so the drain runs in the production lifecycle, not only on explicit pipe-shutdown. New regression test writes 3 events, closes the writer, immediately calls shutdown, and asserts all 3 events reach the broadcaster.
fix(session-host): enforce provider plugin env allowlist via the plugin manifest at the RPC layer.PluginSessionBackend::dispatchwas forwarding every key inSessionRequest.env_varsstraight through to the plugin's spawn-timeenv_allowlist, copying them into the per-call RPCenvparam AND embedding the full merged launch env underextras.runtime_contract.cli.launch.env, bypassing the manifest gate documented indocs/guides/plugin-author-guide.md§ 9. A provider plugin whose manifest declared noenv_requiredcould still receive the runner's sanitized launch env wholesale (e.g.OPENAI_API_KEYreaching a Claude plugin that never asked for it). Fix intersects request env keys against (PLUGIN_BASE_ENV_ALLOWLIST∪manifest.env_required) at the dispatch boundary and applies the SAME filter to all three leak channels — spawn allowlist, RPCenvparam, and runtime_contract launch env. Six new regression tests cover empty-manifest scrubbing, manifest-declared pass-through, the RPC-param surface, and the runtime_contract surface (including no-op behaviour when the path is absent or wrong-shape).
docs(security): align signature-policy default text with code (Warn, notStrict). Several places — thePolicyMode/PolicyMode::default_for_installrustdoc, theSignaturePolicy::default_installrustdoc,effective_policy_modeinops_plugin.rs, the v0.4.12 entry inCHANGELOG.md, and the "v0.4.12 temporary default" section indocs/reference/security.md— claimed v0.4.13 would flip the install-time default back toStrict. The code atsignature_verifier.rs::default_for_installcontinues to returnWarnintentionally; the docs were stale. Text now reflects the shipped behaviour and the recommended--signature-policy strictopt-in for production. No behaviour change.fix(web): drive transport protocol lifecycle (transport/start+transport/shutdown) per spec.animus web serve/animus web openonly calledinitializeand (optionally)transport/infoon installedtransport_backendplugins, then waited in the foreground. The animus-protocol v0.1.13 spec (§13) requires the host to issuetransport/startwith aTransportConfigafterinitializeso the plugin can bind its listener, andtransport/shutdownbefore process exit so it can drain in-flight requests. The ops_web path now drives the full lifecycle: afterinitializeit sendstransport/startwith the control socket path + project root, and the newPluginHost::shutdown_transport()helper issuestransport/shutdown(with a bounded 5 s timeout) before the genericshutdownRPC. Legacy plugins that pre-date the lifecycle (current launchapp-devanimus-transport-http,animus-transport-graphql, andanimus-web-uibind insideinitialize) respond with METHOD_NOT_FOUND / METHOD_NOT_SUPPORTED and are handled gracefully with a deprecation warning so the v0.4.x web surface keeps working while the ecosystem upgrades. New plugin-host tests intests/concurrency.rsassert the spec ordering (initialize→ work →transport/shutdown→shutdown) and the legacy METHOD_NOT_FOUND swallow.fix(workflow): plumb--var KEY=VALUEthrough the control wire.animus workflow run --task-id ... --var FOO=bar --jsonwas silently dropping the user-supplied vars when the daemon was running because the control-pathWorkflowRunRequestwas sent withparams: Default::default(). Vars now round-trip viaparams["vars"]and reachWorkflowRunInput::with_varson the daemon side, matching the local path. Same fix applies toworkflow_executeover the control wire.fix(queue): stop silently swapping--workflow-reffor the project default when the daemon is running.animus queue enqueue --task-id ... --workflow-ref custom --jsonwas routing through the wire-sidequeue/enqueuethat only carriestask_id + priority, then the daemon loaded the default workflow ref. Now the CLI degrades to the local path whenever--workflow-refis set so the user's requested workflow is honored. (Wire-side fix needs a newworkflow_reffield onQueueEnqueueRequest, deferred until the external protocol crate gains it.)fix(plugin): stop silently ignoring--include-system-pathover the control wire.animus plugin list/info/ping/call --include-system-path --jsonwas routing through the daemon (which hardcodesinclude_system_path: false), dropping the flag. The CLI now stays on the local discovery path when the flag is set.fix(daemon preflight): exit non-zero when required plugins are missing.animus daemon preflightwas printing"ok": falseinside the payload but exiting 0 with"ok": trueon the outer envelope — contradictingdocs/getting-started/installation.md's contract and silently passing CI gates. Now exits 2 (invalid_input) when required roles are missing, exit 1 (internal) on transient plugin-discovery failures, and exit 0 only when all required roles are satisfied. The error envelope carries the actionableanimus plugin install ...fix message.fix(daemon preflight): surface plugin-discovery failures instead of swallowing them. The standalone preflight previously useddiscover_installed_plugins(...).unwrap_or_default(), masking real discovery errors (broken install index, IO failures) as "no plugins installed". Now propagates discovery errors as exit code 1 with a clear message, distinct from "ran successfully and found gaps".fix(init walkthrough): don't ship the first-contact daemon in a degraded state.animus init --walkthrough --auto-startwas installing providers-only and then booting the daemon with--skip-preflight, hiding missing subject/transport backends from the operator. Now installs the full required-role set (--include-subjects --include-transports) AND drops--skip-preflightfrom the daemon spawn so any leftover gaps surface as actionable preflight errors before the daemon boots.fix(protocol)[P1]: isolate scopes by canonical root even when origin matches.protocol::scoped_state_rootfirst hashes the canonical project root into a<repo-name>-<12 hex>scope, but if that directory did not yet exist it fell back tofind_existing_scope_by_origin, which would return any scope sharing the same git remote URL. Two distinct clones of the same repo could therefore alias onto one scope and silently shareworkflow.db, logs, worktrees, and state. The fallback now compares the candidate scope's recorded.project-rootmarker against the requested canonical root and only adopts the existing directory when (a) no marker is present (legacy unmigrated scopes) or (b) the recorded path no longer canonicalizes (the user moved the repo on disk). Two regression tests cover the same-origin isolation guarantee and the moved-clone adoption path.
fix(plugin-install): fail closed on corrupt plugin lockfile; add--force-rewrite-lockfileescape hatch.animus plugin installandanimus plugin install-defaultspreviously caughtPluginLockfile::load_defaulterrors with atracing::warn!and continued with an empty in-memory lockfile that was then saved over the corrupt file on success. This was fail-OPEN against the tamper/audit boundary: an attacker who corrupted.animus/plugins.lock(or who hand-edited it incorrectly) could trigger a fresh install with no integrity checks and silently lose the recordedsha256history. The install path now REFUSES with an actionable error that names the corrupt path, surfaces the underlying loader error chain, and documents two remediation paths: (1) restore the lockfile from version control / backup, or (2) re-run with--force-rewrite-lockfileto discard and rebuild. Path (2) emits an explicitwarn!noting integrity history was reset. Flag is wired through bothinstallandinstall-defaults; MCP and control-plane install surfaces default to fail-closed (no override). New tests cover the refusal and the override paths.fix(daemon-health): apply manifestenv_requiredto plugin probes.daemon_health()incrates/orchestrator-daemon-runtime/src/control/dispatch.rswas probing each plugin withPluginSpawnOptions::default(), which scrubs env down to the base allowlist and never forwards manifest-declared vars. As a result, provider plugins that need credentials (e.g.OPENAI_API_KEY,ANTHROPIC_API_KEY,LINEAR_API_TOKEN) were spawned without them during the probe and reported false-unhealthy even when the daemon environment carried the keys. The probe now builds spawn options viaPluginSpawnOptions::for_manifest(&plugin.manifest.env_required, ...)— the same path used bysubject_dispatch.rsandschedule/trigger_supervisor.rs. When arequired = truevar is missing from the daemon environment we emit adaemon_health.probewarn so the operator can correlate the unhealthy row with the missing secret. (3 new unit tests covering the present / missing / no-env-declared shapes.)
- Log redaction now scrubs secret-keyed JSON metadata, not just
regex-matched values. Previously,
redact_json_valuerecursed intometaobjects and only ran the content regex against string values, so a payload likemeta({"api_key":"sk_live_..."})persisted the raw secret toevents.jsonlbecause the bare value did not match anykey=valuecontent pattern. The recursion now also checks each JSON object key against a default secret-key set (api_key,apikey,token,access_token,refresh_token,id_token,secret,client_secret,password,passwd,pwd,authorization,bearer,private_key,signing_key,x-api-key). Matching is case-insensitive and treats_and-as equivalent, and the list also includes no-separator variants (privatekey,signingkey,accesstoken, etc.) so camelCase keys such asprivateKey,signingKey,accessTokenare redacted as well as snake_case and kebab-case forms (X-API-Key,access-token,Authorization). The value-content regex path is unchanged and still fires for plain strings such as"description":"my api_key=sk_live_abc was leaked". Override the secret-key list with the newANIMUS_LOG_REDACT_KEYSenvironment variable (comma-separated; replaces defaults). Documented indocs/reference/observability.md. (P2 from external audit.)
Operational hardening of the v0.4.12 plugin extraction. Several pieces of
infrastructure shipped in v0.4.12 (signature verifier, process quota,
durability fsync, doctor diagnostics) but were not actually invoked on
the user path; v0.4.13 wires them up. Plus the v0.4.13 W-bundle ships
plugin lockfile, audit log, subprocess workflow_events back-channel,
runtime quotas, and an onboarding overhaul (animus init, animus doctor).
fix(installer): invoke keyless cosign TrustedPublisher verifier on install + close cross-repo SAN attack. The keyless cosign rewrite (v0.4.12 commit 518c0d9e) landedverify_plugin_installinorchestrator-plugin-host::signature_verifierbutanimus plugin installwas still routing through a localverify_with_cosignwith a weak^https://github.com/<owner>/<repo>/.+regex that would have accepted a bundle signed bylaunchapp-dev/animus-subject-linearagainst an install oflaunchapp-dev/animus-provider-claude(cross-repo SAN attack). Fix delegates to the host verifier with the TrustedPublisher regex pinned per-install to the specific repo segment, and re-applies the trusted-signers allowlist on the host verdict. 6 new tests including the attack-shape regression.
feat(durability bundle): plugin lockfile + audit log + subprocess workflow_events back-channel + runtime quotas..animus/plugins.lockpinssha256(artifact)andsha256(signature_bundle); install appends, upgrade refuses on mismatch without--force.~/.animus/<scope>/audit.jsonlis an append-only audit log of plugin installs / signatures / quota denials with 10 MB rotation. Subprocess phases now receive workflow events via a per-run Unix-domain-socket back-channel ($TMPDIR/animus-event-pipes/<daemon-pid>/<subject>-<hex>.sock) instead of relying on direct broadcaster handles.RuntimeQuotascaps trigger backlog (1000), subscriber memory (10 MB/sub), plugin process count (50), workflow concurrency (10).feat(plugin-host): enforce process quota viaProcessSlotFactoryat spawn site. ThePluginProcessSlotRAII guard from the W-bundle is now actually acquired inhost.rs::spawn_with_options. Wiring uses a trait (ProcessSlotFactory) the daemon installs at startup to avoid a circular dep between plugin-host and daemon-runtime. Spawn returnsProcessSlotError::Exhaustedwhen at cap; the slot is released eagerly on shutdown.feat(durability): fsync session checkpoints + phase markers + task state writes (W3). All atomic writes now callFile::sync_all()+fsync_rename()so a crash mid-write cannot leave torn state.feat(daemon): deferProcessManagerbroadcaster lookup to spawn time so subprocess workflow events actually fire. The lookup was happening at construction time, beforerun_daemoninstalled the broadcaster, always returningNone.
feat(onboarding):animus initinteractive walkthrough + hello-world workflow template. First-run experience: detects whether plugins are installed, offers--auto-install, drops a.animus/skeleton with a bundledhello-world.yamlworkflow you can run immediately.feat(onboarding):animus doctorpolish — 8 actionable diagnostic categories. Replaces the previous monolithic health output with category-scoped checks (daemon socket, plugin install state, signature cache, audit log presence, etc.) each with concrete remediation steps.
-
fix(web): partition transport plugins by$ui/webcapability soanimus web openpicks the UI instead of an API transport. Previouslyops_web.rskeyed offplugin_kind = "web_ui", but no installed plugin declares that kind —animus-web-uiships astransport_backendlike the API transports, so the UI bucket was empty andtransport-httpwon the sort. The browser opened the API endpoint instead of the UI. Fix:ops_web.rsnow scans each transport plugin's manifestcapabilities(whichextra_capabilitiesflattens into at discovery time) and treats any plugin advertising$ui/webas the UI surface.animus web openopens the UI URL when one is installed and falls back to the highest-priority API transport with a warning explaining how to remediate.animus web serveprintsUI:andAPI (<name>):on their own labelled lines and includesui_url/api_url/serves_uiin the JSON envelope.Follow-up required in
launchapp-dev/animus-web-uiv0.1.2: declare"$ui/web"in its manifest via the v0.1.13extra_capabilitiesextension point. Until that plugin ships, machines with only the default transports installed still open the API endpoint and get the warning above. -
fix(doctor): avoid nested tokio runtime in daemon RPC check.probe_daemon_healthwas constructing a current-threadRuntimeand callingblock_onwhile already inside the#[tokio::main]runtime drivinghandle_doctor, panicking with "Cannot start a runtime from within a runtime" whenever the daemon control socket was present and--skip-subprocesswas off. Fix converts the probe to async and propagates.awaitthroughrun_all_checks. -
fix(durability): skip marker writes for--phaseruns + canonicalize provider alias on restart resume. Two follow-ups from codex round 8: the--phasefilter path now usespersist_phase_output_without_markerso partial workflow runs do not advance the workflow marker, and restart-resume now normalizesoai-runner/animus-oai-runner→oaibefore looking up the resume target. -
fix(init): create.animus/beforeinstall-defaults+ suppress child JSON on parent stdout. Two codex round-9 P2s in the newanimus init --walkthrough. (1) The plugin install was running before the template copy created.animus/, soPluginLockfile::default_pathfell back to~/.animus/plugins.lock; subsequentanimus plugin lock list/verifythen looked at the now-existing project-local lockfile and missed the walkthrough's entries. Fix creates.animus/first so the lockfile location is stable. (2)animus init --walkthrough --jsonwas passing--jsonto the child install / daemon subprocesses while inheriting their stdout, producing multiple JSON documents on the parent stdout. Fix pipes (and discards) child stdout whenjson=true, inherits in human mode so progress still streams. -
fix(workflow-runner-v2): unify plugin_pack_fixture tests with the crate-wide state serializer.plugin_pack_fixture_testshad its own localenv_lock()mutex while every other HOME-mutating test usedcrate::test_env::scoped_state_serializer. Parallel runs raced onHOMEbetween the two groups, surfacing intermittently asagent_state::tests::memory_delete_entry_by_id_removes_only_matching_entryfailures depending on which test got HOME first. Fix routes the 4 plugin_pack_fixture tests through the shared serializer so a single mutex covers all HOME mutators.cargo test -p workflow-runner-v2(default parallel) now passes 120 tests with no flakes. -
**
fix(daemon): enforce default workflow concurrency fromRuntimeQuotas- bind event-pipe synchronously.** Two daemon-runtime correctness
fixes from user codex round 9: (1)
RuntimeQuotas.workflow_concurrency_max(default 10) was documented butProcessManager::new()only honored the env var; the cap was effectively unbounded for typical operators.daemon_runnow installsRuntimeQuotas::from_env()before constructingProcessManager, which seeds its spawn cap from the quota struct.schedule_headroom(pool_size, active)now caps the effective pool atmin(pool_size, runtime_quota)so scheduler and trigger paths never select more work than the spawn site will accept. Without this, oversizedpool_sizeconfigs would silently consume schedules and drain webhook events without runners ever starting. (2)SubprocessEventPipe::bindspawned an async bind task and waited on astd::sync::mpsc::Receiverfrom the calling thread, which could deadlock a current-thread runtime and stall a multi-thread worker. Newbind_syncbinds the socket synchronously on the calling thread and spawns only the reader task on the current Tokio runtime.
- bind event-pipe synchronously.** Two daemon-runtime correctness
fixes from user codex round 9: (1)
docs: refresh plugin version refs after registry bumps. Installation, upgrading, web-dashboard, and CLI reference docs now matchplugin_registry.rsconstants.plugin_types.rshelp text cross-references the registry instead of hard-coded versions to prevent future drift.
chore(cleanup): drop dead code, dedupe audit test, fix duplicate test attr, remove unused imports. 5 → 0 workspace build warnings.chore(scripts): adddispatch-wave.shhelper for worktree-isolated parallel agent dispatch. Standardizes thegit worktree add+ per-agent branch + merge / cleanup flow for parallel sub-agent waves. Not on the user path — used by the maintainer when driving multi-agent cleanup waves like this release.
Closes the v0.4 arc. Finishes plugin extraction (web stack, subject adapters,
all in-tree provider mirrors, and llm-cli-wrapper are gone), ships the
durability items that were carrying // TODO: v0.5 notes, and turns the
plugin host into a first-class component of daemon startup with preflight +
auto-install. Approximately 11.5K lines of code removed from this repo; the
replacement code lives in 18 standalone plugin repositories under
launchapp-dev/.
feat(daemon preflight + auto-install): plugin presence check on startup. Newanimus daemon preflightstandalone subcommand prints the current installed-vs-required matrix;animus daemon start/animus daemon runnow run the same check before booting. Default posture is refuse-to-start when any required role is unsatisfied — the error surfaces the exactanimus plugin install ...command to remediate.--auto-installinstalls the daemon's recommended default plugin for any missing role fromlaunchapp-devreleases before continuing.--skip-preflightis the escape hatch for dev iteration. JSON envelope isanimus.daemon.preflight.v1.feat(plugin install-defaults): bulk install for the standard 5 providers. Newanimus plugin install-defaultsinstalls the claude / codex / gemini / opencode / oai provider plugins at v0.2.1. Flags:--include-oai-agent(oai-agent v0.1.1),--include-subjects(default / requirements / linear / sqlite / markdown subject backends),--include-transports(transport-http + transport-graphql + web-ui). Uses the same install pipeline asanimus plugin install, so signature verification and integrity checks apply uniformly.feat(daemon workflow events): workflow/events ControlClient subscription. Daemon-side broadcaster now emitsphase_started/phase_completed/workflow_completed/workflow_failednotifications onworkflow/events. Subscribers can filter byworkflow_idor by kinds. Closes the matching v0.5 deferral.
feat(notifications log): per-run JSONL. Each run now writes~/.animus/<scope>/runs/<id>/notifications.jsonlwith{ seq, ts, phase, ... }rows. 100 MB rotation; partial trailing lines are dropped on replay. UI clients can reconnect and replay from anyseq.- **
feat(session checkpointing + auto-resume): per-phase<phase>.session.jsonholds{ provider, session_id, status }. On daemon restart, the scheduler attemptsprovider.resume_agentthrough the plugin that originally ran the phase. Failures land asBlockedwith an explicit reason rather than silently re-running. - **
feat(idempotency markers): newidempotency: idempotent | sideeffecting | unknownfield on phases (defaultunknown). Crash recovery auto-retriesidempotentphases; everything else lands asBlockedwith an actionable hint.animus workflow resume <id> --forceis the manual override. - **
feat(atomic completion markers):<phase>.completedis now written via tmp → fsync → rename afterpersist_phase_output. Closes the "output exists, daemon crashed before state transition" race. - **
feat(plugin supervisor): per-plugin restart budget of 3 in 60s with a 5-minuteDisabledcooldown when exhausted. Death-like errors auto-retry once before propagating; structured JSON-RPC errors propagate immediately. - **
refactor(plugin-host): typedHostError::ConnectionLostplusclassify(&HostError) -> RetryDecisionAPI replaces the previous string-substring death-like classifier.
refactor(web): delete in-tree web stack (orchestrator-web-server+orchestrator-web-api+orchestrator-web-contracts, ~9K LOC).animus web serveandanimus web opennow discover installedtransport_backend+web_uiplugins, spawn them, and open the browser. The standalonelaunchapp-dev/animus-transport-{http,graphql}v0.2.x andlaunchapp-dev/animus-web-uiv0.1.0 replace the in-tree implementation.animus plugin install-defaults --include-transportsinstalls the standard set.refactor(subject backends): deleteinproc_subject_backend.rs(~1000 LOC). All subject operations route through theSubjectRouterto an installed plugin —launchapp-dev/animus-subject-defaultv0.1.1 forkind=task,launchapp-dev/animus-subject-requirementsv0.1.6 forkind=requirement(with legacy JSON state compat), pluslaunchapp-dev/animus-subject-{linear,sqlite,markdown}v0.1.4 for the other kinds. The env varsANIMUS_DAEMON_DISABLE_BUILTIN_TASK_ADAPTERandANIMUS_DAEMON_DISABLE_BUILTIN_REQUIREMENTS_ADAPTERare now no-ops. UseANIMUS_DAEMON_DISABLE_SUBJECT_PLUGINS=1to skip subject discovery entirely.refactor(llm-cli-wrapper): crate deleted (~5,882 LOC across the multi-phase retirement). Session DTOs, native backends, the parser, and the error types now live in upstreamanimus-session-backendv0.1.10. Thecli/launch.rssymbols moved intoagent-runner::runner::launch.lookup_binary_in_pathwas inlined aswhich::whichat the few remaining call sites.CapabilityNotSupportedmoved intoorchestrator-session-host::error.refactor(providers): delete the 5 in-tree provider mirrors (animus-provider-{claude,codex,gemini,opencode,oai}). The standalonelaunchapp-dev/animus-provider-*v0.2.1 repos are now the canonical implementations and ship streaming notifications.refactor(protocol mirror): delete in-treeanimus-provider-protocolmirror (-408 LOC). Now consumed from upstream viaanimus-protocolv0.1.10.
animus-protocolpins bumped to v0.1.10. Path through v0.1.6 (animus-transport-protocolcrate +transport_backend_mainentrypoint) → v0.1.7 (AgentNotification+NotificationSink+ProviderBackend::run_agent_streaming) → v0.1.8 (subject/delete wire verb +ControlClientfor cross-process control) → v0.1.9 (subscription API:subject_watch/daemon_events/daemon_logs_follow) → v0.1.10 (ControlClient::workflow_events).
- All 14 pre-existing baseline test failures resolved: 4 rewritten
against the current surface, 8 deleted (they referenced commands
removed in v0.4.4), 1 marked
#[ignore]because of a pre-existing race that is not in this release's scope, 1 already passes after the surrounding refactors. - New:
launchapp-dev/animus-plugin-testkitv0.1.0 — conformance harness with 8 baseline scenarios validated end-to-end againstanimus-provider-claude.
animus web serveno longer boots an in-process axum server. Requires the transport + UI plugins installed first; runanimus plugin install-defaults --include-transportsoranimus daemon preflightfor the exact remediation command.- Daemon requires at least one provider plugin installed at start.
Use
animus daemon start --auto-installoranimus plugin install-defaultsfirst.--skip-preflightis the dev escape hatch. ANIMUS_DAEMON_DISABLE_BUILTIN_TASK_ADAPTERandANIMUS_DAEMON_DISABLE_BUILTIN_REQUIREMENTS_ADAPTERare no-ops. The in-tree subject adapters are gone; install the corresponding subject_backend plugin (see--include-subjects).
refactor(security): cosign keyless OIDC verification replaces the key-based PEM trust path.Everylaunchapp-dev/animus-*release pipeline (animus-transport-{graphql,http},animus-web-ui, and the sixanimus-provider-*repos) signs through GitHub Actions OIDC + Sigstore Fulcio + Rekor, never against a static signing key. The pre-v0.4.12 verifier incrates/orchestrator-plugin-host/src/signature_verifier.rsshelled out tocosign verify-blob --key <PEM>against a bakedLAUNCHAPP_DEV_COSIGN_PUBLIC_KEY_PEMplaceholder, which could never match a real keyless bundle. v0.4.12 rewrites the verifier to keyless: trust is now anchored on the per-publisheridentity_regex+ OIDC issuer combination held inTrustedPublisher. The built-inTrustedPublisher::launchapp_dev()matches^https://github\.com/launchapp-dev/[^/]+/\.github/workflows/release\.yml@refs/tags/v.*against issuerhttps://token.actions.githubusercontent.com, which is exactly the cert SAN that GitHub Actions OIDC bakes into the Fulcio-issued cert for every standardized release. Manual verification:cosign verify-blob --certificate-identity-regexp <regex> --certificate-oidc-issuer <issuer> --bundle <.bundle> <artifact>— seedocs/reference/security.md.- Removed: the baked PEM constant
LAUNCHAPP_DEV_COSIGN_PUBLIC_KEY_PEM, the seed-on-first-strict-install helperseed_launchapp_dev_trusted_key, the~/.animus/trusted-keys/lookup (default_trusted_keys_dir,resolve_trusted_key_for), and thecrates/orchestrator-plugin-host/trusted-keys/launchapp-dev.pubfile. Thedirscrate dependency drops out oforchestrator-plugin-hostalongside. Operators can also delete their local~/.animus/trusted-keys/directory, the GH orgCOSIGN_PRIVATE_KEY/COSIGN_PASSWORDsecrets, and any~/.animus/keys/launchapp-dev.{key,pub,password}files — none of them are read by v0.4.12. Sigstore Fulcio + Rekor (built into thecosignbinary) is now the only trust anchor. - CLI flag
--trust-keyis deprecated and a no-op. Keyless cosign verification has no static public-key trust anchor, so the flag is retained only to avoid breaking existing install scripts. Passing it logs a deprecation warning and proceeds via the normal keyless path. Removal targeted for a future release. security(plugin install): install-time signature policy default iswarn. Now that the trust anchor is real (Fulcio + Rekor) rather than a placeholder PEM,warnrecordssignature_statusand logs a stderr warning on missing / invalid / untrusted signatures without failing the install. Operators wanting fail-closed enforcement opt in per-install viaanimus plugin install --signature-policy strict <repo>. Seedocs/reference/security.md.
Cleanup + automation hardening. Lands the v0.4.10 Node 24 sweep across the plugin matrix and promotes the release-automation tooling from "future work" to "shipped".
ci: merged Node 24 PRs across 15 plugin repos (deadline 2026-06-02). Each plugin repo'schore/force-node24-actionsPR (PR #1, opened in v0.4.10) is now landed onmain. 14 merged clean;animus-protocolwas held back because the existingcargo fmtjob is failing on a pre-existing drift inanimus-plugin-runtime/src/log.rs:241unrelated to the Node 24 env-var change — flagged for v0.4.12.ci(release-automation): add compat-test / validate-manifests / check-signatures scripts. Thelaunchapp-dev/animus-release-automationrepo (shipped in v0.4.10 as v0.1.0 withmatrix.sh+cascade.sh) now ships three additional scripts:compat-test.shruns the in-treeorchestrator-plugin-hostprotocol_driftcontract test against each plugin's pinnedanimus-plugin-protocoltag;validate-manifests.shvalidates every plugin'splugin.tomlagainstanimus.plugin.v1;check-signatures.shaudits cosign bundles on each plugin's latest release against thetrusted-signers.yamldefaults. README updated to v0.1.1 with usage + dependency notes.
Patch release. Picks up v0.4.9 deferrals plus the broader cleanup the user has been queuing — the long-deferred workflows_list wire migration (HTTP only), live per-plugin health/check RPC fan-out, log redaction wired into every emit site, the two pre-existing flaky tests fixed at the root cause, and a FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 sweep ahead of the 2026-06-02 GitHub Actions Node 20 cutover.
http(workflows_list): wire shape on/api/v1/workflows. The HTTP endpoint now servesWorkflowRunSummaryrows (id,definition,status,subject_id,started_at,finished_at) instead of the fullOrchestratorWorkflow. Daemon-internal fields (phases,machine_state,current_phase,checkpoint_metadata, decision history) no longer leak through the public HTTP API.statusis kebab-case (in-progress-style) on the wire;Escalatedcollapses toPausedbecause the wire status enum lacks the variant. GraphQLworkflowsandworkflowsPaginatedstill serve the fullOrchestratorWorkflowshape, so the embedded web UI is unaffected.
feat(daemon health): live per-pluginhealth/checkfan-out.daemon_health()now spawns each discovered plugin one-shot, runs theinitializehandshake, callshealth/check, and shuts the plugin down — all under a 3s per-plugin deadline. Probes fire concurrently viafutures_util::future::join_all, so the wall time is roughly one probe regardless of plugin count. Failures land asUnhealthyrows with the error string inlast_error; the daemon's own status staysHealthybecause plugin-side trouble is an observability concern, not a daemon-liveness one. A long-lived plugin host pool is still v0.5 work, butdaemon healthis not hot-path enough to need it.feat(orchestrator-logging): redaction wired intoLogger::write_entry. The v0.4.9redact_log_entrybuilding block moved fromorchestrator_daemon_runtime::control::log_redactintoorchestrator_logging::log_redactand is invoked fromLogger::write_entryimmediately before serialization. Every emit site that goes through the logger picks up redaction automatically; the old module path remains as a re-export so external callers stay source-compatible. New regression test (write_entry_redacts_msg_in_persisted_line) reads the persistedevents.jsonlline and asserts the secret never reached disk.feat(workflows_list):workflows_list_summaryAPI method. NewWebApiService::workflows_list_summary(query)returnsListPage<WorkflowRunSummary>— the projection helperworkflow_to_run_summarymaps localOrchestratorWorkflowrows onto the wire shape. The HTTP handler at/api/v1/workflowscalls this method; the existingworkflows_listis kept for GraphQL.
fix(tests):plugin_registry_path_falls_back_to_legacy_when_canonical_missing. The test was settingANIMUS_CONFIG_DIRto a non-empty path while asserting thatdefault_config_path()returns the legacy~/.config/animus/plugins.yaml. Theconfig_dir_overriddenguard indefault_config_path()intentionally skips the legacy fallback whenANIMUS_CONFIG_DIRis set, so the test was actually deterministically failing — but ENV_GUARD poisoning and other tests interacting through the same mutex were masking it as a "flake". The fix explicitly clearsANIMUS_CONFIG_DIRfor the duration of the test (using the existingEnvVarGuard) so the legacy-fallback branch is exercised.fix(tests):install_succeeds_after_org_added_to_trusted+ 4 siblings. All five trusted-orgs tests inops_pluginwere callingstd::env::set_var("ANIMUS_TRUSTED_ORGS", …)andremove_varbare, with no serialization. Concurrent test threads racing on the same process-global env var were the documented flake source. Fix: newTRUSTED_ORGS_ENV_GUARDprocess-level mutex plus a smallScopedEnvRAII helper that restores the prior value on drop (panic-safe). All five tests now serialize through the mutex.
No plugin-repo cascade in v0.4.10 — animus-protocol stays pinned at v0.1.4. The wire shape adopted by item A already lives in v0.1.4, so no protocol bump was required.
chore(ci):FORCE_JAVASCRIPT_ACTIONS_TO_NODE24set across all in-tree workflows. GitHub Actions runners deprecate Node 20 on 2026-06-02; the env var forces JS-based actions onto Node 24 ahead of the cutover. Patched into all 8.github/workflows/*.ymlfiles as a top-levelenv:block.
- Release automation across the 15-repo ecosystem. Scoping was completed (lighter path: a separate
launchapp-dev/animus-release-automationrepo with a CLI tool that runs the version-pin matrix check + cascade-PR generation), but the implementation work was deferred so the v0.4.10 budget could land items A through F. Filed as the v0.4.11 / v0.5 starting point.
Patch release. Picks up v0.4.8 deferrals plus a handful of originally-planned items: the plugin-repo cascade onto animus-protocol v0.1.4, daemon-health per-plugin enumeration, a log redaction layer, and a cleanup pass on the stale AgentPool references that have been confusing agent reports for several patches.
feat(daemon health): per-plugin section.DaemonHealthResponsenow carries a populatedplugins: Vec<PluginHealth>whenever the daemon's control wire serves the request: each discovered plugin contributes aname/kind/statusrow. Livehealth/checkRPC fan-out per plugin is intentionally deferred — without a long-lived plugin host pool the daemon would have to one-shot every plugin process per health probe, which is too expensive for a frequently-polled endpoint. The CLI's human renderer prints a compactplugins:table when the wire path is taken; the JSON envelope passes the full wire shape through unchanged.feat(daemon-runtime): log redaction layer. Neworchestrator_daemon_runtime::control::log_redactmodule that scrubs secret-shaped values fromLogEntry.msg,LogEntry.meta(recursively over nested JSON),LogEntry.content, andLogEntry.errorbefore the entry is handed to a storage sink. Default pattern matchesapi_key|apikey|api-key|password|token|secret|authorizationfollowed by:or=and a value; additional patterns can be appended viaANIMUS_LOG_REDACT_PATTERNS(comma-separated regex). Five unit tests cover the default patterns, custom patterns, nested JSON traversal, and the no-op path. The redactor is shipped as a public function — wiring it into every emit path is a v0.4.10 follow-up so the patch boundary stays bounded.
All 12 plugin repos were re-pinned to animus-protocol v0.1.4 and bumped to their next patch version. The cascade is git-additive: each repo got one chore: bump to v0.1.x + pin animus-protocol v0.1.4 commit + a new tag, pushed to main.
| Repo | Old | New |
|---|---|---|
| animus-subject-linear | 0.1.2 | 0.1.3 |
| animus-subject-sqlite | 0.1.2 | 0.1.3 |
| animus-subject-markdown | 0.1.2 | 0.1.3 |
| animus-subject-requirements | 0.1.2 | 0.1.3 |
| animus-provider-claude | 0.1.1 | 0.1.2 |
| animus-provider-codex | 0.1.1 | 0.1.2 |
| animus-provider-gemini | 0.1.1 | 0.1.2 |
| animus-provider-opencode | 0.1.1 | 0.1.2 |
| animus-provider-oai | 0.1.1 | 0.1.2 |
| animus-trigger-webhook | 0.1.0 | 0.1.1 |
| animus-trigger-slack | 0.1.0 | 0.1.1 |
| animus-log-storage-file | 0.1.0 | 0.1.1 |
The triggers and log-storage repos previously pinned animus-protocol via branch = "main"; they now pin tag = "v0.1.4" explicitly so the wire surface is reproducible.
- AgentPool references aligned with reality. The CLI and daemon-runtime comment blocks claiming "AgentPool carries
#[allow(dead_code)]" referred to a struct that no longer exists in the tree. Comments inops_agent/control_routing.rsandcontrol/routing.rsupdated to state plainly: the wire surface stays pass-through because there is no daemon-side agent pool yet, and CLI callers degrade to the local in-process path.
web-api workflows_listcontinues to returnListPage<OrchestratorWorkflow>. Migrating to the wireWorkflowListResponserequires both a contract change (pagination model, status casing) and the dropped fields (phases,machine_state) propagated through the GraphQL surface and downstream typed tests. The web-api crate has stalled multiple agents on this exact migration; held back another patch to keep v0.4.9 from sprawling.- Log redaction at every emit site. v0.4.9 lands the redactor as a public function plus tests. Calling it from
Logger::write_entry(or from each emit builder) is the next step; the patch ships the building block without changing emit semantics yet. - Live
health/checkRPC fan-out per plugin. Per-plugin status currently reportsHealthyfor any discoverable plugin. Real per-plugin health probes require a long-lived plugin host pool with per-process status caches.
Wire + UX patch. Picks up the v0.4.7-deferred web-api work that needed an animus-protocol bump, ships the plugin-runtime structured log macros, and polishes the animus subject CLI for the single-backend project case.
feat(protocol): bump animus-protocol to v0.1.4.WorkflowResumeRequestcarries an optionalfeedbackfield so approval-gated workflows can pass reviewer comments through resume.QueueReorderRequestaccepts a multi-entry form (subject_ids: Vec<String>) alongside the single-id shape.animus-plugin-runtimeships a newlogmodule with crate-levelinfo!/warn!/error!/debug!/trace!macros that emitlog/entryJSON-RPC notifications via the existing stdout pipe; each*_mainentrypoint installs the global emitter automatically.feat(web-api): routequeue/reorderandworkflow/resumethrough the daemon control wire. With the v0.1.4 wire surface in place, the web-api handlers prefer the daemon's control RPC, falling back to the local path only when the daemon isn't running.workflow/resumenow carries the reviewer feedback string end-to-end.feat(cli):animus subjecthonorsdefault_subject_kindfrom.animus/config.json. New--kindbecomes optional; falls back to the config default (which seeds to"task"for new projects). Operators can nowanimus subject listwithout re-typing--kind taskevery invocation. A missing config default and missing flag prints a helpful error listing the lookup precedence.feat(plugin-host): hierarchical kind matching inSubjectRouter. Plugins may declare glob kinds liketask.*; resolution does exact-match first, then longest matching glob prefix. Duplicate equal-prefix globs are rejected at registration time. Five new tests cover the precedence rules.feat(llm-cli-wrapper): typedError::CapabilityNotSupported. Callers can pattern-match on the typed variant instead of greppingExecutionFailedstrings; the cancel-routing path now emits the typed error when a plugin's handshakecapabilities.cancellationisfalse.
test(cli): delete the stalejson_success_envelope_contract_is_stabletest. It still droveanimus task stats, which was removed in v0.4.4 cleanup, so it had been red on every run since.
web-api workflows_listcontinues to returnListPage<OrchestratorWorkflow>instead of the leaner wireWorkflowListResponse. The two shapes differ on pagination model (offset/total vs cursor), status casing (snake_case vs kebab-case), and the wire summary lacksphases,machine_state, and several other fields the handler currently exposes. Migration is a contract change acrosspaginated_success_response, ETag computation, the GraphQL surface, and downstream typed tests.feat(daemon health): per-plugin health. The brief asked for aplugins:section inanimus daemon healththat calls each installed plugin'shealth/checkRPC. The plumbing change spans the daemon-runtime health snapshot, the control wire'sdaemon/healthresponse, and CLI rendering — held back to keep the v0.4.8 surface bounded.- Plugin repo cascade (10 plugins). Each subject/provider/trigger/log-storage plugin still pins animus-protocol v0.1.3. The v0.1.4 protocol crate is wire-additive so existing plugins continue to work; per-plugin re-pinning + retagging ships in v0.4.9.
Wire-routing patch: the daemon's daemon/logs control method finally drives animus logs tail, and MCP gets the matching surface. Two of the three deferred web-api handlers stay deferred with v0.4.8-tagged notes; the cross-repo plugin runtime cascade ships in a separate window.
feat(logs): routeanimus logs tailthrough the control wire when the daemon is up. AddsControlClient::daemon_logs(streaming consumer with a caller-side limit), wires the daemon'sdaemon/logssurface to read historical entries via the activeLogStorageDispatch, and updatesops_logs::handle_logs_tailso the daemon-up branch no longer opensevents.jsonldirectly. Operators get a single transport regardless of which side is serving; the plugin live-tail path (long-livedlog_storage_backendplugin host) stays deferred but the contract is now wire-first.feat(mcp): addanimus.logs.tailtool. Mirrors theanimus.subject.*pattern — typed input struct, args builder,run_toolshell-out — so agents can pull the daemon's log tail through MCP without re-implementing the wire-fallback logic.
web-api workflows_listkeeps returningListPage<OrchestratorWorkflow>. Migrating to the leaner wireWorkflowListResponseneeds a router + OpenAPI contract change and a GraphQL/web-UI follow-up.web-api queue_reorderstays on the local Vec-of-subject-ids path. WireQueueReorderRequestis single-id + anchor + position; lifting requires a multi-id variant in animus-protocol v0.1.4.web-api workflows_resumewith feedback stays local.WorkflowResumeRequestlacks a feedback field on the wire; adding it needs an animus-protocol v0.1.4 bump.- Plugin runtime
log::{info!, warn!, …}macros + per-plugin cascade. Building theanimus_plugin_runtime::logmacros and re-pinning the 10+ subject/provider/trigger/log-storage plugin repos onto v0.1.4 is a separate window.
CI workflow cleanup. Two workflows have been failing on every commit since v0.4.1:
ci(protocol-drift): bumpSTANDALONE_TAGv0.1.1→v0.1.3to match the in-tree consumer. The workflow was trying to check out a tag that exists onanimus-protocolbut predated the wire-format expansions the in-tree crate now consumes, so the structural-drift assertion fired on every run.ci(release): remove thedocker-publishjob. It pushed toregistry.fly.io/ao-daemon, a registry slug that predates theao→animusrename and no longer resolves. Can be re-added when there's a real registry target to point at; GitHub Release binary artifacts (the load-bearing publish path) are unaffected.
No Rust or CLI changes. Pure CI hygiene.
Hotfix release. Two bugs in v0.4.4 + v0.4.3:
- fix(daemon-control): gate Unix-socket server on #[cfg(unix)] so Windows release CI compiles. v0.4.2/0.4.3/0.4.4 all failed the Windows build for the same reason. Windows now falls back to the in-process service path; named-pipe equivalent is a future enhancement.
- test: delete 18 cli_e2e tests that exercised v0.4.4-deleted commands (animus task / animus requirements lifecycles). Unit tests (321/321) cover the new SubjectBackend surface.
Cleanup release that drops the legacy CLI command surfaces v0.4.3 made redundant. The in-tree SubjectBackend adapters (InTreeTaskSubjectBackend, InTreeRequirementsSubjectBackend) plus the external subject_backend plugin ecosystem cover the use cases through the unified animus subject --kind <kind> surface.
feat(cli): delete legacyanimus taskcommand tree. Replaced byanimus subject --kind taskagainst the in-tree task adapter. Underlyingorchestrator-core::services::task*services are untouched and continue to back both the subject adapter and the daemon's workflow runtime.feat(cli): delete legacyanimus requirementscommand tree. Replaced byanimus subject --kind requirementagainst the in-tree requirements adapter.orchestrator-core::services::requirements*is preserved.feat(cli): delete legacyanimus cloudcommand tree. Animus-sync legacy surface; cloud sync now ships as an out-of-tree plugin.feat(cli): deleteanimus setupcommand.animus initis the supported onboarding entry point.feat(cli): deleteanimus nowcommand. Overlaps withanimus status; consolidate on the unified status dashboard.feat(cli): deleteanimus errorscommand tree. Folded intoanimus history.feat(mcp): deleteao_task_*/animus.task.*MCP tool family. Useanimus.subject.*withkind=task.feat(mcp): deleteao_requirements_*/animus.requirements.*MCP tool family. Useanimus.subject.*withkind=requirement.- Companion cleanup: removes the now-unused
shared/parsing.rstask/requirement value parsers, thecli_types/shared_types.rstask/requirement help-text constants, and theservices/runtime/stale_in_progress.rssummary helpers (only the deleted task handler consumed them).
orchestrator-coreservices for tasks and requirements stay intact — both the new subject adapters and the daemon's workflow runtime continue to depend on them.- All other CLI command groups (
daemon,agent,project,queue,workflow,history,git,skill,model,pack,plugin,runner,status,output,mcp,web,init,doctor,trigger,logs,subject) are unchanged.
Controller-as-plugin migration plus the daemon-wiring foundation that makes the v0.4.x plugin ecosystem operational. CLI, MCP, and WebAPI all route through the daemon's new Unix-socket control protocol when the daemon is running, falling back to in-process calls when it isn't.
feat(daemon): Unix-socket control server + control protocol. Daemon now exposes~/.animus/<repo-scope>/control.sock(0700 perms) speaking newline-delimited JSON-RPC 2.0 peranimus-control-protocolv0.1.3. 47 method constants across 7 groups (subject, plugin, daemon, workflow, agent, queue, project). Auto-starts at daemon launch; opt-out viaANIMUS_DAEMON_DISABLE_CONTROL_SERVER=1. EmitsDaemonRunEvent::ControlServerResolvedso operators can find the socket path inevents.jsonl.feat(cli): every command tries the control socket first, falls back to local.animus plugin/daemon/workflow/queue/agent/subjectcommands open the control socket when present and route through it; one-shot operations (no daemon) keep working via the existing in-process path. Preserves theanimus.cli.v1JSON envelope on both paths.feat(mcp): tool surface routes through control protocol via CLI subprocess.ao_workflow_*,ao_queue_*,ao_plugin_*,ao_daemon_*, andao_agent_*MCP tools shell out to the migrated CLI, which routes via control when the daemon is up. Six newanimus.subject.*MCP tools added (list, get, create, update, next, status) to mirror the CLI surface.feat(web-api): REST handlers route through control protocol (9 of 12 migrated).queue_list,queue_stats,queue_hold,queue_release,workflows_get,workflows_run,workflows_pause,workflows_resume,workflows_cancelall try control first, fall back to directServiceHubcalls. Deferred:workflows_list,queue_reorder,workflows_resumewith feedback — wire-shape contract changes pending.feat(daemon-logs): LogStorageBackend plugin discovery +animus logs tail. Daemon discovers installedlog_storage_backendplugins at startup; if exactly one is found, it becomes the active log sink. Falls back to the in-treeLoggerwritingevents.jsonlwhen no plugin is installed. Newanimus logs tail [--plugin] [--level] [--since] [--follow] [--json]CLI reads from whichever backend is active. Opt-out viaANIMUS_DAEMON_DISABLE_LOG_STORAGE_PLUGIN=1.feat(daemon-subject): SubjectBackend plugin discovery +animus subjectCLI. Daemon discovers installedsubject_backendplugins, builds theSubjectRouterfrom their declaredsubject_kinds, rejects duplicate-kind claims at startup. Genericanimus subject list/get/create/update/next/status --kind <kind>routes through whichever plugin owns that kind. Built-inInTreeTaskSubjectBackend+InTreeRequirementsSubjectBackendauto-register underkind=taskandkind=requirementso legacyanimus taskandanimus requirementskeep working unchanged.
refactor(control): relocateControlClienttoorchestrator-daemon-runtime. Lived inorchestrator-clioriginally;orchestrator-web-apicouldn't depend on it (circular). Moved to the daemon-runtime crate which both already depend on. No behavior change; mechanical import updates across the CLI.refactor(plugin-runtime): subject dispatch accepts<kind>/<verb>method names. Aligns the runtime's dispatch table with the daemon'sSubjectRouterkind-keyed routing. Shipped asanimus-protocolv0.1.2; subject plugin repos (linear, sqlite, markdown, requirements) cut v0.1.2 to consume it.
- Legacy CLI command deletion (
Command::Task,Command::Requirements, hiddenReview,Qa) plus matching MCP tools (ao_task_*,ao_requirements_*). The in-tree task/requirements services keep working via the new SubjectBackend adapters; deletion is a follow-up cleanup commit. (Shipped in v0.4.4.) workflows_listweb-api migration (router + OpenAPI contract change required)queue_reorderweb-api migration (multi-id wire support needed)- TTL eviction for the session-keyed host cache (deferred from the cancel-keep-host-alive MVP)
- AgentPool integration (still
allow(dead_code)) — agent dispatch returnsNotSupportedover the control wire pending the daemon-side query surface
feat(plugin): cosign signature verification on plugin install.animus plugin install <owner/repo>now checks for a sigstore cosign signature bundle (<asset>.tar.gz.bundle) alongside the binary asset. When present, the install verifies it via thecosignCLI (shell-out for v0.4.x; in-processsigstore-rsplanned for v0.5+). Default mode: verify-if-present and recordsignature_status: verified|unsignedin~/.animus/plugins.yaml. New flags:--require-signaturerefuses install when no bundle is published or verification fails;--skip-signaturebypasses verification entirely;--trusted-signers <PATH>points at a YAML allowlist (default~/.animus/trusted-signers.yaml). A FAILING signature always refuses install. Thecosignbinary is a soft dependency — when missing, installs degrade tosignature_status: unsignedrather than failing.animus plugin listgains aSIGcolumn surfacing the recorded status. Plugin release workflows (launchapp-dev/animus-*) ship signing as of plugin tagv0.1.2+via GitHub Actions OIDC (no secrets to manage). See docs/architecture/plugin-signing.md.feat(workflow-config):${VAR}env-var interpolation in workflow YAML..animus/workflows.yaml,.animus/workflows/*.yaml, and pack-shipped workflow overlays now support shell-style${VAR},${VAR:-default}, and${VAR:?error}interpolation. Substitution runs before YAML parsing so every string scalar — subject backend configs, provider tokens, MCPenvblocks, phase env overrides — accepts the same syntax uniformly. Unset required vars fail fast with the YAML file path + line number. Use$$to embed a literal$. Recommended for non-secret config like team IDs, base URLs, and feature flags; credentials still belong in the daemon's process environment, not in YAML. See docs/reference/configuration.md.
Same-day follow-up to v0.4.1. Ships the full v0.4.0 plugin ecosystem: every
provider plugin promised by the v0.4.0 release is now live in its own
launchapp-dev/animus-* repository with green CI, alongside the protocol
crates, a public plugin scaffold template, and the first subject backend
(Linear). The in-tree CLI gains the matching animus plugin install <owner/repo> and animus plugin new surfaces for one-command install +
bootstrap, and several rough edges in animus init and plugin discovery
caught during release verification are fixed.
Eight standalone repositories under launchapp-dev,
each tagged v0.1.0 with green CI:
animus-protocol— 5-crate workspace publishinganimus-plugin-protocol,animus-subject-protocol,animus-provider-protocol,animus-plugin-runtime, andanimus-session-backend. CI workflow added.animus-plugin-template— subject + provider scaffolds consumed byanimus plugin new. CI workflow runscargo checkagainst both kinds on every push.animus-subject-linear— Linear GraphQLSubjectBackendreference implementation. Credentials load leniently so--manifestprobes work credential-free.animus-provider-claude— Claude Code CLI wrapper provider.animus-provider-codex— Codex CLI wrapper provider.animus-provider-gemini— Gemini CLI wrapper provider.animus-provider-opencode— OpenCode CLI wrapper provider.animus-provider-oai— OpenAI-compatible HTTP provider. Lenient credential loading so--manifestworks without a configured API key.
animus plugin install <owner/repo>[@tag]: Install a plugin directly from a public GitHub release. The CLI resolves the latest release (or the supplied tag), downloads the matching architecture asset, verifies the published checksum, drops the binary into~/.animus/plugins/, and registers it in~/.animus/plugins.yaml. Mutually exclusive with--path/--url. New--tag <TAG>and--latestflags make scripted installs explicit. Local-file (--path) and direct-URL (--url --sha256) installs from v0.4.0 still work unchanged.animus plugin new --kind <subject|provider|trigger> --name <name>: Scaffold a brand-new plugin project fromlaunchapp-dev/animus-plugin-template. Clones the template (or reads--template-path <PATH>for offline use), substitutes the per-kind/per-name variables, and writes a buildable Rust project to./animus-<kind>-<name>/(override with--out-dir). The output project iscargo build-clean from the first commit.- Plugin install dir is configurable: New
--plugin-dir <PATH>flag onanimus plugin install/uninstall, plus the new$ANIMUS_PLUGIN_DIRenv var, override the default~/.animus/plugins/location. Discovery scans the same directory automatically. - Plugin registry consolidated under
~/.animus/: The plugin registry file moves from~/.config/animus/plugins.yamlto~/.animus/plugins.yaml. The legacy path is still read transparently on first run; the nextanimus plugin installrewrites it to the new location. Two new helpers (plugins_registry_path()andlegacy_plugins_registry_path()) are re-exported fromorchestrator-plugin-hostfor tooling that needs to point at either.
animus initno longer emits legacy wrapper YAMLs alongside template files (commitf7c85a11, bug #22):animus init --template task-queuewas writing both the registry's inline-content workflow files and three hardcoded wrapper YAMLs (standard-workflow.yaml,hotfix-workflow.yaml,research-workflow.yaml) becauseFileServiceHub::newran the hardcoded scaffold insidebootstrap_project_base_configsbeforewrite_template_fileshad a chance to drop registry files. Reorder: write template files first, bootstrap second. The scaffold'shas_existing_yamlshort-circuit now sees the registry files and skips the wrappers. Also fixes an undercount of three inapply.written_filesbecause the scaffold was writing outside the tracked path. Verified: a freshanimus init --non-interactive --template task-queuenow produces 5 YAML files (was 8).- Plugin discovery surfaces failed-manifest probes instead of silent-dropping them (commit
8394a73):discover_configuredandscan_dirwere swallowing manifest-fetch errors, so installed plugins that failed--manifestinvocation (the symptom:animus-provider-oaibefore the lenient-credential fix) silently disappeared fromanimus plugin listwith no diagnostic. NewDiscoveryWarningstruct +PluginDiscovery::discover_with_warnings()API returns failed-probe plugins alongside successes, with the binary's stderr + exit code included.tracing::warn!fires at every drop site.animus plugin list --jsongains a top-levelwarningsarray; human output prints each warning to stderr. Theanimus.plugin.listMCP tool result gains a parallelwarningsfield. Regression tests cover failed--manifestprobes for explicit-config plugins, missing configured binaries, and failedscan_dirmanifest probes. animus-provider-oai --manifestworks credential-free (shipped inanimus-provider-oaiv0.1.0): Manifest probes no longer requireOPENAI_API_KEYto be set. The lenient-config loader returns a manifest with anerrorcapability hint when credentials are missing, instead of exiting with code 1.animus-subject-linear --manifestworks credential-free (shipped inanimus-subject-linearv0.1.0): Same fix as the oai provider — credential validation deferred from--manifestto the firstsubject/listcall.
animus-protocolrepo now has CI:cargo check+cargo testacross the 5-crate workspace on every push.animus-plugin-templaterepo now has CI: Validates both the subject scaffold and the provider scaffold compile cleanly viacargo check.
README.md+CLAUDE.mddrop "extraction in progress" framing: Both update to describe the shipped 22-crate workspace plus the 8 standalone plugin repositories under launchapp-dev. The CLAUDE.md "Current Baseline" section now reflects v0.4.0 plugin extraction as complete rather than in flux.docs/migration/v0.3-to-v0.4.mdmarked complete: Migration guide now documents the actual shipped install surfaces (public-repoowner/repo@tag, local--path, URL--url --sha256, and scaffold viaanimus plugin new) rather than describing them as planned.docs/reference/cli/index.md: Adds theanimus plugin install <owner/repo>andanimus plugin newflag surfaces, the new--plugin-diroverride, and the updated default registry path. Discovery-order line updated to point at~/.animus/plugins.yaml.docs/architecture/subject-backend-plugins.md: Resolves the v0.4.0 "open questions" section against the v0.1.0 protocol shape that actually shipped. Adds links to the liveanimus-protocolandanimus-subject-linearrepositories.
[[bin]] name = "ao-workflow-runner"incrates/workflow-runner-v2/Cargo.tomlis still the legacy bin name (from v0.4.1 known-follow-ups list; not yet renamed).- Release archive filenames still use the
ao-v0.4.X-prefix; the release script's archive-naming convention still needs to be updated toanimus-v0.4.X-.
Same-day patch for issues caught during v0.4.0 release verification.
- Plugin discovery config path renamed:
default_config_path()incrates/orchestrator-plugin-host/src/discovery.rs:174previously read~/.config/ao/plugins.yaml— a stale path the v0.4.0 hard rename missed. v0.4.1 reads from~/.config/animus/plugins.yaml. Users with a config at the old path need tomv ~/.config/ao ~/.config/animuson upgrade. - Docker image build broken upstream: the v0.4.0 Release Binaries workflow's Docker step failed because OpenCode upstream changed its release-asset format from
opencode_linux_${ARCH}(single binary) toopencode-linux-${ARCH}.tar.gz(tarball). Animus'sDockerfilenow pulls the tarball and extracts theopencodebinary out of it. - Docker stack finished rename:
Dockerfileanddocker-compose.yamlboth had leftoveraoreferences the rename missed —mkdir -p /root/.ao→mkdir -p /root/.animus, compose service nameao→animus, imageghcr.io/launchapp-dev/ao:latest→ghcr.io/launchapp-dev/animus:latest, volumeao-data:/root/.ao→animus-data:/root/.animus, entrypointao→animus.
- Reference docs cover the v0.4.0 MCP surface:
docs/reference/mcp-tools.mdnow documents theanimus.skill.{list,get,search}andanimus.memory.{get,list,append,clear}families, including thecapabilities.memory: truegating model.docs/reference/cli/index.mdgets a "Selected Command Flags" section coveringanimus init --update-registry, the SHA256-required-with-URL plugin install rule, and the--include-system-pathopt-in.docs/reference/configuration.mdenv var table expanded from 4 to ~30 entries.docs/reference/data-layout.mdcovers the new~/.animus/packs/, agent-host skill probe paths, and the resolution-paths table grew from 7 to 14 entries. - Narrative-docs
AO/aoprose sweep: 49 docs acrossdocs/{design,concepts,guides,architecture,contributing,reference,internals}/plus the VitePress site config had bare project-name mentions ofAO/aothat survived sub-agent A's code-focused rename. All now readAnimus. Also folded into this pass: stale crate counts (17-crate workspace→Rust-only Cargo workspace (around 20 crates)), stale literal command examples (ao status,ao now,ao git, etc.), branch prefixao/<task-id>→animus/<task-id>indocs/concepts/worktrees.mdto matchcrates/orchestrator-git-ops/src/daemon_git_worktree.rs, and stalePROJECT_ROOTenv var fallback indocs/reference/cli/global-flags.mdper CLAUDE.md guidance.
[[bin]] name = "ao-workflow-runner"incrates/workflow-runner-v2/Cargo.tomlis still the legacy bin name. Rename pending.- Release archive filenames still use the
ao-v0.4.X-prefix (e.g.ao-v0.4.0-aarch64-apple-darwin.tar.gz). The release script's archive-naming convention needs to be updated toanimus-v0.4.X-.
This is a major release and the v0.3.x → v0.4.0 break is intentionally large. Three commitments land together:
- One name across every surface. The partial v0.3.x rebrand from AO to Animus is finished. There is no longer an
ao.*namespace. Every MCP tool, environment variable, configuration directory, pack id, JSON envelope, plugin crate, and standalone plugin repository uses theanimusname. No deprecation aliases. See the naming contract and migration guide. - The architecture commits to plugin-first. Subjects (units of dispatchable work) are now pluggable so teams can keep using Jira / Linear / GitHub Issues / Notion as their source of truth. Provider plugins (Claude, Codex, Gemini, OpenAI-compatible, OpenCode) move from in-tree workspace crates to standalone repositories. The plugin protocol crates move to a dedicated
launchapp-dev/animus-protocolrepository and ship as published Rust SDK crates that any plugin author depends on. - The skill system rearchitects around the ecosystem-standard SKILL.md format. Bundled YAML skills become an installable
animus.core-skillspack. Skills installed for other agent hosts (Claude Code, Codex, Cursor, OpenCode) become discoverable subject sources for Animus workflows.
Expect breaking changes throughout. The migration is small in absolute terms because the v0.3.x install base is small; the rename is a deliberate inflection point that commits to Animus as an ecosystem rather than a single tool.
- Full rename to
animus(no aliases). Everyao.*MCP tool name becomesanimus.*(58 tools). EveryAO_*environment variable becomesANIMUS_*(51 vars). The project-local config directory.ao/becomes.animus/. Scoped runtime state at~/.ao/<repo-scope>/moves to~/.animus/<repo-scope>/. Pack idsao.task/ao.review/ao.requirementbecomeanimus.task/animus.review/animus.requirement. The JSON output envelope schemaao.cli.v1becomesanimus.cli.v1. The plugin protocol host name"ao"becomes"animus". The in-tree provider crates rename fromao-provider-*toanimus-provider-*;ao-plugin-smokebecomesanimus-plugin-smoke. The migration guide has the complete list with before/after examples. - Bundled YAML skills moved to an installable pack.
BUILTIN_SKILL_YAMLSis removed. The 19 skills that previously shipped baked into the binary are now distributed as theanimus.core-skillspack, auto-installed duringanimus init/animus setup. Existing references resolve via theInstalledsource. animus plugin install --url <url>now requires--sha256. Previously optional; URL-sourced installs without an integrity check are rejected at the CLI layer.- Plugin discovery no longer scans
$PATHby default. Discovery is limited to.animus/plugins/and$ANIMUS_PLUGIN_PATH. Restoring the prior behavior requires explicit--include-system-pathon the discovery surface. - SKILL.md frontmatter: Animus-specific fields move under a vendor namespace.
tool_policy,extra_args,env,mcp_servers,adapters,codex_config_overridesmove from top-level frontmatter toanimus:in the SKILL.md frontmatter. Keeps SKILL.md portable across the ecosystem (Claude Code, Codex, Cursor, gstack) by isolating Animus-specific fields. - Subjects are now pluggable. Native
animus taskis one backend among many. Workflows can declaresubject_type:to operate over Linear/Jira/GitHub Issues subjects via plugin backends. Existing workflows that omitsubject_type:continue using the native task backend with no change. - Plugin protocol crates move to standalone repository.
orchestrator-plugin-protocolis replaced byanimus-plugin-protocolpublished fromlaunchapp-dev/animus-protocol. The newanimus-subject-protocol,animus-provider-protocol, andanimus-plugin-runtimecrates also publish from that repository. Plugin authors depend on these crates from crates.io rather than path-depending on the core workspace. - Provider plugins move to standalone repositories.
animus-provider-{claude, codex, gemini, oai, opencode}each ship from their own repository underlaunchapp-dev/. The daemon image installs them at build time from crates.io.
- Naming: one name everywhere. See naming contract.
- Plugin-first architecture: subjects, providers, triggers, and skills are all plugins. The core is the orchestration runtime; plugins are independent release artifacts. See subject backend plugins.
- Standalone plugin repositories: every plugin lives in its own GitHub repository under
launchapp-dev/animus-{kind}-{name}. The repository name, crate name, and binary name all match. Plugin authors generate new plugins from theanimus-plugin-templatescaffold viaanimus plugin new --kind <kind> --name <name>.
- Stdio plugin host foundation: New
orchestrator-plugin-host(in core) plusanimus-plugin-protocol(extracted tolaunchapp-dev/animus-protocol) implement newline-delimited JSON-RPC 2.0 plugin protocol with handshake, request/response, notifications, and streaming over stdio. - Plugin host wired through the runtime: CLI, MCP server, subject dispatch, and daemon all route through the new plugin host. New
animus plugin install/list/getcommands andanimus.plugin.*MCP tools. - Claude and Codex shipped as stdio plugins: First-class
animus-provider-claudeandanimus-provider-codexprovider plugins running over the new protocol, distributed from their own repositories. - Three additional providers plus mock + runtime:
animus-provider-gemini,animus-provider-opencode,animus-provider-oai, plus the in-treeanimus-provider-mock(testing) and the sharedanimus-plugin-runtimecrate (used by every provider for the RPC loop, streaming, and logging). - Plugin smoke test:
animus-plugin-smokecrate exercises the end-to-end plugin contract for CI. - Subject backend plugins: New
animus-subject-protocoldefines theSubjectBackendtrait + normalized Subject schema. First reference implementation:animus-subject-linearships from its own repository. Theanimus plugin newscaffold +animus-plugin-templatemake new subject backends a single-command bootstrap. - Template-driven
animus init: External template registry (defaultlaunchapp-dev/animus-project-templates) cloned and cached under~/.animus/template-registries/<registry-id>/. Three bundled templates:task-queue,conductor,direct-workflow. Flags:--template <id>,--path <local-dir>,--non-interactive,--force,--update-registry. - Template registry commit pinning:
animus initcaptures the registry HEAD on first clone into.commitmetadata and refuses to silently fast-forward on subsequent runs. Use--update-registryto fetch latest and re-pin. - Bootstrap packs externalized: Templates carry
[[packs]]declarations that activate during init (e.g.animus.task,animus.requirement). - Markdown skill installs:
animus skill installsupports SKILL.md format (frontmatter + body) alongside YAML. Body content lands inprompt.system. Animus-specific runtime fields live under theanimus:frontmatter namespace. - Agent memory and communication config: New per-agent memory state plus inter-agent messaging plumbed into phase prompt rendering. Memory MCP tools (
animus.memory.{get, list, append, clear}) capability-gated onagent.capabilities.memory == true. - Plugin streaming + structured logging: Provider RPC loop streams chunks back over notifications; structured logs flow to the host stderr sink.
animus plugin install --url <https-url>: Download a plugin binary from an HTTPS URL with mandatory SHA256 integrity verification.
animus-subject-protocolcrate: New workspace crate (also published fromlaunchapp-dev/animus-protocol) defining theSubjectBackendtrait, normalizedSubjectschema (SubjectId,SubjectStatus,SubjectFilter,SubjectList,SubjectPatch,SubjectSchema,ChangeKind,SubjectChangedEvent), and JSON-RPC method constants (subject/list,subject/get,subject/update,subject/watch,subject/schema).animus-provider-protocolcrate: New workspace crate defining theProviderBackendtrait,AgentRunRequest/AgentRunResponse/AgentResumeRequest/AgentCancelRequest/ProviderManifest/ProviderCapabilities/TokenUsagetypes, andBackendErrorwith structured RPC error mapping.animus-plugin-runtime: Generalized fromao-provider-runtime. Providessubject_backend_main()andprovider_main()entry points, full stdin/stdout JSON-RPC loop, lifecycle method handling (initialize/initialized/$/ping/shutdown/exit/health/check), and theanimus:shape future plugin authors target.- Subject backend design doc:
docs/architecture/subject-backend-plugins.mddefines the v0.4.0 plugin contract —SubjectBackendtrait, normalized schema, JSON-RPC method set, workflow YAML binding, nativeanimus taskmigration plan, and 8 open questions for red-team.
animus.core-skillspack: The 19 bundled YAML skills now ship as an installable pack atcrates/orchestrator-config/config/bundled-packs/animus.core-skills/. Auto-installs duringanimus initandanimus setupand pins itself in.animus/state/pack-selection.v1.json. Users cancd ~/.animus/packs/animus.core-skills/0.1.0/skills/to read, fork, or override any of the 27 catalog skill names. Pack updates ship viaanimus skill install— no binary upgrade required.PackManifest::workflowsis now optional: Skills-only packs are first-class. Validation requires at least one of[workflows]or[skills].PackSkillsis a sibling toPackWorkflows.SkillSourceOrigin::AgentHost { host, scope }: New variant scans skills installed for other agent hosts. Six hosts wired viaAGENT_HOST_SPECS: Claude Code (~/.claude/skills/,<project>/.claude/skills/), Codex (~/.codex/skills/), OpenCode (~/.config/opencode/skills/), Cursor (~/.cursor/skills/), Kiro (~/.kiro/skills/), Slate (~/.slate/skills/). Project-scoped variants take precedence over global. Each scope isProject | Global.- Two-tier trust model:
strip_structural_fields_for_agent_hostremovestool_policy,extra_args,env,mcp_servers,adapters,codex_config_overrides, andcapabilitiesfrom any AgentHost-discovered skill at parse time. AgentHost skills can only contribute prompt text + directives. To use a host-discovered skill's full structural fields, the user must explicitly runanimus skill install --pathto promote it to the high-trustInstalledsource. animus:vendor namespace in SKILL.md frontmatter: Animus-specific runtime fields (tool_policy,mcp_servers,model.preferred,adapters, etc.) now live underanimus:in the SKILL.md frontmatter, keeping SKILL.md portable across other agent hosts (Claude Code, Codex, Cursor, gstack) that don't recognize Animus-specific keys. Top-level placement of these fields is no longer parsed (hard cut, no deprecation warning). The trust strip still applies even whenanimus:namespace is present in an AgentHost source.- Source chain priority:
AgentHost::Global < AgentHost::Project < Builtin < Installed (pack + registry) < User < Project.load_skill_sourcesreturns sources in lowest-to-highest order; resolution iterates in reverse for highest-priority match. docs/architecture/skill-system.md: New design doc covering the source chain, trust tiers, vendor namespace, andanimus.core-skillspack layout.
animus.skill.list: Enumerate all skills across every source (Builtin, Installed, AgentHost::Project, AgentHost::Global, User, Project). Each entry carriessource(variant tag) +source_detailwith provenance: registry/source/version for Installed; host/scope plusstructural_fields_stripped: trueandtrust_tier: "prompt_text_only"for AgentHost. Optionalsourcefilter (builtin,installed,agent_host, or a host id likeclaude-code).animus.skill.get: Fetch the fullSkillDefinitionfor a skill by name. AgentHost-source responses include anoticefield explaining that structural fields were stripped at parse time.animus.skill.search: Case-insensitive substring match over name, description, and tags. Optional source filter and result limit.
External agents (Claude Code session driving Animus, workflow-spawned subprocesses connected over MCP) can now discover the skill catalog at runtime — including the user's existing ~/.claude/skills/ library that v0.3.x couldn't reach — without shelling out to animus skill list --json or hardcoding skill names.
- Honor configured rework routing (#024f0161):
reworkverdicts now respect the configured routing target instead of defaulting to the immediately preceding phase. - Plugin handshake hangs blocked:
PluginHost::handshakenow wraps the initialize round trip in a 30-second timeout so a non-responsive plugin can't deadlock daemon startup. expect()panics removed from plugin code paths:animus.plugin.callMCP tool and the provider runtime'sinitialize/health/checkpaths now return structured RPC errors instead of panicking when serialization or registry initialization fails.git clone/git pullerrors surfaced: Template registry sync now captures stdout/stderr from git subprocess invocations and includes them in error messages (was previously swallowed).
- Naming contract:
docs/architecture/naming-contract.mdcommits to one name across every surface (Animus). Cross-linked from architecture index, VitePress nav,CONTRIBUTING.md, andCLAUDE.md. - Subject backend plugin protocol:
docs/architecture/subject-backend-plugins.mddefines the v0.4.0 subject backend plugin design. - Skill system v0.4.0:
docs/architecture/skill-system.md— source chain, trust tiers,animus:namespace,animus.core-skillspack layout. - Migration guide:
docs/migration/v0.3-to-v0.4.mdwalks through every breaking change with before/after examples covering all 14 surfaces (rename, plugin discovery, template registry pinning, skill system, subject backend pluggability). feature-status.md: Template-driven project init flipped from In-Flight to Shipped.docs/architecture/index.md: Updated to reflect the all-animusnaming, the ~20-crate workspace size, and the v0.4.0 subject backend plugin link.CLAUDE.md(project guide): Updated state paths from.ao/→.animus/, env var references fromAO_*→ANIMUS_*, and the working-rules naming guidance to the all-animusmodel.
- Cargo aliases renamed:
cargo ao-fmt/ao-lint/ao-bin-build/ao-bin-check→cargo animus-fmt/animus-lint/animus-bin-build/animus-bin-check. CI release workflow updated.
- Plugin discovery executes
--manifeston every candidate found in.animus/plugins/and$ANIMUS_PLUGIN_PATHwith no signature, allowlist, or sandboxing.$PATHis opt-in (off by default). Treat the install / discovery surface as trusted-only until a richer trust model lands. - Subject backend reference plugin (
animus-subject-linear) andanimus plugin newscaffold command are still on the v0.4.0 roadmap; the protocol crate stack is in place to support them. Nativeanimus taskmigration to satisfy theSubjectBackendtrait also remains pending. - The optional
animus.skill.registry.listMCP tool was deferred —SkillRegistrySourceConfigispub(super)toops_skilland exposing it cleanly needs a thinpub fn list_skill_registries()accessor first. Follow-up.
- Default decision contract evidence types (TASK-222): Implementation phase decision contract now accepts the common evidence kinds agents actually return (
bug_confirmed,fix_identified, etc.), not onlyfiles_modified. First workflow runs no longer fail immediately when an agent does an analysis-only pass. phase_decision.evidenceis now optional when no required evidence types are configured.- Strip secrets from tracked state: Removed
.ao/sync.jsonfrom tracking; it can contain auth tokens. - Rustfmt + lint cleanups:
runtime_contract.rsmethod chain and test formatting.
animus task status --force: Allow forced status transitions for skip/cancel paths.- Surface agent file-edit decisions: When an agent runs an analysis phase and modifies no files, the phase output now explains why (permissions, working directory, prompt constraints, or "no change needed") instead of leaving the user guessing.
- Restructured getting-started to lead with autonomous mode.
- README updated with v0.3.0 feature surface.
- Animus daemon Docker image: Multi-stage
Dockerfileproduces alinux/amd64image with minimal dependencies, sized for Fly.io cloud deployment. - Claude Code, Codex, and OpenCode bundled in the daemon image: The cloud daemon ships with all three coding CLIs pre-installed so spawned workflow agents work without per-tenant install steps.
- Docker image publish in release workflow: CI now pushes
registry.fly.io/ao-daemonon tagged releases.
- Auto-start daemon and agent-runner on sync workflow execution (TASK-221): Fixes "first-run socket error" when invoking a sync workflow without a daemon already running.
- Dockerfile Rust bump to 1.89:
async-graphqlrequires rustc 1.89+.
- Cloud status integration: ao cloud status calls /api/cli/status for cloud projects and daemons — show user's cloud projects, daemon states, and active workflows from the cloud API
- Cloud API routing: ao-cli route deploy commands through cloud API instead of direct Fly.io — animus cloud deploy create/destroy/start/stop/status should call /api/cli/daemons/* endpoints instead of requiring Fly.io credentials locally
- Auto-detect project linking from git remote + GitHub App installation
- Config alignment: animus cloud push now sends .ao/ config files
- Device auth flow: implement animus cloud login device auth flow
- ACP evaluation: research ACP spec and design AO as ACP server for IDE integration
- Deploy subcommands: add start/stop/status/create/destroy deployment subcommands
- Feature branch workflow: enable feature branch workflow in standard task pipelines
- Cloud CLI: evolve ao sync into ao cloud CLI subcommands
- Memory MCP: wire ao-memory-mcp into default workflow phases
- Marketplace MVP: Skill marketplace MVP — GitHub registry fetch + pack download
- Event triggers Phase 2: Generic Webhook Support
- Event triggers Phase 1: file watcher support
- Remote MCP servers: HTTP/remote MCP server support
- Cloud linking: rustfmt fix for cloud.rs auto-detect linking
- Config test isolation: improve orchestrator-config test isolation and lock handling
- Model references: replace broken glm-5 model refs with claude-haiku, disable pr-sweep schedule
- Sparkcube removal: remove sparkcube tool_profile refs blocking all daemon workflows
- CLI E2E tests: resolve all 6 storage-mismatch and warning failures
- Lint warnings: resolve all clippy warnings workspace-wide
- Daemon task state: fix last failing unit test — daemon_run task-state-change assertion
- Config test assertion: align test assertion with standard-workflow ID rename
- Env lock handling: recover from poisoned env lock in tests
- Path references: replace hardcoded fixture paths and absolute paths with CARGO_MANIFEST_DIR
- Rebrand to animus: rename primary binary from 'ao' to 'animus', maintain 'ao' as alias
- Help text updates: update all help text and error messages from 'ao' to 'animus'
- Test updates: update test assertions for rebrand to 'animus'
- Unused dependencies: strip unused MCP servers — remove filesystem, sequential-thinking, memory, rust-docs
- Public repo preparation: clean README.md with Animus branding, verify ELv2 LICENSE, remove hardcoded local paths, add CONTRIBUTING.md, ensure .gitignore covers sensitive files
- README updates: fix typo and update GitHub release links
- Formatting fixes: rustfmt fixes for line length, import ordering, and cloud status command
See git history for earlier releases.