Skip to main content

machi_workflow/
host.rs

1//! Host request protocol (side-effect boundary).
2
3use serde::{Deserialize, Serialize};
4use tokio::sync::oneshot;
5
6/// Options for `agent()` / `parallel()` host spawns.
7#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8pub struct AgentOpts {
9    /// Prompt text.
10    #[serde(default)]
11    pub prompt: String,
12    /// Optional label.
13    #[serde(default)]
14    pub label: Option<String>,
15    /// Optional model override.
16    #[serde(default)]
17    pub model: Option<String>,
18    /// Capability mode string (`full`, `read_only`, `plan`).
19    #[serde(default)]
20    pub capability_mode: Option<String>,
21    /// Optional JSON schema for structured output.
22    #[serde(default)]
23    pub output_schema: Option<serde_json::Value>,
24    /// Optional phase tag for UI.
25    #[serde(default)]
26    pub phase: Option<String>,
27    /// Optional agent type / definition name for host resolution.
28    #[serde(default)]
29    pub agent_type: Option<String>,
30    /// When true, host may fork parent conversation context into the child.
31    #[serde(default)]
32    pub fork_context: bool,
33    /// Resume a prior nested agent run id when the host supports it.
34    #[serde(default)]
35    pub resume_from: Option<String>,
36    /// Max output tokens hint for the child sample.
37    #[serde(default)]
38    pub max_output_tokens: Option<u64>,
39}
40
41/// Result returned from a host agent spawn.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct AgentResult {
44    /// Host-assigned agent id.
45    pub agent_id: String,
46    /// Success flag.
47    pub success: bool,
48    /// Output payload.
49    pub output: serde_json::Value,
50    /// Cancelled flag.
51    pub cancelled: bool,
52    /// Tokens used (best effort).
53    pub tokens_used: u64,
54    /// Duration ms.
55    pub duration_ms: u64,
56}
57
58/// Budget snapshot.
59#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
60pub struct BudgetState {
61    /// Total budget when capped.
62    pub total: Option<u64>,
63    /// Spent slots.
64    pub spent: u64,
65    /// Reserved but not spent.
66    pub reserved: u64,
67    /// Remaining when capped.
68    pub remaining: Option<u64>,
69}
70
71/// Host-side failures.
72#[derive(Debug, Clone, thiserror::Error)]
73pub enum HostError {
74    /// Agent call quota exceeded.
75    #[error("workflow agent-call quota exceeded: requested {requested}, maximum {maximum}")]
76    AgentCallQuotaExceeded {
77        /// Requested count.
78        requested: u64,
79        /// Maximum allowed.
80        maximum: u64,
81    },
82    /// Budget exhausted.
83    #[error("workflow token/agent budget exceeded")]
84    BudgetExceeded,
85    /// Cancelled.
86    #[error("workflow cancelled")]
87    Cancelled,
88    /// Capability not supported by this host.
89    #[error("unsupported in this context: {0}")]
90    Unsupported(String),
91    /// Generic host failure.
92    #[error("host failure: {0}")]
93    Failed(String),
94}
95
96/// Requests the pure engine sends to the host.
97#[derive(Debug)]
98pub enum WorkflowHostRequest {
99    /// Reserve agent call slots.
100    ReserveAgentCalls {
101        /// Count to reserve.
102        count: u64,
103        /// Reply channel.
104        reply: oneshot::Sender<Result<(), HostError>>,
105    },
106    /// Release unused reservations.
107    ReleaseAgentCalls {
108        /// Count to release.
109        count: u64,
110        /// Reply channel.
111        reply: oneshot::Sender<Result<(), HostError>>,
112    },
113    /// Spawn a nested agent and wait for completion.
114    SpawnAgent {
115        /// Spawn options.
116        opts: AgentOpts,
117        /// Reply channel.
118        reply: oneshot::Sender<Result<AgentResult, HostError>>,
119    },
120    /// Phase notification (non-journaled UI signal).
121    Phase {
122        /// Phase title.
123        title: String,
124        /// True when replaying.
125        replayed: bool,
126    },
127    /// Log line.
128    Log {
129        /// Message.
130        message: String,
131        /// True when replaying.
132        replayed: bool,
133    },
134    /// Structured telemetry event (optional host support).
135    Telemetry {
136        /// Event name.
137        name: String,
138        /// Arbitrary fields.
139        fields: serde_json::Value,
140        /// True when replaying.
141        replayed: bool,
142    },
143    /// Budget query.
144    BudgetQuery {
145        /// Reply channel.
146        reply: oneshot::Sender<Result<BudgetState, HostError>>,
147    },
148    /// Render a named template (optional).
149    RenderTemplate {
150        /// Template name.
151        name: String,
152        /// Template variables.
153        vars: serde_json::Value,
154        /// Reply channel.
155        reply: oneshot::Sender<Result<String, HostError>>,
156    },
157    /// Write a scratch file in the host run workspace (optional).
158    WriteScratchFile {
159        /// Scratch file name.
160        name: String,
161        /// File content.
162        content: String,
163        /// Reply channel (resolved path or id).
164        reply: oneshot::Sender<Result<String, HostError>>,
165    },
166    /// Read a scratch file (optional).
167    ReadScratchFile {
168        /// Scratch file name.
169        name: String,
170        /// Reply channel.
171        reply: oneshot::Sender<Result<String, HostError>>,
172    },
173    /// Git diff since a commit (optional).
174    GitDiffSince {
175        /// Commit-ish.
176        commit: String,
177        /// Reply channel.
178        reply: oneshot::Sender<Result<String, HostError>>,
179    },
180}
181
182impl WorkflowHostRequest {
183    /// Stable kind string for journaling.
184    #[must_use]
185    pub const fn kind(&self) -> &'static str {
186        match self {
187            Self::ReserveAgentCalls { .. } => "reserve_agent_calls",
188            Self::ReleaseAgentCalls { .. } => "release_agent_calls",
189            Self::SpawnAgent { .. } => "spawn_agent",
190            Self::Phase { .. } => "phase",
191            Self::Log { .. } => "log",
192            Self::Telemetry { .. } => "telemetry",
193            Self::BudgetQuery { .. } => "budget",
194            Self::RenderTemplate { .. } => "render_template",
195            Self::WriteScratchFile { .. } => "write_scratch_file",
196            Self::ReadScratchFile { .. } => "read_scratch_file",
197            Self::GitDiffSince { .. } => "git_diff_since",
198        }
199    }
200}