pi-fabric 0.22.2 → 0.22.3
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.
- package/README.md +15 -13
- package/docs/agents.md +291 -0
- package/docs/architecture.md +73 -0
- package/docs/audit-trace.md +112 -0
- package/docs/certification.md +134 -0
- package/docs/compaction.md +177 -0
- package/docs/configuration.md +245 -0
- package/docs/interface.md +87 -0
- package/docs/memory-recall.md +347 -0
- package/docs/programmatic-compaction.md +208 -0
- package/docs/providers.md +50 -0
- package/docs/schema-enforcement.md +165 -0
- package/docs/skills.md +45 -0
- package/docs/state-layer.md +197 -0
- package/package.json +2 -1
- package/skills/fabric-advisor/SKILL.md +6 -2
- package/skills/fabric-ambient/SKILL.md +8 -4
- package/skills/fabric-ambient/references/setup.md +37 -18
- package/skills/fabric-council/SKILL.md +79 -13
- package/skills/fabric-exec/SKILL.md +2 -2
- package/skills/fabric-exec/references/agents.md +1 -1
- package/skills/fabric-fusion/SKILL.md +130 -62
- package/skills/fabric-guide/SKILL.md +27 -0
- package/skills/fabric-rlm/SKILL.md +243 -24
- package/skills/fabric-schema/SKILL.md +33 -6
- package/skills/fabric-supervisor/SKILL.md +6 -2
- package/skills/fabric-workflow/SKILL.md +92 -21
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# External providers
|
|
2
|
+
|
|
3
|
+
Normal `pi.registerTool()` tools are [captured automatically](configuration.md#captured-extension-tools). Extensions can still opt into the versioned provider protocol when they need to expose non-tool capabilities, richer risk declarations, or a large virtual action catalog without registering one Pi tool per action.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import {
|
|
8
|
+
FABRIC_PROVIDER_DISCOVER_EVENT,
|
|
9
|
+
FABRIC_PROVIDER_REGISTER_EVENT,
|
|
10
|
+
type FabricProvider,
|
|
11
|
+
type FabricProviderDiscovery,
|
|
12
|
+
} from "pi-fabric/protocol";
|
|
13
|
+
|
|
14
|
+
export default function extension(pi: ExtensionAPI) {
|
|
15
|
+
const provider: FabricProvider = {
|
|
16
|
+
name: "example",
|
|
17
|
+
description: "Example actions",
|
|
18
|
+
async list() {
|
|
19
|
+
return [];
|
|
20
|
+
},
|
|
21
|
+
async describe() {
|
|
22
|
+
return undefined;
|
|
23
|
+
},
|
|
24
|
+
async invoke() {
|
|
25
|
+
return null;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
pi.events.emit(FABRIC_PROVIDER_REGISTER_EVENT, {
|
|
30
|
+
version: 1,
|
|
31
|
+
provider,
|
|
32
|
+
overwrite: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
pi.events.on(FABRIC_PROVIDER_DISCOVER_EVENT, (event: FabricProviderDiscovery) => {
|
|
36
|
+
event.register(provider, { overwrite: true });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Providers own their schemas, state, and execution semantics. Pi Fabric validates arguments, enforces the declared risk policy, records nested-call audits, and propagates cancellation. A provider can enrich the generic [activity surface](interface.md#data-driven-activity) without registering a TUI component:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
async invoke(actionName, args, context) {
|
|
45
|
+
context.activity?.({ type: "entity", id: job.id, kind: "custom", name: job.name });
|
|
46
|
+
context.activity?.({ type: "progress", message: "Indexing package 3/12" });
|
|
47
|
+
context.activity?.({ type: "metrics", tokens: 4200, toolCalls: 9 });
|
|
48
|
+
return job.result;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Schema enforcement
|
|
2
|
+
|
|
3
|
+
Schema enforcement is an opt-in host authorization and local-file transaction layer over Fabric. It is disabled by default for compatibility.
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"schema": {
|
|
8
|
+
"mode": "enforce",
|
|
9
|
+
"certificateTtlMs": 30000,
|
|
10
|
+
"maxFiles": 100,
|
|
11
|
+
"maxBytes": 10485760,
|
|
12
|
+
"trustedCommands": {
|
|
13
|
+
"focused-tests": {
|
|
14
|
+
"command": "pnpm",
|
|
15
|
+
"args": ["exec", "vitest", "run", "tests/focused.test.ts"],
|
|
16
|
+
"shell": false,
|
|
17
|
+
"timeoutMs": 30000
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`mode` is `off`, `audit`, or `enforce`. Invalid values normalize to `off`. The certificate TTL is bounded to 1 second–10 minutes, file count to 1–1,000, and transaction bytes to 1 KiB–100 MiB. The default is `off`, 30 seconds, 100 files, and 10 MiB. Mode is fixed when the session initializes; a config reload cannot change it until the next session.
|
|
25
|
+
|
|
26
|
+
Global Fabric configuration is trusted host input. Project `.pi/fabric.json` is read only when Pi marks the project trusted. Therefore `trustedCommands` cannot be supplied through a model action or an untrusted project configuration.
|
|
27
|
+
|
|
28
|
+
## Modes
|
|
29
|
+
|
|
30
|
+
- **off** preserves existing action authorization and tool visibility. The `schema.*` control plane is available but does not gate other actions.
|
|
31
|
+
- **audit** allows existing behavior and publishes a durable `would_block` event to `fabric.schema` for every nested Fabric action or top-level Pi tool call enforce mode would deny.
|
|
32
|
+
- **enforce** permits only this extension's exact, source-provenanced `fabric_exec` definition at the top level. A `tool_call` gate blocks every other built-in, external-extension, and SDK `customTools` call before execution. Nested calls are admitted only while an owned outer invocation is active and their id begins with Fabric's generated `NESTED_TOOL_CALL_ID_PREFIX`; those calls have already passed registry authorization. The central resolved-action gate remains authoritative inside Fabric, so direct refs and computed `tools.call` refs share the same decision.
|
|
33
|
+
|
|
34
|
+
Enforce mode allows discovery and workflow display operations plus these exact host-owned actions:
|
|
35
|
+
|
|
36
|
+
- `pi.read`, `pi.grep`, `pi.find`, `pi.ls`;
|
|
37
|
+
- `memory.recall`, `memory.expand`, `memory.sessions`;
|
|
38
|
+
- `state.get`, `state.history`, `state.complexity`;
|
|
39
|
+
- `mesh.self`, `mesh.read`, `mesh.members`, `mesh.get`, `mesh.list`;
|
|
40
|
+
- `compact.status`;
|
|
41
|
+
- `schema.status`, `schema.hypothesize`, `schema.verify`, `schema.commit`, `schema.abort`.
|
|
42
|
+
|
|
43
|
+
It blocks `pi.edit`, `pi.write`, `pi.bash`, all agent/actor actions, mesh and state writes or execution, `compact.request` and `compact.cancel`, MCP, captured extensions, and every external provider regardless of its declared risk. A provider claiming `risk: "read"` does not bypass this exact-reference policy. Guard failures are recorded in the existing typed execution trace with `failureStage: "guard"`.
|
|
44
|
+
|
|
45
|
+
Persistent actors are not restored and host-event actor dispatch is disabled for an enforce session. Subagent execution is disabled and agent actions are blocked. Capture `keepVisible`, descriptor risk, claimed source metadata, or tool visibility cannot authorize a second top-level path. A colliding external or SDK tool named `fabric_exec` is blocked unless Pi's canonical `sourceInfo.path` identifies this extension entry exactly.
|
|
46
|
+
|
|
47
|
+
## Transaction protocol
|
|
48
|
+
|
|
49
|
+
Use all steps in one `fabric_exec` invocation. Certificates are bound to its `parentToolCallId` and cannot be transferred to a later call.
|
|
50
|
+
|
|
51
|
+
1. `schema.hypothesize` stores a nonempty typed evidence set with a label and summary. It binds the durable hypothesis to the current state head/version, workspace fingerprint, workspace generation, and invocation.
|
|
52
|
+
2. `schema.verify` checks that all bindings are still current, evaluates every evidence item, and fingerprints before and after evidence. Missing, empty, nonconfirmed, errored, timed-out, cancelled, or workspace-changing evidence fails closed. Only a fully confirmed run issues a cryptographically random, TTL-bounded certificate.
|
|
53
|
+
3. `schema.commit` requires both Fabric write and execute approval. It compare-and-swap consumes the certificate exactly once, checks all bindings again, captures durable before images, applies bounded declared operations sequentially, and requires a nonempty typed postcondition set. Any failed operation, undeclared drift, or failed postcondition restores all declared paths. A failed rollback is recorded as `quarantined`; a successful rollback is `rolled_back`.
|
|
54
|
+
4. `schema.abort` closes an uncommitted hypothesis and optionally its certificate. Fabric also abandons active artifacts when their `fabric_exec` invocation ends.
|
|
55
|
+
|
|
56
|
+
Known Schema actions use the typed first-class proxy; reserve `tools.call()` for refs computed at runtime:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const hypothesis = await schema.hypothesize({
|
|
60
|
+
label: "update-parser",
|
|
61
|
+
summary: "The parser accepts the new local form without changing existing cases",
|
|
62
|
+
evidence: [
|
|
63
|
+
{ kind: "file_sha256", path: "src/parser.ts", sha256: "sha256:<64 hex>" },
|
|
64
|
+
{ kind: "trusted_command", name: "focused-tests" }
|
|
65
|
+
]
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const verification = await schema.verify({
|
|
69
|
+
hypothesisId: hypothesis.hypothesisId
|
|
70
|
+
});
|
|
71
|
+
const { certificate: _certificate, ...safeVerification } = verification;
|
|
72
|
+
if (!verification.verified || !verification.certificate) {
|
|
73
|
+
if (verification.certificate) {
|
|
74
|
+
await schema.abort({
|
|
75
|
+
hypothesisId: hypothesis.hypothesisId,
|
|
76
|
+
certificate: verification.certificate
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return { status: "failed", verification: safeVerification };
|
|
80
|
+
}
|
|
81
|
+
const parserEvidence = verification.results.find(
|
|
82
|
+
(result) => result.evidence.path === "src/parser.ts",
|
|
83
|
+
);
|
|
84
|
+
if (!parserEvidence?.observedSha256) {
|
|
85
|
+
await schema.abort({
|
|
86
|
+
hypothesisId: hypothesis.hypothesisId,
|
|
87
|
+
certificate: verification.certificate
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
status: "failed",
|
|
91
|
+
reason: "missing observed SHA-256",
|
|
92
|
+
verification: safeVerification
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const commit = await schema.commit({
|
|
97
|
+
hypothesisId: hypothesis.hypothesisId,
|
|
98
|
+
certificate: verification.certificate,
|
|
99
|
+
operations: [{
|
|
100
|
+
kind: "edit",
|
|
101
|
+
path: "src/parser.ts",
|
|
102
|
+
oldText: "old literal",
|
|
103
|
+
newText: "new literal",
|
|
104
|
+
expectedSha256: parserEvidence.observedSha256
|
|
105
|
+
}],
|
|
106
|
+
postconditions: [
|
|
107
|
+
{ kind: "file_contains", path: "src/parser.ts", literal: "new literal" },
|
|
108
|
+
{ kind: "trusted_command", name: "focused-tests" }
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
return { status: commit.outcome === "committed" ? "success" : "failed", commit };
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Evidence and preconditions
|
|
115
|
+
|
|
116
|
+
Evidence is data, not model-provided shell. Confirmed evidence about an existing file returns its verification-time `observedSha256`, which can be used as the commit precondition without enabling a shell:
|
|
117
|
+
|
|
118
|
+
- `file_exists: { path }`;
|
|
119
|
+
- `file_absent: { path }`;
|
|
120
|
+
- `file_contains: { path, literal }`, using literal containment;
|
|
121
|
+
- `file_sha256: { path, sha256 }`;
|
|
122
|
+
- `trusted_command: { name }`, selecting a host-configured command without model arguments.
|
|
123
|
+
|
|
124
|
+
No command text, source text, or prose is classified with a regex policy. SHA-256 syntax and provider/config identifiers receive ordinary structural validation only.
|
|
125
|
+
|
|
126
|
+
Trusted commands are part of the trusted computing base. With `shell: false` (the default), Fabric calls the configured executable with the configured static argv directly, without shell interpretation. With `shell: true`, `command` is the complete trusted shell program and `args` is normalized to an empty array; this avoids pretending shell argv can be escaped portably. Both forms are trusted configuration, never model input. Configure verification commands to be local, deterministic, and read-only. Fabric detects workspace changes after a command, but it cannot prevent a misconfigured trusted command from producing transient or non-workspace side effects.
|
|
127
|
+
|
|
128
|
+
Commit operations are exact and local:
|
|
129
|
+
|
|
130
|
+
- `write` requires `expected: { absent: true }` or `expected: { sha256 }`;
|
|
131
|
+
- `edit` requires `expectedSha256`, and `oldText` must occur exactly once;
|
|
132
|
+
- `delete` requires `expectedSha256`.
|
|
133
|
+
|
|
134
|
+
Paths must be project-relative regular-file paths. Absolute paths, `..` escape, symbolic links in any component, non-file targets, and missing parent directories are rejected. A transaction does not create directories. Before-image bytes plus write/edit payload bytes must fit `maxBytes`; distinct declared paths must fit `maxFiles`.
|
|
135
|
+
|
|
136
|
+
A hypothesis can mark `complexityReduction: true`. The committed outcome reports `complexityReductionCertified: true` only after its nonempty postconditions all pass. This certifies only those stated postconditions; it is not a proof of semantic equivalence or an objective complexity measurement.
|
|
137
|
+
|
|
138
|
+
## Workspace binding and durability
|
|
139
|
+
|
|
140
|
+
In Git worktrees, the deterministic fingerprint covers:
|
|
141
|
+
|
|
142
|
+
- exact `HEAD` identity (or an unborn head);
|
|
143
|
+
- the NUL-delimited index stage listing;
|
|
144
|
+
- path and content/type hashes for every tracked worktree path;
|
|
145
|
+
- path and content/type hashes for untracked, non-ignored paths.
|
|
146
|
+
|
|
147
|
+
Git is invoked with machine-oriented output (`-z` where paths are present); human `git status` prose is never parsed. The session `cwd` must be the worktree root. Unsupported tracked non-file entries (for example, a submodule directory) fail the snapshot rather than silently weakening it. Outside Git, Fabric recursively hashes eligible project files with conservative file/byte bounds and skips dependency/build/cache directories. Host mesh and transaction-journal storage is excluded from the protected snapshot so recording a hypothesis does not invalidate itself.
|
|
148
|
+
|
|
149
|
+
Hypotheses, certificates, gate reports, and outcomes are durable mesh records/events under `schema/*` and topic `fabric.schema`. A workspace generation compare-and-swap serializes successful outcomes in addition to a cross-process commit lock. Before images are written with owner-only permissions under the mesh transaction journal before mutation. On initialization, an `applying` journal whose owner process is gone is restored; an unreadable or unrestorable journal remains available for operator quarantine. Certificates are CAS-consumed before the first file operation, so a crash cannot make one reusable.
|
|
150
|
+
|
|
151
|
+
A clean commit advances the Schema generation and best-effort appends a normal state outcome transition. State-transition contention does not falsify the already durable Schema workspace outcome.
|
|
152
|
+
|
|
153
|
+
## Exact guarantee and limitations
|
|
154
|
+
|
|
155
|
+
Subject to the host process, filesystem, trusted Fabric configuration, configured trusted commands, and Pi's canonical tool lifecycle/provenance behaving as trusted components, enforce mode guarantees that **model-originated mutation of regular files under the initialized local workspace can be authorized only by this extension's source-provenanced top-level `fabric_exec` and one same-invocation, fresh-certificate `schema.commit` path**, with explicit preconditions, declared paths, bounded captured before images, nonempty typed postconditions, single-use CAS consumption, and rollback reporting.
|
|
156
|
+
|
|
157
|
+
The guarantee is deliberately narrower than “all effects are transactional”:
|
|
158
|
+
|
|
159
|
+
- it does not cover remote services, network calls, databases, device files, other processes, or writes performed outside Fabric; enforce mode blocks model access to those provider channels rather than pretending to roll them back;
|
|
160
|
+
- it does not provide a kernel sandbox against a malicious extension, SDK host, or host process; trusted host code can invoke effects without model tool calls or falsify lifecycle/provenance data;
|
|
161
|
+
- nested-call admission relies on Pi delivering Fabric's reserved generated id prefix unchanged and on Fabric tracking an active owned outer invocation; arbitrary top-level ids with that prefix are still sent through the gate when no owned outer call is active;
|
|
162
|
+
- trusted commands are an explicit TCB and can have effects if configured unsafely;
|
|
163
|
+
- filesystem rollback cannot be perfectly atomic across process death or hostile concurrent writers; journals recover declared regular files and quarantine failures;
|
|
164
|
+
- file postconditions and tests are scoped evidence, not proof;
|
|
165
|
+
- host metadata under the configured mesh root is intentionally outside the protected fingerprint.
|
package/docs/skills.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Pi Fabric skills
|
|
2
|
+
|
|
3
|
+
Pi Fabric uses a core-first, user-opt-in skill hierarchy.
|
|
4
|
+
|
|
5
|
+
## Invocation contract
|
|
6
|
+
|
|
7
|
+
- `fabric-exec` is the only model-invoked skill. It teaches normal Pi core work through `fabric_exec`, `pi.*`, discovery, and stable provider proxies.
|
|
8
|
+
- Every advanced workflow is user-invoked (`disable-model-invocation: true`) and absent from the model catalog. Agent policy says not to read one autonomously or delegate from one user-only skill to another; this is not a filesystem authorization boundary.
|
|
9
|
+
- `/skill:fabric-guide` is the user-only router. It recommends an exact advanced command and stops; it never invokes the recommendation.
|
|
10
|
+
- User-facing descriptions summarize commands. Only the `fabric-exec` description spends always-on model context.
|
|
11
|
+
|
|
12
|
+
This keeps the parent agent behaving like regular Pi until the user explicitly opts into orchestration, recursion, Schema, ambient actors, or swarm coordination.
|
|
13
|
+
|
|
14
|
+
## Information hierarchy
|
|
15
|
+
|
|
16
|
+
1. Keep required ordered actions and checkable completion criteria in `SKILL.md`.
|
|
17
|
+
2. Use a **hard pointer** for material that must be loaded before execution.
|
|
18
|
+
3. Use a **branch pointer** only when some runs need the material and others do not.
|
|
19
|
+
4. Use a **soft pointer** for optional depth that improves quality but is not required for correctness.
|
|
20
|
+
5. Skill-owned references live beside their owning skill. Package-level profiles may point to that single source of truth.
|
|
21
|
+
|
|
22
|
+
A mandatory pointer is disclosure for legibility and single-source maintenance, not a per-run token saving. Do not split always-required executable code merely to shorten a skill.
|
|
23
|
+
|
|
24
|
+
## Authoring rules
|
|
25
|
+
|
|
26
|
+
- One meaning, one source of truth.
|
|
27
|
+
- Prefer stable leading words already used by Fabric: **one program**, **bounded**, **verifier**, **decision point**, **evidence loop**, **CAS claim**, and **outside observer**.
|
|
28
|
+
- Add a checkable **Completion criterion** only when it matches runtime behavior and does not encourage whole-flow retries.
|
|
29
|
+
- Classify dependencies as hard, branch-conditioned, or soft.
|
|
30
|
+
- Apply the no-op test sentence by sentence: remove text that does not change model behavior.
|
|
31
|
+
- State the positive target behavior; reserve prohibitions for safety or invocation boundaries.
|
|
32
|
+
- Preserve executable TypeScript examples and their contract tests. Expensive fan-out returns `success`, `partial`, or `failed`; partial results never imply an automatic whole-flow retry.
|
|
33
|
+
|
|
34
|
+
## User-invoked workflows
|
|
35
|
+
|
|
36
|
+
- `/skill:fabric-guide` — choose a workflow.
|
|
37
|
+
- `/skill:fabric-workflow` — finite fan-out/pipeline work with verification.
|
|
38
|
+
- `/skill:fabric-council` — same-model role diversity.
|
|
39
|
+
- `/skill:fabric-fusion` — multi-model deliberation.
|
|
40
|
+
- `/skill:fabric-rlm` — recursive context decomposition.
|
|
41
|
+
- `/skill:fabric-schema` — evidence-gated mutation.
|
|
42
|
+
- `/skill:fabric-advisor` — persistent peer advice.
|
|
43
|
+
- `/skill:fabric-supervisor` — persistent goal supervision.
|
|
44
|
+
- `/skill:fabric-ambient` — direct advisor/supervisor profile router.
|
|
45
|
+
- `/skill:fabric-swarm` — durable actor coordination.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# State layer (fabric-schema)
|
|
2
|
+
|
|
3
|
+
The `state` provider is a typed, labeled world-model layer over mesh storage. It records claims, attached executable evidence, verification outcomes, and compare-and-swap state transitions. By itself it provides durable process state and fail-closed reporting; in the default `schema.mode: "off"` it is not a gate on direct Pi tools such as `pi.edit` or `pi.bash`.
|
|
4
|
+
|
|
5
|
+
The separate opt-in Schema transaction layer adds `audit` and `enforce` modes. In enforce mode, state reads remain available but `state.transition`, `state.verify`, `state.goal`, and `state.checkGoal` are blocked from model-originated calls; mutations use the host-owned `schema.*` transaction control plane instead. See [Schema enforcement](./schema-enforcement.md).
|
|
6
|
+
|
|
7
|
+
## Claim, evidence attachment, and certification
|
|
8
|
+
|
|
9
|
+
These are separate states:
|
|
10
|
+
|
|
11
|
+
1. A **claim** is the transition `summary` and its labeled move from one world-model state to another.
|
|
12
|
+
2. **Evidence attachment** stores shell commands on the transition. Attachment means only that the commands can be replayed; it does not mean they ran or passed.
|
|
13
|
+
3. **Certification** occurs only when `state.verify` selects at least one transition, runs at least one evidence command, and every result is `confirmed` (exit 0). A successful verification emits a `state.certified` event. When that verification targets the current head, its latest certificate is also CAS-persisted in `state/current` so current certification survives event-window truncation.
|
|
14
|
+
|
|
15
|
+
Verification fails closed. A missing current or requested target, empty evidence, non-zero exit, spawn error, timeout, cancellation, or certification-publish failure returns `certified: false` and `violated: true`. When the current head is targeted, Fabric first CAS-revokes its durable certificate, then best-effort emits `state.violated` with every blocking reason. A publication failure therefore does not preserve the old current certificate; the report includes `reportingError` because no durable failure event exists. `violated` is preserved for compatibility; callers should make the positive check `if (!verification.certified)`. A successful result is durable only after `state.certified` is published.
|
|
16
|
+
|
|
17
|
+
Evidence commands are arbitrary shell commands and are legacy trusted workflow input. They execute with the invoking process's authority. The state layer executes the command exactly as shell input and does not parse, classify, or infer meaning from its prose or output. Tests, type checks, and greps can be strong, useful evidence for a scoped claim, but passing tests are evidence rather than proof.
|
|
18
|
+
|
|
19
|
+
## Schema-inspired mapping
|
|
20
|
+
|
|
21
|
+
| Concept | Pi Fabric implementation |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| Editable labeled world state | Mesh key `state/current`, advanced with compare-and-swap. |
|
|
24
|
+
| Append-only timeline | Mesh topic `fabric.state`; transition and verification events remain inspectable. |
|
|
25
|
+
| Replayable evidence | `state.verify` executes commands attached to selected transitions. |
|
|
26
|
+
| Certification | A fail-closed verification report plus durable `state.certified` event and digests. |
|
|
27
|
+
| Surprise | `state.violated` records non-confirmed results and blocking reasons. |
|
|
28
|
+
| Representation revision | A `kind: "representation"` transition establishes the active history boundary. |
|
|
29
|
+
| Complexity reduction | Decision-point reduction requires attached evidence and remains pending until later verification succeeds. |
|
|
30
|
+
| Executable goal | `state.goal({ check })` stores a predicate; `state.checkGoal()` executes it. |
|
|
31
|
+
|
|
32
|
+
The `fabric-schema` skill uses these facilities as workflow discipline when Schema mode is off, and uses the `schema.*` transaction API when audit or enforce mode is selected. The state provider alone does not prevent bypass; the central ActionRegistry gate is what supplies enforce-mode authorization.
|
|
33
|
+
|
|
34
|
+
## Storage format
|
|
35
|
+
|
|
36
|
+
Storage is mesh-native. Raw mesh reads can inspect all state-layer records.
|
|
37
|
+
|
|
38
|
+
### Topic `fabric.state`
|
|
39
|
+
|
|
40
|
+
The append-only JSONL event log contains:
|
|
41
|
+
|
|
42
|
+
- **`transition`** — a versioned proposal: `data: { protocolVersion: 1, phase: "proposed", label, from?, to, summary, evidence?, tags?, kind?, complexity?, certificationStatus?, ts }`.
|
|
43
|
+
- **`transition.committed`** — makes its referenced proposal visible after all ledger and head CAS writes succeed.
|
|
44
|
+
- **`transition.rejected`** — records a failed proposal and its rollback/quarantine status. A proposal is never visible merely because this marker exists.
|
|
45
|
+
- **`state.certified`** — emitted only after successful verification. Its data contains bounded `targets`, the verification-time `head`, `evidenceDigest`, `resultDigest`, `certificationStatus: "certified"`, and `ts`.
|
|
46
|
+
- **`state.violated`** — best-effort reporting for failed-closed verification. Its data contains bounded non-confirmed `results`, blocking `reasons`, selected `targets`, the verification-time `head`, and both digests.
|
|
47
|
+
- **`state.goal.met`** — emitted when the executable goal predicate passes.
|
|
48
|
+
|
|
49
|
+
A certificate target includes the transition's stable `transitionId`, `label`, and `to`. When verification has a head, the certificate also records its `transitionId`, label, destination, and CAS `version`. Certification and violation events are folded in sequence for each target. A transition receives a certified overlay only when its latest durable verification outcome is `state.certified`; a later `state.violated` removes that overlay. Certificate currentness additionally requires the complete recorded head identity—transition ID, label, destination, and CAS version—to equal the committed current head.
|
|
50
|
+
|
|
51
|
+
For a successful verification of the current head, `state/current` is CAS-rewritten with the latest current certificate. The stored certificate binds to the version produced by that CAS, rather than the pre-write version, so persisting it does not make itself stale. If the CAS loses to head advancement, the event remains a certificate of its target but is returned with `current: false` and is not attached to the new head. A later failed verification targeting the same current head publishes `state.violated` and CAS-removes the durable certificate. A new transition replaces the whole head value and therefore drops the prior current certificate naturally.
|
|
52
|
+
|
|
53
|
+
Both digests use SHA-256 and a `sha256:<hex>` representation. `evidenceDigest` deterministically covers the full ordered target identities and attached commands. `resultDigest` deterministically covers the ordered statuses, full-output digests and byte counts, bounded prefixes, omitted-byte metadata, command/claim digests, and failures. The result digest therefore changes when any byte of command output or failure details changes even when the returned output prefix is truncated.
|
|
54
|
+
|
|
55
|
+
Inspect the raw values with:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const events = await mesh.read({ topic: "fabric.state" });
|
|
59
|
+
const head = await mesh.get({ key: "state/current" });
|
|
60
|
+
const goal = await mesh.get({ key: "state/goal" });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Key `state/current`
|
|
64
|
+
|
|
65
|
+
The compare-and-swap head contains the transition identity and claim:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"protocolVersion": 2,
|
|
70
|
+
"commitProof": { "version": 1, "status": "committed" },
|
|
71
|
+
"transitionSequence": 42,
|
|
72
|
+
"label": "applied-auth-patch",
|
|
73
|
+
"from": "guard-planned",
|
|
74
|
+
"to": "guard-applied",
|
|
75
|
+
"summary": "Refresh-token rotation now holds the lock",
|
|
76
|
+
"kind": "state",
|
|
77
|
+
"transitionId": "<mesh event id>",
|
|
78
|
+
"ts": 1700000000000,
|
|
79
|
+
"evidence": ["grep -RIn 'lock' src/auth/refresh.ts"],
|
|
80
|
+
"tags": ["auth"]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The mesh entry's `version` is exposed as `head.version`. New protocol-2 heads are first CAS-written with commit proof `{ version: 1, status: "pending" }` and the proposal's original mesh sequence. After `transition.committed` publishes, a second CAS changes the proof to `committed`. A structurally valid committed proof remains readable without the proposal or marker in the bounded mesh read window. Pending proof never becomes durable merely because events age out: it is accepted only while a matching commit marker is visible, and a retained rejection marker overrides that commit marker. Existing protocol-1 heads retain their event-marker validation behavior, while older unversioned legacy heads retain their compatibility behavior.
|
|
85
|
+
|
|
86
|
+
The head stores the complete current transition fields needed to reconstruct its timeline record: original `sequence`, `label`, `from`, `to`, `summary`, `evidence`, `tags`, `kind`, `ts`, complexity delta, and transition-level certification status when present. If the current transition event ages out, history synthesizes only this validated current record; it never reconstructs an arbitrary non-current record from state keys.
|
|
87
|
+
|
|
88
|
+
A complexity-reduction head initially stores `certificationStatus: "pending"`. `state.get` overlays a validated durable or retained-event certificate as `certified`. The durable certificate itself is stored in `state/current`; it is omitted from reads if its full head binding is stale or a newer retained violation supersedes it.
|
|
89
|
+
|
|
90
|
+
### Keys `state/complexity/<file>`
|
|
91
|
+
|
|
92
|
+
Each supported file declared in a transition has a CAS ledger entry:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"file": "src/auth/refresh.ts",
|
|
97
|
+
"language": "typescript/javascript",
|
|
98
|
+
"count": 4,
|
|
99
|
+
"lastDelta": -2,
|
|
100
|
+
"ts": 1700000000000
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The entry is the latest recorded observation. The event log retains baselines and deltas.
|
|
105
|
+
|
|
106
|
+
### Key `state/goal`
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{ "check": "pnpm typecheck && pnpm test", "description": "green suite" }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Actions
|
|
113
|
+
|
|
114
|
+
Known actions use the typed first-class `state.<action>(args)` proxy. Use `tools.describe({ ref: "state.<action>" })` to inspect a schema and `tools.call()` only for refs computed at runtime.
|
|
115
|
+
|
|
116
|
+
### `state.transition` — risk: `write`
|
|
117
|
+
|
|
118
|
+
`{ label, from?, to, summary, evidence?, tags?, kind?, complexity?: { files: string[] }, force? }`
|
|
119
|
+
|
|
120
|
+
The provider validates `from` against the current committed head's `to` when supplied, appends a versioned proposal, CAS-writes complexity ledgers, CAS-advances `state/current` as pending, appends its commit marker, and CAS-upgrades the head's durable proof to committed. `force: true` preserves the existing mismatch/contention override behavior. Reads, history, verification selection, and certificate targeting ignore proposed or rejected transitions. Legacy transition events with no `phase` remain committed for compatibility.
|
|
121
|
+
|
|
122
|
+
When `complexity.files` is present, project-relative TS/JS/TSX/JSX files are counted immediately. The first supported observation is a baseline; later observations compare with and update the ledger. Unsupported files are reported but do not enter the ledger.
|
|
123
|
+
|
|
124
|
+
A negative net delta is rejected unless at least one non-empty behavior-preservation command is attached. Acceptance means **evidence-attached and pending**, not certified. The transition event and returned head contain `certificationStatus: "pending"`. The write action does not execute evidence secretly. Only a later successful `state.verify` emits a certificate; `state.history` and `state.get` can then expose which reduction transition it covers.
|
|
125
|
+
|
|
126
|
+
`kind: "representation"` establishes the history archive boundary described below.
|
|
127
|
+
|
|
128
|
+
Returns `{ event, head }`.
|
|
129
|
+
|
|
130
|
+
### `state.get` — risk: `read`
|
|
131
|
+
|
|
132
|
+
Returns `{ head, goal, complexity, certification, recentLabels }`. `certification.current` is a certificate only when its recorded head identity still equals the current head. The current head and its certified overlay survive event truncation for protocol-2 committed heads. `certification.recent` includes the durable current certificate plus retained visible event certificates; non-current certification history remains bounded by mesh event retention.
|
|
133
|
+
|
|
134
|
+
### `state.history` — risk: `read`
|
|
135
|
+
|
|
136
|
+
`{ label?, limit?, includeArchived? }` folds transitions into the ordered label graph and returns `{ transitions, labels, certifications }`. Matching committed transitions expose a certificate only when it is their latest verification outcome and `certificationStatus: "certified"`; a committed reduction without a latest successful outcome remains `pending`. Proposals and rejected transitions never appear. If retention removes the committed current transition event, the fold adds only the record reconstructed from a validated protocol-2 current head. Older non-current transitions and certificates are not synthesized and remain bounded by mesh retention.
|
|
137
|
+
|
|
138
|
+
The fold finds the last representation transition and excludes earlier transitions and their certificates by default. `includeArchived: true` reveals the full append-only transition history and permits archived certificates to be shown. A `label` filter matches `label`, `from`, or `to` within the selected archive view.
|
|
139
|
+
|
|
140
|
+
### `state.complexity` — risk: `read`
|
|
141
|
+
|
|
142
|
+
`{ files? }` counts requested project-relative files and compares them with the ledger. Omit `files` to inspect all recorded files. The result includes supported-language counts, current and recorded deltas, unsupported entries, and `netDelta`.
|
|
143
|
+
|
|
144
|
+
### `state.verify` — risk: `execute`
|
|
145
|
+
|
|
146
|
+
`{ labels?, includeArchived?, timeoutMs? }` selects the current head when `labels` is omitted, or active transitions matching the supplied labels. Archived transitions are excluded unless `includeArchived: true`.
|
|
147
|
+
|
|
148
|
+
Commands run sequentially with a per-command timeout (default 30 seconds). Combined stdout/stderr is streamed rather than accumulated without limit. Each report retains at most a 32 KiB UTF-8 prefix per command and includes `outputBytes`, `outputOmittedBytes`, and a digest of the complete byte stream. Claims, commands, and errors are also byte-bounded in results and carry digests/omission metadata. Verification events use smaller bounded prefixes, bounded result/reason arrays, and target chunks so each payload stays comfortably below the default 256 KiB mesh limit. Each result includes `{ claim, command, status, exitCode, output, outputBytes, outputOmittedBytes, outputDigest, error? }`, where `status` is:
|
|
149
|
+
|
|
150
|
+
- `confirmed` for exit 0;
|
|
151
|
+
- `violated` for a non-zero exit;
|
|
152
|
+
- `error` for spawn failure, timeout, or cancellation.
|
|
153
|
+
|
|
154
|
+
The report adds `{ certified, violated, certificationStatus, evidenceDigest, resultDigest, failures, certificate? }`. `certified` is true if and only if one or more evidence commands ran and every result was confirmed. All other outcomes are blocking and publish `state.violated`. A successful run publishes `state.certified` and returns its certificate. If the selected targets include the unchanged current head, it then CAS-persists that certificate in `state/current` with a binding to the resulting head version. A failed run targeting the unchanged current head CAS-revokes any stored certificate after violation publication succeeds.
|
|
155
|
+
|
|
156
|
+
An explicitly empty `labels` array is an empty selection and fails closed. Requesting an archived, proposed, rejected, or otherwise uncommitted label without a visible committed match also fails closed as a missing active target.
|
|
157
|
+
|
|
158
|
+
On POSIX, each command shell is the leader of a detached process group; timeout or abort sends `SIGKILL` to that group and waits for the shell/stdio close before returning. This covers descendants that remain in that process group, but cannot cover a descendant that deliberately creates a different process group/session. On Windows, timeout/abort awaits a bounded `taskkill /T /F` attempt and falls back to direct child termination; Windows cleanup is explicitly best effort and does not claim that every independently detached descendant is gone.
|
|
159
|
+
|
|
160
|
+
### `state.goal` — risk: `write`
|
|
161
|
+
|
|
162
|
+
`{ check, description? }` stores the executable goal predicate at `state/goal`.
|
|
163
|
+
|
|
164
|
+
### `state.checkGoal` — risk: `execute`
|
|
165
|
+
|
|
166
|
+
`{ timeoutMs? }` runs the goal predicate and reports `{ passed, output, exitCode, error? }`. It uses the same 32 KiB command-output cap; a passing `state.goal.met` event stores a smaller bounded prefix plus full-stream digest and omission metadata. Goal checks are separate from state certification.
|
|
167
|
+
|
|
168
|
+
## Complexity rule
|
|
169
|
+
|
|
170
|
+
The built-in complexity implementation supports `.ts`, `.js`, `.tsx`, and `.jsx`. It lexes tokens without an AST dependency and counts statement decision keywords:
|
|
171
|
+
|
|
172
|
+
- `if`, including the `if` in `else if`;
|
|
173
|
+
- `case` and `default` in a switch body;
|
|
174
|
+
- `catch`, including optional catch binding;
|
|
175
|
+
- `for` and `while`.
|
|
176
|
+
|
|
177
|
+
Strings, template/JSX prose, regular-expression literals, and comments are skipped; `${...}` and JSX expression code are tokenized. Ternaries, `&&`, `||`, optional chaining, and nullish coalescing do not count. Unsupported languages return `supported: false`.
|
|
178
|
+
|
|
179
|
+
## Determinism and contention
|
|
180
|
+
|
|
181
|
+
The head is CAS-advanced with a bounded eight-attempt retry loop. Its initial value is pending, and a proposal is not history until its commit marker follows successful ledger and head writes. On ledger, head, or commit-marker failure, the layer CAS-restores each completed write from its captured before-value (or CAS-deletes a newly created value) and best-effort emits `transition.rejected`. Deleted-key versions are carried in rejection metadata so later CAS creation remains version-aware. After marker publication, the transaction is committed and must not be rolled back; the normal path performs one more CAS to make that fact independent of event retention. On CAS failure the layer re-reads and re-validates `from`:
|
|
182
|
+
|
|
183
|
+
- if the transition still chains from the new head, it retries with the new version;
|
|
184
|
+
- if the chain is broken, it raises a contention error naming the actual head.
|
|
185
|
+
|
|
186
|
+
`force: true` skips both the pre-append mismatch check and contention re-validation. If every restoration succeeds, the rejected proposal stays invisible and prior ledger/head values are restored. If restoration or rejected-marker publication fails, the thrown error explicitly reports rollback quarantine/reporting failure; the proposal still has no commit marker and is never folded into history or verification. Certification changes the head's mesh version only when persisting or revoking the current certificate; it does not alter transition sequence or archive ordering.
|
|
187
|
+
|
|
188
|
+
### Crash and retention limits
|
|
189
|
+
|
|
190
|
+
- A crash before `transition.committed` leaves a pending head that fails closed. A crash after the marker is durable but before the proof-upgrade CAS leaves a pending head that is accepted only while that marker remains in the bounded read window. If the marker ages out first, the head fails closed and later transitions are blocked until an operator repairs or removes the quarantined pending value. The normal non-crashing path upgrades the proof immediately.
|
|
191
|
+
- Protocol-2 committed heads, their one current transition record, and their latest successfully persisted current certificate are independent of `mesh.maxReadEvents`. Protocol-1 versioned heads still require retained commit evidence; unversioned legacy heads keep their historical compatibility behavior.
|
|
192
|
+
- Non-current transitions and certificates are event history and may age out. The state key is deliberately not an unbounded history mirror.
|
|
193
|
+
- Certificate revocation and violation publication are separate durable writes. Revocation is attempted first, so a publication failure cannot by itself preserve an old current certificate. If the revocation state write fails for a non-contention storage error, the report includes `reportingError`; a successfully retained violation suppresses the old certificate while visible, but after that event ages out the stale durable certificate can reappear. CAS loss caused by concurrent head advancement or a later verification is safe: the operation does not modify the winning head, and event order/current binding prevents the losing result from becoming current.
|
|
194
|
+
|
|
195
|
+
## Activity
|
|
196
|
+
|
|
197
|
+
The provider emits mesh entity activity for transitions, verification runs, and goal sets, plus progress updates. These surface in the Fabric dashboard and widget.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-fabric",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.3",
|
|
4
4
|
"description": "A programmable tool and agent runtime for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist/",
|
|
20
20
|
"skills/",
|
|
21
|
+
"docs/",
|
|
21
22
|
"README.md",
|
|
22
23
|
"LICENSE",
|
|
23
24
|
"THIRD_PARTY_NOTICES.md"
|
|
@@ -8,7 +8,7 @@ disable-model-invocation: true
|
|
|
8
8
|
|
|
9
9
|
Create the advisor with Fabric primitives; do not install an advisor extension. Treat skill arguments as an optional focus.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Hard pointer: read `../fabric-ambient/references/setup.md` completely before setup, then use its program with:
|
|
12
12
|
|
|
13
13
|
- `strings.name`: `advisor`
|
|
14
14
|
- `strings.events`: `["agent_settled","tool_error"]`
|
|
@@ -22,4 +22,8 @@ You are an ambient peer advisor for the main coding agent. Review the supplied p
|
|
|
22
22
|
Prefer silence. Return {"action":"silent"} when work is on track. Return {"action":"message","message":"..."} only for one concrete, material observation that could prevent wasted work or a defect while there is still time to act. Cite the evidence and recommendation tersely as advice, not an order. Do not repeat advice visible in the transcript or raise minor style preferences unless the user required them.
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`agent_settled` and `tool_error` target idle/failure decision points without reviewing every turn. `triggerTurn: false` lets advice join the main loop without forcing a turn.
|
|
25
|
+
`agent_settled` and `tool_error` target idle/failure decision points without reviewing every turn. `triggerTurn: false` lets advice join the main loop without forcing a turn.
|
|
26
|
+
|
|
27
|
+
## Completion criterion
|
|
28
|
+
|
|
29
|
+
Complete only when setup returns an `advisor` with both events, read-only native tools, `triggerTurn: false`, and no recreation warning. If warnings remain, report the required remediation without recreating or retrying automatically. Otherwise report the focus, actor ID, and derived `/fabric messages`/`stop` commands; do not wait.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fabric-ambient
|
|
3
|
-
description: Creates a
|
|
3
|
+
description: Creates a persistent Pi Fabric supervisor or advisor profile. Use for ambient supervision, ongoing peer review, an advisor, or a goal watcher without another extension.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Fabric Ambient Actors
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Choose and execute the matching profile directly; never bounce the user to another skill after they invoked this one. Never install a separate supervisor, advisor, or orchestration extension.
|
|
10
10
|
|
|
11
11
|
Choose from the first argument or infer from the request:
|
|
12
12
|
|
|
13
13
|
- `supervisor <goal>`: verify progress toward a concrete goal and steer only on missing work, drift, failure, or completion.
|
|
14
14
|
- `advisor [focus]`: review turns and surface only material correctness advice.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Hard pointer: read `references/setup.md` completely before setup and use its shared program. Always pass every named string, using an empty `model` when unset. Do not ask for details already present.
|
|
17
17
|
|
|
18
18
|
## Supervisor profile
|
|
19
19
|
|
|
@@ -37,4 +37,8 @@ Use `name=advisor`, `events=["turn_end"]`, and `triggerTurn=false`. Append any r
|
|
|
37
37
|
You are an ambient peer advisor reviewing the main coding agent. Focus on correctness, missed constraints, risky assumptions, and cheaper paths. Inspect with read-only tools only when needed. Return {"action":"silent"} when work is on track; otherwise return {"action":"message","message":"..."} for one concrete, material observation. Cite evidence and a terse recommendation as advice, not an order. Do not repeat visible advice.
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
The supervisor can wake an idle session; the per-turn advisor must not.
|
|
40
|
+
The supervisor can wake an idle session; the per-turn advisor must not.
|
|
41
|
+
|
|
42
|
+
## Completion criterion
|
|
43
|
+
|
|
44
|
+
Complete when setup returns an actor matching the selected profile, events, trigger policy, and native tools with no recreation warning. If warnings remain, report remediation without automatically rerunning setup. Otherwise report the profile, actor ID, and derived `/fabric messages`/`stop` commands; do not wait.
|
|
@@ -5,6 +5,7 @@ Pass `strings.name`, `strings.instructions`, JSON `strings.events`, `strings.tri
|
|
|
5
5
|
```ts
|
|
6
6
|
const events = JSON.parse(π.events) as FabricActorHostEvent[];
|
|
7
7
|
const triggerTurn = π.triggerTurn === "true";
|
|
8
|
+
const desiredTools = ["read", "grep", "find", "ls"];
|
|
8
9
|
let model: string | undefined;
|
|
9
10
|
let runner: FabricAgentRunner | undefined;
|
|
10
11
|
if (π.model) {
|
|
@@ -18,38 +19,56 @@ if (π.model) {
|
|
|
18
19
|
})));
|
|
19
20
|
} catch {}
|
|
20
21
|
const needle = π.model.toLowerCase();
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const exact = models.filter((entry) => entry.key.toLowerCase() === needle);
|
|
23
|
+
const fuzzy = exact.length === 0
|
|
24
|
+
? models.filter((entry) =>
|
|
25
|
+
entry.id.toLowerCase().includes(needle) ||
|
|
26
|
+
entry.name.toLowerCase().includes(needle)
|
|
27
|
+
)
|
|
28
|
+
: exact;
|
|
29
|
+
if (fuzzy.length !== 1) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
fuzzy.length === 0
|
|
32
|
+
? `Model "${π.model}" not found: ${models.map((entry) => entry.key).join(", ")}`
|
|
33
|
+
: `Model "${π.model}" is ambiguous: ${fuzzy.map((entry) => entry.key).join(", ")}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
model = fuzzy[0].key;
|
|
37
|
+
runner = fuzzy[0].runner;
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
const existing = (await agents.actors()).find(
|
|
32
41
|
(actor) => actor.name === π.name && actor.status !== "stopped",
|
|
33
42
|
);
|
|
34
43
|
if (existing) {
|
|
44
|
+
const runnerMatches = !runner || existing.runner === runner;
|
|
45
|
+
const warnings = [
|
|
46
|
+
...(existing.status !== "idle" ? [`actor is ${existing.status}; wait until idle or stop it before reconfiguration`] : []),
|
|
47
|
+
...(existing.responseMode !== "directive" ? ["recreate for responseMode=directive"] : []),
|
|
48
|
+
...(existing.coalesce !== true ? ["recreate for coalesce=true"] : []),
|
|
49
|
+
...(existing.topics.length !== 0 ? ["recreate without topic subscriptions"] : []),
|
|
50
|
+
...(!runnerMatches ? [`runner "${runner}" requires recreation`] : []),
|
|
51
|
+
...(runnerMatches && model && existing.model !== model ? [`model "${model}" requires a dashboard change or recreation`] : []),
|
|
52
|
+
];
|
|
53
|
+
if (warnings.length) return { reused: false, actor: existing, warnings };
|
|
54
|
+
|
|
35
55
|
await agents.setInstructions({ id: existing.id, instructions: π.instructions });
|
|
56
|
+
if (
|
|
57
|
+
existing.tools?.length !== desiredTools.length ||
|
|
58
|
+
desiredTools.some((tool) => !existing.tools?.includes(tool))
|
|
59
|
+
) {
|
|
60
|
+
await agents.setTools({ id: existing.id, tools: desiredTools });
|
|
61
|
+
}
|
|
36
62
|
if (existing.events.length !== events.length || events.some((event) => !existing.events.includes(event))) {
|
|
37
63
|
await agents.setEvents({ id: existing.id, events });
|
|
38
64
|
}
|
|
39
65
|
if (existing.delivery !== "steer" || existing.triggerTurn !== triggerTurn) {
|
|
40
66
|
await agents.setDeliveryPolicy({ id: existing.id, delivery: "steer", triggerTurn });
|
|
41
67
|
}
|
|
42
|
-
const runnerMatches = !runner || existing.runner === runner;
|
|
43
|
-
const modelMatches = !model || existing.model === model;
|
|
44
68
|
return {
|
|
45
69
|
reused: true,
|
|
46
70
|
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
|
-
],
|
|
71
|
+
warnings: [],
|
|
53
72
|
};
|
|
54
73
|
}
|
|
55
74
|
|
|
@@ -61,11 +80,11 @@ const actor = await agents.create({
|
|
|
61
80
|
delivery: "steer",
|
|
62
81
|
triggerTurn,
|
|
63
82
|
coalesce: true,
|
|
64
|
-
tools:
|
|
83
|
+
tools: desiredTools,
|
|
65
84
|
...(runner ? { runner } : {}),
|
|
66
85
|
...(model ? { model } : {}),
|
|
67
86
|
});
|
|
68
87
|
return { started: true, actor };
|
|
69
88
|
```
|
|
70
89
|
|
|
71
|
-
Reuse updates instructions/events/delivery. Recreate for runner, `responseMode`,
|
|
90
|
+
Reuse updates instructions/events/delivery/native tools. Recreate for runner, model, `responseMode`, `coalesce`, or topics. Extension and provider availability follows the configured runner and actor extension policy. Report ID/warnings and messages/stop; do not wait.
|