Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5395683
chore(ci): set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 ahead of 2026-06-02…
Shooksie May 22, 2026
a6e37ed
feat(0.7): owner-scoped schedules + SubjectDispatch.actor
Shooksie Jun 29, 2026
e4384bd
feat(plugin-protocol): multi-kind plugins (plugin_kinds + serves_kind…
Shooksie Jul 2, 2026
d0fd56a
feat(environment): scaffold environment plugin kind + RPC contract (v…
Shooksie Jul 2, 2026
f3a1d9f
feat(config): add WorkflowConfigSource::Plugin (plugin-sourced base c…
Shooksie Jul 4, 2026
a61e544
feat(subject): make subject optional end-to-end (retire adhoc sentine…
Shooksie Jul 7, 2026
7a5ef89
feat(orchestrator): add PhaseDecision.verdict_key for custom routing …
Shooksie Jul 8, 2026
0d6bb3b
feat(plugin-protocol): declare supports_mcp as a first-class manifest…
Shooksie Jul 9, 2026
a1a3e62
environment-protocol: add environment/exec_session + journal notifica…
Shooksie Jul 16, 2026
c8b3403
environment-protocol: add ExecSessionRequest.workflow_id (REQ-052 one…
Shooksie Jul 17, 2026
f2e52cc
feat(env): node-management wire types (list/get/teardown_node/reap)
Shooksie Jul 19, 2026
ed98db0
feat(control): restore WorkflowList total + workflow_ref onto the one…
Shooksie Jul 19, 2026
9c00c00
feat(subject): add actor-scoped v2 request contract (TASK-970)
Shooksie Jul 25, 2026
954cc76
feat(chat): carry canonical agent identity on conversations
Shooksie Jul 26, 2026
05adc98
feat(conversation): expose operation reservation owner
Shooksie Jul 26, 2026
6b88922
feat(conversation): add authenticated tenant scope
Shooksie Jul 26, 2026
91768e9
feat(conversation): define shared chat operation authority
Shooksie Jul 26, 2026
e3ea744
Merge shared chat operation protocol
Shooksie Jul 26, 2026
e44a665
Merge TASK-970 actor-scoped subject protocol for rc.12
Shooksie Jul 26, 2026
b7a74ce
Merge v0.7 actor and chat protocols
Shooksie Jul 26, 2026
2f8078f
feat(conversation): fence shared assistant appends
Shooksie Jul 26, 2026
c7117a3
feat(config): type application chat control policy
Shooksie Jul 26, 2026
1d98e30
fix(config): make chat policy revocation explicit
Shooksie Jul 26, 2026
68d6159
fix(config): reject malformed chat policies
Shooksie Jul 26, 2026
d406ec7
Merge TASK-1014 application chat control policy
Shooksie Jul 26, 2026
365c66a
Merge Node 24 Actions cutover
Shooksie Jul 26, 2026
7767eb7
feat(application): publish shared chat wire contracts
Shooksie Jul 26, 2026
765f8eb
refactor(config): reuse application control scalars
Shooksie Jul 26, 2026
bc4d0d6
style: format protocol workspace
Shooksie Jul 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ on:
pull_request:
branches: [main]

env:
# GitHub Actions runner Node 20 -> Node 24 deprecation (effective 2026-06-02).
# Forces JS-based actions onto Node 24 ahead of the cutover.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
check:
name: cargo check
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ on:
permissions:
contents: write

env:
# GitHub Actions runner Node 20 -> Node 24 deprecation (effective 2026-06-02).
# Forces JS-based actions onto Node 24 ahead of the cutover.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
source-tarball:
name: source tarball
Expand Down
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,73 @@ This file tracks notable changes to the workspace tag stream
source of truth for individual crate bumps. Tags map roughly to
"workspace cuts" — a tag may bump multiple crates at once.

## Unreleased

### Added

`animus-config-protocol` 0.1.2: optional, typed
`AgentProfile.application_chat_controls` policy with bounded control enums and
configured references. Profile omission remains backward-compatible; overlay
omission inherits, explicit `null` revokes, and a concrete object replaces.
Unknown fields, duplicate or oversized lists, and invalid configured references
fail during canonical deserialization.

`animus-plugin-protocol`: additive
`conversation_operation_fenced_append_v1` capability and
`ConversationAppendMessageRequest.operation_fence`. Shared backends validate
the exact operation lease and active conversation reservation atomically with
assistant-message insertion, preventing a provider result from a reclaimed or
terminalized runtime from becoming canonical.

`animus-subject-protocol` 0.2.0 adds a non-downgradable authenticated subject
surface:

- Required `SubjectRequestContext` carrying a typed `Actor` plus optional
request, correlation, and idempotency identifiers.
- Typed v2 list/get/create/update/status/delete request shapes and distinct
`subject/v2/<verb>` / `<kind>/v2/<verb>` method names.
- `ActorScopedSubjectBackend`, separate from the legacy global
`SubjectBackend`, so a v2 request can never silently fall through to v1.
- JSON Schema exports and compatibility tests for the new wire types.

`animus-plugin-protocol`: shared multi-host chat operation authority via the
additive `conversation/operation_*` reserve, load, renew, execution-bind,
release, user-accept, and terminalize RPCs. The authenticated tenant/actor plus
repository, conversation, and caller key form the durable partition. Opaque
leases fence every mutation by token and expiry; reclaims rotate authority,
terminal receipts are immutable, and replay/load never expose lease tokens.

`animus-plugin-protocol`: `ConversationScope.tenant_id`, an optional-on-wire,
1..=128-character opaque server-selected workspace/tenant partition key carried
by every conversation-store request. Shared backends include it in every
conversation/message key and validate it against the authenticated transport
actor, failing closed unless an operator explicitly pins a legacy tenant.
Conversation creation stamps owner from that authenticated call context, and
ordinary metadata saves cannot transfer or clear ownership.

`animus-plugin-protocol`: optional
`ConversationMeta.active_operation_id` on the canonical load/save-meta path.
The field durably identifies which keyed chat operation owns a revision
reservation so that operation can recover after a crash. It is absent by
default for backward compatibility, constrained to 1..=128 ASCII alphanumeric
or `._:-` characters, writable only through `conversation/save_meta`, and
intentionally omitted from create requests and list summaries.

`animus-plugin-protocol` (0.1.18): `PluginManifest.supports_mcp: Option<bool>`
— a first-class, plugin-DECLARED capability field the kernel reads instead of
hardcoding per-tool MCP behavior in a name table (REQUIREMENT-039 / TASK-277).

- `PROTOCOL_VERSION` bumped `1.1.0` -> `1.2.0` (backward-compatible minor: the
field is optional and `#[serde(default, skip_serializing_if = "Option::is_none")]`).
- Back-compat: absent = undeclared; the kernel keeps its historical default
(provider plugins are MCP-capable). Only an explicit `false` opts a provider out.
- `animus-plugin-runtime` (0.2.2): `Plugin::supports_mcp(bool)` builder so a Rust
plugin author can declare the flag. The provider runtime (`provider_main`) emits
`None` for now — auto-mapping from `ProviderCapabilities.mcp` is deferred because
that flag defaults `false` and would regress providers relying on the default.
- This is the proof-of-pattern field for the wider REQUIREMENT-039 cleanup
(launch template, permission-mode flag, reasoning-effort, default model, ...).

## v0.1.21 — config_source write-back (`config/write`)

### Added
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ members = [
"animus-durable-store-protocol",
"animus-memory-store-protocol",
"animus-notifier-protocol",
"animus-environment-protocol",
"protocol",
"animus-config-protocol",
"animus-application-protocol",
]
resolver = "2"

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
| Crate | Standalone-compilable? | Notes |
|---|---|---|
| `animus-plugin-protocol` | yes | Wire types only; no external Animus deps. |
| `animus-application-protocol` | yes | Policy-neutral application wire types, generated JSON Schemas, and cross-language scalar limits. |
| `animus-subject-protocol` | yes | Pure trait + schema definitions. |
| `animus-provider-protocol` | yes | Pure trait + schema definitions. |
| `animus-trigger-protocol` | yes | Pure trait + schema definitions for push-driven event sources (Slack, webhooks, file watchers, cron). |
Expand All @@ -24,6 +25,7 @@ The protocol + subject + provider + runtime crates are usable today via git path
| Crate | Purpose |
|---|---|
| [`animus-plugin-protocol`](./animus-plugin-protocol) | Wire types every plugin uses: `RpcRequest`, `RpcResponse`, `RpcNotification`, `RpcError`, error codes, `InitializeParams` / `InitializeResult`, `PluginManifest`, `HealthCheckResult`. |
| [`animus-application-protocol`](./animus-application-protocol) | Application-facing action/resource vocabularies, closed chat controls, durable chat receipts, and machine-readable UTF-8/numeric limits. Portals still own policy; spatial clients still own world state. |
| [`animus-subject-protocol`](./animus-subject-protocol) | `SubjectBackend` trait + normalized `Subject` schema for backends like Linear, Jira, GitHub Issues, Notion, Asana — anything with a system-of-record API. |
| [`animus-provider-protocol`](./animus-provider-protocol) | `ProviderBackend` trait + `AgentRunRequest`/`AgentRunResponse` shapes for LLM provider plugins (Claude, Codex, Gemini, OpenAI-compatible, on-prem). |
| [`animus-trigger-protocol`](./animus-trigger-protocol) | `TriggerBackend` trait + `TriggerEvent`/`TriggerSchema` shapes for push-driven event sources (Slack mentions, generic webhooks, file watchers, cron). |
Expand All @@ -32,6 +34,12 @@ The protocol + subject + provider + runtime crates are usable today via git path

`animus-plugin-protocol` is the only required dependency for non-Rust plugin authors — and even then only as a reference. Any process that emits the documented JSON over stdio is a compatible Animus plugin.

Application consumers regenerate the committed Draft 2020-12 artifacts with
`cargo run -p animus-application-protocol --bin animus-application-protocol-export-schema`.
The generated `_limits.json` is normative alongside JSON Schema: JSON Schema
`maxLength` counts Unicode characters, while Animus transport ceilings count
encoded UTF-8 bytes. Downstream validators must enforce both.

## Subject backend quickstart (Rust)

Cargo.toml:
Expand Down
21 changes: 21 additions & 0 deletions animus-application-protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "animus-application-protocol"
version = "0.1.0"
edition = "2021"
license = "MIT"
authors = ["Launchapp.dev"]
homepage = "https://github.com/launchapp-dev/animus-cli"
repository = "https://github.com/launchapp-dev/animus-protocol"
description = "Application-facing wire contracts shared by Animus runtimes, portals, and spatial clients."

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
schemars = "1.0"

[dev-dependencies]
tempfile = "3"

[[bin]]
name = "animus-application-protocol-export-schema"
path = "src/bin/export_schema.rs"
149 changes: 149 additions & 0 deletions animus-application-protocol/src/bin/export_schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//! Export JSON Schema artifacts for application-facing Animus wire types.

use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

use animus_application_protocol::{
AllowedAction, AllowedApplicationChatControls, ApplicationChatControls,
ApplicationChatReceiptFrame, ApplicationChatTurnStatus, ApplicationResourceKind,
ResourceVisibility, APPLICATION_CHAT_CONTROLS_SCHEMA, MAX_APPLICATION_CHAT_CONTROLS_BYTES,
MAX_APPLICATION_CHAT_CONTROL_REF_BYTES, MAX_APPLICATION_CHAT_ERROR_BYTES,
MAX_APPLICATION_CHAT_SEQUENCE, MAX_APPLICATION_PROTOCOL_STRING_BYTES,
};
use schemars::{schema_for, Schema};

fn default_out_dir() -> PathBuf {
let base = env::var_os("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.and_then(|dir| dir.parent().map(Path::to_path_buf))
.unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
base.join("schemas").join("animus-application-protocol")
}

fn parse_out_dir(args: &[String]) -> Option<PathBuf> {
let mut iter = args.iter().skip(1);
while let Some(arg) = iter.next() {
match arg.as_str() {
"--out" | "-o" => return iter.next().map(PathBuf::from),
other if other.starts_with("--out=") => {
return Some(PathBuf::from(&other["--out=".len()..]));
}
_ => {}
}
}
None
}

/// Build all public application schema artifacts.
pub fn all_schemas() -> Vec<(&'static str, Schema)> {
vec![
("AllowedAction", schema_for!(AllowedAction)),
(
"ApplicationResourceKind",
schema_for!(ApplicationResourceKind),
),
("ResourceVisibility", schema_for!(ResourceVisibility)),
(
"ApplicationChatControls",
schema_for!(ApplicationChatControls),
),
(
"AllowedApplicationChatControls",
schema_for!(AllowedApplicationChatControls),
),
(
"ApplicationChatReceiptFrame",
schema_for!(ApplicationChatReceiptFrame),
),
(
"ApplicationChatTurnStatus",
schema_for!(ApplicationChatTurnStatus),
),
]
}

/// Write one file per type and a combined `_all.json` bundle.
pub fn export_to(out_dir: &Path) -> std::io::Result<usize> {
fs::create_dir_all(out_dir)?;
let schemas = all_schemas();
let mut defs: BTreeMap<String, serde_json::Value> = BTreeMap::new();
for (name, schema) in &schemas {
let pretty = serde_json::to_string_pretty(schema).expect("schema serializes");
fs::write(out_dir.join(format!("{name}.json")), format!("{pretty}\n"))?;
let mut value = serde_json::to_value(schema).expect("schema serializes");
if let Some(object) = value.as_object_mut() {
if let Some(serde_json::Value::Object(inner)) = object.remove("$defs") {
for (key, definition) in inner {
defs.entry(key).or_insert(definition);
}
}
object.remove("$schema");
}
defs.insert((*name).to_string(), value);
}
let limits = serde_json::json!({
"schema": "animus.application.limits.v1",
"application_chat_controls_schema": APPLICATION_CHAT_CONTROLS_SCHEMA,
"application_chat_controls_max_utf8_bytes": MAX_APPLICATION_CHAT_CONTROLS_BYTES,
"application_chat_control_ref_max_utf8_bytes": MAX_APPLICATION_CHAT_CONTROL_REF_BYTES,
"application_protocol_string_max_utf8_bytes": MAX_APPLICATION_PROTOCOL_STRING_BYTES,
"application_chat_error_max_utf8_bytes": MAX_APPLICATION_CHAT_ERROR_BYTES,
"application_chat_sequence_max": MAX_APPLICATION_CHAT_SEQUENCE,
});
let limits_pretty = serde_json::to_string_pretty(&limits).expect("limits serialize");
fs::write(out_dir.join("_limits.json"), format!("{limits_pretty}\n"))?;
let bundle = serde_json::json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "animus-application-protocol",
"x-animus-limits": limits,
"$defs": defs,
});
let pretty = serde_json::to_string_pretty(&bundle).expect("bundle serializes");
fs::write(out_dir.join("_all.json"), format!("{pretty}\n"))?;
Ok(schemas.len())
}

fn main() -> ExitCode {
let args: Vec<String> = env::args().collect();
let out_dir = parse_out_dir(&args).unwrap_or_else(default_out_dir);
match export_to(&out_dir) {
Ok(count) => {
println!("wrote {count} schemas + _all.json to {}", out_dir.display());
ExitCode::SUCCESS
}
Err(error) => {
eprintln!("export-schema: {error}");
ExitCode::FAILURE
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn export_is_complete() {
let tmp = tempfile::tempdir().unwrap();
let count = export_to(tmp.path()).unwrap();
assert_eq!(count, all_schemas().len());
let bundle: serde_json::Value =
serde_json::from_str(&fs::read_to_string(tmp.path().join("_all.json")).unwrap())
.unwrap();
for (name, _) in all_schemas() {
assert!(bundle["$defs"].get(name).is_some(), "missing {name}");
}
let limits: serde_json::Value =
serde_json::from_str(&fs::read_to_string(tmp.path().join("_limits.json")).unwrap())
.unwrap();
assert_eq!(limits["application_protocol_string_max_utf8_bytes"], 512);
assert_eq!(
limits["application_chat_sequence_max"],
9_007_199_254_740_991_u64
);
assert_eq!(bundle["x-animus-limits"], limits);
}
}
Loading
Loading