1use serde_json::{Value, json};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct DefaultGrant {
13 pub root: bool,
14 pub workflows: bool,
15 pub subagents: bool,
16 pub user: bool,
18 pub agent: bool,
20}
21
22const ALL: DefaultGrant = DefaultGrant {
23 root: true,
24 workflows: true,
25 subagents: true,
26 user: false,
27 agent: false,
28};
29const ROOT_WF: DefaultGrant = DefaultGrant {
30 root: true,
31 workflows: true,
32 subagents: false,
33 user: false,
34 agent: false,
35};
36const ROOT_ONLY: DefaultGrant = DefaultGrant {
37 root: true,
38 workflows: false,
39 subagents: false,
40 user: false,
41 agent: false,
42};
43
44#[derive(Debug, Clone)]
46pub struct Contract {
47 pub name: &'static str,
48 pub description: &'static str,
49 pub input: Value,
50 pub output: Value,
51 pub builtin: bool,
52 pub grant: DefaultGrant,
53 pub family: &'static str,
55}
56
57fn obj(props: Value, required: &[&str]) -> Value {
58 json!({"type": "object", "properties": props, "required": required, "additionalProperties": false})
59}
60fn open_obj(props: Value, required: &[&str]) -> Value {
61 json!({"type": "object", "properties": props, "required": required})
62}
63fn any() -> Value {
64 json!({})
65}
66fn s(desc: &str) -> Value {
67 json!({"type": "string", "description": desc})
68}
69fn arr(items: Value) -> Value {
70 json!({"type": "array", "items": items})
71}
72
73pub fn contracts() -> Vec<Contract> {
75 let mut v = Vec::new();
76 let mut c = |name: &'static str,
77 family: &'static str,
78 description: &'static str,
79 input: Value,
80 output: Value,
81 builtin: bool,
82 grant: DefaultGrant| {
83 v.push(Contract {
84 name,
85 description,
86 input,
87 output,
88 builtin,
89 grant,
90 family,
91 });
92 };
93
94 c(
96 "instruction.read",
97 "instruction",
98 "Read the agent's current instruction (the brief it operates under).",
99 obj(json!({}), &[]),
100 open_obj(
101 json!({"text": {"type": "string"}, "source": {"enum": ["static", "resource"]}, "uri": {"type": "string"}, "version": {"type": "string"}}),
102 &["text", "source"],
103 ),
104 true,
105 ALL,
106 );
107 c(
108 "instruction.subscribe",
109 "instruction",
110 "(Re)subscribe to the instruction resource, or switch to another URI; an update re-reads it and wakes the agent.",
111 obj(
112 json!({"uri": s("The resource URI (omit to re-subscribe to the current one)")}),
113 &[],
114 ),
115 open_obj(
116 json!({"subscribed": {"type": "boolean"}, "uri": {"type": "string"}}),
117 &["subscribed"],
118 ),
119 true,
120 ROOT_ONLY,
121 );
122
123 c(
125 "subagent.run",
126 "subagent",
127 "Spawn a subagent with its own instruction. mode: sync (wait for the result), async (get a handle), detached (fire and forget), warm (stays alive; send it messages).",
128 obj(
129 json!({
130 "instruction": s("The subagent's brief"),
131 "mode": {"enum": ["sync", "async", "detached", "warm"], "default": "sync"},
132 "workflow": s("Run this workflow instead of a free agent loop"),
133 "tools": arr(json!({"type": "string"})),
134 "servers": arr(json!({"type": "string"})),
135 "limits": open_obj(json!({"steps": {"type": "integer"}, "tokens": {"type": "integer"}, "deadline": {"type": "string"}, "memory": s("OS memory cap for the child process, e.g. \"512MB\" (RLIMIT_AS)"), "cpu": s("OS CPU-time cap, e.g. \"5m\" (RLIMIT_CPU)")}), &[]),
136 "priority": {"enum": ["low", "normal", "high"], "default": "normal", "description": "Contention priority: low sheds first under pressure and runs nicer; high schedules first (and asks the OS for more, best-effort)."},
137 "context": arr(open_obj(json!({"role": {"type": "string"}, "content": {"type": "string"}}), &["role", "content"])),
138 "output_contract": s("What the result must look like"),
139 "output_schema": {"type": "object"},
140 "skills": arr(json!({"type": "string"}))
141 }),
142 &["instruction"],
143 ),
144 open_obj(
145 json!({"handle": {"type": "string"}, "status": {"type": "string"}, "result": any()}),
146 &["handle", "status"],
147 ),
148 true,
149 ROOT_WF,
150 );
151 c(
152 "subagent.send",
153 "subagent",
154 "Send a message into a warm subagent (steer it).",
155 obj(
156 json!({"handle": s("The subagent handle"), "message": s("The message")}),
157 &["handle", "message"],
158 ),
159 open_obj(
160 json!({"ok": {"type": "boolean"}, "handle": {"type": "string"}}),
161 &["ok"],
162 ),
163 true,
164 ROOT_WF,
165 );
166 c(
167 "subagent.kill",
168 "subagent",
169 "Cancel and stop a subagent.",
170 obj(
171 json!({"handle": s("The subagent handle"), "reason": s("Why")}),
172 &["handle"],
173 ),
174 open_obj(
175 json!({"ok": {"type": "boolean"}, "handle": {"type": "string"}}),
176 &["ok"],
177 ),
178 true,
179 ROOT_WF,
180 );
181 c(
182 "subagent.status",
183 "subagent",
184 "The status (and result, when finished) of a subagent.",
185 obj(json!({"handle": s("The subagent handle")}), &["handle"]),
186 open_obj(
187 json!({"handle": {"type": "string"}, "status": {"type": "string"}, "mode": {"type": "string"}, "result": any(), "error": {"type": "string"}}),
188 &["handle", "status"],
189 ),
190 true,
191 ROOT_WF,
192 );
193 c(
194 "subagent.await",
195 "subagent",
196 "Wait for an async subagent to finish (bounded by timeout) and return its result.",
197 obj(
198 json!({"handle": s("The subagent handle"), "timeout": s("Duration, e.g. 30s")}),
199 &["handle"],
200 ),
201 open_obj(
202 json!({"handle": {"type": "string"}, "status": {"type": "string"}, "result": any(), "error": {"type": "string"}}),
203 &["handle", "status"],
204 ),
205 true,
206 ROOT_WF,
207 );
208 c(
209 "subagent.list",
210 "subagent",
211 "List the subagents of this instance.",
212 obj(json!({}), &[]),
213 open_obj(
214 json!({"subagents": arr(json!({"type": "object"}))}),
215 &["subagents"],
216 ),
217 true,
218 ROOT_WF,
219 );
220
221 c(
223 "code.run",
224 "code",
225 "Run code in a sandbox (only available when mapped to a sandbox MCP server).",
226 obj(
227 json!({"language": s("e.g. python, bash"), "code": s("The program"), "files": {"type": "object"}, "timeout": s("Duration")}),
228 &["language", "code"],
229 ),
230 open_obj(
231 json!({"stdout": {"type": "string"}, "stderr": {"type": "string"}, "exit_code": {"type": "integer"}, "files": {"type": "object"}}),
232 &[],
233 ),
234 false,
235 ROOT_WF,
236 );
237
238 c(
240 "memory.get",
241 "memory",
242 "Read a value from the agent's durable memory.",
243 obj(json!({"key": s("The key")}), &["key"]),
244 open_obj(
245 json!({"found": {"type": "boolean"}, "key": {"type": "string"}, "value": any(), "meta": {"type": "object"}}),
246 &["found"],
247 ),
248 true,
249 ALL,
250 );
251 c(
252 "memory.set",
253 "memory",
254 "Write a JSON value to the agent's durable memory (optional TTL).",
255 obj(
256 json!({"key": s("The key"), "value": any(), "ttl": s("Duration after which the value expires")}),
257 &["key", "value"],
258 ),
259 open_obj(
260 json!({"ok": {"type": "boolean"}, "key": {"type": "string"}, "meta": {"type": "object"}}),
261 &["ok"],
262 ),
263 true,
264 ALL,
265 );
266 c(
267 "memory.list",
268 "memory",
269 "List memory keys (optionally by prefix).",
270 obj(
271 json!({"prefix": s("Key prefix"), "limit": {"type": "integer", "minimum": 1}}),
272 &[],
273 ),
274 open_obj(
275 json!({"keys": arr(json!({"type": "object"})), "truncated": {"type": "boolean"}}),
276 &["keys"],
277 ),
278 true,
279 ALL,
280 );
281 c(
282 "memory.delete",
283 "memory",
284 "Delete a memory key.",
285 obj(json!({"key": s("The key")}), &["key"]),
286 open_obj(
287 json!({"ok": {"type": "boolean"}, "key": {"type": "string"}}),
288 &["ok"],
289 ),
290 true,
291 ALL,
292 );
293
294 c(
296 "artifact.create",
297 "artifact",
298 "Create an artifact (a named piece of content delivered with the task).",
299 obj(
300 json!({"name": s("File-like name"), "mime": s("MIME type, default text/plain"), "content": any(), "from_step": s("Take the content from a workflow step output"), "sensitive": {"type": "boolean"}}),
301 &["name"],
302 ),
303 open_obj(
304 json!({"id": {"type": "string"}, "name": {"type": "string"}, "size": {"type": "integer"}, "sha256": {"type": "string"}}),
305 &["id"],
306 ),
307 true,
308 ALL,
309 );
310 c(
311 "artifact.get",
312 "artifact",
313 "Read an artifact by id.",
314 obj(json!({"id": s("Artifact id")}), &["id"]),
315 open_obj(
316 json!({"id": {"type": "string"}, "name": {"type": "string"}, "mime": {"type": "string"}, "content": any(), "size": {"type": "integer"}, "sha256": {"type": "string"}}),
317 &["id"],
318 ),
319 true,
320 ALL,
321 );
322 c(
323 "artifact.delete",
324 "artifact",
325 "Delete an artifact.",
326 obj(json!({"id": s("Artifact id")}), &["id"]),
327 open_obj(json!({"ok": {"type": "boolean"}}), &["ok"]),
328 true,
329 ALL,
330 );
331 c(
332 "artifact.list",
333 "artifact",
334 "List artifacts.",
335 obj(
336 json!({"prefix": s("Name prefix"), "limit": {"type": "integer"}}),
337 &[],
338 ),
339 open_obj(
340 json!({"artifacts": arr(json!({"type": "object"}))}),
341 &["artifacts"],
342 ),
343 true,
344 ALL,
345 );
346
347 c(
349 "workflow.run",
350 "workflow",
351 "Start a run of a named workflow (with inputs).",
352 obj(
353 json!({"name": s("Workflow name"), "inputs": {"type": "object"}, "start": s("Which start node to fire (default: manual/once)"), "wait": {"type": "boolean", "description": "Wait for the run to finish and return its output"}, "timeout": s("Duration when waiting")}),
354 &["name"],
355 ),
356 open_obj(
357 json!({"run": {"type": "string"}, "status": {"type": "string"}, "output": any(), "task": {"type": "string"}}),
358 &["run", "status"],
359 ),
360 true,
361 ROOT_WF,
362 );
363 c(
364 "workflow.create",
365 "workflow",
366 "Define a new workflow (dialect 3) at runtime.",
367 obj(
368 json!({"definition": {"type": "object"}, "arm": {"type": "boolean"}}),
369 &["definition"],
370 ),
371 open_obj(
372 json!({"name": {"type": "string"}, "hash": {"type": "string"}, "armed": {"type": "boolean"}}),
373 &["name"],
374 ),
375 true,
376 ROOT_ONLY,
377 );
378 c(
379 "workflow.update",
380 "workflow",
381 "Replace a workflow definition (live runs keep their pinned hash).",
382 obj(
383 json!({"name": s("Workflow name"), "definition": {"type": "object"}}),
384 &["name", "definition"],
385 ),
386 open_obj(
387 json!({"name": {"type": "string"}, "hash": {"type": "string"}}),
388 &["name"],
389 ),
390 true,
391 ROOT_ONLY,
392 );
393 c(
394 "workflow.delete",
395 "workflow",
396 "Delete a workflow definition (disarms it; live runs finish).",
397 obj(json!({"name": s("Workflow name")}), &["name"]),
398 open_obj(json!({"ok": {"type": "boolean"}}), &["ok"]),
399 true,
400 ROOT_ONLY,
401 );
402 c(
403 "workflow.list",
404 "workflow",
405 "List workflows and their runs.",
406 obj(json!({}), &[]),
407 open_obj(
408 json!({"workflows": arr(json!({"type": "object"}))}),
409 &["workflows"],
410 ),
411 true,
412 ALL,
413 );
414 c(
415 "workflow.status",
416 "workflow",
417 "The status of a run (or of every run of a workflow).",
418 obj(json!({"run": s("Run id"), "name": s("Workflow name")}), &[]),
419 open_obj(json!({"runs": arr(json!({"type": "object"}))}), &["runs"]),
420 true,
421 ALL,
422 );
423 c(
424 "workflow.cancel",
425 "workflow",
426 "Cancel a run.",
427 obj(json!({"run": s("Run id"), "reason": s("Why")}), &["run"]),
428 open_obj(
429 json!({"ok": {"type": "boolean"}, "status": {"type": "string"}}),
430 &["ok"],
431 ),
432 true,
433 ROOT_WF,
434 );
435 c(
436 "workflow.pause",
437 "workflow",
438 "Pause a run (or disarm a workflow's start nodes). With `before_step`, \
439 set a BREAKPOINT instead: the run keeps going and pauses just before \
440 that step starts, so it can be inspected in the state it is in rather \
441 than one effect later. Durable — it survives a restart.",
442 obj(
443 json!({"run": s("Run id"), "name": s("Workflow name"),
444 "before_step": s("Pause just before this step starts (a breakpoint)")}),
445 &[],
446 ),
447 open_obj(
448 json!({"ok": {"type": "boolean"}, "break_before": {"type": "string"}}),
449 &[],
450 ),
451 true,
452 ROOT_ONLY,
453 );
454 c(
455 "workflow.resume",
456 "workflow",
457 "Resume a paused run (or re-arm a workflow).",
458 obj(json!({"run": s("Run id"), "name": s("Workflow name")}), &[]),
459 open_obj(json!({"ok": {"type": "boolean"}}), &["ok"]),
460 true,
461 ROOT_ONLY,
462 );
463 c(
464 "workflow.signal",
465 "workflow",
466 "Send a named signal (with a payload) into a run, or start a workflow whose start node listens for it.",
467 obj(
468 json!({"name": s("Signal name"), "payload": any(), "run": s("Target run id (optional)")}),
469 &["name"],
470 ),
471 open_obj(json!({"delivered": {"type": "integer"}}), &["delivered"]),
472 true,
473 ALL,
474 );
475 c(
476 "workflow.wait",
477 "workflow",
478 "Wait for a run to finish and return its output.",
479 obj(
480 json!({"run": s("Run id"), "timeout": s("Duration")}),
481 &["run"],
482 ),
483 open_obj(
484 json!({"run": {"type": "string"}, "status": {"type": "string"}, "output": any()}),
485 &["run", "status"],
486 ),
487 true,
488 ROOT_WF,
489 );
490
491 c(
493 "plan.create",
494 "plan",
495 "Create (or replace) this conversation's working plan: a goal and an ordered list of items.",
496 obj(
497 json!({"goal": s("The goal"), "items": arr(json!({"oneOf": [{"type": "string"}, open_obj(json!({"title": {"type": "string"}, "detail": {"type": "string"}}), &["title"])]}))}),
498 &["goal", "items"],
499 ),
500 open_obj(
501 json!({"goal": {"type": "string"}, "items": arr(json!({"type": "object"}))}),
502 &["goal", "items"],
503 ),
504 true,
505 ALL,
506 );
507 c(
508 "plan.get",
509 "plan",
510 "Read this conversation's plan.",
511 obj(json!({}), &[]),
512 open_obj(json!({"plan": any(), "progress": {"type": "string"}}), &[]),
513 true,
514 ALL,
515 );
516 c(
517 "plan.update",
518 "plan",
519 "Advance the plan: set an item's status/note, bind it to a run/subagent, insert an item, or reorder.",
520 obj(
521 json!({
522 "item": {"description": "Item id (number) or exact title", "oneOf": [{"type": "integer"}, {"type": "string"}]},
523 "status": {"enum": ["pending", "in_progress", "done", "blocked", "skipped"]},
524 "note": s("A short note"), "title": s("New title"), "detail": s("New detail"),
525 "bind": open_obj(json!({"run": {"type": "string"}, "subagent": {"type": "string"}, "task": {"type": "string"}}), &[]),
526 "insert": open_obj(json!({"title": {"type": "string"}, "detail": {"type": "string"}, "after": {"type": "integer"}}), &["title"]),
527 "reorder": arr(json!({"type": "integer"}))
528 }),
529 &[],
530 ),
531 open_obj(
532 json!({"goal": {"type": "string"}, "items": arr(json!({"type": "object"})), "progress": {"type": "string"}}),
533 &["goal", "items"],
534 ),
535 true,
536 ALL,
537 );
538 c(
539 "plan.clear",
540 "plan",
541 "Clear the plan (the goal is met or abandoned).",
542 obj(json!({}), &[]),
543 open_obj(json!({"ok": {"type": "boolean"}}), &["ok"]),
544 true,
545 ALL,
546 );
547
548 c(
550 "ask_human",
551 "human",
552 "Ask the human (the conversation's principal) a question and wait for the answer.",
553 obj(
554 json!({"question": s("The question"), "schema": {"type": "object", "description": "Expected answer shape"}, "to": s("Principal or conversation to ask (default: the current one)"), "timeout": s("Duration")}),
555 &["question"],
556 ),
557 open_obj(
558 json!({"reply": any(), "timed_out": {"type": "boolean"}}),
559 &[],
560 ),
561 true,
562 ALL,
563 );
564 c(
565 "sleep",
566 "time",
567 "Wait for a duration (durable: survives restarts).",
568 obj(
569 json!({"duration": s("Duration, e.g. 30s, 5m")}),
570 &["duration"],
571 ),
572 open_obj(json!({"slept_ms": {"type": "integer"}}), &["slept_ms"]),
573 true,
574 ALL,
575 );
576 c(
577 "await",
578 "time",
579 "Wait until a condition holds (CEL over memory/resources/steps/signals) or a timeout elapses.",
580 obj(
581 json!({"condition": s("CEL expression"), "on": arr(json!({"type": "string"})), "timeout": s("Duration")}),
582 &["condition"],
583 ),
584 open_obj(
585 json!({"satisfied": {"type": "boolean"}, "value": any()}),
586 &["satisfied"],
587 ),
588 true,
589 ALL,
590 );
591 c(
592 "context.compact",
593 "context",
594 "Compact this context: summarize older messages, keep the recent ones verbatim.",
595 obj(
596 json!({"target_tokens": {"type": "integer"}, "keep_last": {"type": "integer"}}),
597 &[],
598 ),
599 open_obj(
600 json!({"version": {"type": "integer"}, "est_tokens": {"type": "integer"}, "folded": {"type": "integer"}}),
601 &["version"],
602 ),
603 true,
604 ALL,
605 );
606 c(
607 "think",
608 "intelligence",
609 "One structured reasoning call (no tools): give a prompt and optionally an output schema; get the object back.",
610 obj(
611 json!({"prompt": s("What to think about"), "output_schema": {"type": "object"}, "reads": arr(json!({"type": "string"})), "skills": arr(json!({"type": "string"}))}),
612 &["prompt"],
613 ),
614 any(),
615 true,
616 ALL,
617 );
618 c(
619 "finish",
620 "lifecycle",
621 "Finish the current unit of work with a status and an optional output.",
622 obj(
623 json!({"status": {"enum": ["completed", "failed", "refused", "cancelled"]}, "output": any(), "reason": s("Why"), "exit": {"type": "boolean", "description": "Root only: exit the daemon"}}),
624 &["status"],
625 ),
626 open_obj(json!({"ok": {"type": "boolean"}}), &["ok"]),
627 true,
628 DefaultGrant {
629 root: true,
630 workflows: false,
631 subagents: true,
632 user: false,
633 agent: false,
634 },
635 );
636 c(
637 "status",
638 "status",
639 "The instance status: runs, subagents, conversations, budget, store.",
640 obj(json!({}), &[]),
641 json!({"type": "object"}),
642 true,
643 DefaultGrant {
644 root: true,
645 workflows: true,
646 subagents: true,
647 user: true,
648 agent: true,
649 },
650 );
651
652 c(
654 "knowledge.search",
655 "knowledge",
656 "Search the knowledge base (RAG over documents).",
657 obj(
658 json!({"query": s("The query"), "top_k": {"type": "integer", "minimum": 1}, "filters": {"type": "object"}}),
659 &["query"],
660 ),
661 open_obj(
662 json!({"hits": arr(open_obj(json!({"id": {"type": "string"}, "uri": {"type": "string"}, "title": {"type": "string"}, "score": {"type": "number"}, "snippet": {"type": "string"}, "metadata": {"type": "object"}}), &[]))}),
663 &["hits"],
664 ),
665 false,
666 ALL,
667 );
668 c(
669 "knowledge.get",
670 "knowledge",
671 "Fetch a knowledge document by id or URI.",
672 obj(
673 json!({"id": s("Document id"), "uri": s("Document URI")}),
674 &[],
675 ),
676 open_obj(
677 json!({"content": {"type": "string"}, "mime": {"type": "string"}, "metadata": {"type": "object"}}),
678 &["content"],
679 ),
680 false,
681 ALL,
682 );
683 c(
684 "knowledge.list",
685 "knowledge",
686 "List knowledge documents.",
687 obj(json!({"prefix": s("Prefix")}), &[]),
688 open_obj(json!({"docs": arr(json!({"type": "object"}))}), &["docs"]),
689 false,
690 ALL,
691 );
692 c(
693 "search.query",
694 "search",
695 "Web/docs/code search through the search server.",
696 obj(
697 json!({"query": s("The query"), "kind": {"enum": ["web", "docs", "code"]}, "limit": {"type": "integer", "minimum": 1}, "freshness": s("e.g. day, week")}),
698 &["query"],
699 ),
700 open_obj(
701 json!({"results": arr(open_obj(json!({"title": {"type": "string"}, "url": {"type": "string"}, "snippet": {"type": "string"}, "source": {"type": "string"}, "published": {"type": "string"}}), &[]))}),
702 &["results"],
703 ),
704 false,
705 ALL,
706 );
707 c(
708 "search.fetch",
709 "search",
710 "Fetch a page's content through the search server.",
711 obj(
712 json!({"url": s("The URL"), "max_bytes": {"type": "integer"}}),
713 &["url"],
714 ),
715 open_obj(
716 json!({"content": {"type": "string"}, "mime": {"type": "string"}, "final_url": {"type": "string"}}),
717 &["content"],
718 ),
719 false,
720 ALL,
721 );
722
723 c(
725 "skills.list",
726 "skills",
727 "List the available skills (name, description, when to use).",
728 obj(json!({}), &[]),
729 open_obj(
730 json!({"skills": arr(json!({"type": "object"}))}),
731 &["skills"],
732 ),
733 true,
734 ALL,
735 );
736 c(
737 "skills.load",
738 "skills",
739 "Load a skill's full instructions into this context.",
740 obj(
741 json!({"name": s("Skill name"), "version": s("Version/hash (optional)"), "arguments": {"type": "object"}}),
742 &["name"],
743 ),
744 open_obj(
745 json!({"loaded": {"type": "boolean"}, "name": {"type": "string"}, "hash": {"type": "string"}, "body": {"type": "string"}}),
746 &["loaded"],
747 ),
748 true,
749 ALL,
750 );
751 c(
752 "skills.unload",
753 "skills",
754 "Drop a loaded skill from this context.",
755 obj(json!({"name": s("Skill name")}), &["name"]),
756 open_obj(json!({"ok": {"type": "boolean"}}), &["ok"]),
757 true,
758 ALL,
759 );
760
761 c(
767 "exec",
768 "exec",
769 "Run a local command (argv — NO shell interpretation) and return {stdout, stderr, exit_code, timed_out}. GUARDED and default-OFF (RFC 0028): runs only allow-listed commands, confined to a working directory, with a timeout, an output cap, and a minimal environment. Enable a local runner via `security.exec` in a build with `--features exec`, or map it onto an MCP server with `tools.overrides` to delegate execution off-box.",
770 obj(
771 json!({
772 "cmd": s("The command to run (argv[0]) — must be in security.exec.allow"),
773 "args": arr(s("Arguments (argv[1..]); passed directly, never through a shell")),
774 "cwd": s("Working directory, relative to and confined within security.exec.workdir"),
775 "stdin": s("Optional standard input for the command"),
776 "timeout": s("Max wall-clock (e.g. `10s`); clamped to the configured maximum")
777 }),
778 &["cmd"],
779 ),
780 open_obj(
781 json!({
782 "stdout": {"type": "string"}, "stderr": {"type": "string"},
783 "exit_code": {"type": "integer"}, "timed_out": {"type": "boolean"}
784 }),
785 &["stdout", "stderr", "exit_code"],
786 ),
787 false,
788 ALL,
789 );
790 v
791}
792
793pub fn names() -> Vec<&'static str> {
795 contracts().into_iter().map(|c| c.name).collect()
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801
802 #[test]
803 fn contracts_are_unique_well_formed_and_cover_the_rfc_table() {
804 let all = contracts();
805 let mut seen = std::collections::BTreeSet::new();
806 for c in &all {
807 assert!(seen.insert(c.name), "duplicate contract {}", c.name);
808 crate::jsonschema::check_schema(&c.input)
809 .unwrap_or_else(|e| panic!("{}: bad input schema: {e:?}", c.name));
810 crate::jsonschema::check_schema(&c.output)
811 .unwrap_or_else(|e| panic!("{}: bad output schema: {e:?}", c.name));
812 assert!(
813 c.name
814 .chars()
815 .all(|ch| ch.is_ascii_alphanumeric() || ch == '.' || ch == '_'),
816 "{}",
817 c.name
818 );
819 }
820 for must in [
821 "instruction.read",
822 "instruction.subscribe",
823 "subagent.run",
824 "subagent.send",
825 "subagent.kill",
826 "subagent.status",
827 "subagent.await",
828 "subagent.list",
829 "code.run",
830 "memory.get",
831 "memory.set",
832 "memory.list",
833 "memory.delete",
834 "artifact.create",
835 "artifact.get",
836 "artifact.delete",
837 "artifact.list",
838 "workflow.run",
839 "workflow.create",
840 "workflow.update",
841 "workflow.delete",
842 "workflow.list",
843 "workflow.status",
844 "workflow.cancel",
845 "workflow.pause",
846 "workflow.resume",
847 "workflow.signal",
848 "workflow.wait",
849 "plan.create",
850 "plan.get",
851 "plan.update",
852 "plan.clear",
853 "ask_human",
854 "sleep",
855 "await",
856 "context.compact",
857 "think",
858 "finish",
859 "status",
860 "knowledge.search",
861 "knowledge.get",
862 "knowledge.list",
863 "search.query",
864 "search.fetch",
865 "skills.list",
866 "skills.load",
867 "skills.unload",
868 ] {
869 assert!(seen.contains(must), "missing contract {must}");
870 }
871 for c in &all {
875 let mapping_only = c.name == "code.run"
876 || c.name == "exec"
877 || c.name.starts_with("knowledge.")
878 || c.name.starts_with("search.");
879 assert_eq!(!c.builtin, mapping_only, "{}", c.name);
880 }
881 assert!(
883 !all.iter()
884 .find(|c| c.name == "finish")
885 .unwrap()
886 .grant
887 .workflows
888 );
889 assert!(all.iter().find(|c| c.name == "status").unwrap().grant.user);
890 }
891}