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