From e18c22ac421e6d554752d52dcaa10052e407dee2 Mon Sep 17 00:00:00 2001 From: Jithin VG Date: Fri, 13 Nov 2026 17:00:36 +0530 Subject: [PATCH] The kernel's operations are a file, and each field says which it is spec/kernel.yaml lists the 76 operations of the seven primitives; kernel-op: on a field of spec/schema.yaml names the operations the field is, read into Field::kernel_ops. every_kernel_operation_is_written_somewhere.rs holds the two files to each other in both directions. pattern-coverage.py reads the operations from the file; primitive-coverage.py adds a keyword's operations' primitives to its own. --- CHANGELOG.md | 25 ++ README.md | 4 +- crates/pact-schema/src/from_doc.rs | 2 + crates/pact-schema/src/lib.rs | 13 + ...y_kernel_operation_is_written_somewhere.rs | 193 ++++++++++ docs/90-REVIEW.md | 2 +- docs/97-PRIMITIVES.md | 283 +++++++-------- scripts/pattern-coverage.py | 106 +----- scripts/primitive-coverage.py | 15 +- site-docs/index.md | 2 +- site-docs/status/verified.md | 4 +- spec/kernel.yaml | 330 ++++++++++++++++++ spec/schema.yaml | 298 ++++++++++++++++ 13 files changed, 1041 insertions(+), 236 deletions(-) create mode 100644 crates/pact-schema/tests/every_kernel_operation_is_written_somewhere.rs create mode 100644 spec/kernel.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index e49d53d..a72b075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ What changed in PACT, newest first. Each entry says what an author can now write, or what now holds that did not; the commits are listed under it for whoever wants the detail. +## 2026-11 — the kernel's operations are a file, and each field says which it is (13 November) + +A runtime that runs PACT one operation at a time needs to know which fields reach each of the 76 +operations of the seven primitives. That list lived in one place, the `OPS` table inside +`scripts/pattern-coverage.py`, and said it in keyword prose (`limits, at-most, fuel, +checks-at-most`), which a runtime can only match against field names by guessing. Nothing here +changes what an author writes or what `pact check` accepts. + +- **`spec/kernel.yaml`** lists the 76 operations: for each, its primitive, what it does, and the + keyword that is it today. `scripts/pattern-coverage.py` reads them from there rather than + keeping its own copy. An operation written as syntax inside a value rather than as a field of its + own (`one of a, b` in a shape line, `input.x` in a binding) says where with `written-in:`. +- **`kernel-op:` on a field of `spec/schema.yaml`** names the operations the field is: + `store.claim` on `action.same-request-key`, `allowance.reach` on `agent.uses`, several where a + field is several (`stage.does`). 295 fields carry one; descriptions, names and `because:` carry + none. The checker reads it into `Field::kernel_ops`, and `pattern-coverage.py --ops` lists each + operation's fields. +- **`crates/pact-schema/tests/every_kernel_operation_is_written_somewhere.rs`** holds the two + files to each other: a `kernel-op:` naming an operation the kernel does not list fails, and so + does an operation that no field names and no `written-in:` places. +- **`scripts/primitive-coverage.py` adds a keyword's operations' primitives to its own.** Its main + ingredient is unchanged; the "takes any part in" counts of docs/97 §6 grow where the table had + given a keyword fewer primitives than its operations have (`agent.accepts` is a signature, so an + Actor as well as a Shape). + ## 2026-11 — a digest moves only when the tree does, and who answered a wait (13 November) Bud Agent Flow asked whether "a new version of PACT never moves the digest of a tree nobody diff --git a/README.md b/README.md index 7803257..2c6ed7c 100644 --- a/README.md +++ b/README.md @@ -998,11 +998,11 @@ framework is an extra. |---|---| | **Design** | Thesis, 28 binding decisions, FRD (120 requirements), 60-row refusal ledger — complete | | **Research** | 14 source-grounded studies, ~15,750 lines, over 140 repos (~15 GB) + 57 papers | -| **Code** | Loader, diagnostics, schema engine, CLI, harness, resolver, evals, SLO — **4969 tests (1648 Rust + 3321 adapter), clippy clean, TypeScript type-checked** | +| **Code** | Loader, diagnostics, schema engine, CLI, harness, resolver, evals, SLO — **4973 tests (1652 Rust + 3321 adapter), clippy clean, TypeScript type-checked** | | **Adapters** | **All 7 named targets**, proven against one shared conformance suite | ```bash -./scripts/test-all.sh # 4969 tests, Rust + 7 adapters, fully offline +./scripts/test-all.sh # 4973 tests, Rust + 7 adapters, fully offline ``` **What is still owed** is not hidden — it lives in diff --git a/crates/pact-schema/src/from_doc.rs b/crates/pact-schema/src/from_doc.rs index 74550f7..96a68e7 100644 --- a/crates/pact-schema/src/from_doc.rs +++ b/crates/pact-schema/src/from_doc.rs @@ -273,6 +273,8 @@ impl Schema { .trim() .to_string(); field.template = f.get("template").and_then(as_bool).unwrap_or(false); + // docs/97 §5.4 — see `Field::kernel_ops`. + field.kernel_ops = names(f, "kernel-op"); group.fields.push(field); } None => diags.push(Diagnostic::error( diff --git a/crates/pact-schema/src/lib.rs b/crates/pact-schema/src/lib.rs index 87b3be5..1ffe02e 100644 --- a/crates/pact-schema/src/lib.rs +++ b/crates/pact-schema/src/lib.rs @@ -417,6 +417,18 @@ pub struct Field { /// template, may appear in (96 §C). Read by `pact_loader::holes`, which /// kept the same list as a Rust const of two field names. pub template: bool, + /// The kernel operations this field is (docs/97 §2 and §5.4), as + /// `spec/kernel.yaml` names them — `store.claim` on + /// `action.same-request-key`, `allowance.reach` on `agent.uses`. Empty on a + /// field that is no operation: a description, a name, a `because:`. + /// + /// Data for the reason `template` is: a runtime that keeps one + /// implementation per operation reads which fields reach it from here, + /// rather than by matching an operation's keyword prose against field + /// names, where `limits` is a group, two fields and every field inside + /// them. `tests/every_kernel_operation_is_written_somewhere.rs` holds the + /// names to `spec/kernel.yaml`. + pub kernel_ops: Vec, } impl Field { @@ -444,6 +456,7 @@ impl Field { model_kind: Vec::new(), plays_role: String::new(), template: false, + kernel_ops: Vec::new(), } } diff --git a/crates/pact-schema/tests/every_kernel_operation_is_written_somewhere.rs b/crates/pact-schema/tests/every_kernel_operation_is_written_somewhere.rs new file mode 100644 index 0000000..da74bf8 --- /dev/null +++ b/crates/pact-schema/tests/every_kernel_operation_is_written_somewhere.rs @@ -0,0 +1,193 @@ +//! Every kernel operation is written somewhere, and every field that says it is +//! one names an operation that exists. +//! +//! `spec/kernel.yaml` lists the 76 operations of the seven primitives (docs/97 +//! §2 and §5.4). `spec/schema.yaml` says on each field which of them the field +//! IS, with `kernel-op:`. The two are kept in two files because they answer two +//! questions — what the kernel can do, and where an author writes it — and a +//! pair of hand-kept lists drifts unless something holds them to each other. +//! This is that thing, in both directions: +//! +//! - a `kernel-op:` naming an operation `spec/kernel.yaml` does not list is a +//! field claiming to be something the kernel cannot do; +//! - an operation no field names and no `written-in:` places is one an author +//! has no way to write, which is the gap docs/97 §9 closed for sixteen +//! operations and which nothing stopped from reopening. +//! +//! The last test runs the same check on the real files with one line changed +//! each way, so neither half can pass by checking nothing. + +use camino::Utf8Path; +use pact_diag::Diagnostics; +use pact_doc::{Node, parse_yaml}; +use pact_schema::Schema; +use pact_schema::from_doc::schema_from_yaml; +use std::collections::{BTreeMap, BTreeSet}; + +/// The seven primitives, and the prefix every operation of each carries. +const PRIMITIVES: [(&str, &str); 7] = [ + ("Shape", "shape"), + ("Origin", "label"), + ("Store", "store"), + ("Actor", "actor"), + ("Flow", "flow"), + ("Check", "check"), + ("Allowance", "allowance"), +]; + +fn read(name: &str) -> String { + let path = Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join(format!("../../spec/{name}")); + std::fs::read_to_string(&path).unwrap_or_else(|_| panic!("spec/{name} is part of the repository")) +} + +fn schema(text: &str) -> Schema { + let mut diags = Diagnostics::new(); + let schema = schema_from_yaml(text, &mut diags); + assert!(!diags.has_errors(), "spec/schema.yaml loads clean"); + schema +} + +/// One operation of `spec/kernel.yaml`. +struct Op { + primitive: String, + meaning: String, + keyword: String, + written_in: Option, +} + +fn kernel(text: &str) -> BTreeMap { + let doc = parse_yaml(text, Utf8Path::new("spec/kernel.yaml")).expect("spec/kernel.yaml parses"); + let ops = doc + .get("ops") + .and_then(Node::as_map) + .expect("spec/kernel.yaml states `ops:`"); + let text_at = |node: &Node, key: &str| node.get(key).and_then(Node::as_str).map(str::to_string); + ops.iter() + .map(|(name, entry)| { + let node = &entry.node; + ( + name.clone(), + Op { + primitive: text_at(node, "primitive").unwrap_or_default(), + meaning: text_at(node, "meaning").unwrap_or_default(), + keyword: text_at(node, "keyword").unwrap_or_default(), + written_in: text_at(node, "written-in"), + }, + ) + }) + .collect() +} + +/// Which fields name each operation, as `.`. +fn named_by(schema: &Schema) -> BTreeMap> { + let mut by: BTreeMap> = BTreeMap::new(); + for group in schema.groups() { + for field in &group.fields { + for op in &field.kernel_ops { + by.entry(op.clone()) + .or_default() + .insert(format!("{}.{}", group.name, field.name)); + } + } + } + by +} + +/// Everything wrong between the two files, one sentence each. +fn problems(schema: &Schema, ops: &BTreeMap) -> Vec { + let mut found = Vec::new(); + let by = named_by(schema); + for (op, fields) in &by { + if !ops.contains_key(op) { + found.push(format!( + "{} say they are `{op}`, and spec/kernel.yaml lists no such operation", + fields.iter().cloned().collect::>().join(", ") + )); + } + } + for (name, op) in ops { + if !by.contains_key(name) && op.written_in.is_none() { + found.push(format!( + "`{name}` is named by no field's `kernel-op:` and says nowhere else it is written (`written-in:`)" + )); + } + } + found +} + +#[test] +fn every_operation_says_its_primitive_what_it_does_and_its_keyword() { + let ops = kernel(&read("kernel.yaml")); + assert_eq!( + ops.len(), + 76, + "docs/97 §5.4 counts 76 operations; a change to that number is a change to the kernel and to the document" + ); + for (name, op) in &ops { + let prefix = PRIMITIVES + .iter() + .find(|(primitive, _)| *primitive == op.primitive) + .map(|(_, prefix)| *prefix) + .unwrap_or_else(|| panic!("`{name}` belongs to '{}', which is not one of the seven", op.primitive)); + assert!( + name.split_once('.').is_some_and(|(head, tail)| head == prefix && !tail.is_empty()), + "`{name}` is a {} operation and is named `{prefix}.`", + op.primitive + ); + assert!(!op.meaning.trim().is_empty(), "`{name}` says what it does"); + assert!(!op.keyword.trim().is_empty(), "`{name}` names the keyword that is it today"); + } +} + +#[test] +fn the_schema_and_the_kernel_agree_in_both_directions() { + let found = problems(&schema(&read("schema.yaml")), &kernel(&read("kernel.yaml"))); + assert!(found.is_empty(), "{}", found.join("\n")); +} + +#[test] +fn a_field_shared_through_an_anchor_carries_its_operation_everywhere_it_is_used() { + // `interface:` and `chosen-by:` are written once, on `knowledge`, and reused + // on `skill` and `tool` by anchor. The attribute travels with them. + let by = named_by(&schema(&read("schema.yaml"))); + let requirement = &by["actor.by-requirement"]; + for field in ["knowledge.interface", "skill.interface", "tool.interface", "tool.chosen-by"] { + assert!(requirement.contains(field), "{field} is `actor.by-requirement`"); + } + let pairs = by.values().map(BTreeSet::len).sum::(); + assert!( + pairs >= 340, + "the schema pairs a field with an operation {pairs} times; it did more than 340 times, \ + and a list that shrinks is one that stopped being kept" + ); +} + +#[test] +fn the_check_fails_on_a_name_nothing_lists_and_on_an_operation_nothing_writes() { + let schema_text = read("schema.yaml"); + let kernel_text = read("kernel.yaml"); + + // A field claiming an operation the kernel does not have. + let claimed = schema_text.replacen("kernel-op: store.fork", "kernel-op: store.spoon", 1); + assert_ne!(claimed, schema_text, "`stage.forks` is `store.fork` in the shipped schema"); + let found = problems(&schema(&claimed), &kernel(&kernel_text)); + assert!( + found.iter().any(|p| p.contains("stage.forks") && p.contains("`store.spoon`")), + "{found:?}" + ); + // ...which also leaves `store.fork` with nowhere it is written. + assert!(found.iter().any(|p| p.starts_with("`store.fork`")), "{found:?}"); + + // An operation whose only place was `written-in:`, with that line gone. + let unplaced = kernel_text.replacen( + " written-in: \"a shape line: `one of a, b`, wherever a field takes an answer shape\"\n", + "", + 1, + ); + assert_ne!(unplaced, kernel_text, "`shape.one-of` is written in a shape line"); + let found = problems(&schema(&schema_text), &kernel(&unplaced)); + assert_eq!( + found, + vec!["`shape.one-of` is named by no field's `kernel-op:` and says nowhere else it is written (`written-in:`)"] + ); +} diff --git a/docs/90-REVIEW.md b/docs/90-REVIEW.md index ddc1afe..73c9445 100644 --- a/docs/90-REVIEW.md +++ b/docs/90-REVIEW.md @@ -80,7 +80,7 @@ them and changes nothing. | | | |---|---| | Schema | **43 groups, 262 field entries, 207 distinct field names** (core 123, expert 69, both 15), 40 closed enums, 27 cross-reference constraints, **0 fields missing `tier:` or `surface:`** | -| Rust | 5 crates, **1648 tests**, `unsafe_code = forbid`, clippy-deny, edition 2024 | +| Rust | 5 crates, **1652 tests**, `unsafe_code = forbid`, clippy-deny, edition 2024 | | Python | **47 modules** (`find adapters/python/src -name "*.py" \| wc -l`), **3321 tests**, **nine transports** — seven model-bound (`anthropic`, `autogen`, `langchain`, `langgraph`, `ollama`, `openai_agents`, `pydantic_ai`), one bound to a remote *agent* (`a2a`), one deterministic mock; `_metering.py`, `_summarise.py` and `_tool_choice.py` are shared code the transports import, which is what the `_` says — offline by default | | TypeScript | 5 modules, 2,305 lines, a second port of **the stepping, the ceilings and the stage path** (`harness.ts:15-21`: *"AC-5.3's bar is the stage path"*) — **not** of the loop entire: no interceptor chain, no durable suspension record, no ledger (`notDoneHere`; a park returns `halted: "suspended"` and writes nothing down). This row is load-bearing for the Phase 3 pricing below (E4) | | CLI | **6 verbs**: `check`, `show`, `waits`, `discover`, `card`, `help` | diff --git a/docs/97-PRIMITIVES.md b/docs/97-PRIMITIVES.md index 8f7e376..fb33b36 100644 --- a/docs/97-PRIMITIVES.md +++ b/docs/97-PRIMITIVES.md @@ -911,7 +911,8 @@ script, which fails on the first one that does not hold: pattern in the snapshot is without one and no recipe is orphaned. 2. **Every construction is typed against the kernel.** Each recipe lists the operations it uses, and every operation must be one of the **76 operations - of the seven primitives** that the script defines in one place (Appendix C). + of the seven primitives** that `spec/kernel.yaml` lists in one place + (Appendix C). A recipe is therefore not free prose: it may use only moves the primitives have. The converse is also checked: every one of the 76 operations is used by at least one pattern, so the kernel carries no dead weight. @@ -1032,15 +1033,22 @@ uncounted. | Primitive | Keywords where it is the **main** ingredient | Keywords it takes **any** part in | |---|---|---| -| Actor | 120 | 165 | -| Flow (including handlers and triggers) | 111 | 140 | -| Store | 110 | 138 | -| Check | 75 | 114 | -| Allowance | 74 | 99 | -| Shape | 31 | 40 | -| Origin | 14 | 26 | +| Actor | 120 | 186 | +| Flow (including handlers and triggers) | 111 | 147 | +| Store | 110 | 143 | +| Check | 75 | 117 | +| Allowance | 74 | 100 | +| Shape | 31 | 50 | +| Origin | 14 | 27 | | none: words for people only (`description`, `because`, `name`) | 34 | 35 | +A keyword that is one of the 76 kernel operations says which in the schema: +`kernel-op:` on the field, naming an operation of `spec/kernel.yaml`. 295 +keywords carry one, and the script adds each operation's primitive to the +keyword's own, so the second column counts them; the first column is +unchanged by it. `scripts/pattern-coverage.py --ops` lists each operation's +fields. + Three readings of this table: - **Six of the seven are the main ingredient of 31 keywords or more.** A @@ -1603,7 +1611,8 @@ the owners of PACT rather than a step left undone. ## Appendix A: every keyword and its primitives Generated by `python3 scripts/primitive-coverage.py --table`. The main -ingredient is listed first. "Flow" includes handlers and triggers. "-" means +ingredient is listed first; the primitive of each kernel operation a keyword's +`kernel-op:` names follows the script's own. "Flow" includes handlers and triggers. "-" means words for a person, with no behaviour. | Keyword | Built from | @@ -1621,7 +1630,7 @@ words for a person, with no behaviour. | `workspace.shapes` | Shape | | `workspace.template-language` | Shape + Origin | | `workspace.time-zone` | Shape | -| `workspace.calendar` | Store | +| `workspace.calendar` | Store + Check | | `workspace.remembers` | Store | | `workspace.detectors` | Check + Shape | | `workspace.owners` | Allowance + Actor | @@ -1648,14 +1657,14 @@ words for a person, with no behaviour. | `workspace.reactions` | Flow + Actor | | `workspace.models` | Store + Actor | | `agent.name` | - | -| `agent.description` | - | +| `agent.description` | - + Shape | | `agent.base` | Store | -| `agent.instructions` | Actor | +| `agent.instructions` | Actor + Shape | | `agent.team` | Actor + Flow | | `agent.teamwork` | Flow + Allowance | | `agent.uses` | Allowance | -| `agent.accepts` | Shape | -| `agent.answers-with` | Shape | +| `agent.accepts` | Shape + Actor | +| `agent.answers-with` | Shape + Actor | | `agent.answers-with-mode` | Shape + Actor | | `agent.checked-by` | Check + Flow | | `agent.checks-at-most` | Allowance + Flow | @@ -1677,13 +1686,13 @@ words for a person, with no behaviour. | `agent.voice` | Flow + Actor | | `agent.turns` | Store | | `variant.when` | - | -| `variant.instructions` | Actor | -| `variant.says` | Actor | +| `variant.instructions` | Actor + Shape | +| `variant.says` | Actor + Shape | | `variant.steps-at-most` | Allowance | | `variant.may-use` | Allowance | | `variant.turns` | Store | | `voice.pipeline` | Flow + Actor | -| `voice.talking` | Flow | +| `voice.talking` | Flow + Shape | | `voice.hears-with` | Actor | | `voice.speaks-with` | Actor | | `voice.speaks-as` | Actor | @@ -1692,7 +1701,7 @@ words for a person, with no behaviour. | `voice.may-be-interrupted` | Flow | | `voice.keeps-the-audio` | Store + Origin | | `turn.from` | Origin + Store | -| `turn.says` | Store | +| `turn.says` | Store + Shape | | `turn.attaches` | Store + Shape | | `turn.calls` | Store + Actor | | `turn.with` | Store | @@ -1704,8 +1713,8 @@ words for a person, with no behaviour. | `program.description` | - | | `program.engine` | Actor | | `program.determinism` | Actor | -| `program.takes` | Shape | -| `program.answers-with` | Shape | +| `program.takes` | Shape + Actor | +| `program.answers-with` | Shape + Actor | | `program.fuel` | Allowance | | `program.body` | Actor + Store | | `program-fuel.instructions-at-most` | Allowance | @@ -1727,14 +1736,14 @@ words for a person, with no behaviour. | `model.also-known-as` | Store | | `model.served-by` | Actor + Allowance | | `model.capabilities` | Actor | -| `model.reasoning` | Store | -| `model.cost` | Store + Allowance | +| `model.reasoning` | Store + Actor | +| `model.cost` | Store + Allowance + Actor | | `model.kind` | Actor | -| `model.benchmarks` | Store | -| `model.speed` | Store | +| `model.benchmarks` | Store + Actor | +| `model.speed` | Store + Actor | | `served-by.runtime` | Actor | | `served-by.endpoint` | Actor + Allowance | -| `served-by.region` | Allowance + Store | +| `served-by.region` | Allowance + Store + Actor | | `model-can.computer-use` | Actor | | `model-can.tool-calling` | Actor | | `model-can.modality-in` | Shape + Actor | @@ -1748,8 +1757,8 @@ words for a person, with no behaviour. | `model-can.dimensions` | Shape + Actor | | `model-can.context-window` | Store + Actor | | `model-speed.first-words-within` | Store | -| `figure.value` | Store | -| `figure.provenance` | Store | +| `figure.value` | Store + Actor | +| `figure.provenance` | Store + Actor | | `provenance.source` | Store | | `provenance.harness` | Store | | `provenance.contamination` | Store | @@ -1765,11 +1774,11 @@ words for a person, with no behaviour. | `needs.computer-use` | Check | | `needs.documents` | Check | | `needs.video` | Check | -| `needs.prefer` | Check + Flow | -| `needs.accept-when` | Check | +| `needs.prefer` | Check + Flow + Actor | +| `needs.accept-when` | Check + Actor | | `needs.context-at-least` | Check | | `needs.because` | - | -| `needs.scores` | Check | +| `needs.scores` | Check + Actor | | `limits.feel` | Check | | `limits.finishes-within` | Allowance | | `limits.starts-from` | Allowance + Flow | @@ -1830,8 +1839,8 @@ words for a person, with no behaviour. | `knowledge.use-when` | Actor | | `knowledge.do-not-use-when` | Actor | | `knowledge.if-unsure` | Actor | -| `knowledge.embedded-by` | Actor | -| `knowledge.reranked-by` | Actor | +| `knowledge.embedded-by` | Actor + Store | +| `knowledge.reranked-by` | Actor + Store | | `knowledge.interface` | Actor | | `knowledge.chosen-by` | Actor + Check | | `knowledge.implements` | Actor | @@ -1850,11 +1859,11 @@ words for a person, with no behaviour. | `skill.chosen-by` | Actor + Check | | `skill.implements` | Actor | | `published.taken-at` | Store | -| `published.digest` | Store | +| `published.digest` | Store + Shape | | `published.server-version` | Store | | `published.instructions` | Origin + Store | | `published.tools` | Store | -| `published-tool.description` | Store | +| `published-tool.description` | Store + Origin | | `published-tool.takes` | Shape | | `published-tool.answers-with` | Shape | | `published-tool.hints` | Store | @@ -1881,8 +1890,8 @@ words for a person, with no behaviour. | `tool.unlisted-actions` | Allowance + Flow | | `action.description` | Actor | | `action.topics` | Origin | -| `action.takes` | Shape | -| `action.answers-with` | Shape | +| `action.takes` | Shape + Actor | +| `action.answers-with` | Shape + Actor | | `action.program` | Actor | | `action.reads-only` | Actor | | `action.needs-a-person` | Flow + Actor | @@ -1907,7 +1916,7 @@ words for a person, with no behaviour. | `question-rule.question` | Actor | | `question-rule.first-times` | Allowance + Store | | `when-this.tool` | Check + Flow | -| `when-this.value` | Check | +| `when-this.value` | Check + Store | | `when-this.arg` | Check | | `when-this.more-than` | Check | | `when-this.less-than` | Check | @@ -1918,16 +1927,16 @@ words for a person, with no behaviour. | `when-this.in-share` | Check + Shape | | `when-this.looks-like` | Check + Shape | | `when-this.is-empty` | Check | -| `comparand.value` | Check | +| `comparand.value` | Check + Store | | `comparand.now-plus` | Check | | `comparand.now-minus` | Check | | `question.description` | - | | `question.says` | Actor | | `question.answer` | Shape | -| `question.shows` | Origin + Actor | +| `question.shows` | Origin + Actor + Allowance | | `question.asked-of` | Actor | -| `question.answer-within` | Allowance + Flow | -| `question.answered-by` | Flow | +| `question.answer-within` | Allowance + Flow + Actor | +| `question.answered-by` | Flow + Actor | | `question.enough-is` | Flow + Check | | `question.asked-in` | Actor | | `question.urgency` | Actor | @@ -1935,7 +1944,7 @@ words for a person, with no behaviour. | `question.needs-signature` | Actor + Check | | `question.starts-with` | Store | | `question.ordered-by` | Flow | -| `question.counts-events-from` | Flow | +| `question.counts-events-from` | Flow + Store | | `question.only-when` | Flow + Check | | `question.reminds-at` | Flow | | `question.if-nobody-answers` | Flow | @@ -2008,7 +2017,7 @@ words for a person, with no behaviour. | `port.in-time-zone` | Shape | | `port.if-missed` | Flow | | `port.per-row-of` | Flow + Store | -| `port.answer-within` | Allowance | +| `port.answer-within` | Allowance + Actor | | `port.same-conversation-when` | Store | | `port.counts-as-the-same-for` | Store + Check | | `port.ordered-by` | Flow | @@ -2018,8 +2027,8 @@ words for a person, with no behaviour. | `port.remembers` | Store | | `workflow.description` | - | | `workflow.base` | Store | -| `workflow.accepts` | Shape | -| `workflow.answers-with` | Shape | +| `workflow.accepts` | Shape + Actor | +| `workflow.answers-with` | Shape + Actor | | `workflow.starts-at` | Flow | | `workflow.steps` | Flow | | `workflow.questions` | Actor + Shape | @@ -2031,12 +2040,12 @@ words for a person, with no behaviour. | `workflow.data-stays-in` | Origin + Allowance + Store | | `workflow.kept-for` | Store | | `workflow.undone-by` | Flow + Actor | -| `workflow.published-as` | Actor + Flow | +| `workflow.published-as` | Actor + Flow + Shape | | `workflow.release` | Flow + Actor + Check | | `moment.at` | Store | | `moment.after` | Shape | | `moment.before` | Shape | -| `moment.counted-in` | Shape + Store | +| `moment.counted-in` | Shape + Store + Check | | `moment.in-time-zone` | Shape | | `ownership.owned-by` | Allowance + Actor | | `ownership.locked` | Allowance | @@ -2049,7 +2058,7 @@ words for a person, with no behaviour. | `stage.does` | Flow + Actor | | `stage.forks` | Store + Flow | | `stage.keeps-taking` | Flow + Store | -| `stage.stops` | Flow | +| `stage.stops` | Flow + Shape | | `stage.says` | Actor | | `stage.asks` | Actor | | `stage.then` | Flow | @@ -2057,7 +2066,7 @@ words for a person, with no behaviour. | `stage.at-most` | Allowance | | `stage.fuel` | Allowance | | `stage.runs-in` | Actor | -| `stage.call` | Flow + Actor | +| `stage.call` | Flow + Actor + Shape | | `stage.may-call` | Allowance | | `stage.bind` | Flow + Store | | `stage.waits-for-result` | Flow | @@ -2067,7 +2076,7 @@ words for a person, with no behaviour. | `stage.over` | Flow + Store | | `stage.identified-by` | Store | | `stage.ordered-by` | Flow | -| `stage.combines-by` | Store | +| `stage.combines-by` | Store + Flow | | `stage.teamwork` | Flow + Allowance | | `stage.steps` | Flow | | `stage.starts-at` | Flow | @@ -2076,14 +2085,14 @@ words for a person, with no behaviour. | `stage.checked-by` | Check | | `stage.checks-at-most` | Allowance | | `stage.undone-by` | Flow + Actor | -| `stage.limits` | Allowance | +| `stage.limits` | Allowance + Flow | | `stage.if-it-fails` | Flow | -| `rung.rules` | Check | -| `rung.agent` | Actor | -| `rung.if-unsure-below` | Check | +| `rung.rules` | Check + Flow | +| `rung.agent` | Actor + Flow | +| `rung.if-unsure-below` | Check + Flow | | `rung.if-it-disagrees` | Flow + Check | -| `rung.asks` | Actor | -| `route-rule.when` | Check | +| `rung.asks` | Actor + Flow | +| `route-rule.when` | Check + Flow | | `route-rule.choose` | Flow | | `route-rule.because` | - | | `if-it-fails.after-that` | Flow | @@ -2160,7 +2169,7 @@ words for a person, with no behaviour. | `state.shared-with` | Store + Allowance | | `state.admitted-when` | Store + Check | | `state.admitted-by` | Store + Check + Actor | -| `state.never-from` | Origin + Allowance | +| `state.never-from` | Origin + Allowance + Check | | `state.survives-shortening` | Store | | `state.stops-being-true-when` | Store + Flow | | `bundle.description` | - | @@ -2428,83 +2437,83 @@ Generated by `python3 scripts/pattern-coverage.py --table` from the snapshot of ## Appendix C: the kernel, every operation of the seven -Generated by `python3 scripts/pattern-coverage.py --ops`. 76 operations. The last column names the PACT keyword that is this operation today; since 2026-11-10 every operation has one. - -| Operation | Primitive | What it does | PACT keyword today | -|---|---|---|---| -| `shape.scalar` | Shape | text, money, yes-or-no, number, date, duration, image, audio, file | accepts / answers-with / takes | -| `shape.one-of` | Shape | a closed set of words | `one of a, b` | -| `shape.record` | Shape | records, lists, maps, optional, a failed marker | `list of`, nested `accepts:` | -| `shape.stream` | Shape | an answer that arrives in parts, each part with a kind | answer shape `stream of `; the harness builds an answer only from `answer` parts (voice `talking: both-at-once` too) | -| `shape.reference` | Shape | a host-issued reference to an agent, run, stored value or Allowance | an `agent`-shaped answer as a `call:` binding, held to `may-call:` at check and run time | -| `shape.validate` | Shape | accept or refuse a value that arrived | answers-with-mode | -| `shape.describe` | Shape | tell a producer the expected form (JSON Schema, MCP inputSchema, a form) | schemas-from, published-tool | -| `shape.parse` | Shape | read a value out of loose text | answers-with-mode: prompted | -| `shape.render` | Shape | fill a template; nothing filled in is read as template again | template-language, `{{run-inputs.x}}` | -| `shape.digest` | Shape | a value's fingerprint | `in-share: ` in `when-this`; a person's yes bound to the digest of the request they saw | -| `label.origin` | Origin | customer, person, tool, retrieval, model, teammate, server, reviewed definition, clock | turn.from, never-from | -| `label.sensitivity` | Origin | a category such as card number or personal data, set by a detector or `hides` | hides, redaction | -| `label.region` | Origin | where a value may live | data-stays-in | -| `label.propagate` | Origin | anything computed from labelled values carries the combined label | `never-from:` checked statically (law 7) and again at run time on every memory write | -| `label.fence` | Origin | only reviewed text instructs; everything else reaches a model as quoted data | question.shows, server prose fenced | -| `label.placeholder` | Origin | a hidden value travels as a reference and is restored only for use-without-seeing | `gets-hidden-values: yes` on an action; hidden values travel as placeholders | -| `store.read` | Store | read a path, or as of a version | `input.x`, `steps.x.result`, `remembers.x` | -| `store.write` | Store | write through the merge rule | remember-as, remembers, writes-to | -| `store.merge` | Store | keep-all, keep-the-latest, merge, add-up, vote, top-n, or a pure program | combines-by | -| `store.claim` | Store | insert-if-absent or compare-and-swap, with an optional lease; take = claim then forget | same-request-key, `same-request-key-across: the-team | the-workspace`, `claim-lasts:` | -| `store.fork` | Store | a copy-on-write branch or snapshot, discarded unless joined out | `forks: yes` on an each, together, repeat or loop stage | -| `store.find` | Store | by key, words or meaning, reranked, at most N | knowledge: looked-up-by, passages-at-most, reranked-by | -| `store.forget` | Store | remove an entry or let it expire | kept-for, drop-parts, stops-being-true-when | -| `store.scope` | Store | step, turn, conversation, run, flow, team, workspace; one per key | lasts, kept-per, counted-per | -| `store.lifetime` | Store | until a moment or an event | kept-for, forget-after | -| `store.durability` | Store | none, checkpoint, durable-execution | workspace.durability | -| `store.version` | Store | every write may create a version with a digest | based-on: name@version, stage.version, tool-snapshot-digest | -| `store.pin` | Store | pinned entries survive tidying | always-keep, survives-shortening | -| `store.host` | Store | the conversation, run record, inbox, run registry, clock, directory, catalogue, definition | turns, population: past-runs, counts-events-from, if-still-running, every, asked-of, catalog, values | -| `actor.model` | Actor | decides by sampling | model, settings, needs | -| `actor.program` | Actor | decides by computing, under fuel | programs, run-code, engine, determinism | -| `actor.tool` | Actor | an outside system decides | tools, actions, resources, MCP | -| `actor.person` | Actor | a human answers; slow and authoritative | questions, asked-of, escalates-to | -| `actor.remote` | Actor | someone else's agent decides | resource-kind: a2a-agent | -| `actor.composite` | Actor | a named Flow with a signature | agents, workflows, published-as | -| `actor.signature` | Actor | what it takes and answers with | accepts, answers-with, takes | -| `actor.effect` | Actor | none, reads, writes, spends, irreversible; with undo and status-check; readable by a Check | computed from reads-only, spends-money, undone-by; Check-readable as `effect:` in `when-this`; shown by `pact discover` | -| `actor.write-set` | Actor | the Store paths an Actor declares it writes | `writes: [, ...]` on an action; overlapping branches are warned | -| `actor.park` | Actor | may wait on a person or an event | ask-someone, answer-within | -| `actor.figures` | Actor | cost, latency, context, capabilities, benchmark scores, with provenance | model-cost, model-speed, figure, provenance | -| `actor.identity` | Actor | who acted, on whose behalf, how strongly established | needs-signature, owned-by, not-the-same-as | -| `actor.location` | Actor | where it runs | served-by.region, runs-in | -| `actor.by-requirement` | Actor | chosen from a catalogue by a Check and a preference | needs, prefer, interface, implements, chosen-by | -| `actor.instantiate` | Actor | make an Actor from a definition value during a run, through the checker (law 8) | may-start: narrowed-new, run-code | -| `flow.ask` | Flow | ask one Actor with inputs bound from Stores; wait, or detach for a run reference | does: call / think / use-tools, call, bind | -| `flow.then` | Flow | every ask ends in a labelled outcome; the label picks the next step | then, outcome, starts-at | -| `flow.choose` | Flow | pick one label from a closed set by rungs: Checks, then Actors, then a person | does: decide, chooses-between, rung, route-rule, if-unsure-below | -| `flow.split` | Flow | fixed branches, one per item, one per stream part, or one per entry of a growing Store | does: each / together, over, teamwork.starts | -| `flow.join` | Flow | everyone, first, k of n passing a Check, deadline, or quiet; then cancel / ignore / detach the rest | waits-for, enough-is, gives-up-after, combines-by; growing sets: `keeps-taking: yes` on an each | -| `flow.await` | Flow | pause until a Check over Stores holds, with a correlation key and a deadline | heard, answer-within, counts-events-from, only-when | -| `flow.scope` | Flow | a region with its own Store fork, Allowance and meters | a stage's steps, team, isolated conversation, limits on a stage | -| `flow.handler` | Flow | before, each part, after, failure, ran-out: change, hold, divert, refuse, retry, back up, undo | interceptors, policies, if-it-fails, when-it-runs-out, context-policy, watch, redaction | -| `flow.starts-when` | Flow | a trigger: a Check over Stores becomes true; with a concurrency key and an overlap rule | ports, every, if-still-running, if-missed, per-row-of | -| `flow.cancel` | Flow | cancel a scope or a run by reference | if-someone-fails: stop-the-others; by reference: `does: stop` with `stops: steps..run` | -| `flow.detach` | Flow | start and get a run reference instead of waiting | waits-for-result: no | -| `flow.name` | Flow | a Flow with a signature is an Actor | agents, workflows, loops | -| `check.compare` | Check | more-than, less-than, is, is-one-of, contains-any-of, is-empty | when-this | -| `check.typed` | Check | money in its currency, durations, now-plus, business days | comparand, moment.counted-in | -| `check.detector` | Check | a named recogniser: built in (card number, email) or declared by the author | redaction.hide; declared: workspace `detectors:`, used as `looks-like:` and in sentences | -| `check.origin` | Check | a test on a value's Origin | never-from | -| `check.count` | Check | counts and order over the run record; distinct in the last k | must-call-before, `called more than n times` | -| `check.aggregate` | Check | shares, percentiles, means over a population | must-pass, metrics, threshold, measured-at | -| `check.combine` | Check | all, any, not | rules lists | -| `check.reachability` | Check | what a part can reach, directly or through others | available-when; `carries:` on a tool, and the three-part leak rule | -| `check.digest` | Check | compare two fingerprints | tool-snapshot-digest; request digests on approvals; `in-share:` | -| `allowance.reach` | Allowance | which Actors and addresses a part may ask | uses, may-use, may-call, allow-egress, who-can-reach-it | -| `allowance.see` | Allowance | who may read a labelled value | hides, question.shows, inspects | -| `allowance.use-without-seeing` | Allowance | pass a value along without reading it | credential-reference | -| `allowance.write` | Allowance | what a part may write, including to the definition | owners, locked, learning.needs-a-person-to-approve | -| `allowance.start` | Allowance | whether and how many helpers a part may start, and how deep | may-start, starts-at-most, nests-at-most | -| `allowance.quantity` | Allowance | steps, tool calls, tokens, money, time, items, attempts, fuel | limits, at-most, fuel, checks-at-most | -| `allowance.period` | Allowance | counted per scope and reset per period | counted-per, resets-every | -| `allowance.pass` | Allowance | handed down, never wider; totals conserved | divides-the-budget, shares | -| `allowance.revoke` | Allowance | taken back, expired, or withdrawn for a period | locked-until, tool-snapshot-max-age; for a period: `rests-after:` with `rests-for:` on a tool | -| `allowance.pause` | Allowance | a meter paused during a scope | paused-during | -| `allowance.value` | Allowance | a grant as a run-time value that can be split and passed | `cost-per-request-from:` a run input, never above `cost-per-request-under:` | +Generated by `python3 scripts/pattern-coverage.py --ops` from `spec/kernel.yaml` and the `kernel-op:` lines of `spec/schema.yaml`. 76 operations. The fourth column names the PACT keyword that is this operation today; since 2026-11-10 every operation has one. The last names the fields that are this operation; an operation written as syntax inside a value (`one of a, b`) has none and says where in `spec/kernel.yaml` (`written-in:`). + +| Operation | Primitive | What it does | PACT keyword today | Fields | +|---|---|---|---|---| +| `shape.scalar` | Shape | text, money, yes-or-no, number, date, duration, image, audio, file | accepts / answers-with / takes | `agent.accepts`, `agent.answers-with`, `agent.run-inputs`, `value.shape`, `program.takes`, `program.answers-with`, `action.takes`, `action.answers-with`, `question.answer`, `port.accepts`, `workflow.accepts`, `workflow.answers-with`, `state.shaped-like` | +| `shape.one-of` | Shape | a closed set of words | `one of a, b` | | +| `shape.record` | Shape | records, lists, maps, optional, a failed marker | `list of`, nested `accepts:` | `workspace.shapes`, `value.rows-are` | +| `shape.stream` | Shape | an answer that arrives in parts, each part with a kind | answer shape `stream of `; the harness builds an answer only from `answer` parts (voice `talking: both-at-once` too) | `voice.talking` | +| `shape.reference` | Shape | a host-issued reference to an agent, run, stored value or Allowance | an `agent`-shaped answer as a `call:` binding, held to `may-call:` at check and run time | `stage.stops`, `stage.call` | +| `shape.validate` | Shape | accept or refuse a value that arrived | answers-with-mode | `agent.answers-with-mode` | +| `shape.describe` | Shape | tell a producer the expected form (JSON Schema, MCP inputSchema, a form) | schemas-from, published-tool | `published-tool.takes`, `published-tool.answers-with`, `tool.schemas-from`, `workflow.published-as` | +| `shape.parse` | Shape | read a value out of loose text | answers-with-mode: prompted | `agent.answers-with-mode` | +| `shape.render` | Shape | fill a template; nothing filled in is read as template again | template-language, `{{run-inputs.x}}` | `workspace.template-language`, `agent.description`, `agent.instructions`, `variant.instructions`, `variant.says`, `turn.says` | +| `shape.digest` | Shape | a value's fingerprint | `in-share: ` in `when-this`; a person's yes bound to the digest of the request they saw | `published.digest`, `when-this.in-share` | +| `label.origin` | Origin | customer, person, tool, retrieval, model, teammate, server, reviewed definition, clock | turn.from, never-from | `turn.from`, `tool.carries` | +| `label.sensitivity` | Origin | a category such as card number or personal data, set by a detector or `hides` | hides, redaction | `workflow.hides`, `redaction.hide` | +| `label.region` | Origin | where a value may live | data-stays-in | `workflow.data-stays-in` | +| `label.propagate` | Origin | anything computed from labelled values carries the combined label | `never-from:` checked statically (law 7) and again at run time on every memory write | `state.never-from` | +| `label.fence` | Origin | only reviewed text instructs; everything else reaches a model as quoted data | question.shows, server prose fenced | `published.instructions`, `published-tool.description`, `question.shows` | +| `label.placeholder` | Origin | a hidden value travels as a reference and is restored only for use-without-seeing | `gets-hidden-values: yes` on an action; hidden values travel as placeholders | `action.gets-hidden-values` | +| `store.read` | Store | read a path, or as of a version | `input.x`, `steps.x.result`, `remembers.x` | `when-this.value`, `comparand.value`, `stage.over` | +| `store.write` | Store | write through the merge rule | remember-as, remembers, writes-to | `workspace.remembers`, `agent.remembers`, `action.remember-as`, `port.remembers`, `workflow.remembers`, `stage.remembers`, `watch.writes-to` | +| `store.merge` | Store | keep-all, keep-the-latest, merge, add-up, vote, top-n, or a pure program | combines-by | `stage.combines-by`, `state.combines-by` | +| `store.claim` | Store | insert-if-absent or compare-and-swap, with an optional lease; take = claim then forget | same-request-key, `same-request-key-across: the-team | the-workspace`, `claim-lasts:` | `action.same-request-key`, `action.same-request-key-across`, `action.claim-lasts` | +| `store.fork` | Store | a copy-on-write branch or snapshot, discarded unless joined out | `forks: yes` on an each, together, repeat or loop stage | `stage.forks` | +| `store.find` | Store | by key, words or meaning, reranked, at most N | knowledge: looked-up-by, passages-at-most, reranked-by | `knowledge.looked-up-by`, `knowledge.split-by`, `knowledge.passages-at-most`, `knowledge.embedded-by`, `knowledge.reranked-by` | +| `store.forget` | Store | remove an entry or let it expire | kept-for, drop-parts, stops-being-true-when | `workflow.kept-for`, `tidy-step.what`, `state.forget-after`, `state.kept-for`, `state.stops-being-true-when` | +| `store.scope` | Store | step, turn, conversation, run, flow, team, workspace; one per key | lasts, kept-per, counted-per | `limits.counted-per`, `state.lasts`, `state.kept-per`, `state.shared-with` | +| `store.lifetime` | Store | until a moment or an event | kept-for, forget-after | `workflow.kept-for`, `state.forget-after`, `state.kept-for` | +| `store.durability` | Store | none, checkpoint, durable-execution | workspace.durability | `workspace.durability` | +| `store.version` | Store | every write may create a version with a digest | based-on: name@version, stage.version, tool-snapshot-digest | `resource.tool-snapshot-digest`, `stage.version`, `bundle.version` | +| `store.pin` | Store | pinned entries survive tidying | always-keep, survives-shortening | `context-policy.always-keep`, `state.survives-shortening` | +| `store.host` | Store | the conversation, run record, inbox, run registry, clock, directory, catalogue, definition | turns, population: past-runs, counts-events-from, if-still-running, every, asked-of, catalog, values | `workspace.calendar`, `workspace.values`, `workspace.models`, `agent.turns`, `variant.turns`, `question.counts-events-from`, `evals.population` | +| `actor.model` | Actor | decides by sampling | model, settings, needs | `workspace.models`, `agent.needs`, `agent.settings`, `agent.model`, `agent.model-for-checking`, `voice.hears-with`, `voice.speaks-with`, `catalog.default`, `catalog.models`, `knowledge.embedded-by`, `knowledge.reranked-by`, `evals.graded-by`, `learning-model.model`, `context-policy.summarised-by` | +| `actor.program` | Actor | decides by computing, under fuel | programs, run-code, engine, determinism | `workspace.programs`, `program.engine`, `program.determinism`, `program.body`, `resource.engines`, `action.program`, `action.projects-with`, `question.checked-by`, `eval-rule.program`, `stage.runs-in`, `outcome.decided-by` | +| `actor.tool` | Actor | an outside system decides | tools, actions, resources, MCP | `workspace.tools`, `workspace.resources`, `resource.resource-kind`, `resource.endpoint`, `tool.connect`, `tool.url`, `tool.method`, `tool.actions`, `action.calls` | +| `actor.person` | Actor | a human answers; slow and authoritative | questions, asked-of, escalates-to | `workspace.questions`, `limits.asks`, `resource.asks-to-run`, `resource.asks-to-connect`, `resource.asks-when-it-changes`, `question-rule.question`, `question.asked-of`, `question.answered-by`, `question.escalates-to`, `workflow.questions`, `release.asks`, `stage.asks`, `rung.asks`, `if-it-fails.asks`, `context-policy.asks`, `teamwork.asks` | +| `actor.remote` | Actor | someone else's agent decides | resource-kind: a2a-agent | `resource.resource-kind` | +| `actor.composite` | Actor | a named Flow with a signature | agents, workflows, published-as | `workspace.agents`, `workspace.workflows`, `agent.team`, `workflow.published-as` | +| `actor.signature` | Actor | what it takes and answers with | accepts, answers-with, takes | `agent.accepts`, `agent.answers-with`, `program.takes`, `program.answers-with`, `action.takes`, `action.answers-with`, `workflow.accepts`, `workflow.answers-with` | +| `actor.effect` | Actor | none, reads, writes, spends, irreversible; with undo and status-check; readable by a Check | computed from reads-only, spends-money, undone-by; Check-readable as `effect:` in `when-this`; shown by `pact discover` | `action.reads-only`, `action.spends-money`, `action.undone-by`, `action.status-check`, `when-this.effect`, `workflow.undone-by`, `stage.undone-by` | +| `actor.write-set` | Actor | the Store paths an Actor declares it writes | `writes: [, ...]` on an action; overlapping branches are warned | `action.writes` | +| `actor.park` | Actor | may wait on a person or an event | ask-someone, answer-within | `question.answer-within`, `port.answer-within`, `stage.does` | +| `actor.figures` | Actor | cost, latency, context, capabilities, benchmark scores, with provenance | model-cost, model-speed, figure, provenance | `model.reasoning`, `model.cost`, `model.benchmarks`, `model.speed`, `model-can.context-window`, `figure.value`, `figure.provenance` | +| `actor.identity` | Actor | who acted, on whose behalf, how strongly established | needs-signature, owned-by, not-the-same-as | `question.not-the-same-as`, `question.needs-signature`, `ownership.owned-by` | +| `actor.location` | Actor | where it runs | served-by.region, runs-in | `served-by.endpoint`, `served-by.region`, `stage.runs-in` | +| `actor.by-requirement` | Actor | chosen from a catalogue by a Check and a preference | needs, prefer, interface, implements, chosen-by | `agent.needs`, `needs.prefer`, `needs.accept-when`, `needs.scores`, `knowledge.interface`, `knowledge.chosen-by`, `knowledge.implements`, `skill.interface`, `skill.chosen-by`, `skill.implements`, `tool.interface`, `tool.chosen-by`, `tool.implements` | +| `actor.instantiate` | Actor | make an Actor from a definition value during a run, through the checker (law 8) | may-start: narrowed-new, run-code | `stage.does`, `teamwork.may-start` | +| `flow.ask` | Flow | ask one Actor with inputs bound from Stores; wait, or detach for a run reference | does: call / think / use-tools, call, bind | `stage.does`, `stage.call`, `stage.bind` | +| `flow.then` | Flow | every ask ends in a labelled outcome; the label picks the next step | then, outcome, starts-at | `workflow.starts-at`, `loop.starts-at`, `stage.then`, `stage.starts-at`, `outcome.used-a-tool`, `outcome.answered`, `outcome.too-many-times`, `outcome.declined`, `outcome.nobody-answered`, `outcome.decided-by`, `outcome.may-go-to` | +| `flow.choose` | Flow | pick one label from a closed set by rungs: Checks, then Actors, then a person | does: decide, chooses-between, rung, route-rule, if-unsure-below | `stage.does`, `stage.chooses-between`, `stage.by`, `rung.rules`, `rung.agent`, `rung.if-unsure-below`, `rung.if-it-disagrees`, `rung.asks`, `route-rule.when`, `route-rule.choose` | +| `flow.split` | Flow | fixed branches, one per item, one per stream part, or one per entry of a growing Store | does: each / together, over, teamwork.starts | `agent.team`, `stage.does`, `stage.over`, `teamwork.starts` | +| `flow.join` | Flow | everyone, first, k of n passing a Check, deadline, or quiet; then cancel / ignore / detach the rest | waits-for, enough-is, gives-up-after, combines-by; growing sets: `keeps-taking: yes` on an each | `question.answered-by`, `stage.keeps-taking`, `stage.combines-by`, `teamwork.waits-for`, `teamwork.enough-is`, `teamwork.gives-up-after` | +| `flow.await` | Flow | pause until a Check over Stores holds, with a correlation key and a deadline | heard, answer-within, counts-events-from, only-when | `question.answer-within`, `question.counts-events-from`, `question.only-when`, `outcome.heard` | +| `flow.scope` | Flow | a region with its own Store fork, Allowance and meters | a stage's steps, team, isolated conversation, limits on a stage | `stage.steps`, `stage.limits` | +| `flow.handler` | Flow | before, each part, after, failure, ran-out: change, hold, divert, refuse, retry, back up, undo | interceptors, policies, if-it-fails, when-it-runs-out, context-policy, watch, redaction | `workspace.policies`, `workspace.redaction`, `workspace.context-policies`, `workspace.interceptors`, `workspace.watch`, `agent.policy`, `agent.context-policy`, `agent.interceptors`, `program-fuel.when-it-runs-out`, `limits.when-it-runs-out`, `workflow.policy`, `stage.if-it-fails`, `if-it-fails.after-that`, `interceptor.when`, `interceptor.rules`, `watch.when` | +| `flow.starts-when` | Flow | a trigger: a Check over Stores becomes true; with a concurrency key and an overlap rule | ports, every, if-still-running, if-missed, per-row-of | `workspace.ports`, `port.kind`, `port.every`, `port.if-still-running`, `port.if-missed`, `port.per-row-of` | +| `flow.cancel` | Flow | cancel a scope or a run by reference | if-someone-fails: stop-the-others; by reference: `does: stop` with `stops: steps..run` | `stage.does`, `stage.stops`, `teamwork.if-someone-fails` | +| `flow.detach` | Flow | start and get a run reference instead of waiting | waits-for-result: no | `reminder.runs`, `port.then-run`, `stage.waits-for-result` | +| `flow.name` | Flow | a Flow with a signature is an Actor | agents, workflows, loops | `workspace.agents`, `workspace.workflows`, `workspace.loops`, `agent.loop` | +| `check.compare` | Check | more-than, less-than, is, is-one-of, contains-any-of, is-empty | when-this | `when-this.more-than`, `when-this.less-than`, `when-this.is`, `when-this.is-one-of`, `when-this.contains-any-of`, `when-this.effect`, `when-this.is-empty` | +| `check.typed` | Check | money in its currency, durations, now-plus, business days | comparand, moment.counted-in | `workspace.calendar`, `comparand.now-plus`, `comparand.now-minus`, `moment.counted-in` | +| `check.detector` | Check | a named recogniser: built in (card number, email) or declared by the author | redaction.hide; declared: workspace `detectors:`, used as `looks-like:` and in sentences | `workspace.detectors`, `detector.looks-like`, `detector.examples`, `when-this.looks-like`, `redaction.hide` | +| `check.origin` | Check | a test on a value's Origin | never-from | `state.never-from` | +| `check.count` | Check | counts and order over the run record; distinct in the last k | must-call-before, `called more than n times` | `eval-rule.must-call-before`, `event.more-than`, `reaction.more-than` | +| `check.aggregate` | Check | shares, percentiles, means over a population | must-pass, metrics, threshold, measured-at | `limits.measured-at`, `evals.must-pass`, `evals.metrics`, `metric.threshold` | +| `check.combine` | Check | all, any, not | rules lists | `question-rule.when`, `question.only-when`, `port.only-when`, `stage.until`, `route-rule.when`, `state.admitted-when` | +| `check.reachability` | Check | what a part can reach, directly or through others | available-when; `carries:` on a tool, and the three-part leak rule | `resource.available-when`, `skill.available-when`, `tool.available-when`, `tool.carries` | +| `check.digest` | Check | compare two fingerprints | tool-snapshot-digest; request digests on approvals; `in-share:` | `resource.tool-snapshot-digest`, `when-this.in-share` | +| `allowance.reach` | Allowance | which Actors and addresses a part may ask | uses, may-use, may-call, allow-egress, who-can-reach-it | `workspace.allow-egress`, `agent.uses`, `variant.may-use`, `tool.actions`, `tool.unlisted-actions`, `port.who-can-reach-it`, `stage.may-use`, `stage.may-call` | +| `allowance.see` | Allowance | who may read a labelled value | hides, question.shows, inspects | `action.inspects`, `question.shows`, `workflow.hides` | +| `allowance.use-without-seeing` | Allowance | pass a value along without reading it | credential-reference | `resource.auth`, `credential-reference.by-reference`, `port.signed-with` | +| `allowance.write` | Allowance | what a part may write, including to the definition | owners, locked, learning.needs-a-person-to-approve | `workspace.owners`, `learning.needs-a-person-to-approve`, `ownership.owned-by`, `ownership.locked` | +| `allowance.start` | Allowance | whether and how many helpers a part may start, and how deep | may-start, starts-at-most, nests-at-most | `limits.starts-at-most`, `limits.nests-at-most`, `teamwork.may-start` | +| `allowance.quantity` | Allowance | steps, tool calls, tokens, money, time, items, attempts, fuel | limits, at-most, fuel, checks-at-most | `agent.checks-at-most`, `agent.limits`, `variant.steps-at-most`, `program.fuel`, `limits.finishes-within`, `limits.cost-per-request-under`, `limits.steps-at-most`, `limits.tool-calls-at-most`, `limits.runs-for-at-most`, `limits.tokens-at-most`, `limits.asks-itself-at-most`, `limits.items-at-most`, `limits.at-once-at-most`, `workflow.limits`, `stage.at-most`, `stage.fuel`, `stage.checks-at-most`, `stage.limits` | +| `allowance.period` | Allowance | counted per scope and reset per period | counted-per, resets-every | `limits.counted-per`, `limits.resets-every` | +| `allowance.pass` | Allowance | handed down, never wider; totals conserved | divides-the-budget, shares | `teamwork.divides-the-budget`, `teamwork.shares` | +| `allowance.revoke` | Allowance | taken back, expired, or withdrawn for a period | locked-until, tool-snapshot-max-age; for a period: `rests-after:` with `rests-for:` on a tool | `resource.tool-snapshot-max-age`, `tool.rests-after`, `tool.rests-for`, `ownership.locked-until` | +| `allowance.pause` | Allowance | a meter paused during a scope | paused-during | `limits.paused-during` | +| `allowance.value` | Allowance | a grant as a run-time value that can be split and passed | `cost-per-request-from:` a run input, never above `cost-per-request-under:` | `limits.cost-per-request-from` | diff --git a/scripts/pattern-coverage.py b/scripts/pattern-coverage.py index 40266a6..5258025 100644 --- a/scripts/pattern-coverage.py +++ b/scripts/pattern-coverage.py @@ -35,100 +35,20 @@ 3. Every operation in OPS is used by at least one pattern. An operation no pattern needs would be a candidate for removal. -OPS also records, for each operation, the PACT keyword that is that operation -today, or "proposed" with the keyword section 9 suggests. `--ops` prints it. +OPS is read from spec/kernel.yaml, which also records, for each operation, the +PACT keyword that is that operation today. `--ops` prints it, with the fields +of spec/schema.yaml whose `kernel-op:` names it. `--check-shipped` runs `pact check` on every shipped example a recipe cites. """ import json, sys, collections, pathlib +import yaml SNAPSHOT = pathlib.Path(__file__).resolve().parent.parent / "research/extracts/agentic-patterns-2026-11-10.json" -# The kernel: every operation of the seven primitives. id: (primitive, meaning, keyword today | "proposed: ...") -OPS = { - # Shape - "shape.scalar": ("Shape", "text, money, yes-or-no, number, date, duration, image, audio, file", "accepts / answers-with / takes"), - "shape.one-of": ("Shape", "a closed set of words", "`one of a, b`"), - "shape.record": ("Shape", "records, lists, maps, optional, a failed marker", "`list of`, nested `accepts:`"), - "shape.stream": ("Shape", "an answer that arrives in parts, each part with a kind", "answer shape `stream of `; the harness builds an answer only from `answer` parts (voice `talking: both-at-once` too)"), - "shape.reference": ("Shape", "a host-issued reference to an agent, run, stored value or Allowance", "an `agent`-shaped answer as a `call:` binding, held to `may-call:` at check and run time"), - "shape.validate": ("Shape", "accept or refuse a value that arrived", "answers-with-mode"), - "shape.describe": ("Shape", "tell a producer the expected form (JSON Schema, MCP inputSchema, a form)", "schemas-from, published-tool"), - "shape.parse": ("Shape", "read a value out of loose text", "answers-with-mode: prompted"), - "shape.render": ("Shape", "fill a template; nothing filled in is read as template again", "template-language, `{{run-inputs.x}}`"), - "shape.digest": ("Shape", "a value's fingerprint", "`in-share: ` in `when-this`; a person's yes bound to the digest of the request they saw"), - # Origin - "label.origin": ("Origin", "customer, person, tool, retrieval, model, teammate, server, reviewed definition, clock", "turn.from, never-from"), - "label.sensitivity": ("Origin", "a category such as card number or personal data, set by a detector or `hides`", "hides, redaction"), - "label.region": ("Origin", "where a value may live", "data-stays-in"), - "label.propagate": ("Origin", "anything computed from labelled values carries the combined label", "`never-from:` checked statically (law 7) and again at run time on every memory write"), - "label.fence": ("Origin", "only reviewed text instructs; everything else reaches a model as quoted data", "question.shows, server prose fenced"), - "label.placeholder": ("Origin", "a hidden value travels as a reference and is restored only for use-without-seeing", "`gets-hidden-values: yes` on an action; hidden values travel as placeholders"), - # Store - "store.read": ("Store", "read a path, or as of a version", "`input.x`, `steps.x.result`, `remembers.x`"), - "store.write": ("Store", "write through the merge rule", "remember-as, remembers, writes-to"), - "store.merge": ("Store", "keep-all, keep-the-latest, merge, add-up, vote, top-n, or a pure program", "combines-by"), - "store.claim": ("Store", "insert-if-absent or compare-and-swap, with an optional lease; take = claim then forget", "same-request-key, `same-request-key-across: the-team | the-workspace`, `claim-lasts:`"), - "store.fork": ("Store", "a copy-on-write branch or snapshot, discarded unless joined out", "`forks: yes` on an each, together, repeat or loop stage"), - "store.find": ("Store", "by key, words or meaning, reranked, at most N", "knowledge: looked-up-by, passages-at-most, reranked-by"), - "store.forget": ("Store", "remove an entry or let it expire", "kept-for, drop-parts, stops-being-true-when"), - "store.scope": ("Store", "step, turn, conversation, run, flow, team, workspace; one per key", "lasts, kept-per, counted-per"), - "store.lifetime": ("Store", "until a moment or an event", "kept-for, forget-after"), - "store.durability": ("Store", "none, checkpoint, durable-execution", "workspace.durability"), - "store.version": ("Store", "every write may create a version with a digest", "based-on: name@version, stage.version, tool-snapshot-digest"), - "store.pin": ("Store", "pinned entries survive tidying", "always-keep, survives-shortening"), - "store.host": ("Store", "the conversation, run record, inbox, run registry, clock, directory, catalogue, definition", "turns, population: past-runs, counts-events-from, if-still-running, every, asked-of, catalog, values"), - # Actor - "actor.model": ("Actor", "decides by sampling", "model, settings, needs"), - "actor.program": ("Actor", "decides by computing, under fuel", "programs, run-code, engine, determinism"), - "actor.tool": ("Actor", "an outside system decides", "tools, actions, resources, MCP"), - "actor.person": ("Actor", "a human answers; slow and authoritative", "questions, asked-of, escalates-to"), - "actor.remote": ("Actor", "someone else's agent decides", "resource-kind: a2a-agent"), - "actor.composite": ("Actor", "a named Flow with a signature", "agents, workflows, published-as"), - "actor.signature": ("Actor", "what it takes and answers with", "accepts, answers-with, takes"), - "actor.effect": ("Actor", "none, reads, writes, spends, irreversible; with undo and status-check; readable by a Check", "computed from reads-only, spends-money, undone-by; Check-readable as `effect:` in `when-this`; shown by `pact discover`"), - "actor.write-set": ("Actor", "the Store paths an Actor declares it writes", "`writes: [, ...]` on an action; overlapping branches are warned"), - "actor.park": ("Actor", "may wait on a person or an event", "ask-someone, answer-within"), - "actor.figures": ("Actor", "cost, latency, context, capabilities, benchmark scores, with provenance", "model-cost, model-speed, figure, provenance"), - "actor.identity": ("Actor", "who acted, on whose behalf, how strongly established", "needs-signature, owned-by, not-the-same-as"), - "actor.location": ("Actor", "where it runs", "served-by.region, runs-in"), - "actor.by-requirement":("Actor", "chosen from a catalogue by a Check and a preference", "needs, prefer, interface, implements, chosen-by"), - "actor.instantiate": ("Actor", "make an Actor from a definition value during a run, through the checker (law 8)", "may-start: narrowed-new, run-code"), - # Flow - "flow.ask": ("Flow", "ask one Actor with inputs bound from Stores; wait, or detach for a run reference", "does: call / think / use-tools, call, bind"), - "flow.then": ("Flow", "every ask ends in a labelled outcome; the label picks the next step", "then, outcome, starts-at"), - "flow.choose": ("Flow", "pick one label from a closed set by rungs: Checks, then Actors, then a person", "does: decide, chooses-between, rung, route-rule, if-unsure-below"), - "flow.split": ("Flow", "fixed branches, one per item, one per stream part, or one per entry of a growing Store", "does: each / together, over, teamwork.starts"), - "flow.join": ("Flow", "everyone, first, k of n passing a Check, deadline, or quiet; then cancel / ignore / detach the rest", "waits-for, enough-is, gives-up-after, combines-by; growing sets: `keeps-taking: yes` on an each"), - "flow.await": ("Flow", "pause until a Check over Stores holds, with a correlation key and a deadline", "heard, answer-within, counts-events-from, only-when"), - "flow.scope": ("Flow", "a region with its own Store fork, Allowance and meters", "a stage's steps, team, isolated conversation, limits on a stage"), - "flow.handler": ("Flow", "before, each part, after, failure, ran-out: change, hold, divert, refuse, retry, back up, undo", "interceptors, policies, if-it-fails, when-it-runs-out, context-policy, watch, redaction"), - "flow.starts-when": ("Flow", "a trigger: a Check over Stores becomes true; with a concurrency key and an overlap rule", "ports, every, if-still-running, if-missed, per-row-of"), - "flow.cancel": ("Flow", "cancel a scope or a run by reference", "if-someone-fails: stop-the-others; by reference: `does: stop` with `stops: steps..run`"), - "flow.detach": ("Flow", "start and get a run reference instead of waiting", "waits-for-result: no"), - "flow.name": ("Flow", "a Flow with a signature is an Actor", "agents, workflows, loops"), - # Check - "check.compare": ("Check", "more-than, less-than, is, is-one-of, contains-any-of, is-empty", "when-this"), - "check.typed": ("Check", "money in its currency, durations, now-plus, business days", "comparand, moment.counted-in"), - "check.detector": ("Check", "a named recogniser: built in (card number, email) or declared by the author", "redaction.hide; declared: workspace `detectors:`, used as `looks-like:` and in sentences"), - "check.origin": ("Check", "a test on a value's Origin", "never-from"), - "check.count": ("Check", "counts and order over the run record; distinct in the last k", "must-call-before, `called more than n times`"), - "check.aggregate": ("Check", "shares, percentiles, means over a population", "must-pass, metrics, threshold, measured-at"), - "check.combine": ("Check", "all, any, not", "rules lists"), - "check.reachability": ("Check", "what a part can reach, directly or through others", "available-when; `carries:` on a tool, and the three-part leak rule"), - "check.digest": ("Check", "compare two fingerprints", "tool-snapshot-digest; request digests on approvals; `in-share:`"), - # Allowance - "allowance.reach": ("Allowance", "which Actors and addresses a part may ask", "uses, may-use, may-call, allow-egress, who-can-reach-it"), - "allowance.see": ("Allowance", "who may read a labelled value", "hides, question.shows, inspects"), - "allowance.use-without-seeing": ("Allowance", "pass a value along without reading it", "credential-reference"), - "allowance.write": ("Allowance", "what a part may write, including to the definition", "owners, locked, learning.needs-a-person-to-approve"), - "allowance.start": ("Allowance", "whether and how many helpers a part may start, and how deep", "may-start, starts-at-most, nests-at-most"), - "allowance.quantity": ("Allowance", "steps, tool calls, tokens, money, time, items, attempts, fuel", "limits, at-most, fuel, checks-at-most"), - "allowance.period": ("Allowance", "counted per scope and reset per period", "counted-per, resets-every"), - "allowance.pass": ("Allowance", "handed down, never wider; totals conserved", "divides-the-budget, shares"), - "allowance.revoke": ("Allowance", "taken back, expired, or withdrawn for a period", "locked-until, tool-snapshot-max-age; for a period: `rests-after:` with `rests-for:` on a tool"), - "allowance.pause": ("Allowance", "a meter paused during a scope", "paused-during"), - "allowance.value": ("Allowance", "a grant as a run-time value that can be split and passed", "`cost-per-request-from:` a run input, never above `cost-per-request-under:`"), -} +# The kernel: every operation of the seven primitives, read from spec/kernel.yaml (one copy, which +# the schema's `kernel-op:` lines are held to). id: (primitive, meaning, keyword today) +OPS = {op: (e["primitive"], e["meaning"], e["keyword"]) + for op, e in yaml.safe_load((SNAPSHOT.parent.parent.parent / "spec/kernel.yaml").read_text())["ops"].items()} M, T, P, PACT = "mechanism", "training", "practice", "pact" @@ -853,8 +773,14 @@ def main(argv): for p in pats: bycat[p["category"]][R[p["slug"]][0]] += 1 for c in sorted(bycat): print(f" {c}: {dict(bycat[c])}") if "--ops" in argv: - print("\n| Operation | Primitive | What it does | PACT keyword today |\n|---|---|---|---|") - for k,(prim,what,kw) in OPS.items(): print(f"| `{k}` | {prim} | {what} | {kw} |") + fields = collections.defaultdict(list) + schema = yaml.safe_load((SNAPSHOT.parent.parent.parent / "spec/schema.yaml").read_text()) + for g, gv in schema["groups"].items(): + for f, fv in (gv.get("fields") or {}).items(): + ops = fv.get("kernel-op", []) if isinstance(fv, dict) else [] + for op in [ops] if isinstance(ops, str) else ops: fields[op].append(f"{g}.{f}") + print("\n| Operation | Primitive | What it does | PACT keyword today | Fields |\n|---|---|---|---|---|") + for k,(prim,what,kw) in OPS.items(): print(f"| `{k}` | {prim} | {what} | {kw} | {', '.join(f'`{x}`' for x in fields[k])} |") if "--check-shipped" in argv: import subprocess, shutil root = SNAPSHOT.parent.parent.parent diff --git a/scripts/primitive-coverage.py b/scripts/primitive-coverage.py index 3bcc7ac..729df46 100755 --- a/scripts/primitive-coverage.py +++ b/scripts/primitive-coverage.py @@ -17,6 +17,10 @@ runs out, and so on). Each word is a feature of its own, so each is mapped too. A word inherits its field's primitives unless CH below says otherwise. +A keyword that IS a kernel operation says so in the schema (`kernel-op:`, the +operations listed in spec/kernel.yaml), and the primitive of each operation it +names is added after the ones G gives it. The main ingredient stays G's. + "-" means the keyword has no behaviour: words written for a person (a description, a because, a name). "Flow" includes handlers and triggers, which is where hooks, interceptors, policies, ports and failure plans live. @@ -172,13 +176,18 @@ "rung.if-it-disagrees": {"keep-its-answer":[F],"go-on":[F,C]}, } d=yaml.safe_load(open('spec/schema.yaml'))['groups'] +KERNEL=yaml.safe_load(open('spec/kernel.yaml'))['ops'] +def with_ops(fv, ps): + """A keyword's primitives: the table's, then the primitive of every kernel operation its `kernel-op:` names.""" + ops=fv.get('kernel-op',[]) if isinstance(fv,dict) else [] + return list(dict.fromkeys(list(ps)+[KERNEL[o]['primitive'] for o in ([ops] if isinstance(ops,str) else ops)])) missing=[g for g in d if g not in G]; extra=[g for g in G if g not in d] assert not missing and not extra, (missing, extra) prim=collections.Counter(); first=collections.Counter(); rows=[] for g,gv in d.items(): dflt,ov=G[g] - for k in (gv.get('fields') or {}): - ps=ov.get(k,dflt); rows.append((g,k,ps)); first[ps[0]]+=1 + for k,fv in (gv.get('fields') or {}).items(): + ps=with_ops(fv,ov.get(k,dflt)); rows.append((g,k,ps)); first[ps[0]]+=1 for p in dict.fromkeys(ps): prim[p]+=1 bad=[r for r in rows if not r[2]] print("keywords mapped:",len(rows),"unmapped:",len(bad)) @@ -193,7 +202,7 @@ for g,gv in d.items(): for k,fv in (gv.get('fields') or {}).items(): for c in (fv.get('choices') or []): - ov=CH.get(f"{g}.{k}",{}); ps=ov.get(str(c), G[g][1].get(k,G[g][0])) + ov=CH.get(f"{g}.{k}",{}); ps=ov.get(str(c), with_ops(fv,G[g][1].get(k,G[g][0]))) crows.append((g,k,str(c),ps)); cfirst[ps[0]]+=1 stale=[(f,c) for f,ov in CH.items() for c in ov if str(c) not in [str(x) for x in (d[f.split(".")[0]]['fields'][f.split(".")[1]].get('choices') or [])]] assert not stale, stale diff --git a/site-docs/index.md b/site-docs/index.md index 7b3a051..c9b4fa3 100644 --- a/site-docs/index.md +++ b/site-docs/index.md @@ -47,7 +47,7 @@ so plainly rather than leaving it to be discovered. | | | |---|---| -| Tests | **4,969** — 1648 Rust, 3321 adapter | +| Tests | **4,973** — 1652 Rust, 3321 adapter | | Schema kinds | **68** | | Framework targets | **7**, over 2 runtimes | | Eve capabilities accounted for | **101 of 101** | diff --git a/site-docs/status/verified.md b/site-docs/status/verified.md index 26741d7..3e4a470 100644 --- a/site-docs/status/verified.md +++ b/site-docs/status/verified.md @@ -12,9 +12,9 @@ from an earlier document. | Claim | Value | How to check | |---|---|---| -| Rust tests | **1648** | `cargo test --workspace` | +| Rust tests | **1652** | `cargo test --workspace` | | Adapter tests | **3321** | `cd adapters/python && uv run pytest tests/ -q` | -| Total | **4,969** | both of the above | +| Total | **4,973** | both of the above | | TypeScript type-checks | clean | `cd adapters/typescript && npx tsc --noEmit` | | Lints | clean | `cargo clippy --all-targets` | | Worked example loads | 498 settings | `cargo run -p pact-cli -- check examples/refund-desk` | diff --git a/spec/kernel.yaml b/spec/kernel.yaml new file mode 100644 index 0000000..e715a89 --- /dev/null +++ b/spec/kernel.yaml @@ -0,0 +1,330 @@ +# The kernel: every operation of PACT's seven primitives (docs/97-PRIMITIVES.md §2 and §5.4). +# +# This is the closed operation vocabulary. A pattern recipe in +# `scripts/pattern-coverage.py` is typed against it, and a runtime that keeps +# one implementation per operation reads its list from here rather than from a +# copy of its own. +# +# Each operation names its primitive, what it does, and the PACT keyword that is +# that operation today. Where it is written is data on the other side: a field +# in `spec/schema.yaml` that IS an operation says so with `kernel-op:`. An +# operation an author writes as syntax inside a value rather than as a field of +# its own (`one of a, b` in a shape line, `input.x` in a binding) says where +# with `written-in:` here. +# +# `crates/pact-schema/tests/every_kernel_operation_is_written_somewhere.rs` +# holds the two files to each other: a `kernel-op:` that names an operation not +# listed here fails, and so does an operation that no field names and no +# `written-in:` places. An operation added here without a decision about where +# an author writes it therefore stops the test rather than going unwritten. +ops: + shape.scalar: + primitive: Shape + meaning: "text, money, yes-or-no, number, date, duration, image, audio, file" + keyword: "accepts / answers-with / takes" + shape.one-of: + primitive: Shape + meaning: "a closed set of words" + keyword: "`one of a, b`" + written-in: "a shape line: `one of a, b`, wherever a field takes an answer shape" + shape.record: + primitive: Shape + meaning: "records, lists, maps, optional, a failed marker" + keyword: "`list of`, nested `accepts:`" + written-in: "a shape line: `list of`, `optional`, and named shapes with parts" + shape.stream: + primitive: Shape + meaning: "an answer that arrives in parts, each part with a kind" + keyword: "answer shape `stream of `; the harness builds an answer only from `answer` parts (voice `talking: both-at-once` too)" + written-in: "a shape line: `stream of `" + shape.reference: + primitive: Shape + meaning: "a host-issued reference to an agent, run, stored value or Allowance" + keyword: "an `agent`-shaped answer as a `call:` binding, held to `may-call:` at check and run time" + written-in: "a shape line: an `agent`-shaped answer" + shape.validate: + primitive: Shape + meaning: "accept or refuse a value that arrived" + keyword: "answers-with-mode" + shape.describe: + primitive: Shape + meaning: "tell a producer the expected form (JSON Schema, MCP inputSchema, a form)" + keyword: "schemas-from, published-tool" + shape.parse: + primitive: Shape + meaning: "read a value out of loose text" + keyword: "answers-with-mode: prompted" + shape.render: + primitive: Shape + meaning: "fill a template; nothing filled in is read as template again" + keyword: "template-language, `{{run-inputs.x}}`" + shape.digest: + primitive: Shape + meaning: "a value's fingerprint" + keyword: "`in-share: ` in `when-this`; a person's yes bound to the digest of the request they saw" + label.origin: + primitive: Origin + meaning: "customer, person, tool, retrieval, model, teammate, server, reviewed definition, clock" + keyword: "turn.from, never-from" + label.sensitivity: + primitive: Origin + meaning: "a category such as card number or personal data, set by a detector or `hides`" + keyword: "hides, redaction" + label.region: + primitive: Origin + meaning: "where a value may live" + keyword: "data-stays-in" + label.propagate: + primitive: Origin + meaning: "anything computed from labelled values carries the combined label" + keyword: "`never-from:` checked statically (law 7) and again at run time on every memory write" + label.fence: + primitive: Origin + meaning: "only reviewed text instructs; everything else reaches a model as quoted data" + keyword: "question.shows, server prose fenced" + label.placeholder: + primitive: Origin + meaning: "a hidden value travels as a reference and is restored only for use-without-seeing" + keyword: "`gets-hidden-values: yes` on an action; hidden values travel as placeholders" + store.read: + primitive: Store + meaning: "read a path, or as of a version" + keyword: "`input.x`, `steps.x.result`, `remembers.x`" + written-in: "a binding: `input.x`, `steps.x.result`, `remembers.x`, `values.x`" + store.write: + primitive: Store + meaning: "write through the merge rule" + keyword: "remember-as, remembers, writes-to" + store.merge: + primitive: Store + meaning: "keep-all, keep-the-latest, merge, add-up, vote, top-n, or a pure program" + keyword: "combines-by" + store.claim: + primitive: Store + meaning: "insert-if-absent or compare-and-swap, with an optional lease; take = claim then forget" + keyword: "same-request-key, `same-request-key-across: the-team | the-workspace`, `claim-lasts:`" + store.fork: + primitive: Store + meaning: "a copy-on-write branch or snapshot, discarded unless joined out" + keyword: "`forks: yes` on an each, together, repeat or loop stage" + store.find: + primitive: Store + meaning: "by key, words or meaning, reranked, at most N" + keyword: "knowledge: looked-up-by, passages-at-most, reranked-by" + store.forget: + primitive: Store + meaning: "remove an entry or let it expire" + keyword: "kept-for, drop-parts, stops-being-true-when" + store.scope: + primitive: Store + meaning: "step, turn, conversation, run, flow, team, workspace; one per key" + keyword: "lasts, kept-per, counted-per" + store.lifetime: + primitive: Store + meaning: "until a moment or an event" + keyword: "kept-for, forget-after" + store.durability: + primitive: Store + meaning: "none, checkpoint, durable-execution" + keyword: "workspace.durability" + store.version: + primitive: Store + meaning: "every write may create a version with a digest" + keyword: "based-on: name@version, stage.version, tool-snapshot-digest" + store.pin: + primitive: Store + meaning: "pinned entries survive tidying" + keyword: "always-keep, survives-shortening" + store.host: + primitive: Store + meaning: "the conversation, run record, inbox, run registry, clock, directory, catalogue, definition" + keyword: "turns, population: past-runs, counts-events-from, if-still-running, every, asked-of, catalog, values" + actor.model: + primitive: Actor + meaning: "decides by sampling" + keyword: "model, settings, needs" + actor.program: + primitive: Actor + meaning: "decides by computing, under fuel" + keyword: "programs, run-code, engine, determinism" + actor.tool: + primitive: Actor + meaning: "an outside system decides" + keyword: "tools, actions, resources, MCP" + actor.person: + primitive: Actor + meaning: "a human answers; slow and authoritative" + keyword: "questions, asked-of, escalates-to" + actor.remote: + primitive: Actor + meaning: "someone else's agent decides" + keyword: "resource-kind: a2a-agent" + actor.composite: + primitive: Actor + meaning: "a named Flow with a signature" + keyword: "agents, workflows, published-as" + actor.signature: + primitive: Actor + meaning: "what it takes and answers with" + keyword: "accepts, answers-with, takes" + actor.effect: + primitive: Actor + meaning: "none, reads, writes, spends, irreversible; with undo and status-check; readable by a Check" + keyword: "computed from reads-only, spends-money, undone-by; Check-readable as `effect:` in `when-this`; shown by `pact discover`" + actor.write-set: + primitive: Actor + meaning: "the Store paths an Actor declares it writes" + keyword: "`writes: [, ...]` on an action; overlapping branches are warned" + actor.park: + primitive: Actor + meaning: "may wait on a person or an event" + keyword: "ask-someone, answer-within" + actor.figures: + primitive: Actor + meaning: "cost, latency, context, capabilities, benchmark scores, with provenance" + keyword: "model-cost, model-speed, figure, provenance" + actor.identity: + primitive: Actor + meaning: "who acted, on whose behalf, how strongly established" + keyword: "needs-signature, owned-by, not-the-same-as" + actor.location: + primitive: Actor + meaning: "where it runs" + keyword: "served-by.region, runs-in" + actor.by-requirement: + primitive: Actor + meaning: "chosen from a catalogue by a Check and a preference" + keyword: "needs, prefer, interface, implements, chosen-by" + actor.instantiate: + primitive: Actor + meaning: "make an Actor from a definition value during a run, through the checker (law 8)" + keyword: "may-start: narrowed-new, run-code" + flow.ask: + primitive: Flow + meaning: "ask one Actor with inputs bound from Stores; wait, or detach for a run reference" + keyword: "does: call / think / use-tools, call, bind" + flow.then: + primitive: Flow + meaning: "every ask ends in a labelled outcome; the label picks the next step" + keyword: "then, outcome, starts-at" + flow.choose: + primitive: Flow + meaning: "pick one label from a closed set by rungs: Checks, then Actors, then a person" + keyword: "does: decide, chooses-between, rung, route-rule, if-unsure-below" + flow.split: + primitive: Flow + meaning: "fixed branches, one per item, one per stream part, or one per entry of a growing Store" + keyword: "does: each / together, over, teamwork.starts" + flow.join: + primitive: Flow + meaning: "everyone, first, k of n passing a Check, deadline, or quiet; then cancel / ignore / detach the rest" + keyword: "waits-for, enough-is, gives-up-after, combines-by; growing sets: `keeps-taking: yes` on an each" + flow.await: + primitive: Flow + meaning: "pause until a Check over Stores holds, with a correlation key and a deadline" + keyword: "heard, answer-within, counts-events-from, only-when" + flow.scope: + primitive: Flow + meaning: "a region with its own Store fork, Allowance and meters" + keyword: "a stage's steps, team, isolated conversation, limits on a stage" + flow.handler: + primitive: Flow + meaning: "before, each part, after, failure, ran-out: change, hold, divert, refuse, retry, back up, undo" + keyword: "interceptors, policies, if-it-fails, when-it-runs-out, context-policy, watch, redaction" + flow.starts-when: + primitive: Flow + meaning: "a trigger: a Check over Stores becomes true; with a concurrency key and an overlap rule" + keyword: "ports, every, if-still-running, if-missed, per-row-of" + flow.cancel: + primitive: Flow + meaning: "cancel a scope or a run by reference" + keyword: "if-someone-fails: stop-the-others; by reference: `does: stop` with `stops: steps..run`" + flow.detach: + primitive: Flow + meaning: "start and get a run reference instead of waiting" + keyword: "waits-for-result: no" + flow.name: + primitive: Flow + meaning: "a Flow with a signature is an Actor" + keyword: "agents, workflows, loops" + check.compare: + primitive: Check + meaning: "more-than, less-than, is, is-one-of, contains-any-of, is-empty" + keyword: "when-this" + check.typed: + primitive: Check + meaning: "money in its currency, durations, now-plus, business days" + keyword: "comparand, moment.counted-in" + check.detector: + primitive: Check + meaning: "a named recogniser: built in (card number, email) or declared by the author" + keyword: "redaction.hide; declared: workspace `detectors:`, used as `looks-like:` and in sentences" + check.origin: + primitive: Check + meaning: "a test on a value's Origin" + keyword: "never-from" + check.count: + primitive: Check + meaning: "counts and order over the run record; distinct in the last k" + keyword: "must-call-before, `called more than n times`" + check.aggregate: + primitive: Check + meaning: "shares, percentiles, means over a population" + keyword: "must-pass, metrics, threshold, measured-at" + check.combine: + primitive: Check + meaning: "all, any, not" + keyword: "rules lists" + written-in: "a list of conditions: every line holds" + check.reachability: + primitive: Check + meaning: "what a part can reach, directly or through others" + keyword: "available-when; `carries:` on a tool, and the three-part leak rule" + check.digest: + primitive: Check + meaning: "compare two fingerprints" + keyword: "tool-snapshot-digest; request digests on approvals; `in-share:`" + allowance.reach: + primitive: Allowance + meaning: "which Actors and addresses a part may ask" + keyword: "uses, may-use, may-call, allow-egress, who-can-reach-it" + allowance.see: + primitive: Allowance + meaning: "who may read a labelled value" + keyword: "hides, question.shows, inspects" + allowance.use-without-seeing: + primitive: Allowance + meaning: "pass a value along without reading it" + keyword: "credential-reference" + allowance.write: + primitive: Allowance + meaning: "what a part may write, including to the definition" + keyword: "owners, locked, learning.needs-a-person-to-approve" + allowance.start: + primitive: Allowance + meaning: "whether and how many helpers a part may start, and how deep" + keyword: "may-start, starts-at-most, nests-at-most" + allowance.quantity: + primitive: Allowance + meaning: "steps, tool calls, tokens, money, time, items, attempts, fuel" + keyword: "limits, at-most, fuel, checks-at-most" + allowance.period: + primitive: Allowance + meaning: "counted per scope and reset per period" + keyword: "counted-per, resets-every" + allowance.pass: + primitive: Allowance + meaning: "handed down, never wider; totals conserved" + keyword: "divides-the-budget, shares" + allowance.revoke: + primitive: Allowance + meaning: "taken back, expired, or withdrawn for a period" + keyword: "locked-until, tool-snapshot-max-age; for a period: `rests-after:` with `rests-for:` on a tool" + allowance.pause: + primitive: Allowance + meaning: "a meter paused during a scope" + keyword: "paused-during" + allowance.value: + primitive: Allowance + meaning: "a grant as a run-time value that can be split and passed" + keyword: "`cost-per-request-from:` a run input, never above `cost-per-request-under:`" diff --git a/spec/schema.yaml b/spec/schema.yaml index 2b49167..a93651b 100644 --- a/spec/schema.yaml +++ b/spec/schema.yaml @@ -76,6 +76,13 @@ # `answer-shape`. Written like `parts:` — one entry per shape, # every spelling that means it — and read by the checker and by # `questions.Shape.parse` from this one copy. +# `kernel-op:` the kernel operations this field IS (docs/97 §5.4), one or a +# list, each one of the 76 in `spec/kernel.yaml` — `store.claim` +# on `same-request-key:`. A runtime that keeps one +# implementation per operation reads which fields reach it here. +# Changes nothing `pact check` accepts. Held to +# `spec/kernel.yaml` in both directions by +# `crates/pact-schema/tests/every_kernel_operation_is_written_somewhere.rs`. # # What `names:` deliberately does NOT cover, because the tree does not carry the # answer: anything the HOST binds rather than the workspace (`port.through:` @@ -172,6 +179,7 @@ groups: choices: [none, checkpoint, durable-execution] surface: S-GOV tier: expert + kernel-op: store.durability help: > what the system running this keeps when a run stops part-way. `none` means nothing survives, so a run that parks for a person starts again @@ -208,6 +216,7 @@ groups: choices: [llm, stt, tts, embedder, judge, reflector, tools, programs] surface: S-EXEC tier: core + kernel-op: allowance.reach help: > which parts of this system are allowed to talk to something outside this box. Empty means nothing is. Adding a role here always needs a @@ -216,6 +225,7 @@ groups: type: map of group:agent surface: S-TOPO tier: core + kernel-op: [actor.composite, flow.name] help: the agents that make up this system workflows: # 02W §2.1. A workflow is a stage loop that belongs to no agent, one file @@ -223,6 +233,7 @@ groups: type: map of group:workflow surface: S-TOPO tier: core + kernel-op: [actor.composite, flow.name] help: > the flows this system runs, one file each under `workflows/`. A workflow belongs to no agent: it calls agents, tools, programs and @@ -272,6 +283,7 @@ groups: # not have run can only fill one of these (WF-5) surface: S-CAP tier: core + kernel-op: shape.record help: > named shapes with parts — `invoice-line: {sku: text, quantity: whole number}` — so `lines: list of invoice-line` can be written anywhere @@ -286,6 +298,7 @@ groups: choices: [holes, jinja] surface: S-CTRL tier: expert + kernel-op: shape.render help: > how the words agents are given fill in what is only known when a run starts. `holes`, what you get if you leave this out, is @@ -310,11 +323,13 @@ groups: names: values surface: S-GOV tier: core + kernel-op: [store.host, check.typed] help: which table in `values/` lists working days and holidays, for `business-days` remembers: type: map of group:state surface: S-GOV tier: core + kernel-op: store.write help: > named memory any agent or workflow here may read as `remembers.`, written only by the stock actions `bud/remember` @@ -326,6 +341,7 @@ groups: type: map of group:detector surface: S-GOV tier: expert + kernel-op: check.detector help: > things your rules can recognise beside the five built in, each named and written as the patterns it looks like. A rule then says `replace @@ -337,6 +353,7 @@ groups: type: map of group:ownership surface: S-GOV tier: core + kernel-op: allowance.write help: > parts that need their owner's yes to change, or that may not be removed, keyed by path — `workflows/one-invoice/steps/tiers`. @@ -349,11 +366,13 @@ groups: type: map of group:tool surface: S-CAP tier: core + kernel-op: actor.tool help: the outside systems these agents may reach resources: type: map of group:resource surface: S-CAP tier: core + kernel-op: actor.tool help: servers, sandboxes and stores the system connects to knowledge: # A7. The commonest enterprise shape — an agent that answers out of a @@ -397,6 +416,7 @@ groups: type: map of group:program surface: S-EXEC tier: expert + kernel-op: actor.program help: > programs this system carries and can run in a locked room — the exact bits of work a model should not be guessing at, like date arithmetic or @@ -428,6 +448,7 @@ groups: type: map of group:value surface: S-GOV tier: core + kernel-op: store.host help: > figures this system uses in more than one place — an approval threshold, a deadline, a step count. Write one here and put @@ -437,6 +458,7 @@ groups: type: map of group:policy surface: S-EXEC tier: core + kernel-op: flow.handler help: the rules about what needs a person, and what must never be sent redaction: # ONE field, where there were two for one idea (R61). `redactions:` was @@ -458,6 +480,7 @@ groups: type: group:redaction surface: S-EXEC tier: core + kernel-op: flow.handler help: > what must never leave this workspace. Write it in a file called `redaction.yaml` next to `workspace.yaml`, and put everything you want @@ -469,6 +492,7 @@ groups: type: map of group:question surface: S-EXEC tier: core + kernel-op: actor.person help: > the things a person can be asked, and what counts as an answer to each. Saying yes or no is one of these, not a separate idea. @@ -486,6 +510,7 @@ groups: type: map of group:port surface: S-TOPO tier: core + kernel-op: flow.starts-when help: > how the outside world reaches these agents — people talking to them, other systems calling in, and the clock. A timer is a port with an @@ -494,11 +519,13 @@ groups: type: map of group:loop surface: S-CTRL tier: expert + kernel-op: flow.name help: named shapes of thinking that agents can point at context-policies: type: map of group:context-policy surface: S-CTRL tier: core + kernel-op: flow.handler help: named rules for keeping a long conversation inside what a model can hold bundles: # G10. `defineExtension` packages tools, connections, skills, instruction @@ -523,6 +550,7 @@ groups: type: map of group:interceptor surface: S-EXEC tier: expert + kernel-op: flow.handler help: > rules that can change what happens mid-run, not merely watch it. Each one says on its own line whether it covers every agent here or only @@ -531,6 +559,7 @@ groups: type: map of group:watch surface: S-GOV tier: core + kernel-op: flow.handler help: > the things to write down as they happen, so that afterwards you can read what the system actually did instead of taking its word for it. @@ -578,6 +607,7 @@ groups: type: group:catalog surface: S-CAP tier: expert + kernel-op: [store.host, actor.model] help: > extra AI models this machine can serve, on top of the ones PACT already knows about. You only need this if you run a model PACT has @@ -609,6 +639,7 @@ groups: required: yes surface: S-GEN tier: core + kernel-op: shape.render help: > one line a colleague would understand. It may carry the same `{{run-inputs.}}` and `{{remembers.}}` holes as @@ -672,6 +703,7 @@ groups: required: yes surface: S-GEN tier: core + kernel-op: shape.render help: > what it should actually do, in plain words. That line can go in `agent.yaml`, or the same words can go in @@ -693,6 +725,7 @@ groups: key-names: agents surface: S-TOPO tier: core + kernel-op: [actor.composite, flow.split] help: > who helps with this work, and what each of them is for. Give each one a folder under `agents/`, and use the sentence you put here as @@ -732,6 +765,7 @@ groups: names: [tools, skills, knowledge, programs, workflows] surface: S-ROUTE tier: core + kernel-op: allowance.reach help: > the tools, skills, sets of documents, carried programs and published workflows this agent may use — a tool it may call, a procedure it may @@ -753,6 +787,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: core + kernel-op: [shape.scalar, actor.signature] help: > what you can send it, one line per thing — for example `message: text` or `photos: images`. The shapes are `text`, @@ -772,6 +807,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: core + kernel-op: [shape.scalar, actor.signature] help: > what it hands back when it is done, one line per thing — for example `decision: one of approved, declined`. The same shapes `accepts:` @@ -781,6 +817,7 @@ groups: choices: [text, native-json-schema, tool, prompted] surface: S-CAP tier: expert + kernel-op: [shape.validate, shape.parse] help: > how the answer is constrained on the wire. Leave it out: PACT picks from the shape you declared above, and records what it picked. @@ -805,6 +842,7 @@ groups: needs-also: [checked-by] surface: S-GOV tier: core + kernel-op: allowance.quantity help: > how many attempts in all, asking again with the reason after each failed check. Leave it out for 2; write `1` to fail at the first miss. @@ -813,6 +851,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: core + kernel-op: shape.scalar help: > values the surrounding system supplies for every run — a customer id, a locale — one line per value, with the same shapes `accepts:` takes. @@ -825,22 +864,26 @@ groups: type: group:needs surface: S-CAP tier: core + kernel-op: [actor.model, actor.by-requirement] help: what the AI model behind it has to be capable of limits: type: group:limits surface: S-GOV tier: core + kernel-op: allowance.quantity help: how fast, how cheap, and how long it may run settings: type: group:settings surface: S-CTRL tier: core + kernel-op: actor.model help: how the model itself is asked to behave policy: type: text names: policies surface: S-EXEC tier: core + kernel-op: flow.handler help: which policy file says what needs a person's approval evals: type: text @@ -894,6 +937,7 @@ groups: names: pact:models surface: S-CAP tier: core + kernel-op: actor.model help: > pin one exact model, if you must. Leave this out and PACT picks. A model that only runs somewhere else is refused here when @@ -925,6 +969,7 @@ groups: needs-also: [loop] surface: S-CAP tier: expert + kernel-op: actor.model help: > a different model for the stages that check work, when the checking deserves a better one than the doing. Needs a `loop:`, because a @@ -939,6 +984,7 @@ groups: pact:loop/answer-more-than-once] surface: S-CTRL tier: expert + kernel-op: flow.name help: > how this agent thinks: `pact:loop/standard`, `pact:loop/plan-then-do`, or the name of a shape you wrote in `loops/`. Leave it out and the @@ -971,6 +1017,7 @@ groups: names: context-policies surface: S-CTRL tier: core + kernel-op: flow.handler help: > which tidying rules to use when the conversation gets too long. Leave it out and no tidying happens at all — a conversation that @@ -980,12 +1027,14 @@ groups: type: map of group:state surface: S-GOV tier: core + kernel-op: store.write help: what this agent keeps between messages, and for how long interceptors: type: list of text names: interceptors surface: S-EXEC tier: expert + kernel-op: flow.handler help: which rules may change what happens as it runs reactions: type: list of text @@ -1036,6 +1085,7 @@ groups: type: list of group:turn surface: S-GEN tier: expert + kernel-op: store.host help: > a short conversation that comes before every real one, one turn per line — who speaks (`from:`) and what they say (`says:`). Use it for a @@ -1061,6 +1111,7 @@ groups: type: text surface: S-GEN tier: expert + kernel-op: shape.render help: > what to say instead of the agent's own instructions. Leave it out to keep them. @@ -1069,6 +1120,7 @@ groups: type: text surface: S-GEN tier: expert + kernel-op: shape.render help: > an extra line added after the agent's instructions, rather than in place of them. Use this for *"Always look the order up before @@ -1078,6 +1130,7 @@ groups: at-least: 1 surface: S-CTRL tier: expert + kernel-op: allowance.quantity help: > how many turns this way of working may take. A model that has to be walked through the work needs more of them. @@ -1090,6 +1143,7 @@ groups: names: [tools, skills, knowledge, programs, workflows] surface: S-ROUTE tier: expert + kernel-op: allowance.reach help: > narrow the tools, procedures and calculations to these, for this way of working only. Leave it out to keep all of them. @@ -1100,6 +1154,7 @@ groups: type: list of group:turn surface: S-GEN tier: expert + kernel-op: store.host help: > a worked conversation to show the model instead of the agent's own `turns:`, for this way of working only — the example a smaller model @@ -1131,6 +1186,7 @@ groups: choices: [takes-turns, both-at-once] surface: S-CAP tier: expert + kernel-op: shape.stream help: > whether the person and the agent take turns, or can both talk at once — the agent stops when it is interrupted and listens. Leave it @@ -1143,6 +1199,7 @@ groups: plays-role: stt surface: S-CAP tier: expert + kernel-op: actor.model help: > which model turns what the person says into words, by name. Leave it out and PACT picks one. A model that only runs somewhere else is @@ -1154,6 +1211,7 @@ groups: plays-role: tts surface: S-CAP tier: expert + kernel-op: actor.model help: > which model speaks the answer, by name. Leave it out and PACT picks one. A model that only runs somewhere else is refused here when @@ -1222,6 +1280,7 @@ groups: required: yes surface: S-GEN tier: expert + kernel-op: label.origin help: > who says this. `user` is the person, `agent` is this agent, and `tool` is what a tool handed back. `system` and `developer` are @@ -1233,6 +1292,7 @@ groups: type: text surface: S-GEN tier: expert + kernel-op: shape.render help: > the words of this turn. On an agent they may carry the same `{{run-inputs.}}` holes as `instructions:`; in an eval case @@ -1319,6 +1379,7 @@ groups: rows-are: table surface: S-CAP tier: expert + kernel-op: shape.scalar help: > what kind of figure this is — `money`, `duration`, `whole-number`, `percent`, `size`, `number`, `text`, `yes-or-no`, or `table` for a @@ -1330,6 +1391,7 @@ groups: names: shapes surface: S-CAP tier: core + kernel-op: shape.record summary: what each row is made of help: on a table, the named shape of each row — an approval matrix, a roster value: @@ -1377,6 +1439,7 @@ groups: required: yes surface: S-CAP tier: expert + kernel-op: actor.program help: > what kind of program this is. `wasm` is the portable one: it runs the same way anywhere and needs nothing installed, so it is the one to use @@ -1392,6 +1455,7 @@ groups: required: yes surface: S-CTRL tier: expert + kernel-op: actor.program help: > whether the same question always gets the same answer. `pure` means it works only from what it is given. `deterministic` means it may look at @@ -1405,6 +1469,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: expert + kernel-op: [shape.scalar, actor.signature] help: > what this program needs to be given, one line per value — the same shapes a tool's `takes:` uses, because it is the same question. @@ -1413,6 +1478,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: expert + kernel-op: [shape.scalar, actor.signature] help: > what it hands back, one line per thing. An answer that does not fit is that call's failure and never a silent wrong number. @@ -1421,6 +1487,7 @@ groups: required: yes surface: S-GOV tier: expert + kernel-op: allowance.quantity help: > what one run of this program may spend before it is stopped. Required: a program with no ceiling is the one thing in this format that could @@ -1434,6 +1501,7 @@ groups: type: anything surface: S-EXEC tier: expert + kernel-op: actor.program help: > the program itself, in a `body/` folder beside this file. Kept as files rather than as settings: PACT records what is there, what kind of file @@ -1486,6 +1554,7 @@ groups: choices: [stop-and-say-so, ask-a-person, answer-with-what-it-has] surface: S-GOV tier: expert + kernel-op: flow.handler help: > what to do when this program reaches a ceiling above. The same three answers `limits:` takes, for the same reason: stopping silently, asking @@ -1539,6 +1608,7 @@ groups: model-kind: [language] surface: S-CAP tier: expert + kernel-op: actor.model help: > which model to use when an agent names none. Leave it out to keep the one PACT already picks. A model that only runs somewhere else is @@ -1548,6 +1618,7 @@ groups: type: map of group:model surface: S-CAP tier: expert + kernel-op: actor.model help: one entry per model, named by whatever your runtime calls it benchmarks: # 96 §B4. The names a score may be recorded under, and how each is @@ -1658,6 +1729,7 @@ groups: type: group:figure surface: S-CAP tier: expert + kernel-op: actor.figures help: > how much thinking it is good for — `simple`, `steady`, `careful` or `deep` — and where that came from. Write `unknown` if nobody has @@ -1667,6 +1739,7 @@ groups: type: group:model-cost surface: S-GOV tier: expert + kernel-op: actor.figures help: what it costs to run kind: # 02W §2.12. Read by `pact check` (`deciding.rs`: WF-17, and an agent @@ -1710,6 +1783,7 @@ groups: type: map of group:figure surface: S-CAP tier: expert + kernel-op: actor.figures help: > published or measured scores, one line per benchmark, each with where the number came from — `MMLU: {value: "81", provenance: {source: the @@ -1720,6 +1794,7 @@ groups: type: group:model-speed surface: S-CAP tier: expert + kernel-op: actor.figures help: > how quickly it answers, and where those figures came from. Read when an agent says `prefer: fastest`, and when a `feel: voice` agent is @@ -1741,6 +1816,7 @@ groups: type: text surface: S-CAP tier: expert + kernel-op: actor.location help: > write `local` when the model runs on this machine. Anything else means the call leaves the box — which a workspace with nothing in @@ -1749,6 +1825,7 @@ groups: type: text surface: S-CAP tier: expert + kernel-op: actor.location help: > the region this runtime serves the model in, as your runtime names it (`us-baa-region`). A workflow whose `data-stays-in:` names a region @@ -1877,6 +1954,7 @@ groups: type: group:figure surface: S-CAP tier: expert + kernel-op: actor.figures help: > how much it can hold at once, in tokens, and where that number came from. Write `unknown` if you cannot find out — a made-up number here @@ -1901,11 +1979,13 @@ groups: required: yes surface: S-CAP tier: expert + kernel-op: actor.figures help: the figure itself, or the word `unknown` if nobody can say provenance: type: group:provenance surface: S-GOV tier: expert + kernel-op: actor.figures help: where this number came from provenance: @@ -2033,6 +2113,7 @@ groups: choices: [cheapest, fastest, best-on-the-evals] surface: S-CAP tier: core + kernel-op: actor.by-requirement help: > among the models that pass, which one PACT picks. `cheapest` is the usual answer and what you get if you leave this out. `fastest` @@ -2064,6 +2145,7 @@ groups: type: list of text surface: S-CAP tier: core + kernel-op: actor.by-requirement help: > the figures a model has to reach to be used for this agent, one comparison per line, all of which have to hold — `mmlu.score > 50`, @@ -2124,6 +2206,7 @@ groups: type: map of threshold surface: S-CAP tier: expert + kernel-op: actor.by-requirement help: > minimum published benchmark scores, one line each, all of which have to hold — for example `MMLU: "> 80"` and `SWE-Verified: "> 40"` @@ -2194,6 +2277,7 @@ groups: type: moment surface: S-GOV tier: core + kernel-op: allowance.quantity needs-also: [when-it-runs-out] help: > how long the whole thing may take. This is the promise you are making @@ -2215,6 +2299,7 @@ groups: names: ^steps surface: S-GOV tier: core + kernel-op: allowance.pause help: waits whose time does not count against `finishes-within:` milestones: type: list of group:reminder @@ -2225,6 +2310,7 @@ groups: type: money surface: S-GOV tier: core + kernel-op: allowance.quantity needs-also: [when-it-runs-out] help: > the most one request may cost, before it is stopped. One request, not @@ -2248,6 +2334,7 @@ groups: at-least: 1 surface: S-GOV tier: core + kernel-op: allowance.quantity needs-also: [when-it-runs-out] help: > how many thinking steps before it must stop. Eve has no such limit at @@ -2259,6 +2346,7 @@ groups: needs-also: [cost-per-request-under] surface: S-GOV tier: expert + kernel-op: allowance.value help: > where this run's money ceiling comes from, when the surrounding system decides it per request — `run-inputs.budget` on an agent, @@ -2286,6 +2374,7 @@ groups: at-least: 1 surface: S-GOV tier: core + kernel-op: allowance.quantity needs-also: [when-it-runs-out] help: > how many tool calls one run may make. Counted apart from steps because @@ -2295,6 +2384,7 @@ groups: type: duration surface: S-GOV tier: core + kernel-op: allowance.quantity needs-also: [when-it-runs-out] help: > wall-clock time before it must stop, however much budget is left. Time @@ -2305,6 +2395,7 @@ groups: at-least: 1 surface: S-GOV tier: core + kernel-op: allowance.quantity needs-also: [when-it-runs-out] help: > how much text one run may read and write, counted the way the model @@ -2322,6 +2413,7 @@ groups: at-least: 1 surface: S-GOV tier: expert + kernel-op: allowance.quantity needs-also: [when-it-runs-out] help: > how many times one request may put this agent to work, counting the @@ -2342,6 +2434,7 @@ groups: needs-also: [when-it-runs-out] surface: S-GOV tier: core + kernel-op: allowance.start help: > how many agents one request may bring in through `may-start:`, counted across every level. Starting one past it is refused, and the @@ -2355,6 +2448,7 @@ groups: needs-also: [when-it-runs-out] surface: S-GOV tier: core + kernel-op: allowance.start help: > how many levels deep agents brought in may themselves bring in others; `1` means the ones this agent starts may start none. Bud's @@ -2367,6 +2461,7 @@ groups: needs-also: [when-it-runs-out] surface: S-GOV tier: core + kernel-op: allowance.quantity help: > on `each`, the most items it will take — required there. With `carry-on` the first N run and the rest are listed as not done. @@ -2375,6 +2470,7 @@ groups: at-least: 1 surface: S-GOV tier: core + kernel-op: allowance.quantity help: > how many may run at the same time: items on an `each`, or runs of this flow when `counted-per:` says so. `1` is one at a time, in order. @@ -2386,6 +2482,7 @@ groups: type: text surface: S-GOV tier: core + kernel-op: [store.scope, allowance.period] help: > what each ceiling here is counted across: `run` (the default), every use of a `connection`, a `team`, or a binding — `input.customer-id` @@ -2395,6 +2492,7 @@ groups: needs-also: [counted-per] surface: S-GOV tier: core + kernel-op: allowance.period help: for a ceiling counted over a period — `1 day`, `30 days` when-it-runs-out: # The first three are an agent's; the other five a workflow's (02W @@ -2408,6 +2506,7 @@ groups: asks: ask-a-person surface: S-GOV tier: core + kernel-op: flow.handler help: > what to do on reaching any ceiling above. Stopping silently, asking a person, and answering as if finished are three different things, so @@ -2423,6 +2522,7 @@ groups: names: [^questions, questions] surface: S-EXEC tier: core + kernel-op: actor.person # "the line above" is a direction to somebody READING this setting. The # refusal quotes it from the other direction — it is already saying which # line, so the sentence came out *"'when-it-runs-out' says @@ -2441,6 +2541,7 @@ groups: choices: [p50, p90, p95, p99, mean, max] surface: S-GOV tier: expert + kernel-op: check.aggregate help: whether these are typical or worst-case numbers # How the model is asked to behave. Closed on purpose: every key here has one @@ -2566,6 +2667,7 @@ groups: a-person-can-be-asked: questions surface: S-ROUTE tier: expert + kernel-op: check.reachability help: > when this is offered to the model at all. Leave it out and it is always offered. Use it for something that only makes sense once the @@ -2602,6 +2704,7 @@ groups: required: yes surface: S-CAP tier: core + kernel-op: [actor.tool, actor.remote] help: > what kind of thing this is: an MCP server, a locked room that runs carried programs, or an agent somewhere else reached over A2A. @@ -2612,6 +2715,7 @@ groups: type: text surface: S-CAP tier: core + kernel-op: actor.tool help: > a name your platform team publishes. Ask them which ones this machine already knows about — it is their list, not a file in here. @@ -2624,6 +2728,7 @@ groups: type: group:credential-reference surface: S-CAP tier: core + kernel-op: allowance.use-without-seeing help: > where the credential is kept — a reference your platform team publishes. Never the credential itself: a spec file may not contain @@ -2650,6 +2755,7 @@ groups: choices: [wasm, python, typescript] surface: S-CAP tier: expert + kernel-op: actor.program help: > which kinds of program this locked room can run. A tool that `connect:`s here and runs a program written for a kind this room cannot @@ -2659,6 +2765,7 @@ groups: names: questions surface: S-EXEC tier: expert + kernel-op: actor.person help: > which question to put to a person before this machine runs a carried program for the first time, by name. The run stops and waits rather @@ -2670,6 +2777,7 @@ groups: names: questions surface: S-EXEC tier: core + kernel-op: actor.person help: > which question to put to a person when this connection has not been allowed yet, by name. The run stops and waits for the answer rather @@ -2713,6 +2821,7 @@ groups: needs-also: [tool-snapshot-taken-at] surface: S-GOV tier: expert + kernel-op: [store.version, check.digest] help: > one digest over everything this server published when somebody reviewed it — every tool, every argument, and every sentence; for an @@ -2740,6 +2849,7 @@ groups: needs-also: [tool-snapshot-digest] surface: S-GOV tier: expert + kernel-op: allowance.revoke help: > how old that digest may be before this connection is refused. Nothing can check a server you cannot reach, so what fails closed offline is @@ -2794,6 +2904,7 @@ groups: names: questions surface: S-EXEC tier: expert + kernel-op: actor.person summary: which question to put to them when the server changed help: > which question to put to a person when `if-it-changes:` says @@ -2891,6 +3002,7 @@ groups: choices: [meaning, words, meaning-and-words] surface: S-CTRL tier: expert + kernel-op: store.find help: > how the runtime finds the right passages. `words` matches the words you typed, which is what you want for clause numbers, product codes and @@ -2902,6 +3014,7 @@ groups: choices: [paragraph, heading, page, whole-file] surface: S-CTRL tier: expert + kernel-op: store.find help: > how a long document is broken up before anything is looked up in it. Leave it out and the runtime picks, which is the usual answer. @@ -2916,6 +3029,7 @@ groups: at-least: 1 surface: S-GOV tier: core + kernel-op: store.find help: > how many pieces of document come back for one question. More is not better: every extra passage is more text a poisoned document could be @@ -2966,6 +3080,7 @@ groups: plays-role: embedder surface: S-CAP tier: expert + kernel-op: [store.find, actor.model] help: > which model turns these documents, and every question asked of them, into numbers for looking things up by meaning — by name. It belongs to @@ -2979,6 +3094,7 @@ groups: plays-role: embedder surface: S-CAP tier: expert + kernel-op: [store.find, actor.model] help: > a model that puts the passages found in order of how well they answer the question, before the best few are handed to the agent. @@ -2994,6 +3110,7 @@ groups: type: yes-no surface: S-CAP tier: expert + kernel-op: actor.by-requirement help: > whether this describes what several interchangeable ones have in common — `email` for Gmail and Outlook, `refund-policy` for each @@ -3004,6 +3121,7 @@ groups: type: text surface: S-ROUTE tier: expert + kernel-op: actor.by-requirement help: > on an interface, who picks which one does the work. `the-host` — what you get if you leave this out — means whatever runs your agents @@ -3016,6 +3134,7 @@ groups: names: knowledge surface: S-CAP tier: expert + kernel-op: actor.by-requirement help: > which interface these documents are one of, by name — an entry in `knowledge` that says `interface: yes`. @@ -3066,6 +3185,7 @@ groups: a-person-can-be-asked: questions surface: S-ROUTE tier: expert + kernel-op: check.reachability help: > when this is offered to the model at all. Leave it out and it is always offered. Use it for something that only makes sense once the @@ -3148,6 +3268,7 @@ groups: names: skills surface: S-CAP tier: expert + kernel-op: actor.by-requirement help: > which interface this procedure is one of, by name — an entry in `skills` that says `interface: yes`. @@ -3170,6 +3291,7 @@ groups: required: yes surface: S-GOV tier: expert + kernel-op: shape.digest help: > one fingerprint over everything recorded below, worked out by `pact-sync`. A run compares it with what the server says as it @@ -3183,6 +3305,7 @@ groups: type: text surface: S-GEN tier: expert + kernel-op: label.fence help: > the wording the server publishes about itself. It reaches a model only fenced off and labelled as the server's, never as this @@ -3201,16 +3324,19 @@ groups: type: text surface: S-GEN tier: expert + kernel-op: label.fence help: the server's own wording for this tool takes: type: anything surface: S-CAP tier: expert + kernel-op: shape.describe help: its arguments, as the JSON Schema the server published answers-with: type: anything surface: S-CAP tier: expert + kernel-op: shape.describe help: what it answers with, as the JSON Schema the server published, if it did hints: type: anything @@ -3235,6 +3361,7 @@ groups: required: yes surface: S-GOV tier: expert + kernel-op: check.detector help: > the patterns it matches, as regular expressions — `ORD-[0-9]{8}`. Any one matching is enough. @@ -3242,6 +3369,7 @@ groups: type: list of text surface: S-GEN tier: expert + kernel-op: check.detector help: > values it must recognise. Each is held against the patterns before the first model call, so a pattern that misses its own example stops @@ -3255,6 +3383,7 @@ groups: required: yes surface: S-CAP tier: core + kernel-op: allowance.use-without-seeing help: > the name your platform team publishes for where this credential is kept — for example `host/payments-credential`. Ask them; it is their @@ -3306,6 +3435,7 @@ groups: a-person-can-be-asked: questions surface: S-ROUTE tier: expert + kernel-op: check.reachability help: > when this is offered to the model at all. Leave it out and it is always offered. Use it for something that only makes sense once the @@ -3329,6 +3459,7 @@ groups: names: resources surface: S-CAP tier: core + kernel-op: actor.tool help: > which server, locked room or remote agent this reaches, by name — one of the entries in `resources`. A name that is not there is @@ -3360,6 +3491,7 @@ groups: needs-also: [method] surface: S-CAP tier: core + kernel-op: actor.tool help: > the address this tool calls — a name your platform team publishes, the same way an `endpoint:` is, with the path under it when it has one @@ -3380,6 +3512,7 @@ groups: choices: [get, post, put, patch, delete] surface: S-CAP tier: core + kernel-op: actor.tool help: > which kind of call to make at the address in `url:` — `get` to look something up, `post` to send something in. Whoever published the @@ -3405,6 +3538,7 @@ groups: choices: [private-data, untrusted-content, a-way-out] surface: S-GOV tier: expert + kernel-op: [label.origin, check.reachability] help: > what this tool brings into a run: `private-data` (records about people, accounts, secrets), `untrusted-content` (web pages, email, @@ -3429,6 +3563,7 @@ groups: needs-also: [rests-for] surface: S-GOV tier: expert + kernel-op: allowance.revoke summary: how many failures in a row before it rests help: > how many failures in a row before this tool is rested — `3`. While it @@ -3439,12 +3574,14 @@ groups: needs-also: [rests-after] surface: S-GOV tier: expert + kernel-op: allowance.revoke summary: how long it rests help: how long a tool rests once `rests-after:` failures in a row put it to rest — `5m` actions: type: map of group:action surface: S-CAP tier: core + kernel-op: [actor.tool, allowance.reach] help: > the only actions this agent may call. Anything else the server offers is refused. For an agent reached over A2A, each action is one @@ -3480,6 +3617,7 @@ groups: names: tools surface: S-CAP tier: expert + kernel-op: actor.by-requirement help: > which interface this tool is one of, by name — an entry in `tools` that says `interface: yes`. It then offers exactly that interface's @@ -3498,6 +3636,7 @@ groups: choices: [this-file, the-snapshot, the-server] surface: S-CAP tier: expert + kernel-op: shape.describe help: > on a tool that connects to an MCP server or a remote agent, where each action's arguments and answer come from. `this-file`, what you @@ -3512,6 +3651,7 @@ groups: choices: [refused, each-call-asks-a-person] surface: S-EXEC tier: expert + kernel-op: allowance.reach help: > what happens to an action the server offers that `actions:` does not list. `refused` is what you get if you leave this out. With @@ -3548,6 +3688,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: core + kernel-op: [shape.scalar, actor.signature] help: > what this action needs to be given, one line per argument — for example `order-number: text` or `amount: money`. This is what the @@ -3570,6 +3711,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: expert + kernel-op: [shape.scalar, actor.signature] help: > what this action hands back, one line per thing — for example `latest: number` or `status: one of ok, degraded`. The same shapes an @@ -3591,6 +3733,7 @@ groups: names: programs surface: S-EXEC tier: expert + kernel-op: actor.program help: > which carried program this action runs, by name — one of the entries in `programs`. Write it on an action whose tool `connect:`s to a sandbox. @@ -3598,6 +3741,7 @@ groups: type: yes-no surface: S-EXEC tier: core + kernel-op: actor.effect help: whether it only looks things up needs-a-person: # F17. Gating ONE action cost three files and thirteen lines — a @@ -3653,6 +3797,7 @@ groups: same-request-key: 'yes' surface: S-EXEC tier: core + kernel-op: actor.effect help: > whether it moves money. Ticking this makes a same-request key necessary, so the same refund cannot be paid twice — that half is @@ -3664,6 +3809,7 @@ groups: type: text surface: S-EXEC tier: core + kernel-op: store.claim help: >- which argument makes two calls "the same call", so it runs once. Write `the-call` when the business has no key of its own, or the flow means to repeat a write with @@ -3707,6 +3853,7 @@ groups: needs-also: [same-request-key] surface: S-EXEC tier: expert + kernel-op: store.claim help: > how far "the same call" reaches. `this-run` is the default and stops one run doing the same thing twice, including after it parks for an @@ -3719,6 +3866,7 @@ groups: type: list of text surface: S-EXEC tier: expert + kernel-op: actor.write-set help: > the places this action changes, by any name you choose — `the checkout`, `orders/`. Two branches of a `together`, or the items @@ -3731,6 +3879,7 @@ groups: type: yes-no surface: S-GOV tier: expert + kernel-op: label.placeholder help: > `yes` lets this action receive values a redaction or `hide-values` rule took out — a card number the payments system must charge. While @@ -3748,6 +3897,7 @@ groups: needs-also: [same-request-key] surface: S-EXEC tier: expert + kernel-op: store.claim help: > how long one call's claim on its key lasts — `5m`. Until then a second call with the same key is refused; after it the key is free again, so @@ -3803,6 +3953,7 @@ groups: names: programs surface: S-CTRL tier: expert + kernel-op: actor.program help: > a carried program that shortens what this action hands back, before the model reads it — for a system that answers with forty fields when the @@ -3818,6 +3969,7 @@ groups: type: text surface: S-EXEC tier: expert + kernel-op: store.write help: > keep what this action answered, under one of the agent's own `remembers:` names. A fact a tool handed back may not be kept where the @@ -3829,6 +3981,7 @@ groups: type: list of text surface: S-EXEC tier: core + kernel-op: allowance.see help: > the arguments an approval rule is allowed to look at — an amount, a quantity. These are exactly the ones the model DOES choose, which is @@ -3841,6 +3994,7 @@ groups: type: text surface: S-EXEC tier: core + kernel-op: actor.effect help: > how to reverse this write once it has succeeded — `docusign/void-envelope`. It is given this call's inputs and its answer, so each input it takes @@ -3852,6 +4006,7 @@ groups: type: text surface: S-EXEC tier: expert + kernel-op: actor.effect help: > a `reads-only` action, written `/`, asked "did this already happen?" before a retry, for systems that time out after @@ -3867,6 +4022,7 @@ groups: type: text surface: S-CAP tier: expert + kernel-op: actor.tool help: > the server's own name for this action, when it is not the name you gave it here — `send_email` for an action you call `send-message`. @@ -3941,6 +4097,7 @@ groups: required: yes surface: S-EXEC tier: core + kernel-op: check.combine help: > the situation that makes this worth asking about. One line per situation, and two shapes exist: `{ tool: payments/issue-refund }`, @@ -3963,6 +4120,7 @@ groups: names: questions surface: S-EXEC tier: core + kernel-op: actor.person help: > which question to put to them, by name — one of the entries in `questions`. The question carries the wording and the shape of the @@ -4017,6 +4175,7 @@ groups: needs-one-of: [is, is-one-of, more-than, less-than, contains-any-of, is-empty, in-share, looks-like] surface: S-EXEC tier: core + kernel-op: store.read help: > the value to look at, as a binding — `steps.match.result`, `run-inputs.requester.verified`, `remembers.ai-reading-paused`, @@ -4060,6 +4219,7 @@ groups: may-be-money: yes surface: S-EXEC tier: core + kernel-op: check.compare help: > above this — `200 USD` for an amount of money, `80` for a score, `{value: steps.po.total}` for another field, or `{now-plus: 14 days}` @@ -4069,6 +4229,7 @@ groups: may-be-money: yes surface: S-EXEC tier: core + kernel-op: check.compare help: > below this — `5000 USD`, `85%`, `{value: steps.po.total}` or `{now-plus: 14 days}`. Compared in the value's own shape; a date @@ -4080,6 +4241,7 @@ groups: type: comparand surface: S-EXEC tier: core + kernel-op: check.compare help: > the one value that makes this hold — for example `is: enterprise` on a customer tier, or `{value: }` for another field. For several @@ -4088,6 +4250,7 @@ groups: type: list of text surface: S-EXEC tier: core + kernel-op: check.compare help: > the values that make this hold, any one of them — for example `is-one-of: [enterprise, government]`. @@ -4095,6 +4258,7 @@ groups: type: list of text surface: S-EXEC tier: core + kernel-op: check.compare help: > on text or a list: holds when any of these appears — the eval shape `must-contain:`, turned round. Words, not patterns. @@ -4105,6 +4269,7 @@ groups: choices: [reads, writes, spends, cannot-be-undone] surface: S-GOV tier: expert + kernel-op: [actor.effect, check.compare] help: > in an approval rule, beside `tool:`, holds when the action called does this — `writes` (changes anything), `spends` (moves money), @@ -4118,6 +4283,7 @@ groups: type: percent surface: S-GOV tier: expert + kernel-op: [shape.digest, check.digest] help: > beside `value:`, holds for this share of the values it could be — `{ value: input.request-id, in-share: 5% }` is true for one request in @@ -4128,6 +4294,7 @@ groups: type: text surface: S-GOV tier: expert + kernel-op: check.detector help: > beside `value:`, holds when the value contains something that looks like this — `card number`, `email address`, or the name of one of @@ -4136,6 +4303,7 @@ groups: type: yes-no surface: S-EXEC tier: core + kernel-op: check.compare help: > `yes` holds when the value is missing, blank, or an empty list; `no` holds when it is not. @@ -4151,12 +4319,14 @@ groups: type: text surface: S-EXEC tier: core + kernel-op: store.read help: > another value, as a binding — `{value: steps.po.total}` now-plus: type: duration surface: S-EXEC tier: core + kernel-op: check.typed help: > this long after now — `{now-plus: 14 days}`. Days are counted on the calendar in the value's zone, so one day is the same time tomorrow @@ -4165,6 +4335,7 @@ groups: type: duration surface: S-EXEC tier: core + kernel-op: check.typed help: > this long before now — `{now-minus: 30 days}` @@ -4215,6 +4386,7 @@ groups: shapes: *the-answer-shapes surface: S-EXEC tier: core + kernel-op: shape.scalar help: > what a valid answer looks like, one line per thing you want back — for example `approved: yes or no` or `amount: money`. The shapes are @@ -4240,6 +4412,7 @@ groups: type: list of text surface: S-EXEC tier: core + kernel-op: [label.fence, allowance.see] help: > which values to put in front of the person — an amount, an order number. They are shown as quoted data in their own box, never folded @@ -4265,6 +4438,7 @@ groups: required: yes surface: S-EXEC tier: core + kernel-op: actor.person help: > who is asked. Naming nobody means nobody can answer, not that anybody can, so this line is never left out — write the team or the person who @@ -4283,6 +4457,7 @@ groups: type: moment surface: S-GOV tier: core + kernel-op: [actor.park, flow.await] help: > how long they have to answer. Once that time is up the next line applies — somebody has to be watching the clock for that to happen, @@ -4297,6 +4472,7 @@ groups: enough-is: enough-of-them surface: S-EXEC tier: core + kernel-op: [actor.person, flow.join] help: > how many of `asked-of:` must answer. With `everyone`, all of them, in any order, and any `no` is a no. Waits drawn side by side are separate @@ -4332,6 +4508,7 @@ groups: type: list of text surface: S-EXEC tier: core + kernel-op: actor.identity help: > who may not answer: a binding to a person (`steps.po.raised-by`), or a stage, meaning whoever answered that stage. Separation of duties. @@ -4339,6 +4516,7 @@ groups: type: yes-no surface: S-EXEC tier: core + kernel-op: actor.identity help: whether the answer needs a fresh sign-in and is recorded as a signature starts-with: type: map of text @@ -4357,6 +4535,7 @@ groups: choices: [whenever-they-arrived, this-wait] surface: S-CTRL tier: core + kernel-op: [store.host, flow.await] help: > `this-wait` ignores events that arrived before this wait began. Left out, an event no wait was listening for is kept for the next wait @@ -4371,6 +4550,7 @@ groups: type: list of group:when-this surface: S-CTRL tier: core + kernel-op: [flow.await, check.combine] help: > in a question a workflow asks of a port, what has to hold of an event for this wait to hear it — every line, each looking at what the event @@ -4406,6 +4586,7 @@ groups: type: list of text surface: S-EXEC tier: core + kernel-op: actor.person help: who it goes to next, when the line above says escalate checked-by: # The check a SHAPE cannot make. `answer:` holds each field to a kind of @@ -4427,6 +4608,7 @@ groups: names: programs surface: S-EXEC tier: expert + kernel-op: actor.program help: > a carried program that looks at the answer before the run goes on with it, by name — for the checks a shape cannot make, like an account @@ -4467,6 +4649,7 @@ groups: names: workflows surface: S-EXEC tier: core + kernel-op: flow.detach help: > start this workflow with the run's data and do not wait for it — the one side action. It never changes this run's path. @@ -4495,6 +4678,7 @@ groups: reviewed-by: sampled-frame surface: S-GOV tier: core + kernel-op: store.host help: > where these cases came from. `authored-enumeration` means you wrote down the situations you thought of — which is a fine place to start, @@ -4545,6 +4729,7 @@ groups: type: percent surface: S-GOV tier: core + kernel-op: check.aggregate help: > the share of examples that must come out right. Set it once, from how many cases you plan to write; it must not move under you when you @@ -4582,6 +4767,7 @@ groups: type: list of group:metric surface: S-GOV tier: expert + kernel-op: check.aggregate help: > ready-made scores to put every answer through, on top of `rules:`. This is the door for somebody who already has a set of measures they @@ -4609,6 +4795,7 @@ groups: surface: S-GOV model-kind: [language] tier: core + kernel-op: actor.model help: > which model grades the judged rules, by name — one of the entries in `models/catalog.yaml`. Point it at a different model from the one doing @@ -4649,6 +4836,7 @@ groups: type: group:call-order surface: S-GOV tier: core + kernel-op: check.count help: one action has to have been called before another one is judged: type: text @@ -4666,6 +4854,7 @@ groups: names: programs surface: S-GOV tier: expert + kernel-op: actor.program help: > a carried `pure` program that passes or fails the answer, with its reason. It is given the answer, what the stage was given and what its @@ -4725,6 +4914,7 @@ groups: type: percent surface: S-GOV tier: expert + kernel-op: check.aggregate help: > how high that score has to be before an answer counts as passing, written `0.8` or `80%`. Leave it out and the bar is half marks, which @@ -4922,6 +5112,7 @@ groups: choices: [tools, permissions, team, limits, evals, policy-clauses] surface: S-GOV tier: core + kernel-op: allowance.write help: changes to what it can DO, which always need review keep-only-if: type: one-of @@ -5035,6 +5226,7 @@ groups: names: pact:models surface: S-CAP tier: expert + kernel-op: actor.model help: > which exact model, by the id `models/catalog.yaml` publishes. Leave it out and PACT picks. One that only runs somewhere else is refused when @@ -5133,6 +5325,7 @@ groups: every: schedule surface: S-TOPO tier: core + kernel-op: flow.starts-when help: > how work arrives. `conversation` is a person talking to it, `schedule` is the clock — write `every:` beside it saying when — @@ -5144,6 +5337,7 @@ groups: type: text surface: S-TOPO tier: core + kernel-op: flow.starts-when # `kind: schedule` with no `every:` is refused, and the refusal quotes # this setting back — *"so nothing says …"*. It quoted the first clause # of the help below and read *"so nothing says when to run — plain words @@ -5173,6 +5367,7 @@ groups: choices: [start, queue, join, cancel-previous, skip, stop] surface: S-CTRL tier: core + kernel-op: flow.starts-when help: > what an arrival does when a run with the same key is open, on any kind of port. `start` begins another anyway; `queue` waits its turn; `join` @@ -5199,6 +5394,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: core + kernel-op: shape.scalar help: > the shape of what arrives, when it differs from the workflow's own `accepts:`. Same-named fields fill the workflow's input; others need @@ -5215,11 +5411,13 @@ groups: type: list of group:when-this surface: S-EXEC tier: core + kernel-op: check.combine help: what has to hold of what arrived, or it is dropped and the drop recorded signed-with: type: group:credential-reference surface: S-EXEC tier: core + kernel-op: allowance.use-without-seeing help: the secret an event must be signed with; anything unsigned is refused at the door in-time-zone: type: text @@ -5231,12 +5429,14 @@ groups: choices: [run-when-back, skip] surface: S-CTRL tier: core + kernel-op: flow.starts-when help: on a timer, what to do about a time that passed while nothing was running per-row-of: type: text names: values surface: S-TOPO tier: core + kernel-op: flow.starts-when help: > on a timer, one run per row of this table, each row being the run's input — for clinics in three zones, `in-time-zone: input.zone`. @@ -5244,6 +5444,7 @@ groups: type: duration surface: S-GOV tier: core + kernel-op: actor.park help: > on `inbound-call`, how long the caller waits for the answer. After that it gets the run's id and a link where the answer will appear. @@ -5281,11 +5482,13 @@ groups: names: workflows surface: S-EXEC tier: core + kernel-op: flow.detach help: with `stop`, a workflow to start afterwards with the run's data, not waited for who-can-reach-it: type: list of text surface: S-EXEC tier: core + kernel-op: allowance.reach help: > who is allowed to start work here. Naming nobody means nobody, not everybody. @@ -5299,6 +5502,7 @@ groups: type: map of group:state surface: S-GOV tier: expert + kernel-op: store.write help: what this port keeps between messages, such as where it last replied # ── WORKFLOW ──────────────────────────────────────────────────────────────── @@ -5329,6 +5533,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: core + kernel-op: [shape.scalar, actor.signature] help: > what starts a run, one line per thing. Every port that `answers:` this workflow fills it, so several ways in reach one input. @@ -5337,6 +5542,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: core + kernel-op: [shape.scalar, actor.signature] help: > what a run hands back, given by its `does: answer` stage. Leave it out for a flow nobody waits on. @@ -5346,6 +5552,7 @@ groups: names: ^steps surface: S-CTRL tier: core + kernel-op: flow.then help: which stage runs first steps: type: map of group:stage @@ -5359,6 +5566,7 @@ groups: type: map of group:question surface: S-EXEC tier: core + kernel-op: actor.person help: > questions only this workflow asks, with the same fields as a file in `questions/`. Every `asks:` in this workflow (a stage's, a limit's) @@ -5368,11 +5576,13 @@ groups: names: policies surface: S-EXEC tier: core + kernel-op: flow.handler help: which policy says what needs a person's yes before it happens limits: type: group:limits surface: S-GOV tier: core + kernel-op: allowance.quantity help: how long, how much and how many, for the whole run evals: type: text @@ -5384,11 +5594,13 @@ groups: type: map of group:state surface: S-GOV tier: core + kernel-op: store.write help: memory that belongs to this flow, kept across its runs hides: type: map of list of text surface: S-EXEC tier: core + kernel-op: [label.sensitivity, allowance.see] help: > values some readers may never see, one line per binding — `input.student-name: [logs]`. Readers are `the-ai`, `logs`, a role, @@ -5397,6 +5609,7 @@ groups: type: text surface: S-GOV tier: core + kernel-op: label.region help: > the region this run, its record and every model call it makes stay in — a region your runtime names, or a binding (`input.region`). A @@ -5405,6 +5618,7 @@ groups: type: moment surface: S-GOV tier: core + kernel-op: [store.lifetime, store.forget] help: > how long a run's record is kept after it ends — `90 days`, or `{at: input.matter-closed-on, after: 2555 days}` (seven years). @@ -5416,6 +5630,7 @@ groups: type: text surface: S-EXEC tier: core + kernel-op: actor.effect help: > how to undo a finished run of this flow when a caller's undo reaches it — a `/` or another workflow. Leave it out and its own @@ -5425,6 +5640,7 @@ groups: choices: [tool, mcp-tool, a2a-agent, api] surface: S-EXEC tier: core + kernel-op: [shape.describe, actor.composite] help: > other ways to reach this flow, built from `accepts:` and `answers-with:`: `tool` lets an agent name it in `uses:`. Who may @@ -5470,6 +5686,7 @@ groups: choices: [calendar-days, business-days] surface: S-GOV tier: core + kernel-op: check.typed help: > `business-days` skips weekends and the holidays in the workspace's `calendar:`. Leave it out for calendar days. @@ -5490,12 +5707,14 @@ groups: type: text surface: S-GOV tier: core + kernel-op: [actor.identity, allowance.write] help: the person or team whose yes a change to this part needs locked: type: one-of choices: [no, here, here-and-below] surface: S-GOV tier: core + kernel-op: allowance.write help: > `here` means it may not be removed in this workspace; `here-and-below` means not in any workspace based on this one either. @@ -5504,6 +5723,7 @@ groups: needs-also: [locked] surface: S-GOV tier: core + kernel-op: allowance.revoke help: > when the lock ends — `{at: values.exam-series-ends}`. Leave it out for a lock with no end. A Brain tier locked this way keeps its model. @@ -5516,6 +5736,7 @@ groups: names: questions surface: S-GOV tier: core + kernel-op: actor.person help: > the question that approves publishing — the same Wait as any other, so several approvers or a quorum is `answered-by:` on it. It is asked @@ -5564,6 +5785,7 @@ groups: names: ^steps surface: S-CTRL tier: expert + kernel-op: flow.then help: which stage runs first # Called `stage` because that is the word the author reads everywhere else — @@ -5612,6 +5834,7 @@ groups: # Was `expert`. A workflow's stage is drawn by a no-code author in the # Outline (02W §2.3), so what it does is a core line. tier: core + kernel-op: [flow.ask, flow.choose, flow.split, flow.cancel, actor.park, actor.instantiate] help: > what happens in this stage. `ask-someone` needs an `asks:` line beside it naming the question, or the stage stops with wording and nothing @@ -5634,6 +5857,7 @@ groups: type: yes-no surface: S-EXEC tier: expert + kernel-op: store.fork help: > `yes` makes each attempt at this stage start from the conversation as it stood when the stage began, so no attempt reads another, and the @@ -5647,6 +5871,7 @@ groups: type: yes-no surface: S-EXEC tier: expert + kernel-op: flow.join help: > on an `each`, `yes` takes items added to the `over:` list while it is running, too, and ends once the list has stopped growing and nothing @@ -5658,6 +5883,7 @@ groups: summary: which detached run it ends surface: S-EXEC tier: expert + kernel-op: [flow.cancel, shape.reference] help: > on a `stop` stage, the run to stop — `steps.recheck.run`, from a `call` with `waits-for-result: no`. What it did is undone as its own @@ -5679,6 +5905,7 @@ groups: names: [^questions, questions] surface: S-EXEC tier: core + kernel-op: actor.person # `does: ask-someone` with no `asks:` is refused, and the refusal quotes # this setting back. Reading the first clause of the help below, it read # *"so nothing says for a stage that does `ask-someone`"* — the @@ -5697,6 +5924,7 @@ groups: type: group:outcome surface: S-CTRL tier: expert + kernel-op: flow.then help: > where to go next, by outcome. There are three outcomes and they are the only ones: `used-a-tool`, `answered`, and `too-many-times` (which @@ -5727,6 +5955,7 @@ groups: names: [tools, skills, knowledge, agents, programs, workflows] surface: S-ROUTE tier: expert + kernel-op: allowance.reach help: > what this stage may reach: any of the tools, written procedures and teammates the agent itself may use, and nothing else. Say nothing and a @@ -5747,6 +5976,7 @@ groups: at-least: 1 surface: S-GOV tier: expert + kernel-op: allowance.quantity help: > how many times this stage may run. Reaching it produces the `too-many-times` outcome, so you can send the agent somewhere sensible @@ -5762,6 +5992,7 @@ groups: type: group:program-fuel surface: S-GOV tier: expert + kernel-op: allowance.quantity summary: how long and how much its code may run help: > on `run-code`, what one run of the code the model writes may spend @@ -5781,6 +6012,7 @@ groups: names: resources surface: S-EXEC tier: expert + kernel-op: [actor.program, actor.location] help: > on `run-code`, which locked room the code runs in, by name — one of the entries in `resources` whose `resource-kind:` is `sandbox` and @@ -5798,6 +6030,7 @@ groups: type: text surface: S-TOPO tier: core + kernel-op: [flow.ask, shape.reference] summary: what this stage runs help: > what this stage runs once: `/`, an agent, a carried @@ -5808,6 +6041,7 @@ groups: needs-also: [call] surface: S-EXEC tier: core + kernel-op: allowance.reach help: > every target a `call:` written as a binding may run. A value outside this list stops the run rather than calling something nobody approved. @@ -5815,6 +6049,7 @@ groups: type: map of text surface: S-EXEC tier: core + kernel-op: flow.ask help: > the inputs, `: ` — `email: input.email`. Fixed wording and figures come from `values.`. A part of an input is written @@ -5828,6 +6063,7 @@ groups: type: yes-no surface: S-CTRL tier: core + kernel-op: flow.detach help: > on a call to a workflow, `no` starts it and goes on at once. The stage gets back the new run as `.run`, or `.outcome: already-done` when that @@ -5836,6 +6072,7 @@ groups: type: text surface: S-GOV tier: core + kernel-op: store.version help: > on a call to a workflow: `latest` (the default), a version number, or a binding to a date (`input.applied-on`) for the version that was in @@ -5846,6 +6083,7 @@ groups: or-one-of: [done] surface: S-CTRL tier: core + kernel-op: flow.choose summary: which labels it can pick, and where each one leads help: > the labels this decision can pick, each with the stage it leads to — @@ -5857,6 +6095,7 @@ groups: type: list of group:rung surface: S-CTRL tier: core + kernel-op: flow.choose help: > who decides, tried in order: written rules, then agents (cheapest first), then a person. A rung that cannot decide passes the decision @@ -5865,6 +6104,7 @@ groups: type: text surface: S-CTRL tier: core + kernel-op: [flow.split, store.read] summary: which list to go through help: > the list to go through: a binding to a list (`input.attachments`). @@ -5894,6 +6134,7 @@ groups: top-n: ["top highest by ", "top lowest by "] surface: S-CTRL tier: core + kernel-op: [store.merge, flow.join] help: > how each field of the items' answers becomes one, one line per field — `codes: vote`, `confidence: top 1 lowest by confidence`. Unnamed @@ -5911,6 +6152,7 @@ groups: type: map of group:stage surface: S-CTRL tier: core + kernel-op: flow.scope help: > the inside of an `each`, a `repeat` or a `together`. Its stages go to each other or to `done`, which ends that item, that round or that branch. @@ -5919,11 +6161,13 @@ groups: names: ^steps surface: S-CTRL tier: core + kernel-op: flow.then help: which inside stage runs first (not on `together`, where every inside stage starts at once) until: type: list of group:when-this surface: S-CTRL tier: core + kernel-op: check.combine help: > on `repeat`, what has to be true for it to stop, checked when each round ends; every line must hold. Leave it out to always run @@ -5932,6 +6176,7 @@ groups: type: map of group:state surface: S-GOV tier: core + kernel-op: store.write help: > on `repeat`, what survives from one round to the next. Read inside as `remembers.` and afterwards as `steps..`. @@ -5949,6 +6194,7 @@ groups: needs-also: [checked-by] surface: S-GOV tier: core + kernel-op: allowance.quantity help: > how many attempts in all, asking again with the reason after each failed check. Leave it out for 2; write `1` to fail at the first miss. @@ -5957,6 +6203,7 @@ groups: type: text surface: S-EXEC tier: core + kernel-op: actor.effect help: > how to undo what this stage did — a `/` or a workflow — in place of its action's own `undone-by:`. @@ -5964,6 +6211,7 @@ groups: type: group:limits surface: S-GOV tier: core + kernel-op: [flow.scope, allowance.quantity] help: > ceilings and a deadline for this stage and everything inside it. On `each`, `items-at-most:` is required here. @@ -5973,6 +6221,7 @@ groups: type: group:if-it-fails surface: S-EXEC tier: core + kernel-op: flow.handler help: > what happens once every attempt at this stage has failed — call a backup, carry on with set values, ask a person, wait for a new @@ -5992,6 +6241,7 @@ groups: type: list of group:route-rule surface: S-EXEC tier: core + kernel-op: flow.choose help: > written rules, tried in order; the first whose conditions all hold picks its label. None holding passes the decision to the next rung. @@ -6000,6 +6250,7 @@ groups: names: agents surface: S-ROUTE tier: core + kernel-op: flow.choose help: > an agent that picks a label. Its `answers-with:` has one `one of` line naming these labels, and may add `not-sure`, which passes the @@ -6022,6 +6273,7 @@ groups: needs-also: [agent] surface: S-GOV tier: core + kernel-op: flow.choose help: > for a decision model, the score under which it passes the decision on to the next rung. A score exactly at the figure decides, and an @@ -6036,6 +6288,7 @@ groups: needs-also: [agent] surface: S-GOV tier: core + kernel-op: flow.choose help: > `go-on` makes this rung a second opinion: it is asked even when a rung before it has decided. The same label, and that decision @@ -6049,6 +6302,7 @@ groups: names: [^questions, questions] surface: S-EXEC tier: core + kernel-op: [actor.person, flow.choose] help: > the person rung: a question whose answer has a `one of` line naming these labels. It is always last. @@ -6062,6 +6316,7 @@ groups: type: list of group:when-this surface: S-EXEC tier: core + kernel-op: [check.combine, flow.choose] help: > the conditions, every one of which must hold. Leave it out on the last rule for "otherwise". @@ -6073,6 +6328,7 @@ groups: required: yes surface: S-EXEC tier: core + kernel-op: flow.choose help: which label this rule picks because: type: text @@ -6100,6 +6356,7 @@ groups: undo: undo surface: S-EXEC tier: core + kernel-op: flow.handler help: > what happens once every attempt has failed. `use-a-backup` calls `backup:` with the same inputs. `carry-on` goes on with the values in @@ -6133,6 +6390,7 @@ groups: names: [^questions, questions] surface: S-EXEC tier: core + kernel-op: actor.person summary: which question to put to them help: which question to put to them when the line above says ask-a-person new-version-of: @@ -6170,6 +6428,7 @@ groups: or-one-of: [done] surface: S-CTRL tier: expert + kernel-op: flow.then help: where to go when the stage called a tool answered: type: text @@ -6177,6 +6436,7 @@ groups: or-one-of: [done] surface: S-CTRL tier: expert + kernel-op: flow.then help: where to go when the stage produced an answer too-many-times: type: text @@ -6184,6 +6444,7 @@ groups: or-one-of: [done] surface: S-CTRL tier: expert + kernel-op: flow.then help: where to go when the stage has run its `at-most:` times declined: # 02W §2.4: what a workflow's `ask-someone` can end in, besides @@ -6193,6 +6454,7 @@ groups: or-one-of: [done] surface: S-CTRL tier: core + kernel-op: flow.then help: > where to go when the person's `yes or no` came back no. Leave it out and the run stops and says so: a no is never quietly carried past. @@ -6202,6 +6464,7 @@ groups: or-one-of: [done, stop-and-say-so] surface: S-CTRL tier: core + kernel-op: flow.then help: > where to go when `answer-within:` arrives with no answer. On a question asked only of the clock, this is the one exit, taken when @@ -6214,6 +6477,7 @@ groups: or-one-of: [done] surface: S-CTRL tier: core + kernel-op: flow.await help: > where to go when an event arrives, one line per port — `{fee-received: gather}`. What it brought is `latest-update.`. @@ -6239,6 +6503,7 @@ groups: needs-also: [may-go-to] surface: S-CTRL tier: expert + kernel-op: [actor.program, flow.then] help: > a carried program that says which stage to go to next, by name — for when where to go depends on what was actually said rather than on what @@ -6258,6 +6523,7 @@ groups: needs-also: [decided-by] surface: S-CTRL tier: expert + kernel-op: flow.then help: > every stage the program above may send this one to, and `done` for finishing. It picks between these and nothing else — a name outside @@ -6297,6 +6563,7 @@ groups: type: list of text surface: S-CTRL tier: core + kernel-op: store.pin help: > what must survive tidying no matter what — for example `the refund policy`, `anything a person approved`. Nothing else in @@ -6320,6 +6587,7 @@ groups: model-kind: [language] surface: S-CAP tier: expert + kernel-op: actor.model help: > which model writes the summary, by name. Keep it separate from the one doing the work: a weaker model summarising its own history compounds @@ -6344,6 +6612,7 @@ groups: names: questions surface: S-EXEC tier: core + kernel-op: actor.person # Same setting, same sentence, same `summary:` — see `limits.asks`. summary: which question to put to them help: > @@ -6360,6 +6629,7 @@ groups: required: yes surface: S-CTRL tier: expert + kernel-op: store.forget help: > which tidying move this is. `summarise-older` folds older messages into one; pictures and voice messages in them are kept beside the summary, @@ -6465,6 +6735,7 @@ groups: required: yes surface: S-CTRL tier: core + kernel-op: flow.handler help: > the moments it runs at. Write one on a line of its own with a `- ` in front, as `..` — a card-number rule that has to @@ -6716,6 +6987,7 @@ groups: - step.message.before surface: S-EXEC tier: core + kernel-op: flow.handler help: > what it does, in plain sentences. Eight sentences exist and they are the only ones, because a sentence nobody can turn into behaviour is a @@ -6830,6 +7102,7 @@ groups: always-may: [hide-values] surface: S-EXEC tier: core + kernel-op: [label.sensitivity, check.detector] help: > what must never leave, in plain sentences — one on each line with a `- ` in front. Three sentences: `anything that looks like a card @@ -6939,6 +7212,7 @@ groups: required: yes surface: S-GOV tier: core + kernel-op: flow.handler help: > the moment to write down, written as `..` — for example `step.tool.completed`, which is every time one of its tools has @@ -6959,6 +7233,7 @@ groups: required: yes surface: S-GOV tier: core + kernel-op: store.write help: > the name of the file to write it down in — for example `tool-calls.jsonl`. One line each time it happens. Just a name, no @@ -7051,6 +7326,7 @@ groups: needs-also: [at] surface: S-CTRL tier: core + kernel-op: check.count help: > how many times the moment has to have happened in one run before this holds — `more-than: 5` beside `at: step.run.started` holds from the @@ -7136,6 +7412,7 @@ groups: needs-also: [at] surface: S-CTRL tier: core + kernel-op: check.count help: > how many times the moment has to have happened in one run before this holds — `more-than: 5` beside `at: step.run.started` holds from the @@ -7271,6 +7548,7 @@ groups: required: yes surface: S-GOV tier: core + kernel-op: store.scope help: > how long it survives. `forever` means across conversations, which is the one thing that needs care: it is memory about a person. @@ -7282,6 +7560,7 @@ groups: shapes: *the-answer-shapes surface: S-CAP tier: expert + kernel-op: shape.scalar help: > what it holds, one line per part in the shapes `accepts:` takes, so a change of shape is caught rather than corrupting @@ -7297,6 +7576,7 @@ groups: type: duration surface: S-GOV tier: core + kernel-op: [store.lifetime, store.forget] help: > the old name of `kept-for:`, read the same way for one more release. Rename it `kept-for:`; the value is unchanged. @@ -7304,6 +7584,7 @@ groups: type: moment surface: S-GOV tier: core + kernel-op: [store.lifetime, store.forget] help: > when to discard it; replaces `forget-after:`. Required in practice for anything remembered about a person. @@ -7314,6 +7595,7 @@ groups: type: text surface: S-GOV tier: core + kernel-op: store.scope help: > keep one of these per value of this input field — `specialty`, `customer-id`. A run reads the one for its own value. @@ -7329,6 +7611,7 @@ groups: needs-also: [comes-from] surface: S-CTRL tier: core + kernel-op: store.merge help: how each round's value joins what is already remembered; `top ` over a round's best (a list) ranks the items it holds, so the best few are carried round to round shared-with: # docs/98 WP-8: the team store. docs/remediation/F7 refused a blackboard @@ -7341,6 +7624,7 @@ groups: choices: [this-agent, the-team] surface: S-EXEC tier: expert + kernel-op: store.scope help: > who may read and write this. `this-agent` is the default: only the agent that remembers it. `the-team` lets every teammate read it and @@ -7350,6 +7634,7 @@ groups: type: list of group:when-this surface: S-GOV tier: expert + kernel-op: check.combine help: > what an entry has to look like to be written here, every line holding — `{ value: entry.status, is-one-of: [open, solved] }`. Read a part of @@ -7387,6 +7672,7 @@ groups: - a teammate surface: S-EXEC tier: core + kernel-op: [check.origin, label.propagate] help: > sources that may never write here — tool output above all. Letting a tool result become remembered instruction is how a single poisoned @@ -7411,6 +7697,7 @@ groups: type: yes-no surface: S-EXEC tier: core + kernel-op: store.pin help: > whether this is still known after the conversation is shortened. Say yes for anything a rule later checks — that a person approved @@ -7426,6 +7713,7 @@ groups: needs-also: [survives-shortening] surface: S-EXEC tier: core + kernel-op: store.forget help: > what makes it stale — for example `the file changes` or `the turn ends`. When one of these happens the fact is forgotten and anything @@ -7445,6 +7733,7 @@ groups: type: text surface: S-META tier: core + kernel-op: store.version help: > which version of it this workspace expects, so an upgrade that changes behaviour is a line somebody edited rather than something that @@ -7568,6 +7857,7 @@ groups: choices: [catalogue, narrowed-new] surface: S-TOPO tier: core + kernel-op: [allowance.start, actor.instantiate] help: > who this agent may bring in while it runs, beyond its `team:`. `catalogue`: any agent of this workspace that runs (not a `base:`), @@ -7598,6 +7888,7 @@ groups: gives-up-after: whoever-answers-in-time surface: S-TOPO tier: core + kernel-op: flow.join help: > how much of the team has to come back before this agent carries on. Leave it out and it waits for all of them. `everyone` waits for all of @@ -7611,6 +7902,7 @@ groups: at-least: 1 surface: S-TOPO tier: core + kernel-op: flow.join # `waits-for: enough-of-them` with no `enough-is:` is a barrier nobody # can clear, so it is refused — and the refusal quoted the first clause # of the help below, which read *"so nothing says with @@ -7625,6 +7917,7 @@ groups: type: duration surface: S-GOV tier: core + kernel-op: flow.join # And the same sentence on the other wait: *"so nothing says with # `whoever-answers-in-time`"*. summary: how long to wait before carrying on @@ -7637,6 +7930,7 @@ groups: choices: [all-at-once, one-after-another] surface: S-CTRL tier: core + kernel-op: flow.split help: > whether the team works at the same time or takes turns. Turns are slower, and are worth it only when a later member needs to read what @@ -7646,6 +7940,7 @@ groups: choices: [evenly, by-share, as-needed] surface: S-GOV tier: core + kernel-op: allowance.pass help: > how the money and thinking room this agent is allowed get shared out among the team. `evenly` gives each the same. `by-share` uses the @@ -7655,6 +7950,7 @@ groups: type: map of percent surface: S-GOV tier: core + kernel-op: allowance.pass help: > with `by-share`, how much of the budget each member gets — written as percentages, one line per member. Everyone under `team:` needs a line @@ -7668,6 +7964,7 @@ groups: asks: ask-a-person surface: S-EXEC tier: core + kernel-op: flow.cancel help: > what happens when one of them cannot answer. `carry-on` says so in the answer and keeps the rest. `stop-the-others` gives up the whole @@ -7678,6 +7975,7 @@ groups: names: [^questions, questions] surface: S-EXEC tier: core + kernel-op: actor.person # Same setting, same sentence, same `summary:` — see `limits.asks`. summary: which question to put to them help: >