Skip to content

Commit 78f44f2

Browse files
authored
feat(actor): v0.6.20 — per-user Actor threading + scoped config resolution (#286)
* feat(actor): thread per-user Actor through kernel + re-key compiled-config cache Repin all v0.1.24 animus-protocol git deps to v0.1.25 and add the new zero-dep `animus-actor` crate. v0.1.25 additively adds `actor: Option<Actor>` to config/control/runner/session request types. control-protocol (v0.1.13 -> v0.1.25), workflow-runner-protocol (v0.5.3 -> v0.1.25) and subject-protocol-wire move with it (wire must share control's subject-protocol rev); queue + subject-protocol-v05 stay on the matched v0.5.10 pair. WU-C: thread `actor: Option<&Actor>` through resolve_plugin_base -> load_via_resident -> config_load_call (serialized into config/load params) and load_workflow_config[_with_metadata]. Re-key the compiled-config cache by (project_root, ActorCacheKey) and fold the actor key into compile_token so one user's compiled config can never be served to another; invalidate_compiled clears every actor partition for a root. ActorCacheKey uses unambiguous JSON (no delimiter-collision between distinct identities). WU-D: relay actor through the control surface (struct passthrough) and populate the runner WorkflowExecuteRequest.actor in execute.rs. The daemon control path spawns a detached runner child that rebuilds its request from the persisted record, so the authenticated actor is relayed to that child via a trusted daemon->child env handoff (WORKFLOW_ACTOR_ENV) carried in the runner overrides. TRUST BOUNDARY: the actor is set ONLY from the authenticated control request; never synthesized from local CLI context, workflow YAML, agent output, or subject content. Tests: per-actor cache isolation (no cross-leak), actor=None == global, invalidate clears all partitions, claim-order independence, delimiter-collision resistance, and the detached-runner actor env round-trip. * feat(actor): scope workflow bootstrap + lifecycle to the actor; relay actor to the MCP sidecar TASK 1 (resolve the deferred bootstrap P2): thread the transport-asserted Actor through the workflow run/bootstrap + the phase-plan / config / retry resolution it calls, so a user's PRIVATE workflow (visible only in that actor's config_source partition) resolves at run-time, not from the global view. - `WorkflowServiceApi::run(input, actor: Option<&Actor>)`: actor-scoped default-workflow-ref / phase-plan / skip-guard / retry resolution. actor=None == today's global behavior. Both FileServiceHub + InMemory impls + all callers updated (system/local/test callers pass None; the control-routed detached start passes the run's actor via DetachedRunnerOverrides). - Added actor-aware variants (old fns delegate to None): `resolve_phase_plan_ for_workflow_ref_for_actor`, `load_workflow_config_or_default_for_actor`, `load_agent_runtime_config_*_for_actor`. - Persist the actor on the run so EVERY later lifecycle transition (resume / pause / cancel / complete / fail / mark_* / resolve_merge_conflict) re-resolves from the same partition — otherwise a private workflow starts then fails at the next transition. Stored in a kernel-owned `workflows.actor` SQLite column (NOT the protocol record); `save()` switched from INSERT OR REPLACE to an ON CONFLICT upsert so the column survives every later save. Test seam gains per-(root,actor) base injection. TASK 2 (WU-G): relay the actor to the per-agent `animus mcp serve` sidecar so an agent's MCP tool calls can scope to the user (per-user integrations / subject / queue). The built-in MCP server is spawned PER AGENT SESSION (runtime_contract mcp.stdio.command), so the actor reaches it per-agent-spawn: - `inject_default_stdio_mcp_for_agent(.., actor)` appends `--actor-json` to the default serve args (in-tree ad-hoc callers pass None; the out-of-tree runner passes WorkflowPhaseRunRequest.actor). - `animus mcp serve --actor-json` parses the Actor and pins it on AoMcpServer (`pinned_actor`), exposed via `pinned_actor()`; `run_tool` (the single tool choke point) emits per-user audit. Full per-tool ENFORCEMENT (scoping the underlying subject/queue/integration RPCs) is the consumer-side change tracked separately. TRUST BOUNDARY: the actor is sourced ONLY from the authenticated run/control request, never from workflow YAML, agent output, or subject content. Tests: actor-private workflow resolves for its actor and NOT for None/another actor; run() persists the actor and it survives a lifecycle save; --actor-json clap parse; inject appends/omits --actor-json; actor round-trips through the serve arg. * chore(release): v0.6.20 — per-user actor threading + scoped config resolution
1 parent 1d2c225 commit 78f44f2

50 files changed

Lines changed: 1132 additions & 302 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 53 additions & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,17 @@ categories = ["command-line-utilities", "development-tools"]
2727
[workspace.dependencies]
2828
# Protocol/wire-type crates live in launchapp-dev/animus-protocol, never in
2929
# this repo. `protocol` + `animus-config-protocol` moved out in v0.6.1; all
30-
# animus-protocol git deps are pinned to the SAME tag (v0.1.24) so transitive
30+
# animus-protocol git deps are pinned to the SAME tag (v0.1.25) so transitive
3131
# animus-subject-protocol resolves to one source (no duplicate-crate type
3232
# mismatches). animus-plugin-protocol/runtime remain in-tree pending a
3333
# follow-up move.
3434
animus-plugin-protocol = { path = "crates/animus-plugin-protocol" }
35-
animus-config-protocol = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.24" }
36-
animus-provider-protocol = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.24" }
37-
animus-session-backend = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.24" }
38-
animus-subject-protocol = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.24" }
39-
protocol = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.24", package = "protocol" }
35+
animus-actor = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.25" }
36+
animus-config-protocol = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.25" }
37+
animus-provider-protocol = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.25" }
38+
animus-session-backend = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.25" }
39+
animus-subject-protocol = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.25" }
40+
protocol = { git = "https://github.com/launchapp-dev/animus-protocol", tag = "v0.1.25", package = "protocol" }
4041
orchestrator-daemon-runtime = { path = "crates/orchestrator-daemon-runtime" }
4142
orchestrator-logging = { path = "crates/orchestrator-logging" }
4243
orchestrator-plugin-host = { path = "crates/orchestrator-plugin-host" }

crates/animus-mcp-oauth/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub fn resolve_server_url(
107107
// case. So: only propagate the load error when a workflow YAML source
108108
// actually exists; otherwise fall back to the builtin default and continue
109109
// to project config.
110-
let loaded = match load_workflow_config_with_metadata(project_root) {
110+
let loaded = match load_workflow_config_with_metadata(project_root, None) {
111111
Ok(loaded) => loaded,
112112
Err(err) if workflow_yaml_present(project_root) => {
113113
return Err(ServerResolutionError::WorkflowConfig(err.to_string()));

0 commit comments

Comments
 (0)