pi-fabric 0.22.0 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ # Ambient actor setup
2
+
3
+ Pass `strings.name`, `strings.instructions`, JSON `strings.events`, `strings.triggerTurn` (`"true"`/`"false"`), and `strings.model` (key/substring; empty when unset).
4
+
5
+ ```ts
6
+ const events = JSON.parse(π.events) as FabricActorHostEvent[];
7
+ const triggerTurn = π.triggerTurn === "true";
8
+ let model: string | undefined;
9
+ let runner: FabricAgentRunner | undefined;
10
+ if (π.model) {
11
+ const models: Array<FabricModelInfo & { runner: FabricAgentRunner }> = (
12
+ await tools.models()
13
+ ).map((entry) => ({ ...entry, runner: "pi" as const }));
14
+ try {
15
+ models.push(...(await agents.models({ runner: "claude" })).map((entry) => ({
16
+ ...entry,
17
+ runner: "claude" as const,
18
+ })));
19
+ } catch {}
20
+ const needle = π.model.toLowerCase();
21
+ const selected = models.find((entry) =>
22
+ entry.key.toLowerCase() === needle ||
23
+ entry.id.toLowerCase().includes(needle) ||
24
+ entry.name.toLowerCase().includes(needle)
25
+ );
26
+ if (!selected) throw new Error(`Model "${π.model}" not found: ${models.map((entry) => entry.key).join(", ")}`);
27
+ model = selected.key;
28
+ runner = selected.runner;
29
+ }
30
+
31
+ const existing = (await agents.actors()).find(
32
+ (actor) => actor.name === π.name && actor.status !== "stopped",
33
+ );
34
+ if (existing) {
35
+ await agents.setInstructions({ id: existing.id, instructions: π.instructions });
36
+ if (existing.events.length !== events.length || events.some((event) => !existing.events.includes(event))) {
37
+ await agents.setEvents({ id: existing.id, events });
38
+ }
39
+ if (existing.delivery !== "steer" || existing.triggerTurn !== triggerTurn) {
40
+ await agents.setDeliveryPolicy({ id: existing.id, delivery: "steer", triggerTurn });
41
+ }
42
+ const runnerMatches = !runner || existing.runner === runner;
43
+ const modelMatches = !model || existing.model === model;
44
+ return {
45
+ reused: true,
46
+ actor: await agents.actorStatus({ id: existing.id }),
47
+ warnings: [
48
+ ...(existing.responseMode !== "directive" ? ["recreate for responseMode=directive"] : []),
49
+ ...(existing.coalesce !== true ? ["recreate for coalesce=true"] : []),
50
+ ...(!runnerMatches ? [`runner "${runner}" requires recreation`] : []),
51
+ ...(runnerMatches && !modelMatches ? [`model "${model}" requires a dashboard change or recreation`] : []),
52
+ ],
53
+ };
54
+ }
55
+
56
+ const actor = await agents.create({
57
+ name: π.name,
58
+ instructions: π.instructions,
59
+ events,
60
+ responseMode: "directive",
61
+ delivery: "steer",
62
+ triggerTurn,
63
+ coalesce: true,
64
+ tools: ["read", "grep", "find", "ls"],
65
+ ...(runner ? { runner } : {}),
66
+ ...(model ? { model } : {}),
67
+ });
68
+ return { started: true, actor };
69
+ ```
70
+
71
+ Reuse updates instructions/events/delivery. Recreate for runner, `responseMode`, or `coalesce`; use dashboard/recreation for model. Report ID/warnings and messages/stop; do not wait.
@@ -1,14 +1,12 @@
1
1
  ---
2
2
  name: fabric-council
3
- description: Runs a bounded multi-perspective Pi Fabric council with independent reviewers and an optional synthesis decision. Use for architecture choices, plans, reviews, and adversarial cross-checking.
3
+ description: Runs a bounded multi-perspective Pi Fabric council with independent reviewers and optional synthesis. Use for architecture choices, plans, reviews, and adversarial cross-checking.
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
7
  # Fabric Council
8
8
 
9
- Use one `fabric_exec` call and `council.run()`. Choose roles that disagree usefully rather than duplicating one another.
10
-
11
- A council runs N role agents concurrently plus a sequential synthesizer, all inside the single `fabric_exec` sandbox. The sandbox has a hard wall-clock ceiling (`executor.timeoutMs`, default 120s; raise it in `fabric.json` or `/fabric` settings for read-heavy councils). Keep roles and per-role tool work bounded so `max(role durations) + synthesizer` fits the ceiling, or the sandbox times out and aborts every in-flight agent. Council and `rlm.query` usage counts toward `budget.spent()` and the `tokenBudget` guard.
9
+ Use one `fabric_exec` call and `council.run()`. Choose three to five roles that disagree usefully rather than duplicating one another, such as correctness, security, operability, maintainability, and requirements skepticism.
12
10
 
