use serde::{Deserialize, Serialize};
use super::scalar::{BoundedJson, CallId, NodeId, TaskId, WireU64, WorkflowId};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum RootEntry {
Agent(RootAgentEntry),
Workflow(RootWorkflowEntry),
}
impl RootEntry {
pub fn root_kind(&self) -> RootKind {
match self {
Self::Agent(_) => RootKind::Agent,
Self::Workflow(_) => RootKind::Workflow,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RootAgentEntry {
pub task: LogicalTask,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run_spec: Option<LogicalAgentSpec>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RootWorkflowEntry {
pub spec: WorkflowSpec,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RootKind {
Agent,
Workflow,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ExecutionFocus {
AgentTurn(AgentTurnFocus),
WorkflowController(WorkflowControllerFocus),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AgentTurnFocus {
pub task_id: TaskId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowControllerFocus {
pub workflow_id: WorkflowId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_task_id: Option<TaskId>,
}
impl ExecutionFocus {
pub fn agent_turn(task_id: TaskId) -> Self {
Self::AgentTurn(AgentTurnFocus { task_id })
}
pub fn workflow_controller(workflow_id: WorkflowId, parent_task_id: Option<TaskId>) -> Self {
Self::WorkflowController(WorkflowControllerFocus {
workflow_id,
parent_task_id,
})
}
pub fn root_kind_hint(&self) -> RootKind {
match self {
Self::AgentTurn(_) => RootKind::Agent,
Self::WorkflowController(_) => RootKind::Workflow,
}
}
pub fn is_nested_in_agent(&self) -> bool {
matches!(
self,
Self::WorkflowController(WorkflowControllerFocus {
parent_task_id: Some(_),
..
})
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogicalTask {
pub goal: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub criteria: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lane: Option<String>,
#[serde(default, skip_serializing_if = "BoundedJson::is_null")]
pub metadata: BoundedJson,
}
impl LogicalTask {
pub fn new(goal: impl Into<String>) -> Self {
Self {
goal: goal.into(),
criteria: Vec::new(),
lane: None,
metadata: BoundedJson::null(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogicalAgentSpec {
pub goal: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub role: Option<AgentRole>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub isolation: Option<AgentIsolation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_inheritance: Option<LogicalContextInheritance>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub verification_contract_id: Option<String>,
#[serde(default, skip_serializing_if = "CapabilityFilter::is_empty")]
pub capability_filter: CapabilityFilter,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exposure_baseline: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub loop_round: Option<LogicalLoopRoundSpec>,
#[serde(default, skip_serializing_if = "BoundedJson::is_null")]
pub metadata: BoundedJson,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogicalLoopRoundSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_rounds: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_sleep_ms: Option<WireU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_sleep_ms: Option<WireU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_action: Option<String>,
}
impl LogicalAgentSpec {
pub fn new(goal: impl Into<String>) -> Self {
Self {
goal: goal.into(),
role: None,
isolation: None,
context_inheritance: None,
verification_contract_id: None,
capability_filter: CapabilityFilter::default(),
exposure_baseline: None,
loop_round: None,
metadata: BoundedJson::null(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentRole {
Explore,
Plan,
Implement,
Verify,
Custom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentIsolation {
Shared,
ReadOnly,
Worktree,
Remote,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LogicalContextInheritance {
None,
SystemOnly,
Full,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CapabilityFilter {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_kinds: Vec<CapabilityKind>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_ids: Vec<String>,
}
impl CapabilityFilter {
pub fn is_empty(&self) -> bool {
self.allowed_kinds.is_empty() && self.allowed_ids.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CapabilityKind {
Tool,
Skill,
Memory,
Knowledge,
McpServer,
Command,
Agent,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowSpec {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub nodes: Vec<WorkflowNode>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowNode {
pub node_id: NodeId,
pub task: LogicalTask,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub depends_on: Vec<NodeId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run_spec: Option<LogicalAgentSpec>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct InitialContext {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub messages: Vec<LogicalMessage>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub knowledge: Vec<KnowledgeEntry>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<CapabilityGrant>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub requested_capabilities: Vec<crate::types::capability::Capability>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogicalMessage {
pub role: MessageRole,
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<CallId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MessageRole {
System,
User,
Assistant,
Tool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KnowledgeEntry {
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tokens: Option<u32>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub pinned: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CapabilityGrant {
pub kind: CapabilityKind,
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CapabilityRef {
pub kind: CapabilityKind,
pub id: String,
}