@passioncode-ai/fabric-agent-adapter 0.51.2 → 0.53.1
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/CHANGELOG.md +160 -0
- package/README.md +8 -4
- package/SECURITY.md +18 -3
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +4 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +8 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +85 -13
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +22 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +44 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +48 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +6 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +7 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +30 -15
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +33 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +146 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +17 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate-rules.mjs +606 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +48 -525
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +43 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +14 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +48 -15
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +4 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +39 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +34 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +149 -45
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +8 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +86 -642
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate_rules.py +766 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +70 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +59 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_mandate.py +56 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +314 -25
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +13 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +4 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +7 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +75 -36
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +28 -14
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +42 -12
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +11 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +12 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +12 -2
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +360 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,165 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.53.1 (2026-10-27)
|
|
4
|
+
|
|
5
|
+
0.53.0 was tagged and never released on npm or GitHub (the plugin marketplace, which reads `main`, offered it until
|
|
6
|
+
0.53.1 merged): its release run failed on Windows, where the FAA-140 tests read a Bash
|
|
7
|
+
command's POSIX paths through Windows path rules. 0.53.1 is 0.53.0 with this fix.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **No transcript read on Windows** (FAA-175): working-with-fabric's Stop hook never reads the session transcript on
|
|
12
|
+
Windows, even once Fabric takes `repositories`; a Bash command's paths there are not the host's. Windows sessions send
|
|
13
|
+
their turn facts as before 0.53.0, without `repositories`. `BashPaths` and the four tests that need the hook itself to
|
|
14
|
+
read (names from Edit and Bash paths, at most ten, one checkout named once, another checkout of the start repository)
|
|
15
|
+
are skipped on Windows; the rest run there, the worker's with the read turned on in the test, and a new test checks that
|
|
16
|
+
the mark turns nothing on there.
|
|
17
|
+
|
|
18
|
+
## 0.53.0 (2026-10-27, not published)
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **The repositories a turn touched** (FAA-140, Fabric CO-367): working-with-fabric's Stop hook names the other Git
|
|
23
|
+
repositories a turn touched, once the running Fabric takes them. Nothing changes for a Fabric before that: the hook
|
|
24
|
+
reads no transcript until a worker has seen `fabric_session_observe` declare `repositories` as an array of items with
|
|
25
|
+
`host`, `owner` and `name` that require nothing else (it then leaves a mark in the hooks' folder, and removes it when
|
|
26
|
+
the schema stops saying so; after a Fabric downgrade, the turns that end before a worker next runs, about a minute, are still read; none of their
|
|
27
|
+
names is sent). With the mark, the
|
|
28
|
+
hook reads the last 512 KB of the session's transcript (earlier turns, message text and tool output included; a secret the session displayed can be there, read in memory only) and uses only the
|
|
29
|
+
current turn's tool names and path arguments: Edit, Write, MultiEdit and NotebookEdit paths, and `cd
|
|
30
|
+
<absolute path>` and `git -C <path>` in Bash commands that hold `cd` or `git`, at most 64 KB of them a turn. A `cd`
|
|
31
|
+
or a read-only `git -C <path> log` names a repository as much as an edit does; a subagent's tool calls are not
|
|
32
|
+
read. Each repository is resolved with one `git rev-parse --show-toplevel` and one `git config` inside the existing
|
|
33
|
+
1.5 s facts budget, and named `{host, owner, name}` from its `origin` (a credential embedded in the URL is read with it by `git config`; it, the port
|
|
34
|
+
and `.git` are dropped in memory, never spooled, logged or sent). The
|
|
35
|
+
repository of the session's folder when the turn ends (the Stop payload's `cwd`; any checkout of it), a repository without a parsable origin and duplicates are left out;
|
|
36
|
+
at most 10 (or the schema's `maxItems`), and an item whose host, owner or name the schema refuses (type, length,
|
|
37
|
+
pattern, enum) is left out, never the turn; no local path leaves the hook (on a self-hosted ssh remote `owner` is the
|
|
38
|
+
server's folder path). The worker sends them as `repositories` on `fabric_session_observe`. With a Fabric that never
|
|
39
|
+
took them nothing is read and nothing is logged; a turn spooled with the mark and delivered to a Fabric that no
|
|
40
|
+
longer takes them goes without them, and the log says so once per session, naming no repository. Measured with the read on, at `f3765f1` (before the gate and the 64 KB cap): the Stop hook's median is 247 ms on a 4.8 MB transcript naming
|
|
41
|
+
six repositories (0.52.0: 109 ms).
|
|
42
|
+
- **A board slot the operator moves to another session stays moved** (FAA-155; contract DEC-0140, proposed; CT-174,
|
|
43
|
+
CT-175). The kit's board responder records the generation it holds each slot at, beside its checkpoint: when a
|
|
44
|
+
receipt's generation changes and before an enroll or replace that can change it; a live holder's renew writes
|
|
45
|
+
nothing, and a clean leave (the session's DELETE released the slots) forgets the record of a slot not moved and with no request
|
|
46
|
+
left. When `com.status` (at connect, or after a call refused `fenced`) shows a later generation, it stops the
|
|
47
|
+
handler, drops its claims, and leaves accepted work held (`lapsed`) and begun work `unknown`; it neither replaces at
|
|
48
|
+
recovery after a restart nor enrolls again when the new holder's lease lapses. Accepted requests stay held on the
|
|
49
|
+
board, unanswered, until the slot is taken back (CT-175 will hand them to the new holder). One `board.slot.moved`
|
|
50
|
+
event (notify) says so, once across restarts. A first start, an equal generation, or an odd or lower generation
|
|
51
|
+
behaves as before. An enroll or replace is recorded with its key before it is sent, so a lost answer to the
|
|
52
|
+
responder's own call is asked again under that key and never read as a move; a replay the board runs for the first
|
|
53
|
+
time (`repeated: false`) after the slot had moved past the record is read as that move, and the slot it took lapses
|
|
54
|
+
unused; a replay refused `idempotency_window_expired` (a retired epoch) forgets the record, so the slot reads as on a
|
|
55
|
+
first start. A recorded move stays even when `com.status` later names no generation for the slot. The record is best
|
|
56
|
+
effort: one that cannot be written (a full disk) never stops a call or the responder,
|
|
57
|
+
and a restart before the next one is recorded may then read as a move. A holder's renew within its live lease writes
|
|
58
|
+
nothing; the reference service now waits at its stop for the responder to leave its board session. `Responder.take_back(capability)` /
|
|
59
|
+
`takeBack(capability)` is the operator's act that brings a moved slot back: it enrolls once the other session lets
|
|
60
|
+
the slot go and takes the held work back by a transfer; a restart or reconnect before that, with work held, replaces
|
|
61
|
+
the other session at once. Moving the slot back to this service in Fabric does not bring it back (the responder
|
|
62
|
+
skips a moved slot until `take_back`; FAA-165). The kit offers no route for it: a service declares one as an
|
|
63
|
+
operator action with its tool and gate `relayed` (the operator's decision; never `none`; on a server that reads
|
|
64
|
+
mandates it also names its effect class) (`fabric-actions/0.1`; the reference service's is FAA-157). The
|
|
65
|
+
notice says begun work is the new holder's to settle and that the take-back action brings the slot back. Both
|
|
66
|
+
twins.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **Step 4 reads Fabric's `spawned_from`** (FAA-156, Fabric 0.3.14): working-with-fabric links a piece filed with
|
|
71
|
+
`fabric_task_create` itself only when the answer says `spawned_from: null` without `proposed: true` (once; a refused link is told to the person
|
|
72
|
+
with `spawned_from_why`), or is filed (`created: true`) with no `spawned_from` (Fabric before 0.3.14) and no `chained: true` with `chained_to`
|
|
73
|
+
the claimed task. `created: false` with a `code` such as `source-unresolved` filed nothing and is never linked. A piece whose `spawned_from` names the claimed task is already linked; one naming another task is
|
|
74
|
+
not linked again, and the person is told which parent Fabric chose (FAA-153). `proposed: true` now says what
|
|
75
|
+
to do: file and link nothing for that piece, tell the person, go on with the rest. The goal display is named as
|
|
76
|
+
Fabric 0.3.14's. New scenario evals `spawned-from-decides-the-link`, `what-the-stop-hook-reads` (FAA-140) and
|
|
77
|
+
`moved-slot-needs-a-take-back-action` (FAA-155).
|
|
78
|
+
- **A responder no longer takes back a slot that moved while it was away** (FAA-155). Before 0.53.0 a restarted
|
|
79
|
+
responder replaced whoever held its slot (at recovery) or enrolled again when that holder lapsed, so a slot taken
|
|
80
|
+
while it was down or after a crash came back by itself. It now stays with the new holder until the operator calls
|
|
81
|
+
`take_back`; a second responder of the same capability is no longer a standby once the slot changed hands. A
|
|
82
|
+
service stopped cleanly with no request left in its checkpoint, rolled back to 0.52 and up again starts as on a first start (the clean leave
|
|
83
|
+
forgot the record); after a crash, a 0.52 run that enrolled the slot and wrote nothing to the checkpoint reads as a move at the next 0.53 start, which
|
|
84
|
+
the take-back action undoes. A `fenced` refusal on accepted work while the slot did not move now keeps the entry `lapsed`
|
|
85
|
+
and the next tick transfers it back and finishes it (0.52 dropped the entry and left the request accepted on the
|
|
86
|
+
board). A refusal raised while the responder recovers at its first connect, before any slot is held, now reconnects
|
|
87
|
+
instead of leaving it `ready` with no slot; one after some slots are held keeps them served (a reconnect still
|
|
88
|
+
counts the previous session's slots as held: FAA-163). A generation written `2.0` now reads as `2` in the Python twin as it always did in Node.
|
|
89
|
+
|
|
90
|
+
## 0.52.0 (2026-10-26)
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
- **Pinned to the contract release `v0.1.0+2026-10-26.12` (`1197a90d`)** (FAA-138). It carries DEC-0126 to DEC-0137;
|
|
95
|
+
`e6c4fc16`, `a3a66eb0` and `52c7d798` stay supported. Brief:
|
|
96
|
+
`docs/evidence/specs/2026-10-26-faa-138-134-repin-1197a90d-brief.md`.
|
|
97
|
+
- **DEC-0126, where the operator said the words (`source`).** A mandate and a decision may carry `source`; both kits
|
|
98
|
+
read it as the contract's schema does, return it in the record (`recordAs`, `ctx.record`, `input_records`), keep it
|
|
99
|
+
on a hand-over, and build it with `operator_mandate(source=)` / `operator_decision(source=)` (Node: `source`).
|
|
100
|
+
`request_meta(..., target_protocols=)` / `requestMeta(traceparent, {targetProtocols})` send it only to a callee whose
|
|
101
|
+
well-known `protocols` list `operator-source/1`, and otherwise keep it in the caller's own record (OA-7; never through
|
|
102
|
+
Fabric's `agent.call` yet, FAA-146). `McpToolServer.well_known_protocols(protocols)` adds `operator-source/1` when
|
|
103
|
+
the server reads `source`. Nothing writes it to an event, a usage report, a log or a page (a test holds that).
|
|
104
|
+
- **A job's approval step reads the operator's mandate** (contract interop C3.3, `conformance.md`; part of FAA-135):
|
|
105
|
+
`job_step` / `jobStep`, `CallContext.approval_step`, `request_input(steps=)` / `requestInput(..., {steps})`; with
|
|
106
|
+
`input_actions` on, an answer's `_meta.mandate` that covers a kept step is read too, under the job's own capability
|
|
107
|
+
as at the start (`action_gate(mandate_capability=)` / `actionGate({mandateCapability})`). Each question replaces the
|
|
108
|
+
kept steps and an answer clears them, so a step outlives neither its question nor its answer.
|
|
109
|
+
- **DEC-0136 closes at the pin:** MD-2a is the pinned contract's rule, the by-name test override is gone, and both
|
|
110
|
+
twins run the contract's 30 `handOn` cases; FAC-SEM-144's message ends `(DEC-0125 MD-1, MD-2; DEC-0136 MD-2a)`.
|
|
111
|
+
Vectors at `.12`: mandate 40 cases and 5 fixtures, actions 67 cases and 16 action calls.
|
|
112
|
+
- working-with-fabric, its board reference, `interop.md` and the shared operator-channel reference cite DEC-0126 OA-1
|
|
113
|
+
to OA-7 and drop the "ahead of the pin" wording; scenario eval `relay-source-goes-only-where-it-is-read`.
|
|
114
|
+
- **DEC-0128 (DL-14a), a hub key per organization context.** `hub_key_certified` / `hubKeyCertified` take the
|
|
115
|
+
token's context (`context=` / `{context}`), and `verify_delegation` and `delegation_gate` (Node: `verifyDelegation`,
|
|
116
|
+
`delegationGate`) take `estate_context` / `estateContext`. An organization's token verifies only under its context's
|
|
117
|
+
hub key (`machines[].contextHubKeys`) while the record pins the organization; a context that is neither `personal`
|
|
118
|
+
nor `org:<orgId>` fails closed (`kid-not-certified`). `verify_approval` takes no context, as the contract declares.
|
|
119
|
+
- Both estate readers check organization pins and context hub keys as the contract's `estate-record-rules.ts` does at
|
|
120
|
+
`.12`: `FAC-SEM-147` is new; `FAC-SEM-073`, `076`, `114` and `134` read context hub keys.
|
|
121
|
+
- `fabric_estate` splits its record rules into the new kit module `fabric_estate_rules` (Node:
|
|
122
|
+
`fabric-estate-rules.mjs`), listed just before it in `kit.json`; `fabric_estate` re-exports every public name, so callers
|
|
123
|
+
keep their imports, and `fabric_kit.py install --module fabric_estate` copies both.
|
|
124
|
+
- An organization instance whose context the record does not pin still starts (`fabric_context`); its tokens are
|
|
125
|
+
refused `kid-not-certified` (brief D5).
|
|
126
|
+
- Vectors regenerated at `.12`: `estate-vectors.json` (the organizations positive, seven context and organization
|
|
127
|
+
semantic fixtures, 38 generated context and revision cases), `hub-key-vectors.json` (`contextCases` 33,
|
|
128
|
+
`contextVerifier` 10); the hostile estate inputs cover `contextHubKeys`, `organizations` and hub-key contexts.
|
|
129
|
+
- **DEC-0135, who a reply reaches** (narrowing). A reply reaches the sender of the message it answers; send
|
|
130
|
+
`deliverTo: "thread"` for every participant, or a list, and on a board pinned before DEC-0135, which refuses the
|
|
131
|
+
member, a new message to the thread instead. The kit's responder posts only fenced replies, which carry none.
|
|
132
|
+
The narrowing: once Fabric adopts DEC-0135 (planned for its 0.3.15; DR-5, for a reader it takes as pinned at or
|
|
133
|
+
after DEC-0135; Fabric to 0.3.14 keeps the old count for every reader), `com.list {unread: true}` and the unread
|
|
134
|
+
count cover only what was delivered to your Project, so a reply sent only to another Project no longer shows.
|
|
135
|
+
`project-board.md` now also says a thread `com.read_ack` with `upTo` marks what your Project did not send, as the
|
|
136
|
+
board always did.
|
|
137
|
+
- **DEC-0130 and DEC-0137, the family and the agent network** (wording, no code change): `delegation.md` names the
|
|
138
|
+
operator's family (T0 and T1) and the agent network, and `estate-record.md` says a tokenless call runs in the
|
|
139
|
+
instance's context.
|
|
140
|
+
- The session hooks' notes on nudges follow Fabric 0.3.14's final behaviour (board `b605a636`, Fabric
|
|
141
|
+
`release/0.3.14` `b97ec351`): a turn's answer shows a nudge, at most five, and a repeated turn answers the same; it
|
|
142
|
+
stays for `fabric_work_list` and `fabric_heartbeat`, so it may come again there. The nudges of turns spooled before a
|
|
143
|
+
`/clear` are therefore not lost (0.51.2's notes said they were).
|
|
144
|
+
|
|
145
|
+
### Added
|
|
146
|
+
|
|
147
|
+
- **Being seen correctly in Fabric** (FAA-151, board `c42071bc`): working-with-fabric Steps 4 and 5 claim the
|
|
148
|
+
person's request as one task titled with it in one line (`origin: {kind: "person", ref}`), file each split with
|
|
149
|
+
`fabric_task_create` while it is claimed, and link it with `fabric_task_link` (`rel: "spawned"`) unless the answer
|
|
150
|
+
says `chained: true` with `chained_to` the claimed task: always in a session opened from its own terminal, which
|
|
151
|
+
Fabric to 0.3.14 never links itself. They report the stage with `step` and `ofSteps`, and send a heartbeat with
|
|
152
|
+
`phase: waiting` before a wait (the wait in `note`, or `waitingOn` for a question asked with `fabric_question_ask`).
|
|
153
|
+
Scenario eval `seen-correctly-goal-and-step`.
|
|
154
|
+
- **Fabric's rule `work.check-every-three-minutes`** (FAA-141, Fabric TO-2, ask `e2a7bec4`): working-with-fabric
|
|
155
|
+
Step 5 and creating-fabric-agents' autonomy reference tell an agent to call `fabric_work_list` every three minutes
|
|
156
|
+
and before any wait, act first on its `nudges` and `waiting_for_you`, read and mark its unread board answers, and
|
|
157
|
+
never leave a held task still. Scenario eval `check-every-three-minutes`.
|
|
158
|
+
- **Nine protocol register entries** (DEC-0126, DEC-0127, DEC-0129, DEC-0131, DEC-0132): `fabric-machine-report/1`,
|
|
159
|
+
`machine-report/1`, `operator-source/1`, `org-authority/1`, `org-authority-draft/1`, `org-key-set/1`,
|
|
160
|
+
`org-key-set-draft/1`, `org-membership/1` and `org-root-registration/1` in `fabric_protocols` and
|
|
161
|
+
`fabric-protocols.mjs`, so the kits' register equals the contract's at the pin.
|
|
162
|
+
|
|
3
163
|
## 0.51.2 (2026-10-26)
|
|
4
164
|
|
|
5
165
|
### Added
|
package/README.md
CHANGED
|
@@ -38,9 +38,12 @@ resume) puts that preamble into the session when the MCP server `fabric` is regi
|
|
|
38
38
|
uses Fabric (one line pointing at `fabric_whoami` in a folder with no sign of it); when Fabric
|
|
39
39
|
is down it adds one line (skip `fabric_whoami`, start Fabric through the broker if the task needs it), and only in a
|
|
40
40
|
project that uses Fabric; Stop records each turn's facts (HEAD, files changed, lines added and removed, never content)
|
|
41
|
-
and the board's unread count
|
|
41
|
+
and the board's unread count, and, once the running Fabric takes them (not on Windows), the other repositories the turn touched (it then
|
|
42
|
+
reads the last 512 KB of the session transcript (message text and tool output included, which can hold a secret the session displayed; read in memory only), uses only tool names and path arguments, and sends host/owner/name, never a
|
|
43
|
+
local path or text); UserPromptSubmit adds one line only when that count rose, and each reminder Fabric gave on a turn once (a turn `watch-mail` delivered prints there instead); SessionEnd releases what the
|
|
42
44
|
session left claimed. Stop, UserPromptSubmit and SessionEnd spool and call Fabric only when the running Fabric
|
|
43
|
-
advertises `session-hooks/1`; Stop and SessionEnd always prune their own stale spool folders. All are silent in a session Fabric launched, never block the session, read no token
|
|
45
|
+
advertises `session-hooks/1`; Stop and SessionEnd always prune their own stale spool folders. All are silent in a session Fabric launched, never block the session, read no Fabric token (an `origin` URL's embedded credential is dropped in memory, never
|
|
46
|
+
kept or sent) and always
|
|
44
47
|
exit 0. `fabric_session.py watch-mail` waits for board mail under a background watcher, and `install-host-hooks
|
|
45
48
|
--host codex|kimi|hermes|opencode|openclaw|pi` installs each host's native context bridge (dry run unless `--apply`).
|
|
46
49
|
Codex receives structured SessionStart JSON; Hermes, OpenCode, OpenClaw and Pi use native plugin APIs.
|
|
@@ -67,7 +70,8 @@ a complete `scripts/sample_service.py`; and `scripts/check_service.py`, a live p
|
|
|
67
70
|
requests other Projects post to its Project on Fabric's project board uses the board responder
|
|
68
71
|
(`scripts/fabric_hub.py` and `scripts/fabric_board.py`, Node `fabric-hub.mjs` and `fabric-board.mjs`): the
|
|
69
72
|
`board.respond` grant, enroll, claim, accept the exact digest, complete, a checkpoint before every step, and restarts
|
|
70
|
-
that reconcile or take accepted work back by a transfer
|
|
73
|
+
that reconcile or take accepted work back by a transfer, unless the slot was moved to another session (until the
|
|
74
|
+
operator's take-back) (the [board reference](plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md)):
|
|
71
75
|
|
|
72
76
|
```bash
|
|
73
77
|
python3 plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py example-agent
|
|
@@ -280,7 +284,7 @@ Scaffolding is non-destructive by default. It writes only the paths declared in
|
|
|
280
284
|
The default pin for new bundles is [`fabric-contract.lock.json`](fabric-contract.lock.json):
|
|
281
285
|
|
|
282
286
|
- version: `0.1.0`
|
|
283
|
-
- commit: `
|
|
287
|
+
- commit: `1197a90dc4b57152cabcc71ad17db7559f35a27f`
|
|
284
288
|
|
|
285
289
|
Every other mention of the default contract revision (this section, the skills' metadata, the
|
|
286
290
|
skill card, `adapt_project.py`) must equal it; `python3 test/validate.py` fails a tree
|
package/SECURITY.md
CHANGED
|
@@ -33,10 +33,25 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
|
|
|
33
33
|
reads by name at run time; the kit itself calls no outside host on its own. A board
|
|
34
34
|
responder talks only to Fabric's hub on loopback.
|
|
35
35
|
- The working-with-fabric session hooks run as the person: Stop and SessionEnd run `git`
|
|
36
|
-
in the session's folder (counts only, at most 1.5 s), read the process table to tell a
|
|
37
|
-
session Fabric launched,
|
|
36
|
+
in the session's folder (counts only, at most 1.5 s; with the mark below and not on Windows, also that folder's own `origin`, only to leave its repository out), read the process table to tell a
|
|
37
|
+
session Fabric launched. Stop also, never on Windows and only once the running Fabric's `fabric_session_observe` takes
|
|
38
|
+
`repositories` (FAA-140; a worker sees it in the tool's schema and leaves a mark), reads the
|
|
39
|
+
last 512 KB of the session's transcript (earlier turns included; only the entries after the
|
|
40
|
+
last prompt the person typed are used; the tail holds message text and tool output too, file contents and
|
|
41
|
+
command output that can carry a secret the session displayed, read in memory only and never spooled, logged or sent)
|
|
42
|
+
and runs `git rev-parse --show-toplevel` and `git config --get remote.origin.url` in the
|
|
43
|
+
other repositories the turn's tool calls name, within the same 1.5 s. Of the transcript they
|
|
44
|
+
use only tool names and path arguments; what leaves the machine is each repository's
|
|
45
|
+
host, owner and name, never a local path, a command or message text (on a self-hosted ssh
|
|
46
|
+
remote, `owner` is the folder path on that server before the repository's name). The mark is
|
|
47
|
+
removed only by a worker that sees a schema without the field, so after Fabric is downgraded
|
|
48
|
+
the turns that end before a worker next runs (about a minute) are still read; none of their
|
|
49
|
+
names is sent. They write their spool and log under the user's cache folder (0600),
|
|
38
50
|
and the delivery worker starts `fabric-mcp` in its own process group and ends that group
|
|
39
|
-
(`killpg`, or `taskkill /T /F` on Windows) at its 10 s deadline. They read no token.
|
|
51
|
+
(`killpg`, or `taskkill /T /F` on Windows) at its 10 s deadline. They read no Fabric token. An
|
|
52
|
+
`origin` URL that embeds a credential (`https://user:token@host/...`) is read by `git config`
|
|
53
|
+
with it; the credential, the port and `.git` are dropped in memory before anything is spooled,
|
|
54
|
+
logged or sent.
|
|
40
55
|
- `adapting-projects-to-fabric/scripts/adapt_project.py` reads the target project without
|
|
41
56
|
executing it and writes only the seven documented bundle paths (`scaffold --dry-run`
|
|
42
57
|
writes nothing); `check --contract` runs the contract checkout's own
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/fabric-agent-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.1",
|
|
4
4
|
"description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "fabric-agent-adapter",
|
|
4
4
|
"displayName": "Fabric Agent Adapter",
|
|
5
5
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.53.1",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). All silent in a session Fabric launched. Every hook reads no token, prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
|
|
2
|
+
"description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count, and, once the running Fabric's fabric_session_observe takes them (never on Windows), the other repositories the turn touched (the last 512 KB of the session transcript, message text and tool output included (a secret the session displayed can be there; read in memory only), is then read and only the current turn's tool names and path arguments are used; host/owner/name leave the machine, never a local path or text) and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). All silent in a session Fabric launched. Every hook reads no Fabric token (a credential embedded in an origin URL is read by git config and dropped in memory, never spooled or sent), prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
5
5
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.53.1"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
|
-
contract-commit: "
|
|
10
|
+
contract-commit: "1197a90dc4b57152cabcc71ad17db7559f35a27f"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Adapting projects to Fabric
|
|
@@ -76,7 +76,7 @@ Use exactly:
|
|
|
76
76
|
|
|
77
77
|
- contract version `0.1.0`;
|
|
78
78
|
- repository `https://github.com/passioncode-ai/fabric-agent-contract`;
|
|
79
|
-
- commit `
|
|
79
|
+
- commit `1197a90dc4b57152cabcc71ad17db7559f35a27f`.
|
|
80
80
|
|
|
81
81
|
This is the default for new bundles. When checking an issued legacy bundle, preserve its
|
|
82
82
|
explicit supported lock and use that exact clean checkout. Never silently upgrade or
|
|
@@ -30,7 +30,10 @@ report, and do not ask again; an agent that serves `surfaces.operatorChannel` al
|
|
|
30
30
|
stay the person's (a `secret` step, contract DEC-0121 AP-4): never create the bot, or ask for or
|
|
31
31
|
store the token's value. Enabling the channel, and removing a webhook found on its token, are the
|
|
32
32
|
operator's decision, taken by them on the agent's page or relayed to its agent in their own words
|
|
33
|
-
with `_meta.decision` (OC-1, OC-2, DEC-0121 AP-2)
|
|
33
|
+
with `_meta.decision` (OC-1, OC-2, DEC-0121 AP-2), including a relay from another of their agents
|
|
34
|
+
that carries their words verbatim, when and where they said them and the scope (DEC-0126 OA-2,
|
|
35
|
+
OA-3; its `source` sent only to an agent whose well-known `protocols` list `operator-source/1`,
|
|
36
|
+
OA-7); never enable it on your own account. The default
|
|
34
37
|
pin carries DEC-0034 and DEC-0035: a served status document is
|
|
35
38
|
checked against the pinned `schemas/operator-channel-status.schema.json`; the channel's runtime
|
|
36
39
|
rules have no probe rule here yet, so report them `NOT_RUN`. An agent with neither stops nor notify
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py
CHANGED
|
@@ -22,10 +22,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
|
|
|
22
22
|
|
|
23
23
|
CONTRACT_VERSION = "0.1.0"
|
|
24
24
|
CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
|
|
25
|
-
CONTRACT_COMMIT = "
|
|
25
|
+
CONTRACT_COMMIT = "1197a90dc4b57152cabcc71ad17db7559f35a27f"
|
|
26
26
|
# #region supported-contract-revisions — docs: README.md#contract-pin
|
|
27
27
|
# Only this declaration may name a legacy revision in live files.
|
|
28
|
-
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
28
|
+
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
29
29
|
# #endregion supported-contract-revisions
|
|
30
30
|
INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
|
|
31
31
|
MCP_REVISION = "2026-07-28"
|
|
@@ -14,10 +14,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
14
14
|
compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
|
|
15
15
|
metadata:
|
|
16
16
|
author: PassionCode.ai
|
|
17
|
-
version: "0.
|
|
17
|
+
version: "0.53.1"
|
|
18
18
|
contract-version: "0.1.0"
|
|
19
19
|
extension: "fabric-service/0.1"
|
|
20
|
-
extension-commit: "
|
|
20
|
+
extension-commit: "1197a90dc4b57152cabcc71ad17db7559f35a27f"
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
# Building Fabric services
|
|
@@ -102,7 +102,10 @@ When the service answers requests other Projects post to its Project on Fabric's
|
|
|
102
102
|
responder in its own process: [`scripts/fabric_hub.py`](scripts/fabric_hub.py) finds the hub and joins,
|
|
103
103
|
[`scripts/fabric_board.py`](scripts/fabric_board.py) enrolls, claims, accepts the exact digest, checkpoints before
|
|
104
104
|
every step and stops on `fenced`, `lease_expired` or `authority_changed` (Node: `fabric-hub.mjs`, `fabric-board.mjs`).
|
|
105
|
-
The request body is data behind the service's own input gate.
|
|
105
|
+
The request body is data behind the service's own input gate. From 0.53.0 a slot the operator moved to another
|
|
106
|
+
session stays moved until `take_back` (`takeBack`): give it an operator action with its tool and gate `relayed`, or it
|
|
107
|
+
stays moved for good (until Fabric's hub forwards decisions, CT-125, the operator reaches it through the service's page
|
|
108
|
+
or its elicitation). Read [the board reference](references/board.md).
|
|
106
109
|
|
|
107
110
|
## Step 2: the non-negotiables
|
|
108
111
|
|
|
@@ -256,7 +259,8 @@ or `human` with its `humanStep`) and declared with `build_well_known(actions=ser
|
|
|
256
259
|
`extensions/actions/0.1`. A `relayed` action called by the operator's own agent with their decision in
|
|
257
260
|
`_meta.decision` is the operator's act: never answer it "operator session required" or send them to the page.
|
|
258
261
|
A service adopts the relayed gate once Fabric's hub forwards decisions (CT-125), as the contract says; until then its tools
|
|
259
|
-
answer as today
|
|
262
|
+
answer as today, except the board responder's take-back action, declared `relayed` now and reached through the
|
|
263
|
+
page or elicitation until CT-125 ([board.md](references/board.md)). Read the interop reference's *Actions* section for when to adopt, the gate, its hooks and its
|
|
260
264
|
refusals.
|
|
261
265
|
|
|
262
266
|
**Say what the agent uses** (`fabric-connections/0.1`, DEC-0096) so Fabric Dashboards draws its chain: the other
|
|
@@ -16,6 +16,7 @@ skill's business (working-with-fabric, `references/project-board.md`).
|
|
|
16
16
|
- Effects, observation, evidence and the reply
|
|
17
17
|
- When the board says stop, and restarts
|
|
18
18
|
- When the operator holds work (DEC-0079)
|
|
19
|
+
- When the operator moves a slot to another session (FAA-155)
|
|
19
20
|
- What a board session may call
|
|
20
21
|
- Looking a Project up in the directory (FAC-SEM-107)
|
|
21
22
|
- Events, the probe and the live smoke
|
|
@@ -50,7 +51,11 @@ responder = fbd.Responder(
|
|
|
50
51
|
hub=fhb.HubClient(), project_id=PROJECT, capabilities=["video.render"],
|
|
51
52
|
credential_file=dirs["data"] / "board.credential", checkpoint_file=dirs["data"] / "board-checkpoint.json",
|
|
52
53
|
handler=render, admit=gate, observe=look_on_disk, emit=log.append, service="example-agent")
|
|
53
|
-
threading.Thread(target=responder.run, args=(stop_event, 5.0), daemon=True)
|
|
54
|
+
board = threading.Thread(target=responder.run, args=(stop_event, 5.0), daemon=True) # SIGTERM: set stop_event
|
|
55
|
+
board.start()
|
|
56
|
+
# ... at stop, after stop_event is set: wait for it to leave its board session (a clean leave forgets its slot
|
|
57
|
+
# records, FAA-155), bounded inside the kit's hard exit, as scripts/sample_service.py does
|
|
58
|
+
board.join(timeout)
|
|
54
59
|
```
|
|
55
60
|
|
|
56
61
|
Node: `new Responder({...same names in camelCase})`, then `responder.run({ signal, drain })`. A service that lists
|
|
@@ -63,7 +68,7 @@ One tick every 15 s ± 5 s (`step()` answers the wait):
|
|
|
63
68
|
|
|
64
69
|
| Step | Board call | Checkpoint written first |
|
|
65
70
|
|---|---|---|
|
|
66
|
-
| hold the slot | `com.enroll` (again at about 20 s while idle: the holder's enroll renews its 60 s lease) | none |
|
|
71
|
+
| hold the slot | `com.enroll` (again at about 20 s while idle: the holder's enroll renews its 60 s lease) | none for a first enroll or a live holder's renew; otherwise the slot's `asking` record (FAA-155) |
|
|
67
72
|
| take a request | `com.claim`, the same key while nothing is claimable | `claimed` |
|
|
68
73
|
| read it | `com.get`: the digest must equal the claim's, and the request must name this Project and capability | none |
|
|
69
74
|
| accept | `com.accept` with the claim's exact fence (request, generation, attempt, digest, revision) | `accepting`, then `accepted` |
|
|
@@ -73,7 +78,8 @@ One tick every 15 s ± 5 s (`step()` answers the wait):
|
|
|
73
78
|
| settle | `com.complete {state, observed, summary?, evidence?}` | `completing`, then the entry is dropped |
|
|
74
79
|
|
|
75
80
|
The checkpoint is fsynced before each call it announces. A write that fails stops the step (`board.blocked`): nothing
|
|
76
|
-
is sent that the checkpoint does not know about.
|
|
81
|
+
is sent that the checkpoint does not know about. The one exception is the slot record: an `asking` or generation
|
|
82
|
+
record that cannot be written is skipped and the enroll or replace still goes out (see below). A lost answer is sent again with the same idempotency key, so the
|
|
77
83
|
board returns the first receipt; while the lease still holds, the session and the running work stay and the call goes
|
|
78
84
|
again before the lease would lapse (FAA-77). One request is in flight at a time, and one fenced call: the fence is
|
|
79
85
|
built, sent and its receipt applied under one lock, so the tick's `com.renew` never carries a revision the handler's
|
|
@@ -86,8 +92,8 @@ from `leaseExpiresAt`.
|
|
|
86
92
|
why not; a refusal accepts the request and completes it `failed_known` with that sentence, so the requester learns it
|
|
87
93
|
and the request does not bounce. A redacted body is refused the same way. The kit never hands a body to a model, a
|
|
88
94
|
shell or a tool; what the handler does with an admitted body (and the checks it needs before a model, a tool or a
|
|
89
|
-
spend sees it) is the service's own responsibility. The kit's responder reads no approval or mandate from a message
|
|
90
|
-
0.
|
|
95
|
+
spend sees it) is the service's own responsibility. The kit's responder reads no approval or mandate from a message yet
|
|
96
|
+
(0.52.0): a request from the operator's own Project may carry `mandate/1` (DEC-0125 MD-3), and a handler acts on it only
|
|
91
97
|
once the kit reads it (FAA-135).
|
|
92
98
|
|
|
93
99
|
## Effects, observation, evidence and the reply
|
|
@@ -101,6 +107,7 @@ once the kit reads it (FAA-135).
|
|
|
101
107
|
- `evidence` is at most 8 references `{id: uri, contentHash: sha256:<hex>, label?}`; `artifact_ref(uri, content=…|path=…)`
|
|
102
108
|
hashes the bytes itself. Two ids with one normal form (DEC-0076) name one artifact and are refused.
|
|
103
109
|
- `reply: {text, format?, artifacts?}` is posted with `com.reply` before the completion. A reply is not a completion.
|
|
110
|
+
It is a fenced reply, so it carries no `deliverTo` and reaches the requester (DEC-0135 DR-3).
|
|
104
111
|
- A handler that raises before any effect completes the request `failed_known` naming only the exception's class.
|
|
105
112
|
- The handler checks `ctx.stopped` in long work: after a session loss its answer is not sent.
|
|
106
113
|
|
|
@@ -108,10 +115,10 @@ once the kit reads it (FAA-135).
|
|
|
108
115
|
|
|
109
116
|
| Answer | What the kit does |
|
|
110
117
|
|---|---|
|
|
111
|
-
| `fenced` | the attempt is another responder's: stop the handler, drop the entry (nothing outside had begun) |
|
|
118
|
+
| `fenced` | the attempt is another responder's: stop the handler, drop the entry (nothing outside had begun); accepted work on a slot this responder recorded holding stays as `lapsed`, and the next tick asks `com.status` whether the slot [moved](#when-the-operator-moves-a-slot-to-another-session-faa-155) |
|
|
112
119
|
| `lease_expired` on accepted work | stop the handler; the board holds the request, and the kit takes it back with `com.responder_replace` and a transfer |
|
|
113
120
|
| `authority_changed`, `not_authorized` | stop claiming; one `board.access.needed` event (notify) saying what to ask for |
|
|
114
|
-
| `fenced` on enroll | another live holder: wait for it to lapse; never replace it on its own initiative |
|
|
121
|
+
| `fenced` on enroll or claim | another live holder: wait for it to lapse; never replace it on its own initiative. When this responder recorded holding the slot, it first reads `com.status`: a newer generation means the slot [moved](#when-the-operator-moves-a-slot-to-another-session-faa-155), and it is not taken again even after that holder lapses |
|
|
115
122
|
| lost after an effect began | keep the entry as `unknown`; `observe(entry)` is asked to settle it with `com.reconcile` |
|
|
116
123
|
| Fabric down, hub missing | bounded backoff (2 s doubling to 5 minutes, jittered), one `board.down` event, no spinning |
|
|
117
124
|
| one call without an answer while the lease holds | `board.down`, but the session and the running work stay; the call goes again, with the same key, before the lease lapses |
|
|
@@ -123,7 +130,8 @@ On restart the checkpoint is read back: settled, queued, claimed or `answered` r
|
|
|
123
130
|
the target Project's participant reply answered it, DEC-0098 and DEC-0102; not settled, the requester may still cancel
|
|
124
131
|
it, but no longer this responder's); `in_progress` and `outcome_unknown` are
|
|
125
132
|
reconciled from what `observe(entry)` answers (observed only; with no observation they stay unknown and notify);
|
|
126
|
-
accepted ones go back to this responder with `com.responder_replace {expectedGeneration, transfer}
|
|
133
|
+
accepted ones go back to this responder with `com.responder_replace {expectedGeneration, transfer}` (unless the slot
|
|
134
|
+
moved to another session meanwhile: they stay `lapsed` until take-back, [below](#when-the-operator-moves-a-slot-to-another-session-faa-155)), whose receipt's
|
|
127
135
|
`transferred` (DEC-0078: `{request, revision, attempt}`) carries the new fence. **A Fabric that does not answer
|
|
128
136
|
`transferred` yet leaves the request held** (`board.request.held`, notify): the kit never claims it back, because the
|
|
129
137
|
contract has no `com.claim` read-back and `com.claim` never moves an accepted request. A known outcome is sent after the
|
|
@@ -159,6 +167,70 @@ A receipt's request may carry `settledBy: "owner"` (DEC-0081): the estate owner
|
|
|
159
167
|
whose responder was gone. Read it as the owner's attestation, not the responder's; it appears only on `completed` or
|
|
160
168
|
`failed_known`, and its absence means the responder settled it.
|
|
161
169
|
|
|
170
|
+
## When the operator moves a slot to another session (FAA-155)
|
|
171
|
+
|
|
172
|
+
The contract's DEC-0140 PR-2 (proposed, after this kit's pin; Fabric plans it for 0.3.15) lets the operator move a
|
|
173
|
+
(Project, capability) slot to another live session of the Project: one transition, as `com.responder_replace` would make
|
|
174
|
+
it for the receiving session (generation up, `claimed` back to `queued`, `in_progress` to `outcome_unknown`, `accepted`
|
|
175
|
+
held and not transferred). The responder keeps that move instead of undoing it:
|
|
176
|
+
|
|
177
|
+
- It records the generation it holds each slot at, in the checkpoint file beside the requests (`slots: {<capability>:
|
|
178
|
+
{project, generation, moved?, asking?}}`). The record is written when a receipt's generation changes and before an
|
|
179
|
+
enroll or a replace that can change it; a live holder's renew (about every 20 s) cannot, and writes nothing. At a
|
|
180
|
+
clean leave (the service stopped; the session's DELETE released its slots) the record of a slot with no request left
|
|
181
|
+
in the checkpoint and not moved is forgotten (a moved slot's record stays, so a clean stop never undoes a move): the slot is free, and whoever takes it next is no move. A file without the record
|
|
182
|
+
reads as before; a kit before 0.53.0 reads the file, ignores the record and drops it at its first checkpoint write. So a service stopped cleanly with no request left in its checkpoint, rolled back to 0.52 and up again starts as on a first
|
|
183
|
+
start. After a crash the record stays: if 0.52 then ran, enrolled the slot and
|
|
184
|
+
wrote nothing to the checkpoint, the next 0.53 start reads that enroll as a move; bring it back with the service's take-back action (below).
|
|
185
|
+
- An enroll or a replace is written into that record with its key before it is sent (`asking`). When its answer is
|
|
186
|
+
lost, the next connect (or the next enroll) sends the same call again under the same key; the board answers the
|
|
187
|
+
stored receipt, whose generation is this service's own, and the slot is then asked for once more for the new board
|
|
188
|
+
session. A refusal is an answer and drops the `asking` record, so a move that happened meanwhile is still seen; except
|
|
189
|
+
`idempotency_window_expired` (a retired epoch refuses even a repeat), which leaves it unknown whether the call
|
|
190
|
+
landed: the whole record is forgotten and the slot reads as on a first start, never as a move. An
|
|
191
|
+
`asking` record that cannot be written (a full disk) is skipped, as the generation record is: the call goes out
|
|
192
|
+
unrecorded, and a lost answer to it, or a restart before the next receipt is recorded, may then read as a move.
|
|
193
|
+
- It reads the slot's generation from `com.status` at every connect (a restart), and after any call refused `fenced`:
|
|
194
|
+
at once for a claim or an enroll, at the next tick for a fenced call on running work. Only a responder with a record
|
|
195
|
+
asks, so a first start sends no extra call. A generation past the recorded one, with no own call of
|
|
196
|
+
this service left unanswered, is a move, whatever made it: the operator, or another session that took the slot while
|
|
197
|
+
this one was away or after it crashed. Before 0.53.0 a restarted responder took such a slot back by itself. So a
|
|
198
|
+
second responder of the same capability is no longer a standby once the slot changed hands: after any change of
|
|
199
|
+
holder it waits for take-back, not for the other's lapse.
|
|
200
|
+
- A `fenced` refusal on accepted work while the slot did not move (the generation unchanged) keeps the entry
|
|
201
|
+
`lapsed`; the next tick takes it back with a transfer and goes on (before 0.53.0 the entry was dropped and the
|
|
202
|
+
request left accepted on the board).
|
|
203
|
+
- Then the slot is `moved`: the running handler is told to stop (`ctx.stopped`); a `claimed` entry is dropped (the move
|
|
204
|
+
returned it to the queue); accepted work stays as `lapsed` and work whose effect began as `unknown` (only the slot's
|
|
205
|
+
holder, or the estate owner, reconciles it now). No `com.claim`, `com.renew`, `com.complete`, `com.enroll` or
|
|
206
|
+
`com.responder_replace` is sent for that capability again: not at recovery after a restart, and not when the new
|
|
207
|
+
holder's lease lapses, nor when com.status later names no generation for the slot (a recorded move stays until
|
|
208
|
+
take-back). Accepted requests stay held on the board, unanswered, until the slot is taken back (CT-175
|
|
209
|
+
will hand them to the new holder). One `board.slot.moved` event (warn, notify) says so, once, also across restarts
|
|
210
|
+
(the record keeps `moved`).
|
|
211
|
+
- A responder that never held the slot (a first start: no record, or a record of another Project), or whose record
|
|
212
|
+
equals the current generation, behaves as before: it enrolls, recovers and replaces its own lapsed work. A recorded or
|
|
213
|
+
reported generation that is not an integer of at least 1, or one not past the record (lower: the record is not this
|
|
214
|
+
board's), is not a move either: today's behaviour, not a stop, and the next receipt rewrites the record. A crash
|
|
215
|
+
between a receipt and its record's write leaves the `asking` record: the next start asks again under the same key and
|
|
216
|
+
records its own generation. Only a replay the board refuses, or one that ran for the first time (`repeated: false`)
|
|
217
|
+
after the slot had moved past the record, reads as a move: the safe side; a slot such a replay took is left to lapse
|
|
218
|
+
unused. A generation written `2.0` is `2`.
|
|
219
|
+
- Only the operator brings the slot back: `take_back(capability)` (`takeBack`) forgets the record; never edit the
|
|
220
|
+
checkpoint's `slots` record by hand to undo a move. In the same board
|
|
221
|
+
session the next tick enrolls once the other session lets the slot go (a live holder is waited for) and takes the work
|
|
222
|
+
left `lapsed` back with a transfer. A restart or reconnect before that, with accepted work held for the slot, takes it
|
|
223
|
+
back at once by `com.responder_replace`, as a restart did before 0.53.0: the operator asked for the slot back, so the
|
|
224
|
+
other session is replaced. In recovery, a refusal that leaves no slot held reconnects; one that leaves some held keeps
|
|
225
|
+
the session and serves them. The kit gives it no route of its own (the reference service has none yet,
|
|
226
|
+
FAA-157): a service built on the kit declares it as an operator action, a button with its tool ([Every button has a
|
|
227
|
+
tool](../SKILL.md), `fabric-actions/0.1`) with gate `relayed` (the operator's decision), never `none`, so no other
|
|
228
|
+
agent can call it on its own; on a server that reads mandates (`agent_id`) it also names its effect class, for example
|
|
229
|
+
`change-policy` (DEC-0125); until Fabric's hub forwards decisions (CT-125) the operator reaches it through the
|
|
230
|
+
service's page or its elicitation, and names it in its docs; otherwise a moved slot stays moved. A move back to this service's own session is not
|
|
231
|
+
detected yet: the receiving side and the hand-over of accepted requests (contract CT-175) wait for Fabric's session
|
|
232
|
+
surface.
|
|
233
|
+
|
|
162
234
|
## What a board session may call
|
|
163
235
|
|
|
164
236
|
A board session (`fabric.board.join`) is served the respond scope only: `com.status`, `com.list`, `com.get`,
|
|
@@ -182,8 +254,8 @@ raises `directory-invalid` rather than answering; an unlisted Project may still
|
|
|
182
254
|
Events go to `emit(kind, level, text, notify=…, data=…)` (`JsonlEventLog.append` fits) with `data {schema, value}`
|
|
183
255
|
checked by FAC-SEM-101 (no credential in data): `board.request.completed`, `board.request.refused`,
|
|
184
256
|
`board.request.skipped`, `board.request.reconciled`, `board.request.held`, `board.attempt.lost`,
|
|
185
|
-
`board.reconcile.needed`, `board.slot.contested`, `board.
|
|
186
|
-
notifying episode said once.
|
|
257
|
+
`board.reconcile.needed`, `board.slot.contested`, `board.slot.moved`, `board.access.needed`, `board.blocked`,
|
|
258
|
+
`board.down`, each notifying episode said once.
|
|
187
259
|
|
|
188
260
|
`check_service.py <id> [--board-kit <the service's fabric_board.py or fabric-board.mjs>]` rule **board.responder**:
|
|
189
261
|
`NOT_RUN` when the well-known `protocols` names no `fabric-project-comms` revision; `FAIL` for a revision this kit does
|
|
@@ -207,12 +279,12 @@ message, retryable, data)`.
|
|
|
207
279
|
|
|
208
280
|
Python (`fabric_board`): `Responder(*, hub, project_id, capabilities, credential_file, checkpoint_file, handler,
|
|
209
281
|
admit, observe=None, emit=None, service, now, rand, spawn, session_factory)` with `step()`, `run(stop, drain)`,
|
|
210
|
-
`shutdown(drain)`; `WorkContext.progress(note)`, `.begin_effect(summary)`, `.stopped`; `BoardSession(grant)`;
|
|
282
|
+
`shutdown(drain)`, `take_back(capability)`; `WorkContext.progress(note)`, `.begin_effect(summary)`, `.stopped`; `BoardSession(grant)`;
|
|
211
283
|
`Checkpoint(path)`; `comms_canonical(value)`, `comms_digest(operation, value)`, `receipt_problems(value, op)`,
|
|
212
284
|
`refusal_problems(value)`, `directory_findings(page)`, `directory_problems(page)`, `find_project(page, project_id,
|
|
213
285
|
capability)`, `artifact_ref(uri, content|path, label)`, `evidence_problems(list)`, `outcome_problems(outcome,
|
|
214
286
|
effect_began)`, `self_check()`; `BoardError`, `Stopped`.
|
|
215
287
|
|
|
216
288
|
Node: the same names in camelCase (`locateHub(root, {alive, estate, now})`, `new HubClient(root, {alive, timeout,
|
|
217
|
-
estate, post})`, `new Responder({...})` with async `step()`, `run({signal, drain})`, `shutdown(drain)
|
|
218
|
-
ctx.progress(note)`, `await ctx.beginEffect(summary)`; `selfCheck()`). Both kits read and write the same checkpoint file.
|
|
289
|
+
estate, post})`, `new Responder({...})` with async `step()`, `run({signal, drain})`, `shutdown(drain)`,
|
|
290
|
+
`takeBack(capability)`; `await ctx.progress(note)`, `await ctx.beginEffect(summary)`; `selfCheck()`). Both kits read and write the same checkpoint file.
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md
CHANGED
|
@@ -55,6 +55,18 @@ hub forwarding a T2 caller). DL-26: a T0 or T1 caller is never required to prese
|
|
|
55
55
|
delegation. A token such a caller presents is still verified and refused when invalid. A T2 caller takes the whole
|
|
56
56
|
profile. Money limits and human gates bind at every tier.
|
|
57
57
|
|
|
58
|
+
**The family and the agent network** (DEC-0130, wording settled by DEC-0137). The operator's family is every agent
|
|
59
|
+
placed on a machine of his estate and his own local sessions; a callee recognises it only by placing the caller in T0
|
|
60
|
+
or T1 (DL-27). It is neither a context (`personal` and `org:<orgId>` stay the only ones) nor a tier: it is open to
|
|
61
|
+
what DL-26 opens, everything open to the operator in the call's context, which for a tokenless call is the callee
|
|
62
|
+
instance's own context. No claim of a caller places it in the family: a call relayed onto loopback stays T2 unless the
|
|
63
|
+
relay proves the origin's tier, and a caller under another OS account stays T2 (DL-28). The agent network is every
|
|
64
|
+
agent a caller can reach, the family included; outside the family a caller is T2, with a token per call (DL-29). Money
|
|
65
|
+
limits, human gates, the contexts, the credential source (never another agent's key or vault slot), the machine
|
|
66
|
+
profiles on calls over the relay, and a relayed action's decision or mandate still bind inside the family (DEC-0137).
|
|
67
|
+
Every T0 process of the owner's account counts as the family, one he did not install included, so a product that must
|
|
68
|
+
hold against a hostile local process adds its own approval (FAA-148). The kit's tiers already do this; nothing changes in code.
|
|
69
|
+
|
|
58
70
|
The token is read from `Fabric-Delegation` first (a forwarded call; present but empty is refused, never skipped),
|
|
59
71
|
then from `Authorization: Bearer` when the bearer is a delegation JWT (DL-9, DL-10). A tool with no declared
|
|
60
72
|
effect is refused (`unknown-state`), never guessed. The checks run in the DL-13 order; nothing about a token is
|
|
@@ -131,6 +143,16 @@ current verified revision as `estate=` / `{ estate }`, as a value or as a functi
|
|
|
131
143
|
applied mark or its revocations is an unreadable source, `estate-unreadable`, in both kits (FAA-78).
|
|
132
144
|
- Every verifier refuses a clock that is not a number of seconds `unknown-state` `no-clock` before row 1: a NaN clock
|
|
133
145
|
would make every time row false and pass an expired token.
|
|
146
|
+
- **Which hub key a token is checked against** (DL-14a, DEC-0128). An organization's tokens are signed by that
|
|
147
|
+
organization context's own hub key (`machines[].contextHubKeys`), under the context issuer its pin names
|
|
148
|
+
(`organizations[].iss`, `urn:fabric:context:<opaque>`, never the estate id). An instance that serves an organization
|
|
149
|
+
passes its context as `estate_context=` / `{ estateContext }` to `delegation_gate` or `verify_delegation`, once the
|
|
150
|
+
record pins its organization (until then its tokens are refused `kid-not-certified`, brief D5), and that pin's `iss`
|
|
151
|
+
as `issuer`: the `kid` is then trusted only when one machine lists it for that context with that key and
|
|
152
|
+
the record pins the organization. Without it, or with `personal`, the personal `hubKey` is read, so a context key
|
|
153
|
+
never verifies a personal token and the personal key never an organization's; any other value (`Org:x`, a trailing
|
|
154
|
+
space, a number) certifies nothing. `verify_approval` takes no context (the contract's `VerifyApprovalOptions`
|
|
155
|
+
declares none at the pin), so it reads the personal keys. Held to `contextCases` and `contextVerifier` in `test/fixtures/contract/hub-key-vectors.json`.
|
|
134
156
|
|
|
135
157
|
## What the kit does not do
|
|
136
158
|
|