13
11
  ```ts
14
12
  const roles = JSON.parse(π.roles) as string[];
@@ -17,25 +15,14 @@ await workflow.configure({
17
15
  description: `${roles.length} independent perspectives with synthesis`,
18
16
  });
19
17
  await phase("Deliberate", { total: roles.length });
20
- const decision = await council.run({
18
+ return council.run({
21
19
  task: π.task,
22
20
  roles,
23
21
  tools: ["read", "grep", "find", "ls"],
24
22
  synthesize: true,
25
23
  });
26
- return decision;
27
24
  ```
28
25
 
29
- Pass the full task and role array through `strings`. Usually use three to five roles. Examples:
30
-
31
- - implementation correctness reviewer
32
- - security and abuse-case reviewer
33
- - test and operability reviewer
34
- - simplicity and maintenance reviewer
35
- - user-requirements skeptic
36
-
37
- Council members must inspect evidence independently. The synthesizer must preserve material disagreement, reject unsupported claims, and make a concrete recommendation. Use `synthesize: false` only when the user asked for raw independent opinions.
38
-
39
- Do not use a council for a simple lookup or a decision with no meaningful competing considerations.
26
+ Pass the task as `strings.task` and the JSON role array as `strings.roles`. Members inspect evidence independently; synthesis must preserve material disagreement, reject unsupported claims, and make a concrete recommendation. Use `synthesize: false` only for raw opinions.
40
27
 
41
- For a long-running deliberation where a member drifts, you can run members with `agents.spawn` and redirect one between its turns via `agents.steer({ id, message })` instead of `council.run`'s blocking fan-out; `agents.status({ id }).pendingMessages` shows the queued steers. See the `agents` reference.
28
+ A council costs N concurrent role agents plus one sequential synthesizer and must fit `executor.timeoutMs`; its usage counts toward the execution budget. Keep roles bounded and set top-level `agentBudget`/`tokenBudget` for large runs. Do not use a council for a simple lookup or a decision with no meaningful competing considerations.
@@ -1,13 +1,10 @@
1
1
  ---
2
2
  name: fabric-exec
3
3
  description: >-
4
- Reference for writing `fabric_exec` TypeScript programs in the isolated
5
- QuickJS runtime (or the trusted unsafe Node-process escape hatch): the mental model (one program, return only the final value), the
6
- core `pi` tools (read/bash/edit/write/grep/find/ls) with exact signatures,
7
- `tools` discovery, `π` named strings, first-class Fabric provider and MCP
8
- proxies, and the validate, describe, retry error loop. Load before your first
9
- `fabric_exec` call and whenever a call errors on argument shape. See
10
- `references/mcp.md` for MCP naming and management.
4
+ Reference for `fabric_exec` TypeScript programs: Pi core tool signatures,
5
+ discovery, provider and MCP proxies, named strings, return shapes, and
6
+ schema-driven error recovery. Load before the first Fabric call or after an
7
+ argument-shape error.
11
8
  ---
12
9
 
13
10
  # fabric_exec — core reference
@@ -29,6 +26,8 @@ One type-checked TS program in a fresh executor (isolated QuickJS by default). O
29
26
 
30
27
  Aliases (normalized to canonical before the host validates args): `cmd`/`shell`/`cmdline`→`command`, `timeoutMs`→`timeout`; `query`/`regex`/`search`→`pattern`; `ic`/`caseInsensitive`→`ignoreCase`; `globPattern`→`glob`; `ctx`→`context`; `max`→`limit`; `file`/`dir`→`path`; `start`→`offset`; `old`→`oldText`; `new`/`replacement`→`newText`; `contents`/`body`/`text`→`content`. Misspelled keys still fail the excess-property type check.
31
28
 
29
+ When a program needs a string containing literal `${...}` (shell snippets, tool arguments, or grep patterns), do not use a TypeScript template literal: TypeScript will interpolate it. Use a plain quoted string or pass the content through `strings` and read it from `π.key`.
30
+
32
31
  ## First-class provider calls
33
32
  Use direct proxies when the action is known. No-argument actions such as `schema.status()`, `state.get()`, and `compact.status()` take no options object. Provider calls still cross the same registry validation, approval, audit, timeout, and cancellation path as generic calls.
34
33
 
