Skip to main content

deepstrike_core/runtime/kernel/wire/
syscall.rs

1//! P1 syscall requests and their causation (spec §7.6).
2//!
3//! A syscall is **not** a sixth wire input class. It reaches the kernel one of exactly two ways,
4//! and in both the caller is derived from kernel-owned state rather than declared by the host:
5//!
6//! * the current operation's provider tool call — recognised while resolving the provider effect;
7//! * a child's request, attached to its `ChildCompleted` fact — the parent kernel derives the
8//!   caller from the attempt.
9//!
10//! There is no `AgentRequest { actor_id }`, and no SDK-side path that fills an actor in.
11
12use serde::{Deserialize, Serialize};
13
14use super::command::TaskUpdate;
15use super::root::{WorkflowNode, WorkflowSpec};
16use super::scalar::{AttemptId, CallId, EffectId, HandleId, TaskId};
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19#[serde(tag = "kind", rename_all = "snake_case")]
20pub enum SyscallRequest {
21    SubmitWorkflow(SubmitWorkflowRequest),
22    AppendWorkflowNodes(AppendWorkflowNodesRequest),
23    ActivateSkill(ActivateSkillRequest),
24    /// The model's task update. Distinct from `HostCommand::UpdateTask` precisely because the
25    /// two have different authority.
26    UpdateTask(UpdateTaskRequest),
27    /// F15 ruling (0.2.66): child→parent only. Deliberately NO model-facing meta-tool reaches
28    /// this variant (`SYSCALL_TOOL_NAMES` has no write surface) — the only legal caller channel
29    /// is a child's `parent_requests`. See the SYSCALL_TOOL_NAMES doc in wire/driver.rs.
30    RequestMemoryWrite(RequestMemoryWriteRequest),
31    RequestMemoryQuery(RequestMemoryQueryRequest),
32    SendMessage(SendMessageRequest),
33    PublishChannel(PublishChannelRequest),
34    ReceiveMailbox(ReceiveMailboxRequest),
35    ReceiveChannel(ReceiveChannelRequest),
36    ReadObject(ReadObjectRequest),
37    /// P3 handle page-in. Opposite in meaning to the host's `SeedKnowledge` command (DEC-9).
38    PageIn(PageInRequest),
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct SubmitWorkflowRequest {
44    pub spec: WorkflowSpec,
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48#[serde(deny_unknown_fields)]
49pub struct AppendWorkflowNodesRequest {
50    pub nodes: Vec<WorkflowNode>,
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55pub struct ActivateSkillRequest {
56    pub name: String,
57    /// Auto-deactivate after this many turns. `None` ⇒ until explicitly deactivated.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub lease_turns: Option<u32>,
60}
61
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63#[serde(deny_unknown_fields)]
64pub struct UpdateTaskRequest {
65    pub update: TaskUpdate,
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69#[serde(deny_unknown_fields)]
70pub struct RequestMemoryWriteRequest {
71    pub proposal: MemoryWriteProposal,
72}
73
74/// A **proposal**, not a record. It carries no tenant/namespace, no record id, no author or trust
75/// level, no timestamp and no session provenance: the kernel derives all of those from the
76/// operation's memory binding, the envelope's accepted time and the syscall causation.
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78#[serde(deny_unknown_fields)]
79pub struct MemoryWriteProposal {
80    pub name: String,
81    pub kind: MemoryKind,
82    pub content: String,
83    #[serde(default, skip_serializing_if = "String::is_empty")]
84    pub description: String,
85    #[serde(default, skip_serializing_if = "Vec::is_empty")]
86    pub evidence_refs: Vec<String>,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91pub enum MemoryKind {
92    User,
93    Feedback,
94    Project,
95    Reference,
96}
97
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99#[serde(deny_unknown_fields)]
100pub struct RequestMemoryQueryRequest {
101    pub query: MemoryQueryProposal,
102}
103
104#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
105#[serde(deny_unknown_fields)]
106pub struct MemoryQueryProposal {
107    #[serde(default, skip_serializing_if = "String::is_empty")]
108    pub text: String,
109    #[serde(default, skip_serializing_if = "Vec::is_empty")]
110    pub kinds: Vec<MemoryKind>,
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub limit: Option<u32>,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116#[serde(deny_unknown_fields)]
117pub struct PageInRequest {
118    /// Must already be reachable in the current P3 handle table.
119    pub handle_id: HandleId,
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123#[serde(deny_unknown_fields)]
124pub struct SendMessageRequest {
125    pub message_id: String,
126    pub to: TaskId,
127    pub message_kind: String,
128    pub payload_handle: HandleId,
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub ttl_turns: Option<u32>,
131}
132
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134#[serde(deny_unknown_fields)]
135pub struct PublishChannelRequest {
136    pub channel_id: String,
137    pub message_id: String,
138    pub subscribers: Vec<TaskId>,
139    pub message_kind: String,
140    pub payload_handle: HandleId,
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub ttl_turns: Option<u32>,
143}
144
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146#[serde(deny_unknown_fields)]
147pub struct ReceiveMailboxRequest {
148    #[serde(default = "default_receive_limit")]
149    pub limit: u32,
150}
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
153#[serde(deny_unknown_fields)]
154pub struct ReceiveChannelRequest {
155    pub channel_id: String,
156}
157
158fn default_receive_limit() -> u32 {
159    16
160}
161
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163#[serde(deny_unknown_fields)]
164pub struct ReadObjectRequest {
165    pub object_id: u32,
166}
167
168/// How the kernel derived the caller of a syscall. Never host-supplied.
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
170#[serde(tag = "kind", rename_all = "snake_case")]
171pub enum SyscallCausation {
172    /// A tool call inside the provider result the kernel is currently resolving.
173    ProviderTool(ProviderToolCausation),
174    /// A request a child attached to its completion fact.
175    ChildAttempt(ChildAttemptCausation),
176}
177
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179#[serde(deny_unknown_fields)]
180pub struct ProviderToolCausation {
181    pub provider_effect_id: EffectId,
182    pub call_id: CallId,
183    pub task_id: TaskId,
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187#[serde(deny_unknown_fields)]
188pub struct ChildAttemptCausation {
189    pub task_id: TaskId,
190    pub attempt_id: AttemptId,
191    /// Position in the child's `parent_requests` list. Derived from that stable order — a host
192    /// never picks it.
193    pub request_seq: u32,
194}