Skip to content

Commit 9e4d4b4

Browse files
committed
v0.5.1: queue/release_pending, run_phase mcp_config, workflow_status paused/pending
Additive protocol fold-ins surfaced by v0.5.0 production use; all changes are backward-compatible. animus-queue-protocol (0.1.0 -> 0.2.0): - METHOD_QUEUE_RELEASE_PENDING ("queue/release_pending") returns an Assigned entry to Pending without canceling it. Fills the gap that previously forced the daemon to call queue/completion(cancelled) when it discovered a subject was already being worked on by another in-flight lease. - QueueReleasePendingParams { entry_id, reason } - QueueReleasePendingResponse { entry_id, status } - New error code QUEUE_ENTRY_NOT_ASSIGNED (-32208); data SHOULD include the actual entry status. animus-workflow-runner-protocol (0.1.0 -> 0.2.0): - WorkflowPhaseRunRequest.mcp_config: Option<serde_json::Value> mirrors WorkflowExecuteRequest.mcp_config so phase-level retries can pass MCP server config to the agent runner. - workflow_status module: PAUSED ("paused") + PENDING ("pending") constants previously collapsed to "running" on the wire. - workflow_status::Parsed + workflow_status::parse() parse helper with Unknown(String) fallback documenting the additive-vocabulary contract: consumers default-match unknown statuses to Running semantics. animus-control-protocol: - Fix pre-existing build break under --all-features: test helper sample_event() referenced Subject / SubjectChangedEvent fields that do not exist in animus-subject-protocol (native_status, status_metadata, attachments, previous_native_status, previous_dispatch_label). Strip the stale field assignments so cargo clippy --all-targets --all-features passes on the v0.5 branch.
1 parent 70a2c1d commit 9e4d4b4

5 files changed

Lines changed: 147 additions & 9 deletions

File tree

animus-control-protocol/src/client.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -939,12 +939,7 @@ mod imp {
939939
created_at: Utc::now(),
940940
updated_at: Utc::now(),
941941
custom: Default::default(),
942-
native_status: None,
943-
status_metadata: serde_json::Value::Null,
944-
attachments: vec![],
945942
},
946-
previous_native_status: None,
947-
previous_dispatch_label: None,
948943
}
949944
}
950945

animus-queue-protocol/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "animus-queue-protocol"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55
license = "MIT"
66
authors = ["Launchapp.dev"]

animus-queue-protocol/src/lib.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize};
2626
pub const KIND: &str = "queue";
2727

2828
/// Per-crate semver protocol version.
29-
pub const PROTOCOL_VERSION: &str = "0.1.0";
29+
pub const PROTOCOL_VERSION: &str = "0.2.0";
3030

3131
/// Add a dispatch to the queue.
3232
pub const METHOD_QUEUE_ENQUEUE: &str = "queue/enqueue";
@@ -51,6 +51,11 @@ pub const METHOD_QUEUE_MARK_ASSIGNED: &str = "queue/mark_assigned";
5151
/// Notify the queue that a workflow has reached a terminal state so the
5252
/// queue can prune the corresponding assigned entry.
5353
pub const METHOD_QUEUE_COMPLETION: &str = "queue/completion";
54+
/// Return an Assigned entry back to Pending without canceling it (used
55+
/// when the daemon discovers the subject is already being worked on by
56+
/// another in-flight lease). Distinct from [`METHOD_QUEUE_RELEASE`] which
57+
/// targets a Held entry.
58+
pub const METHOD_QUEUE_RELEASE_PENDING: &str = "queue/release_pending";
5459

5560
// =====================================================================
5661
// Status vocabulary
@@ -248,6 +253,24 @@ pub struct QueueMarkAssignedRequest {
248253
pub workflow_id: Option<String>,
249254
}
250255