@@ -78,3 +77,5 @@ Multi-agent orchestration is opt-in: load `/skill:fabric-workflow`, `/skill:fabr
78
77
  `agents.main()` returns the dashboard-owning root Pi session; `agents.peers()` lists other live root sessions in the shared project mesh as `Peer <session-prefix>` targets. Peers support `agents.steer()` and `agents.followUp()` by exact id.
79
78
 
80
79
  Agent requests and persistent actors accept `runner: "pi" | "claude"`. Pi is the default and is required for `recursive: true`, `rlm.query()`, and actors that must call Fabric or mesh APIs themselves. Claude invokes the official `claude -p` harness; it supports mapped Claude Code tools and host-managed persistent actors, but not recursive/direct Fabric APIs. Use `agents.models({ runner: "claude" })` for runtime-enumerated `claude/<value>` model keys.
80
+
81
+ Omit `timeoutMs` for subagents and actors unless requesting longer than the configured `subagents.timeoutMs` (60 minutes by default). Per-call values below the configured default are ignored.
@@ -1,16 +1,14 @@
1
1
  ---
2
2
  name: fabric-fusion
3
- description: Multi-model deliberation. A panel of up to 8 distinct models answers a task in parallel (each with web access), then a judge model compares their responses into structured analysis (consensus, contradictions, coverage gaps, unique insights, blind spots) that the caller turns into a better final answer. Use for research, expert critique, and compare-and-contrast where the cost of being wrong outweighs the cost of a few extra completions.
3
+ description: Multi-model deliberation. Up to 8 distinct models answer in parallel with web-capable tools, then a judge compares consensus, contradictions, coverage gaps, unique insights, and blind spots. Use when the cost of being wrong justifies multiple completions.
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
7
  # Fabric Fusion
8
8
 
9
- Use one `fabric_exec` call. A panel of up to 8 distinct models answers the task in parallel, each with web access, then a judge model compares (does not merge) their responses and returns structured analysis: consensus, contradictions, partial coverage, unique insights, blind spots. Return that analysis; the caller writes the final answer from it. This mirrors OpenRouter's Fusion Router (`openrouter/fusion`): the panel and the judge are the inner deliberation, and the program hands back analysis rather than a merged answer.
9
+ Use one `fabric_exec` call for a 1–8 model panel followed by one judge. The judge compares rather than merges responses; return its structured analysis so the caller writes the final answer. Use fusion for model-diverse research or critique, not tactical work or a lookup.
10
10
 
11
- Use when a single model is not enough: research questions, expert critique, compare-and-contrast, or anything where the cost of being wrong outweighs the cost of a few extra completions. Do not use it for a short tactical prompt or a lookup with no meaningful competing considerations.
12
-
13
- Pass the task and panel through `strings` and reference them as `π.*`. `π.task` is the prompt. `π.panel` is OpenRouter's `analysis_models`, a JSON `Array<{ model, label? }>` of 1–8 models; `label` defaults to the model id and is used only for attribution in the judge's input and the dashboard, never injected into a member's prompt. `π.judge` is OpenRouter's judge `model` (defaults to the first panel model). `π.tools` is the panel+judge tool allowlist (defaults to read, grep, find, ls, and bash; `bash` is the `web_search`/`web_fetch` analog via `gsearch`/`curl`). `π.thinking` is the reasoning effort for panel+judge (maps to OpenRouter's `reasoning`; defaults to `subagents.thinking`, medium). Pass every referenced key, using empty string for the optionals (`judge`, `tools`, `thinking`) when not setting them.
11
+ Pass every key: `strings.task`; JSON `strings.panel` as `Array<{ model, label? }>`; and optional `strings.judge`, `strings.tools`, and `strings.thinking` as empty strings when unset. Labels are attribution only. Tools default to `read`, `grep`, `find`, `ls`, and `bash`; thinking defaults to configured subagent thinking.
14
12
 
15
13
  ```ts
