1use serde::{Deserialize, Serialize};
4
5use super::scalar::{BoundedJson, CallId, NodeId, TaskId, WireU64, WorkflowId};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19#[serde(tag = "kind", rename_all = "snake_case")]
20pub enum RootEntry {
21 Agent(RootAgentEntry),
22 Workflow(RootWorkflowEntry),
23}
24
25impl RootEntry {
26 pub fn root_kind(&self) -> RootKind {
27 match self {
28 Self::Agent(_) => RootKind::Agent,
29 Self::Workflow(_) => RootKind::Workflow,
30 }
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37#[serde(deny_unknown_fields)]
38pub struct RootAgentEntry {
39 pub task: LogicalTask,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub run_spec: Option<LogicalAgentSpec>,
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46#[serde(deny_unknown_fields)]
47pub struct RootWorkflowEntry {
48 pub spec: WorkflowSpec,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
53#[serde(rename_all = "snake_case")]
54pub enum RootKind {
55 Agent,
56 Workflow,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(tag = "kind", rename_all = "snake_case")]
78pub enum ExecutionFocus {
79 AgentTurn(AgentTurnFocus),
80 WorkflowController(WorkflowControllerFocus),
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86#[serde(deny_unknown_fields)]
87pub struct AgentTurnFocus {
88 pub task_id: TaskId,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(deny_unknown_fields)]
93pub struct WorkflowControllerFocus {
94 pub workflow_id: WorkflowId,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub parent_task_id: Option<TaskId>,
99}
100
101impl ExecutionFocus {
102 pub fn agent_turn(task_id: TaskId) -> Self {
103 Self::AgentTurn(AgentTurnFocus { task_id })
104 }
105
106 pub fn workflow_controller(workflow_id: WorkflowId, parent_task_id: Option<TaskId>) -> Self {
107 Self::WorkflowController(WorkflowControllerFocus {
108 workflow_id,
109 parent_task_id,
110 })
111 }
112
113 pub fn root_kind_hint(&self) -> RootKind {
117 match self {
118 Self::AgentTurn(_) => RootKind::Agent,
119 Self::WorkflowController(_) => RootKind::Workflow,
120 }
121 }
122
123 pub fn is_nested_in_agent(&self) -> bool {
126 matches!(
127 self,
128 Self::WorkflowController(WorkflowControllerFocus {
129 parent_task_id: Some(_),
130 ..
131 })
132 )
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142#[serde(deny_unknown_fields)]
143pub struct LogicalTask {
144 pub goal: String,
145 #[serde(default, skip_serializing_if = "Vec::is_empty")]
146 pub criteria: Vec<String>,
147 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub lane: Option<String>,
151 #[serde(default, skip_serializing_if = "BoundedJson::is_null")]
152 pub metadata: BoundedJson,
153}
154
155impl LogicalTask {
156 pub fn new(goal: impl Into<String>) -> Self {
157 Self {
158 goal: goal.into(),
159 criteria: Vec::new(),
160 lane: None,
161 metadata: BoundedJson::null(),
162 }
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173#[serde(deny_unknown_fields)]
174pub struct LogicalAgentSpec {
175 pub goal: String,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub role: Option<AgentRole>,
178 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub isolation: Option<AgentIsolation>,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub context_inheritance: Option<LogicalContextInheritance>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub verification_contract_id: Option<String>,
187 #[serde(default, skip_serializing_if = "CapabilityFilter::is_empty")]
188 pub capability_filter: CapabilityFilter,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub exposure_baseline: Option<Vec<String>>,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub loop_round: Option<LogicalLoopRoundSpec>,
196 #[serde(default, skip_serializing_if = "BoundedJson::is_null")]
197 pub metadata: BoundedJson,
198}
199
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201#[serde(deny_unknown_fields)]
202pub struct LogicalLoopRoundSpec {
203 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub max_rounds: Option<u32>,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub min_sleep_ms: Option<WireU64>,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub max_sleep_ms: Option<WireU64>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub default_action: Option<String>,
211}
212
213impl LogicalAgentSpec {
214 pub fn new(goal: impl Into<String>) -> Self {
215 Self {
216 goal: goal.into(),
217 role: None,
218 isolation: None,
219 context_inheritance: None,
220 verification_contract_id: None,
221 capability_filter: CapabilityFilter::default(),
222 exposure_baseline: None,
223 loop_round: None,
224 metadata: BoundedJson::null(),
225 }
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(rename_all = "snake_case")]
231pub enum AgentRole {
232 Explore,
233 Plan,
234 Implement,
235 Verify,
236 Custom,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
240#[serde(rename_all = "snake_case")]
241pub enum AgentIsolation {
242 Shared,
243 ReadOnly,
244 Worktree,
245 Remote,
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
249#[serde(rename_all = "snake_case")]
250pub enum LogicalContextInheritance {
251 None,
252 SystemOnly,
253 Full,
254}
255
256#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
258#[serde(deny_unknown_fields)]
259pub struct CapabilityFilter {
260 #[serde(default, skip_serializing_if = "Vec::is_empty")]
261 pub allowed_kinds: Vec<CapabilityKind>,
262 #[serde(default, skip_serializing_if = "Vec::is_empty")]
263 pub allowed_ids: Vec<String>,
264}
265
266impl CapabilityFilter {
267 pub fn is_empty(&self) -> bool {
268 self.allowed_kinds.is_empty() && self.allowed_ids.is_empty()
269 }
270}
271
272#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
273#[serde(rename_all = "snake_case")]
274pub enum CapabilityKind {
275 Tool,
276 Skill,
277 Memory,
278 Knowledge,
279 McpServer,
280 Command,
281 Agent,
282}
283
284#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
291#[serde(deny_unknown_fields)]
292pub struct WorkflowSpec {
293 #[serde(default, skip_serializing_if = "String::is_empty")]
294 pub name: String,
295 #[serde(default, skip_serializing_if = "Vec::is_empty")]
296 pub nodes: Vec<WorkflowNode>,
297}
298
299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
300#[serde(deny_unknown_fields)]
301pub struct WorkflowNode {
302 pub node_id: NodeId,
303 pub task: LogicalTask,
304 #[serde(default, skip_serializing_if = "Vec::is_empty")]
305 pub depends_on: Vec<NodeId>,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub run_spec: Option<LogicalAgentSpec>,
308}
309
310#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
315#[serde(deny_unknown_fields)]
316pub struct InitialContext {
317 #[serde(default, skip_serializing_if = "Vec::is_empty")]
318 pub messages: Vec<LogicalMessage>,
319 #[serde(default, skip_serializing_if = "Vec::is_empty")]
320 pub knowledge: Vec<KnowledgeEntry>,
321 #[serde(default, skip_serializing_if = "Vec::is_empty")]
322 pub capabilities: Vec<CapabilityGrant>,
323}
324
325#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
326#[serde(deny_unknown_fields)]
327pub struct LogicalMessage {
328 pub role: MessageRole,
329 pub content: String,
330 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub tokens: Option<u32>,
333 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub tool_call_id: Option<CallId>,
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
339#[serde(rename_all = "snake_case")]
340pub enum MessageRole {
341 System,
342 User,
343 Assistant,
344 Tool,
345}
346
347#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
350#[serde(deny_unknown_fields)]
351pub struct KnowledgeEntry {
352 pub content: String,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub key: Option<String>,
355 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub tokens: Option<u32>,
357 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
358 pub pinned: bool,
359}
360
361#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
363#[serde(deny_unknown_fields)]
364pub struct CapabilityGrant {
365 pub kind: CapabilityKind,
366 pub id: String,
367 #[serde(default, skip_serializing_if = "Option::is_none")]
368 pub description: Option<String>,
369}
370
371#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
373#[serde(deny_unknown_fields)]
374pub struct CapabilityRef {
375 pub kind: CapabilityKind,
376 pub id: String,
377}