256+
/// Request for [`METHOD_QUEUE_RELEASE_PENDING`].
257+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
258+
pub struct QueueReleasePendingParams {
259+
/// Entry id to return to Pending.
260+
pub entry_id: String,
261+
/// Audit reason describing why the entry is being released back.
262+
pub reason: String,
263+
}
264+
265+
/// Response for [`METHOD_QUEUE_RELEASE_PENDING`].
266+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
267+
pub struct QueueReleasePendingResponse {
268+
/// Entry id whose status was changed.
269+
pub entry_id: String,
270+
/// New status — always [`status::PENDING`] on success.
271+
pub status: String,
272+
}
273+
251274
/// Request for [`METHOD_QUEUE_COMPLETION`].
252275
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
253276
pub struct QueueCompletionRequest {
@@ -303,6 +326,10 @@ pub mod error_codes {
303326
pub const QUEUE_LEASE_WORKFLOW_ID_COUNT_MISMATCH: i32 = -32206;
304327
/// Project root mismatch.
305328
pub const PROJECT_BINDING_MISMATCH: i32 = -32207;
329+
/// Entry was not in Assigned status (e.g., `release_pending` on a
330+
/// pending or held entry). The error `data` payload SHOULD include the
331+
/// entry's actual status.
332+
pub const QUEUE_ENTRY_NOT_ASSIGNED: i32 = -32208;
306333
}
307334

308335
#[cfg(test)]
@@ -321,6 +348,25 @@ mod tests {
321348
assert_eq!(back, r);
322349
}
323350

351+
#[test]
352+
fn release_pending_round_trips() {
353+
let p = QueueReleasePendingParams {
354+
entry_id: "ent_1".into(),
355+
reason: "duplicate-in-flight".into(),
356+
};
357+
let v = serde_json::to_value(&p).unwrap();
358+
let back: QueueReleasePendingParams = serde_json::from_value(v).unwrap();
359+
assert_eq!(back, p);
360+
361+
let r = QueueReleasePendingResponse {
362+
entry_id: "ent_1".into(),
363+
status: status::PENDING.into(),
364+
};
365+
let v = serde_json::to_value(&r).unwrap();
366+
let back: QueueReleasePendingResponse = serde_json::from_value(v).unwrap();
367+
assert_eq!(back, r);
368+
}
369+
324370
#[test]
325371
fn mutation_response_omits_not_found_when_false() {
326372
let r = QueueMutationResponse {

animus-workflow-runner-protocol/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "animus-workflow-runner-protocol"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55
license = "MIT"
66
authors = ["Launchapp.dev"]

animus-workflow-runner-protocol/src/lib.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,76 @@ pub const METHOD_WORKFLOW_RUN_PHASE: &str = "workflow/run_phase";
3939

4040
/// Per-crate semver protocol version. Reported via
4141
/// [`animus_plugin_protocol::KindCapability::crate_version`].
42-
pub const PROTOCOL_VERSION: &str = "0.1.0";
42+
pub const PROTOCOL_VERSION: &str = "0.2.0";
4343

4444
// =====================================================================
4545
// Status vocabulary (referenced from string fields below).
4646
// =====================================================================
4747

4848
/// Allowed values for [`WorkflowExecuteResult::workflow_status`].
49+
///
50+
/// Additive vocabulary policy: consumers MUST default-match unknown status
51+
/// strings to [`RUNNING`] semantics so older clients continue to behave
52+
/// safely when newer runners emit values they have not learned yet. New
53+
/// constants since v0.2.0: [`PAUSED`] and [`PENDING`].
4954
pub mod workflow_status {
5055
/// Workflow completed all phases successfully.
5156
pub const COMPLETED: &str = "completed";
5257
/// Workflow is still running (returned only when a single phase was
5358
/// requested or the workflow paused mid-stream).
5459
pub const RUNNING: &str = "running";
60+
/// Workflow is paused for a manual gate; the host MUST NOT advance it.
61+
/// Added in protocol v0.2.0.
62+
pub const PAUSED: &str = "paused";
63+
/// Workflow is queued but has not yet started. Added in protocol
64+
/// v0.2.0.
65+
pub const PENDING: &str = "pending";
5566
/// Workflow failed in a terminal way.
5667
pub const FAILED: &str = "failed";
5768
/// Workflow was escalated to a human reviewer.
5869
pub const ESCALATED: &str = "escalated";
5970
/// Workflow was cancelled by the host or by an upstream signal.
6071
pub const CANCELLED: &str = "cancelled";
72+
73+
/// Parsed workflow status, including an `Unknown` fallback so callers
74+
/// can default-match forward-compatible wire values without losing the
75+
/// original string.
76+
#[derive(Debug, Clone, PartialEq, Eq)]
77+
pub enum Parsed {
78+
/// [`COMPLETED`]
79+
Completed,
80+
/// [`RUNNING`]
81+
Running,
82+
/// [`PAUSED`]
83+
Paused,
84+
/// [`PENDING`]
85+
Pending,
86+
/// [`FAILED`]
87+
Failed,
88+
/// [`ESCALATED`]
89+
Escalated,
90+
/// [`CANCELLED`]
91+
Cancelled,
92+
/// Wire value not recognized by this version of the protocol crate.
93+
/// Consumers SHOULD treat this as [`Parsed::Running`] for safety.
94+
Unknown(String),
95+
}
96+
97+
/// Parse a wire status string into [`Parsed`]. Unknown strings round-
98+
/// trip via [`Parsed::Unknown`] rather than erroring; this is the
99+
/// additive-vocabulary contract.
100+
pub fn parse(s: &str) -> Parsed {
101+
match s {
102+
COMPLETED => Parsed::Completed,
103+
RUNNING => Parsed::Running,
104+
PAUSED => Parsed::Paused,
105+
PENDING => Parsed::Pending,
106+
FAILED => Parsed::Failed,
107+
ESCALATED => Parsed::Escalated,
108+
CANCELLED => Parsed::Cancelled,
109+
other => Parsed::Unknown(other.to_string()),
110+
}
111+
}
61112
}
62113

63114
/// Allowed values for [`PhaseResultSnapshot::status`] /
@@ -283,6 +334,11 @@ pub struct WorkflowPhaseRunRequest {
283334
/// Phase routing config (opaque).
284335
#[serde(default, skip_serializing_if = "Option::is_none")]
285336
pub phase_routing: Option<Value>,
337+
/// Opaque MCP runtime config — same shape as
338+
/// [`WorkflowExecuteRequest::mcp_config`]. Lets phase-level retries
339+
/// pass MCP server config to the agent runner.
340+
#[serde(default, skip_serializing_if = "Option::is_none")]
341+
pub mcp_config: Option<Value>,
286342
}
287343

288344
/// Result of [`METHOD_WORKFLOW_RUN_PHASE`].
@@ -396,6 +452,47 @@ mod tests {
396452
assert_eq!(back.workflow_ref.as_deref(), Some("standard"));
397453
}
398454

455+
#[test]
456+
fn workflow_status_parse_recognizes_v02_additions() {
457+
use workflow_status::{parse, Parsed};
458+
assert_eq!(parse("paused"), Parsed::Paused);
459+
assert_eq!(parse("pending"), Parsed::Pending);
460+
assert_eq!(parse("running"), Parsed::Running);
461+
assert_eq!(parse("completed"), Parsed::Completed);
462+
match parse("future-value") {
463+
Parsed::Unknown(s) => assert_eq!(s, "future-value"),
464+
other => panic!("expected Unknown, got {other:?}"),
465+
}
466+
}
467+
468+
#[test]
469+
fn phase_run_request_carries_mcp_config() {
470+
let req = WorkflowPhaseRunRequest {
471+
execution_cwd: "/tmp".into(),
472+
workflow_id: "wf_1".into(),
473+
workflow_ref: "standard".into(),
474+
subject_id: "TASK-1".into(),
475+
subject_title: "t".into(),
476+
subject_description: "d".into(),
477+
phase_id: "impl".into(),
478+
phase_attempt: 0,
479+
phase_timeout_secs: None,
480+
model_override: None,
481+
tool_override: None,
482+
task_complexity: None,
483+
rework_context: None,
484+
pipeline_vars: HashMap::new(),
485+
dispatch_input: None,
486+
schedule_input: None,
487+
phase_routing: None,
488+
mcp_config: Some(serde_json::json!({"servers": []})),
489+
};
490+
let v = serde_json::to_value(&req).unwrap();
491+
assert!(v.get("mcp_config").is_some());
492+
let back: WorkflowPhaseRunRequest = serde_json::from_value(v).unwrap();
493+
assert_eq!(back.mcp_config, req.mcp_config);
494+
}
495+
399496
#[test]
400497
fn phase_event_round_trips() {
401498
let e = PhaseEvent::Decision {

0 commit comments

Comments
 (0)