16
14
  type FusionAnalysis = {
@@ -130,12 +128,8 @@ await workflow.event({ message: `Fusion complete · ${members.length}-model pane
130
128
  return analysis;
131
129
  ```
132
130
 
133
- The default panel size is 3 (OpenRouter's Quality preset). Pick a panel by intent; these mirror OpenRouter's presets, which you encode directly since pi-fabric has no model catalog: the strongest all-round models you have (`general-high`), a cheaper panel with one frontier judge (`general-budget`), or a latency-homogeneous panel (models with similar TTFT, so none gates the fan-out) for fast agentic turns (`general-fast`).
134
-
135
- Cost is N panel + 1 judge: a 3-model panel is roughly 4× a single answer. The run counts toward `budget.spent()` and the `tokenBudget` guard; `subagents.budgetUsd` bounds total spend. Set `agentBudget` and `tokenBudget` on `fabric_exec` when the panel or per-member work is large.
136
-
137
- Deliberation is bounded to one level. Members and the judge run as plain `agent()` calls without `recursive: true`, so they do not receive `fabric_exec` and cannot launch their own fusion panel, the same invariant OpenRouter enforces with its `x-openrouter-fusion-depth` header. Unlike OpenRouter, where the outer model decides per request whether to call `openrouter:fusion`, here the caller decides by running this skill; once invoked, the panel + judge always run.
131
+ Choose distinct models by intent: strongest available, budget-balanced with a frontier judge, or similar-latency models for faster fan-out. The default panel size is three. Cost is N panel calls plus one judge; set top-level `agentBudget` and `tokenBudget`, while `subagents.budgetUsd` bounds spend.
138
132
 
139
- Web access requires `bash`, which is gated by the `execute` approval policy; without it, members answer from their own knowledge (a panel without web tools). Members run concurrently up to `subagents.maxConcurrent` (default 4); raise it in `fabric.json` or `/fabric` settings to run larger panels fully in parallel. OpenRouter exposes `max_tool_calls`, `max_completion_tokens`, and `temperature` per inner call; pi-fabric has no per-call equivalents, so members and the judge inherit provider defaults, with `thinking` for reasoning effort.
133
+ Panel members and the judge are plain, non-recursive agents, so deliberation is one level. `bash` enables web access through local search/fetch commands and requires execute approval. Concurrency is capped by `subagents.maxConcurrent`; inner calls otherwise inherit provider limits and use `thinking` for reasoning effort.
140
134
 
141
- Do not use fusion for a single-model lookup, a simple edit, or a decision with no meaningful competing considerations; use a plain `agent()` or `council.run()` instead. For same-model, role-diverse review (one model, several perspectives), `/skill:fabric-council` is the closer match; fusion is for model-diverse deliberation.
135
+ Use `/skill:fabric-council` instead for same-model, role-diverse review. Use a plain agent when competing model perspectives do not justify the cost.
@@ -1,26 +1,14 @@
1
1
  ---
2
2
  name: fabric-rlm
3
- description: Recursively decomposes a large task by delegating bounded subtasks to child Pi agents via rlm.query(), where each child gets a fresh context window and can itself recurse. Use for tasks too big for one context window — whole-repo audits, massive-context analysis, multi-file refactors.
3
+ description: Recursively decomposes oversized tasks into bounded child Pi agents with fresh context windows. Use for whole-repo audits, massive-context analysis, and multi-file refactors that do not fit one context.
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
- # Fabric Recursive Decomposition (RLM)
7
+ # Fabric Recursive Decomposition
8
8
 
9
- Inspired by Recursive Language Models: an LLM with a sub-call function can recursively decompose problems by self-delegation. `rlm.query()` is `agents.run()` with `recursive: true` — it spawns a child Pi agent with a fresh context window that can call `rlm.query()` again, bounded by `subagents.maxDepth`.
9
+ Use recursion for context size, not mere difficulty. If the relevant material fits one context, work directly. Otherwise pass the root task as `strings.task` and use one `fabric_exec` program to size up → orient → delegate bounded chunks → combine.
10
10
 
11
- Recurse not because a task is hard, but because it is too big for one context window. Each child gets a fresh context budget; you get back only its compact answer instead of all the raw material.
12
-
13
- ## Core pattern: size up → search → delegate → combine
14
-
15
- 1. **Size up** — `wc -l`, `wc -c`, file counts, grep scope. If it fits one context window, do it directly; do not delegate.
16
- 2. **Search** — `grep`, `find`, `ls`, `read` to orient before delegating.
17
- 3. **Delegate** — hand clear, bounded subtasks to children. Use `rlm.query()` when the child should be able to recurse again; use `agent()` for a one-shot leaf worker that does not need to recurse.
18
- 4. **Combine** — aggregate, deduplicate, resolve conflicts, produce the final output.
19
- 5. **Do it directly when it's small** — never delegate what you can do in one step.
20
-
21
- ## One fabric_exec program
22
-
23
- Turn the request into a single type-checked `fabric_exec` program. The program holds the decomposition loop and branches. Pass the root task through `strings.task`.
11
+ `rlm.query()` is `agents.run({ runner: "pi", recursive: true })`: each child gets a fresh context and may recurse up to `subagents.maxDepth`. Use plain `agent()` for leaves that do not need recursion.
24
12
 
25
13
  ```ts
26
14
  await workflow.configure({
@@ -28,10 +16,9 @@ await workflow.configure({
28
16
  description: "Size up, delegate bounded subtasks, combine",
29
17
  });
30
18
 
31
- // Size up and orient with read-only tools before delegating.
32
19
  await phase("Orient", { total: 1 });
33
20
  const scope = await agent<{ paths: string[] }>(
34
- `Identify the bounded set of files relevant to this task. Return structured output.\n\nTask:\n${π.task}`,
21
+ `Identify only the files relevant to this task.\n\nTask:\n${π.task}`,
35
22
  {
36
23
  label: "scope",
37
24
  tools: ["read", "grep", "find", "ls"],
@@ -44,15 +31,12 @@ const scope = await agent<{ paths: string[] }>(
44
31
  },
45
32
  );
46
33
 
47
- // Delegate each bounded subtask to a recursive child. Each child gets a fresh
48
- // context window and may call rlm.query() again up to subagents.maxDepth.
49
34
  await phase("Decompose", { total: scope.paths.length });
50
35
  const findings = await parallel(
51
- scope.paths.map((p) => () =>
36
+ scope.paths.map((path) => () =>
52
37
  rlm.query({
53
- runner: "pi",
54
- task: `Analyze ${p} for this objective and report concrete, bounded findings.\n\nObjective:\n${π.task}`,
55
- name: `analyze ${p}`.slice(0, 50),
38
+ task: `Analyze ${path} for this objective; return concrete evidence.\n\nObjective:\n${π.task}`,
39
+ name: `analyze ${path}`.slice(0, 50),
56
40
  tools: ["read", "grep", "find", "ls"],
57
41
  }),
58
42
  ),
@@ -60,27 +44,16 @@ const findings = await parallel(
60
44
  );
61
45
 
62
46
  await phase("Combine", { total: 1 });
63
- const summary = await agent(
64
- `Synthesize these independent findings into one compact result. Drop unsupported claims and resolve conflicts:\n${JSON.stringify(findings)}`,
47
+ return agent(
48
+ `Deduplicate, reconcile, and synthesize these findings; drop unsupported claims:\n${JSON.stringify(findings)}`,
65
49
  { label: "combine", tools: ["read", "grep", "find", "ls"] },
66
50
  );
67
- return summary;
68
51
  ```
69
52
 
70
- Adapt the phases, tools, and fan-out to the request. Use `schema` on a worker when machine-readable output makes aggregation safer.
71
-
72
- ## When to recurse vs do it directly
73
-
74
- - A 30-line file or a single function: read it and act directly. No delegation.
75
- - A multi-file refactor: delegate per file or per module and combine.
76
- - A whole-repo audit or massive-context analysis: size up first, fan out bounded subtasks, then synthesize.
77
-
78
- ## Guardrails
53
+ Guardrails:
79
54
 
80
- - `subagents.maxDepth` bounds recursion depth; `subagents.budgetUsd` optionally bounds total USD spend across the whole tree (0 disables it).
81
- - When a budget is active, each recursive result carries a `budget` summary (`limit`, `spent`, `remaining`, `tokens`). Stop spawning children when `remaining` is low and finish the remaining work directly.
82
- - Approving the initial recursive call delegates only the `agent` risk capability to children; network, execution, and write approvals are not inherited. Give children edit tools only when they must change files.
83
- - Deeper children should be more conservative: fewer sub-calls, more direct action. Teach the pattern once — size-first → search → chunk → delegate → combine — and let it repeat at every depth.
84
- - For edits, partition ownership by path or set `worktree: true`; never let concurrent recursive children edit the same files.
85
- - The pre-spawn budget check is best-effort (concurrent children can overshoot slightly); the race-free ceiling is `subagents.maxPerExecution`.
86
- - A recursive child that drifts can be redirected without losing its context: `agents.steer({ id, message })` is delivered between the child's turns (after its current tool calls, before the next LLM call), and `agents.status({ id }).pendingMessages` shows the queued steers. Prefer steering over abandoning a child that has accumulated useful context. See the `agents` reference.
55
+ - Size scope before spawning; deeper children should make fewer calls and act more directly.
56
+ - Partition edit ownership by path or use `worktree: true`; concurrent children must not edit the same files.
57
+ - `subagents.maxDepth` bounds each branch. The shared `subagents.budgetUsd` ledger is a best-effort whole-tree spend guard, not a hard cap; concurrent branches can overshoot its pre-spawn check. `maxPerExecution` and top-level `agentBudget` cap calls only in the current process.
58
+ - `budget.remaining()` reports the current execution's token budget from completed usage; it does not expose agent-call or USD budget. Concurrent fan-out can overshoot that observation, so dispatch sequentially or in checked batches when the token ceiling matters.
59
+ - Initial approval delegates only agent risk; network, execution, and write approvals are not inherited. Redirect a valuable drifting child with `agents.steer` rather than discarding its context.
@@ -1,54 +1,42 @@
1
1
  ---
2
2
  name: fabric-schema
3
- description: Uses Fabric's typed Schema evidence loop and, when enabled, the host-owned bounded local-file transaction channel. Use when surprise must void a plan and mutation claims need explicit postconditions.
3
+ description: Uses Fabric's typed Schema evidence loop and, when enabled, its bounded local-file transaction channel. Use when surprise must void a plan and mutation claims need explicit postconditions.
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
7
  # Fabric Schema
8
8
 
9
- First read the active mode through the typed first-class provider proxy. Use `tools.call()` only when a provider ref is computed at runtime.
9
+ Start with `const status = await schema.status();`.
10
10
 
11
- ```ts
12
- const status = await schema.status();
13
- ```
14
-
15
- - `off` is the compatibility default. `state.*` can be used as workflow discipline, but it does not gate direct `pi.edit`, `pi.write`, or `pi.bash`.
16
- - `audit` preserves current behavior and durably reports actions that enforce mode would block.
17
- - `enforce` is a host authorization boundary for model-originated protected-workspace file changes. Direct mutations, agents, state/mesh writes, compaction requests, MCP, extensions, and external providers are blocked. Use one same-`fabric_exec` `schema.hypothesize → schema.verify → schema.commit` sequence.
11
+ - `off`: compatibility mode; state discipline is optional and does not gate direct `pi.edit`, `pi.write`, or `pi.bash`.
12
+ - `audit`: reports actions enforce mode would block without changing behavior.
13
+ - `enforce`: host authorization for protected-workspace file changes. Reads remain available; mutations require one same-`fabric_exec` `schema.hypothesize → schema.verify → schema.commit` sequence. Direct mutations, agents, state/mesh writes, compaction, MCP, extensions, and external providers are blocked.
18
14
 
19
- Evidence is not proof. A typed evidence item is a falsifiable observation or a host-configured check. Verification confirms that the item held at one fingerprinted workspace state. Postconditions confirm scoped observations after declared file operations. Passing either does not establish general semantic correctness.
15
+ Evidence is not proof. Verification confirms falsifiable observations at one fingerprinted workspace state; postconditions confirm scoped observations after declared operations. Either can pass without establishing general semantic correctness.
20
16
 
21
17
  ## Enforce loop
22
18
 
23
- Observe with allowed reads. Build literal or SHA evidence; use a trusted command only by a name supplied in trusted Fabric configuration. Never invent shell text: enforce-mode evidence has no command field or model arguments.
19
+ Observe, hypothesize with literal/SHA evidence or a host-configured trusted command, verify, then commit only with the returned short-lived certificate and observed SHA:
24
20
 
25
21
  ```ts
26
22
  await pi.read({ path: "src/parser.ts" });
27
-
28
23
  const hypothesis = await schema.hypothesize({
29
24
  label: "parser-local-form",
30
- summary: "Relative to the current head, the declared parser edit accepts the local form while focused tests remain green",
25
+ summary: "The declared parser edit accepts the local form while focused checks remain green",
31
26
  evidence: [
32
27
  { kind: "file_contains", path: "src/parser.ts", literal: "old literal" },
33
- { kind: "trusted_command", name: "parser-focused-tests" }
34
- ]
28
+ { kind: "trusted_command", name: "parser-focused-tests" },
29
+ ],
35
30
  });
31
+ const verification = await schema.verify({ hypothesisId: hypothesis.hypothesisId });
32
+ if (!verification.verified || !verification.certificate) return verification;
36
33
 
37
- const verification = await schema.verify({
38
- hypothesisId: hypothesis.hypothesisId
39
- });
40
-
41
- if (!verification.verified || !verification.certificate) {
42
- // Missing, empty, stale, nonconfirmed, errored, timed-out, cancelled, or
43
- // workspace-changing evidence voids the plan. Do not act.
44
- return verification;
45
- }
46
- const parserEvidence = verification.results.find(
34
+ const observed = verification.results.find(
47
35
  (result) => result.evidence.path === "src/parser.ts",
48
- );
49
- if (!parserEvidence?.observedSha256) return { void: true, reason: "missing observed SHA-256" };
36
+ )?.observedSha256;
37
+ if (!observed) return { void: true, reason: "missing observed SHA-256" };
50
38
 
51
- const outcome = await schema.commit({
39
+ return schema.commit({
52
40
  hypothesisId: hypothesis.hypothesisId,
53
41
  certificate: verification.certificate,
54
42
  operations: [{
@@ -56,56 +44,51 @@ const outcome = await schema.commit({
56
44
  path: "src/parser.ts",
57
45
  oldText: "old literal",
58
46
  newText: "new literal",
59
- expectedSha256: parserEvidence.observedSha256
47
+ expectedSha256: observed,
60
48
  }],
61
49
  postconditions: [
62
50
  { kind: "file_contains", path: "src/parser.ts", literal: "new literal" },
63
- { kind: "trusted_command", name: "parser-focused-tests" }
64
- ]
51
+ { kind: "trusted_command", name: "parser-focused-tests" },
52
+ ],
65
53
  });
66
- return outcome;
67
54
  ```
68
55
 
69
- Use `write` with `expected: { absent: true }` for a new file or `expected: { sha256 }` for replacement. Use `delete` with `expectedSha256`. Paths are project-relative regular files; path and symlink escape are rejected. Keep every operation local, declared, and small.
56
+ Any missing, stale, failed, timed-out, cancelled, or workspace-changing evidence voids the plan. Never invent trusted-command shell text or arguments: names resolve to static host configuration.
70
57
 
71
- The certificate is random, short-lived, single-use, and bound to the hypothesis, state head/version, workspace fingerprint/generation, and current `fabric_exec` invocation. Do not return it for later use. Fabric abandons an uncommitted hypothesis/certificate when this invocation ends. Call `schema.abort` explicitly when stopping early:
58
+ The certificate is random, single-use, invocation-bound, and tied to the hypothesis, state head/version, workspace fingerprint, and generation. Do not return it for later use. If stopping after verification, call this with the values from that verification:
72
59
 
73
- ```ts
60
+ ```text
74
61
  await schema.abort({
75
62
  hypothesisId: hypothesis.hypothesisId,
76
- certificate: verification.certificate
63
+ certificate: verification.certificate,
77
64
  });
78
65
  ```
79
66
 
80
- A commit result is `committed`, `rolled_back`, or `quarantined`. Treat only `committed` as success. `rolled_back` means declared paths were restored after failure. `quarantined` means rollback could not be fully established and needs operator inspection.
67
+ Operations are project-relative regular files:
81
68
 
82
- ## Evidence vocabulary
69
+ - `edit`: `oldText`, `newText`, `expectedSha256`
70
+ - `write`: `expected: { absent: true }` for creation or `{ sha256 }` for replacement
71
+ - `delete`: `expectedSha256`
83
72
 
84
- - `{ kind: "file_exists", path }`
85
- - `{ kind: "file_absent", path }`
86
- - `{ kind: "file_contains", path, literal }` — literal containment, not a regex
87
- - `{ kind: "file_sha256", path, sha256 }`
88
- - `{ kind: "trusted_command", name }` — static host-configured executable/argv
73
+ Path/symlink escape is rejected. Keep operations declared, local, and small. Treat only `committed` as success; `rolled_back` restored declared paths after failure, while `quarantined` means rollback needs operator inspection.
89
74
 
90
- Trusted commands are an explicit trusted computing base. They should be deterministic, local, and read-only. Tests and type checks are evidence for their scoped claim, not mathematical proof. Remote/network/database effects are not transactional and remain blocked in enforce mode.
75
+ Evidence/postcondition forms are `file_exists`, `file_absent`, `file_contains` (literal, not regex), `file_sha256`, and `trusted_command` (static host executable/argv). Trusted commands should be deterministic, local, and read-only. Remote/network/database effects are not transactional and remain blocked in enforce mode.
91
76
 
92
- For a claimed complexity reduction, set `complexityReduction: true` on the hypothesis and include behavior-preservation postconditions. Fabric reports it certified only after all postconditions pass; this does not turn tests into proof or objectively measure complexity.
77
+ For a complexity-reduction claim, set `complexityReduction: true` and include behavior-preservation postconditions. Certification means those scoped checks passed, not that complexity was objectively measured.
93
78
 
94
79
  ## Off-mode state discipline
95
80
 
96
- When mode is `off`, the older labeled state layer remains useful:
81
+ When mode is `off`, the independent labeled state layer can still record and verify a falsifiable claim:
97
82
 
98
83
  ```ts
99
84
  const transition = await state.transition({
100
85
  label: "claim",
101
86
  to: "claim-stated",
102
87
  summary: "A falsifiable delta",
103
- evidence: ["pnpm exec vitest run tests/focused.test.ts"]
88
+ evidence: ["pnpm exec vitest run tests/focused.test.ts"],
104
89
  });
105
90
  const verification = await state.verify();
106
91
  if (!verification.certified) return { void: true, transition, verification };
107
92
  ```
108
93
 
109
- This is discipline over the typed state provider, not an enforcement boundary. `state` evidence and goals accept trusted workflow shell commands, unlike strict Schema evidence. In enforce mode those state write/execute actions are blocked; use `schema.*`.
110
-
111
- See `docs/schema-enforcement.md` for the exact guarantee, trusted-command argv/shell behavior, fingerprint coverage, recovery model, and limitations. See `docs/state-layer.md` for the independent labeled state timeline.
94
+ This is workflow discipline, not enforcement. See `../../docs/schema-enforcement.md` for the guarantee and recovery model, and `../../docs/state-layer.md` for the labeled timeline.
@@ -1,75 +1,20 @@
1
1
  ---
2
2
  name: fabric-supervisor
3
- description: Starts a persistent, event-driven Pi Fabric supervisor that watches the main session toward a concrete goal and steers only when needed. Use for long-running goal supervision without another extension.
3
+ description: Starts a persistent Pi Fabric supervisor that watches the main session toward a concrete goal and steers only when needed. Use for long-running goal supervision without another extension.
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
7
  # Fabric Supervisor
8
8
 
9
- Start an emergent supervisor using `fabric_exec`; never install or invoke an external supervisor extension.
9
+ Create the supervisor with Fabric primitives; do not install a supervisor extension. Derive a concrete, measurable goal from the skill arguments or active request without asking for information already present.
10
10
 
11
- Derive the goal from the skill arguments. If arguments are empty, use the active user request and conversation. Make the goal concrete and measurable without asking for information already present.
11
+ Read `../fabric-ambient/references/setup.md` completely, then use its program with:
12
12
 
13
- Call `fabric_exec` once. Put the completed supervisor prompt in `strings.instructions` and the stable name `supervisor` in `strings.name`, then execute:
14
-
15
- ```ts
16
- await workflow.configure({
17
- name: `Supervisor · ${π.name}`,
18
- description: "Persistent ambient goal supervision",
19
- });
20
- await phase("Start actor", { total: 1 });
21
- const current = await agents.actors();
22
- const existing = current.find((actor) => actor.name === π.name && actor.status !== "stopped");
23
- if (existing) {
24
- const migrated: string[] = [];
25
- await agents.setInstructions({ id: existing.id, instructions: π.instructions });
26
- if (
27
- existing.events.length !== 2 ||
28
- !existing.events.includes("agent_settled") ||
29
- !existing.events.includes("tool_error")
30
- ) {
31
- await agents.setEvents({ id: existing.id, events: ["agent_settled", "tool_error"] });
32
- migrated.push("events");
33
- }
34
- if (existing.delivery !== "steer" || existing.triggerTurn !== true) {
35
- await agents.setDeliveryPolicy({
36
- id: existing.id,
37
- delivery: "steer",
38
- triggerTurn: true,
39
- });
40
- migrated.push("deliveryPolicy");
41
- }
42
- const warnings = [
43
- ...(existing.responseMode !== "directive" ? ["responseMode should be directive; recreate the actor"] : []),
44
- ...(existing.coalesce !== true ? ["coalesce should be true; recreate the actor"] : []),
45
- ];
46
- return {
47
- reused: true,
48
- actor: await agents.actorStatus({ id: existing.id }),
49
- migrated,
50
- warnings,
51
- };
52
- }
53
-
54
- const actor = await agents.create({
55
- name: π.name,
56
- instructions: π.instructions,
57
- events: ["agent_settled", "tool_error"],
58
- responseMode: "directive",
59
- delivery: "steer",
60
- triggerTurn: true,
61
- coalesce: true,
62
- tools: ["read", "grep", "find", "ls"],
63
- });
64
- return {
65
- started: true,
66
- actor,
67
- inspect: `/fabric messages ${actor.id.slice(0, 8)}`,
68
- stop: `/fabric stop ${actor.id.slice(0, 8)}`,
69
- };
70
- ```
71
-
72
- Use this prompt, replacing `GOAL`:
13
+ - `strings.name`: `supervisor`
14
+ - `strings.events`: `["agent_settled","tool_error"]`
15
+ - `strings.triggerTurn`: `true`
16
+ - `strings.model`: model key or substring, or an empty string when unset
17
+ - `strings.instructions`: the prompt below with `GOAL` replaced
73
18
 
74
19
  ```text
75
20
  You are an ambient supervisor for this goal:
@@ -78,19 +23,11 @@ You are an ambient supervisor for this goal:
78
23
  GOAL
79
24
  </goal>
80
25
 
81
- Review each supplied parent-session event and recent transcript as an outside observer, not a second implementer.
26
+ Review the supplied parent-session event and recent transcript as an outside observer, not a second implementer.
82
27
 
83
- Return {"action":"silent"} while work is productively advancing. Return {"action":"message","message":"..."} only when the main agent is idle with material work missing, is drifting from the goal, is stuck after a tool error, or needs one concrete next action. Keep a steer direct and at most three sentences. Do not repeat prior guidance. Never request credentials or invent a decision only the user can make.
28
+ Return {"action":"silent"} while work is productively advancing. Return {"action":"message","message":"..."} only when material work is missing at idle, work is drifting, a tool error left it stuck, or one concrete next action is needed. Keep guidance direct and at most three sentences. Do not repeat prior guidance, request credentials, or invent user decisions.
84
29
 
85
- The goal is complete only when the requested result and its relevant validation are evident in the transcript. At that point return {"action":"stop","message":"Goal verified complete."}.
30
+ The goal is complete only when the requested result and relevant validation are evident. Then return {"action":"stop","message":"Goal verified complete."}.
86
31
  ```
87
32
 
88
- ## Heuristic: steer on good signals, not every turn
89
-
90
- The supervisor subscribes to `agent_settled` and `tool_error`, not `turn_end` or `input`. It runs at decision points (idle and on failures) and stays silent while work is productively advancing, so it does not invoke a model review on every turn. Intervene only on a concrete, high-confidence signal: material work missing at idle, drift from the goal, a stuck state after a tool error, or verified completion.
91
-
92
- After creation, report the goal, actor short ID, and inspect/stop commands. Do not wait for it; host events drive it across later turns.
93
-
94
- ## Steering running subagents
95
-
96
- A supervisor (or any orchestrator) can redirect a running worker without discarding its context: `agents.steer({ id, message })` delivers between the child's turns, and `agents.status({ id }).pendingMessages` shows the queued steers. See the `agents` reference. Prefer this over stopping and respawning a worker that has accumulated useful context but is drifting from the goal. Because Pi actors run with Fabric, the supervisor can call `agents.main()` plus `agents.steer`/`agents.followUp` to message the user-facing Main session, redirect a running child directly, or route to an exact agent id in another process.
33
+ Idle/error events avoid a model run on every turn. `triggerTurn: true` lets a material steer resume an idle Main session. Report the goal and setup result; do not wait for the actor.