Multi-App Coordination
Overview
Section titled “Overview”Single-capability invocations handle one action in one application. Real workflows span multiple applications and multiple steps. PCP provides a coordination layer that composes individual capability invocations into structured multi-app workflows with defined failure handling and rollback semantics.
The coordinator runs inside the System Intelligence layer. It receives a coordination plan from SI, dispatches individual capability invocations to the compositor in the specified order, and aggregates results or handles failures according to the plan’s strategy.
Coordination Primitives
Section titled “Coordination Primitives”PCP supports three coordination primitives that cover the common patterns of multi-app workflows. All three are injected with a StepExecutor implementation supplied by the daemon (the dispatcher-backed executor).
pub struct Step { app_id, capability_id, params: Value }
#[async_trait]pub trait StepExecutor { async fn execute_step(&self, step: &Step) -> Result<Value, (String /* err */, u64 /* latency_ms */)>;}Sequence
Section titled “Sequence”Actions execute in order. If any step fails, the sequence stops immediately. This is a fail-fast model: no subsequent steps run after a failure.
Consider a workflow that reads an email, extracts a meeting time, creates a calendar event, and sends a confirmation. If the calendar creation step fails, the sequence stops right there. No confirmation message is sent, because the precondition for that confirmation (a successful calendar event) was never met.
The sequence primitive guarantees that later steps never observe a state where an earlier step has failed. Each step can trust that all prior steps completed successfully.
Parallel
Section titled “Parallel”Multiple actions execute concurrently. The coordinator waits for all to complete before returning. Results from each action are gathered into a single response object, keyed by action identifier. Unlike sequences, parallel execution records failures from every step rather than stopping at the first error.
A search workflow provides a good example: searching mail, checking the calendar, and looking up a contact can all run at the same time. None of these actions depends on the result of another, so parallel execution reduces total latency. The caller receives all three results together once every action finishes.
Parallel execution does not imply any ordering between the concurrent actions. Two parallel file writes to the same path will race, and the coordinator does not arbitrate that conflict. The caller is responsible for ensuring that parallel actions do not have conflicting side effects.
Conditional
Section titled “Conditional”A conditional dispatches actions based on the result of a previous step. The coordinator evaluates a condition against the output of a completed step, then routes to one of two or more branches. If the guard evaluates to false, the else branch runs. Guard failure is also recorded in the trace.
For example: if an email contains an attachment, save it to disk and notify the user. Otherwise, just read the body text and summarize it. The condition checks a property of the email payload (presence of an attachment), and the coordinator dispatches the appropriate branch without involving the caller in a second round trip.
Truthiness for the guard follows simple rules: null maps to false, booleans are used as-is, non-zero numbers are true, non-empty strings and non-empty arrays/objects are true.
Conditionals can nest. A conditional branch can itself contain a sequence, a parallel group, or another conditional. This nesting allows the coordinator to express complex decision trees without requiring SI to issue multiple serial requests.
Trace Output
Section titled “Trace Output”Every coordination execution yields a Trace structure that records what happened, step by step. This trace is the authoritative record of the coordination operation and is written to the audit log.
pub struct Trace { pub trace_id: Uuid, pub executed_at: DateTime<Utc>, pub primitive: String, // "sequence", "parallel", or "conditional" pub steps: Vec<TraceStep>,}
pub struct TraceStep { pub app_id: String, pub capability_id: String, pub status: TraceStepStatus, // succeeded, failed, skipped pub result: Option<Value>, pub error: Option<String>, pub latency_ms: u64,}The trace_id is a UUID that ties together all audit entries for a single coordination operation. Each step records its app, capability, status, result, error (if any), and latency. For parallel executions, all steps appear in the trace regardless of failure, making it possible to see which concurrent operations succeeded and which did not.
Authorization
Section titled “Authorization”All three coordination capabilities require Admin authentication with confirmation. This is not a default setting; it is enforced at the protocol level. Non-admin clients cannot invoke sequence, parallel, or conditional execution. The confirmation gate applies on top of the Admin requirement, so even an Admin principal must explicitly approve a coordination operation before it executes.
Coordination API
Section titled “Coordination API”The coordinator exposes two primary methods corresponding to the sequence and parallel primitives. Conditionals are expressed through the Coordinate::when variant, which wraps a condition expression and two or more branch plans.
// Sequence: fail-fastlet result = coordinator.sequence(vec![ Coordinate::invoke("mail.read", params), Coordinate::invoke("calendar.create", params), Coordinate::invoke("comm.send_message", params),]).await;
// Parallel: gather-alllet results = coordinator.parallel(vec![ Coordinate::invoke("mail.search", params1), Coordinate::invoke("calendar.list_events", params2), Coordinate::invoke("files.search", params3),]).await;
// Conditional: branch on resultlet result = coordinator.sequence(vec![ Coordinate::invoke("mail.read", params), Coordinate::when( Condition::field_exists("attachments"), vec![Coordinate::invoke("file.save", save_params)], vec![Coordinate::invoke("text.get", body_params)], ),]).await;Each Coordinate::invoke carries a capability ID and its parameters. The coordinator resolves the target application from the capability ID and dispatches through the compositor’s normal capability routing. The coordination layer adds no new capability types; it composes existing ones.
Transaction Model
Section titled “Transaction Model”Multi-step workflows run inside a transaction. The transaction model provides a consistent framework for timeout handling, failure recovery, and audit correlation.
Transaction Strategies
Section titled “Transaction Strategies”PCP offers two strategies that control what happens when a step fails.
ATOMIC means all steps must succeed. If any step fails, the coordinator triggers defined rollback actions to revert the state changes made by prior steps. The transaction either fully completes or fully rolls back. There is no partial completion state visible to the caller.
A file move provides a concrete example. The implementation is a copy followed by a delete: copy the file from A to B, then delete the original at A. If the copy succeeds but the delete fails, the file system now has two copies. The ATOMIC strategy’s rollback action deletes the copy at B, restoring the original single-copy state.
BEST_EFFORT means steps execute sequentially, and if a step fails, the transaction records the failure and continues with the remaining steps. No rollback occurs. The caller receives a result object that lists which steps succeeded and which failed.
A cleanup workflow demonstrates the fit. Closing all non-essential applications before a system update does not require all closures to succeed. If one application refuses to close, the transaction continues closing the others. The caller can inspect the result to see which closures failed and decide whether to retry or force-close.
Transaction Properties
Section titled “Transaction Properties”Every transaction carries four properties:
A transaction ID (UUID) that ties together all log entries, capability invocations, and rollback actions from a single coordination plan. Audit trails use this ID to reconstruct the full lifecycle of a multi-step operation.
A timeout that caps the total execution time. The default is 30 seconds. Callers can override this per transaction. When the timeout fires, all pending steps are cancelled immediately.
Per-step timeout budgets that the coordinator enforces within the overall transaction timeout. If a transaction has a 30-second total timeout and five steps, the coordinator can allocate 5 seconds per step, or assign uneven budgets (10 seconds for a network call, 2 seconds for a local file operation). Step timeouts are optional; without explicit budgets, steps run until the transaction-level timeout fires.
Rollback actions (ATOMIC strategy only) that define how to undo each step. Rollback actions execute in reverse order of the original steps. If steps A, B, and C executed in that order and step C fails, the coordinator runs rollback for B, then rollback for A.
Error Handling
Section titled “Error Handling”Four error conditions occur within coordinated transactions.
Transaction timeout fires when the total elapsed time exceeds the configured timeout. All pending steps are cancelled. Under the ATOMIC strategy, the coordinator initiates rollback for all completed steps. Under the BEST_EFFORT strategy, the coordinator records the timeout as a failure for each pending step and returns partial results for the completed steps.
Step failure occurs when an individual capability invocation returns an error. Under ATOMIC, the coordinator stops the sequence and begins rollback. Under BEST_EFFORT, the coordinator records the failure and proceeds to the next step.
Ambiguous target occurs when the coordinator cannot resolve a capability ID to a single application or surface. This is a planning-time failure, not a runtime failure. The transaction fails immediately with an AMBIGUOUS_TARGET error before any steps execute. No rollback is needed because no state changes occurred.
App unresponsive occurs when a target application does not respond to a capability invocation within its step timeout. The coordinator treats this as a step failure and applies the strategy’s failure handling. The compositor marks the application as unresponsive, which may trigger escalation (notification to the user, retry with elevated privileges, or forced termination depending on configuration).