@passioncode-ai/fabric-agent-adapter 0.78.0 → 0.80.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +256 -0
- package/README.md +14 -7
- package/bin/fabric-agent-adapter.js +26 -4
- 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 +6 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +13 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +3 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +23 -8
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +6 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +6 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +34 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/README.md +67 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/dashboard_service.py +252 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.mjs +168 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.py +179 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/README.md +57 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.mjs +181 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.py +237 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +11 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +26 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +14 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +44 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +10 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +15 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +37 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +30 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +48 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +9 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +16 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +344 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +54 -20
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +8 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +4 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +55 -14
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +8 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +3 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +16 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +97 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +50 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +49 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +9 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +358 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +41 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +17 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +24 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +110 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +12 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +543 -37
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +4 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +12 -5
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +42 -7
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +6 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +13 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +13 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +32 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/working-rules.md +54 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +12 -4
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +27 -26
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +4 -4
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/cleanup.md +30 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +59 -38
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +52 -26
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/remote-sessions.md +75 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +28 -13
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/task-context.md +58 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/waits.md +36 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +14 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +169 -46
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +146 -19
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +78 -3
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +9 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Clean up after yourself
|
|
2
|
+
|
|
3
|
+
The operator's rule (board message `bfc0e14e`, 2026-11-12, FAA-301). What you created for a task is yours to remove
|
|
4
|
+
when the task is done; what anyone else holds is not.
|
|
5
|
+
|
|
6
|
+
## After a merge
|
|
7
|
+
|
|
8
|
+
- **Remove your own worktrees and branches** once the branch is merged: `git worktree remove <path>`, then
|
|
9
|
+
`git branch -d <branch>` and `git push origin --delete <branch>`, then **`git fetch --prune`**.
|
|
10
|
+
- **A branch with commits found nowhere else is archived before it is deleted**: `git push origin
|
|
11
|
+
<branch>:refs/archive/<YYYY-MM-DD>/<branch>` (and the same ref locally), checked with `git ls-remote origin
|
|
12
|
+
'refs/archive/*'`, then deleted. `git branch --contains` and `git log <branch> --not --remotes` tell you whether it
|
|
13
|
+
holds such commits.
|
|
14
|
+
- **Remove build output and dependency folders** (`node_modules`, `dist`, `build`, `.next`, `target`, `.venv`,
|
|
15
|
+
`coverage`) in your own abandoned or merged copies. Prune tool caches only with their own commands.
|
|
16
|
+
- **Stop your own processes**: dev servers, browsers, watchers and test runs you started.
|
|
17
|
+
|
|
18
|
+
## Never
|
|
19
|
+
|
|
20
|
+
- another session's worktree, a worktree Git marks `locked` (`git worktree list --porcelain`), or a process you did not
|
|
21
|
+
start;
|
|
22
|
+
- **a Git-tracked file**: before removing a folder, `git -C <repo> ls-files -- <dir>` must print nothing;
|
|
23
|
+
- a name list built across a whole disk or projects folder: remove what you named, one path at a time.
|
|
24
|
+
|
|
25
|
+
## The checklist
|
|
26
|
+
|
|
27
|
+
- [ ] Merged branches: worktree, local branch and remote branch removed; `git fetch --prune` run.
|
|
28
|
+
- [ ] Unique commits archived under `refs/archive/<date>/` before any delete.
|
|
29
|
+
- [ ] Build and dependency folders removed only after `git ls-files -- <dir>` printed nothing.
|
|
30
|
+
- [ ] Own processes stopped; nothing of another session's touched.
|
package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md
CHANGED
|
@@ -4,7 +4,11 @@ When a board request reaches a project that has no live responder and no live se
|
|
|
4
4
|
project's folder to do it (Fabric's on-demand spec, sections 3.4 to 3.6, at Fabric commit `1329b60f`). That session
|
|
5
5
|
does the one request, reports, completes it and ends. Nobody watches it, so it never waits for a person. The
|
|
6
6
|
dispatcher ships in Fabric 0.3.16 (released 2026-11-05, tag `v0.3.16` at `bcead0ef`), on by default since
|
|
7
|
-
`a6f0ffda`.
|
|
7
|
+
`a6f0ffda`. Fabric 0.3.17 (2026-11-08) adds CO-409 (`babb2dc1`, `09ddb2fc`), CO-506 (`027cb809`: a session's own
|
|
8
|
+
Project is granted `board.respond` at `com.enroll`) and the slot condition in `board.read-at-start` and `com.enroll`'s
|
|
9
|
+
description (`dbfbfce4`, `12d57bdd`, `4f3c742a`); the latest release is v0.3.19 (2026-11-11). "Fabric's main" below
|
|
10
|
+
names the commit a change landed at, each in v0.3.17 or earlier unless it is called unreleased, as CO-563 is
|
|
11
|
+
(`780022af`, expected in the release after v0.3.19). The hooks recognise a dispatched session from
|
|
8
12
|
adapter 0.63.0 on, so a launcher can start one today.
|
|
9
13
|
|
|
10
14
|
## Contents
|
|
@@ -53,13 +57,19 @@ hook, or a Switchboard before 0.7.8 started the run, the launcher's appended exi
|
|
|
53
57
|
## What the session does
|
|
54
58
|
|
|
55
59
|
1. **Look, then read the packet.** `fabric_whoami` first, as in any session: until it, the session holds no other
|
|
56
|
-
Fabric tool, and it returns the session's rules.
|
|
60
|
+
Fabric tool, and it returns the session's rules. A headless Claude Code run (`claude -p`) that still sees only
|
|
61
|
+
`fabric_whoami` among Fabric's tools after that call met Fabric's CO-563 (`fabric-mcp` offered the rest only once the
|
|
62
|
+
session was admitted, and `-p` reads its list of tools once; fixed at `780022af`, expected in the release after
|
|
63
|
+
v0.3.19): it cannot read or answer the board, so it says so in its exit report (an `in_progress` checkpoint naming
|
|
64
|
+
CO-563 when it holds the lease, then the retro), never claims the board was read, and ends (FAA-296). Then `observatory_checkpoint_latest {workflowId}`. Its `goal`
|
|
57
65
|
describes the request (its text is on the board); its `constraints` carry the exit contract, the three floors and
|
|
58
66
|
the operator's mandate when the request has one: obey them first. Its `artifacts` are references only,
|
|
59
67
|
`kind: "other"` with `ref` `board-request:<uuid>`, `board-thread:<uuid>`, `capability:<name>`,
|
|
60
|
-
`fabric-project:<uuid>`, `deadline:<time>` and `run-ends-at:<time
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
`fabric-project:<uuid>`, `deadline:<time>` and `run-ends-at:<time>`. Take the board ids from the environment and
|
|
69
|
+
the first line first; the packet's `board-request:`, `board-thread:` and `fabric-project:` refs are the fallback
|
|
70
|
+
(Observatory keeps them with their UUIDs since 0.34.0: OBS-323, PR #437 at `63e2cc43`, its answer `7e5b4e2d` on
|
|
71
|
+
board thread `7ae9ac3a`), while it still redacts a `board-message:` ref, so a run started for a board message finds
|
|
72
|
+
no message ref in the packet (FAA-295). No message body travels in the packet: read the request with `com.get {message: <request>}` (a request's id
|
|
63
73
|
is its message's) and its thread with `com.list {thread: <thread>}`, the `thread` `com.get` answered, under the
|
|
64
74
|
session's own credential. The folder is the working directory. The body's `credentials` block names the
|
|
65
75
|
keys the request needs, by name only; reach them with `use_secret.py run`. `observatory_checkpoint_latest` also
|
|
@@ -76,56 +86,60 @@ hook, or a Switchboard before 0.7.8 started the run, the launcher's appended exi
|
|
|
76
86
|
3. **Take the request.** `com.status` once, for the epoch every idempotency key names and the slot's generation.
|
|
77
87
|
`com.enroll` for the request's capability, then `com.claim {idempotency, capability, request}`, `com.accept`, and
|
|
78
88
|
`com.effect_begin` before the work has an effect. A `com.enroll` refused `not_authorized` (the run's Project holds
|
|
79
|
-
no `board.respond` grant for the capability) takes no slot, asks for no grant itself (no `fabric.access.request`) and
|
|
80
|
-
session has no route to that grant (`fabric.access.request` is the door's and a binding's, never
|
|
81
|
-
`fabric-mcp`, and Fabric's Agent access only decides a request already waiting), and work without a slot
|
|
82
|
-
fence, no `com.progress` and a live session may take the request in parallel.
|
|
83
|
-
`
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
no `board.respond` grant for the capability) takes no slot, asks for no grant itself (no `fabric.access.request`) and
|
|
90
|
+
does no work: a session has no route to that grant (`fabric.access.request` is the door's and a binding's, never
|
|
91
|
+
exposed by `fabric-mcp`, and Fabric's Agent access only decides a request already waiting), and work without a slot
|
|
92
|
+
has no fence, no `com.progress` and a live session may take the request in parallel. From Fabric 0.3.17 (CO-506,
|
|
93
|
+
`027cb809`) a session's own Project is granted it at `com.enroll`, so the refusal comes only where CO-506 grants
|
|
94
|
+
nothing, for the reason Fabric's `board.own-respond` log line names: a machine marked as an organization's (the mark
|
|
95
|
+
in Fabric's settings), no live credential for the session or its runner (the agent enrolls again), a standing denial
|
|
96
|
+
("Снять отказ" in Agent access) or a full ask queue (the operator answers the asks that wait, or they expire)
|
|
97
|
+
(Fabric, thread `1add3c03`, `315c5b54`). Before 0.3.17 every session is refused. A run cannot tell from the board
|
|
98
|
+
which Fabric it runs on: when the refusal names no reason, its reply and question name both cases. When `com.get`
|
|
99
|
+
reads the request `queued` and `com.status`'s entry for the request's capability reads `absent` at generation 0 (no
|
|
100
|
+
service of the project ever held it; `com.status` lists the capability of every open request, up to the 256
|
|
101
|
+
capabilities Fabric lists, so a missing entry or an unavailable board is the error rule),
|
|
86
102
|
the run answers it once with `com.reply` (`replyTo` the request, from the target Project, which answers
|
|
87
|
-
it: DEC-0098, DEC-0102) declining it
|
|
88
|
-
it again then (the reply ends the queue, so nothing
|
|
103
|
+
it: DEC-0098, DEC-0102) declining it with the reason and what opens it (before 0.3.17: a Fabric with CO-506; from
|
|
104
|
+
it: the reason above clearing), and that the requester sends it again then (the reply ends the queue, so nothing
|
|
105
|
+
takes it later; never say it waits). Since only the operator
|
|
89
106
|
opens that gate and the decline shows the operator no gate (at most an unread answer on the requester's Project
|
|
90
107
|
until it is read), it then asks the operator once:
|
|
91
108
|
`fabric_question_ask` with `kind: access`, `about: board.gate.<capability>`, the `commandId` the contract names (UUID
|
|
92
109
|
v5 of the name `board.gate` in the request id's namespace, `gate_question_id` in `fabric_session_cli.py`, so every run
|
|
93
110
|
on that request gets the same question back) and a `text` naming the request (its id, the requesting Project, the
|
|
94
|
-
capability), the gate (this Project's `board.respond` grant for that capability) and what opens it (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
log line names and what lifts it: a machine marked as an organization's (the mark in Fabric's settings), no live credential (the agent
|
|
99
|
-
enrolls again), a standing denial ("Снять отказ" in Agent access) or a full ask queue (the
|
|
100
|
-
operator answers the asks that wait, or they expire); Fabric, thread `1add3c03`, `315c5b54`); it waits for no answer (FAA-224; the tool needs no slot; Fabric keeps the
|
|
111
|
+
capability), the gate (this Project's `board.respond` grant for that capability) and what opens it (the reply's
|
|
112
|
+
reason and what lifts it; at a slot a holder held, also the Project's service enrolling again, which takes the
|
|
113
|
+
request itself without opening this gate; on Fabric 0.3.16 nothing else does, since Agent access only decides a
|
|
114
|
+
request a service filed); it waits for no answer (FAA-224; the tool needs no slot; Fabric keeps the
|
|
101
115
|
`about` with the question and settles no later question by it yet (CO-519), so each gate question reaches the operator and a
|
|
102
116
|
repeat with the same `commandId` returns the same question: Fabric, thread `1add3c03`, `5b216035`). A question that fails is left (the decline already answered the request), and the closing write's
|
|
103
117
|
`notes` say so, since then nothing about the gate reached the operator. Then it closes the workflow `done` once
|
|
104
118
|
`com.get` reads it answered (step 6), and writes the retro (step 7). When the request is not queued, or the slot
|
|
105
119
|
reads `stale`, `active`, or `absent` at a generation of 1 or more (a holder enrolled on it: a
|
|
106
|
-
service of this Project, or
|
|
120
|
+
service of this Project, or from 0.3.17 a session; one that stopped cleanly reads `absent`, and a service takes the
|
|
107
121
|
request when it rejoins, so a decline would take it from it; Fabric serves a session no `held` flag yet), it sends no
|
|
108
122
|
`com.reply` but still asks the operator with the same `fabric_question_ask` (a question answers no request and takes
|
|
109
|
-
nothing from the service;
|
|
110
|
-
|
|
123
|
+
nothing from the service; from 0.3.17 such a refusal comes from the reason `board.own-respond` names, so it is never
|
|
124
|
+
silent: FAA-241, Fabric `4f3c742a`; a question that fails is named in the
|
|
111
125
|
`notes` of one more in_progress checkpoint), then the error rule below holds and the request stays as it is (a queued request goes to
|
|
112
126
|
the operator after two runs with no answer). A
|
|
113
127
|
capability listed only by a service's live grant, with no enroll yet, also reads `absent` at generation 0: a run
|
|
114
128
|
declines it, which a service asking and enrolling at once (ADR-0186) seldom leaves time for.
|
|
129
|
+
A gate question is moot once the request is answered or taken (the usual end at a slot a service held is that
|
|
130
|
+
service rejoining and taking it): the question stays open, since Fabric settles none by its `about` (CO-519) and no
|
|
131
|
+
Fabric through v0.3.19 lets an agent close one, so a run or session that later finds the request answered, or taken
|
|
132
|
+
and completed, says so once in its closing `notes`, naming the question (its `about` and `commandId`) (FAA-248).
|
|
115
133
|
After the decline, the target's request no longer waits for the operator on the board (an answered request is settled: no second run,
|
|
116
134
|
no unmoved or to-operator finding; the question above is what reaches the operator, when Fabric took it); the requester reads the decline, and until it reads and acknowledges it (`com.read_ack`) Fabric
|
|
117
135
|
shows it on the requester's Project in «На проверке» as an answer nobody read,
|
|
118
|
-
and the closing write's `notes` say the request was declined
|
|
119
|
-
Fabric's dispatcher counts an answered request as settled and starts no second run for it.
|
|
120
|
-
(Fabric's `main` `027cb809`) a session's own Project grants it at `com.enroll` itself, and this branch is taken only
|
|
121
|
-
where CO-506 grants nothing (a machine marked as an organization's, no live credential for the session or its runner,
|
|
122
|
-
a standing denial of that ask, or a full ask queue); the reply then names CO-506 though it shipped, and the real gate is that grant (left open in FAA-215).
|
|
136
|
+
and the closing write's `notes` say the request was declined and why.
|
|
137
|
+
Fabric's dispatcher counts an answered request as settled and starts no second run for it.
|
|
123
138
|
FAA-219; Fabric's answer in board thread `1add3c03` (option (b), 2026-11-05), the responder's run's rule plus the
|
|
124
|
-
slot condition, which Fabric's `board.read-at-start` took from the adapter (FAA-231,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
tool description the same slot condition since `12d57bdd`, FAA-231, in no release yet); the refusal text waits
|
|
139
|
+
slot condition, which Fabric's `board.read-at-start` took from the adapter (FAA-231, `dbfbfce4`, in Fabric 0.3.17);
|
|
140
|
+
Fabric 0.3.16's `com.enroll` description tells a session to ask the grant, which it cannot, and its own
|
|
141
|
+
`not_authorized` refusal says to reply with the answer; step 3 wins over both (Fabric 0.3.17 gives `com.enroll`'s
|
|
142
|
+
tool description the same slot condition, `12d57bdd`, FAA-231); the refusal text waits
|
|
129
143
|
for its CO-507. An earlier run may have left the request taken: `com.get` on the
|
|
130
144
|
request shows its state, and the latest checkpoint (step 1) what that run recorded. An accepted request is never
|
|
131
145
|
claimed again: take it over with `com.responder_replace {expectedGeneration, transfer}` (its receipt carries the new
|
|
@@ -180,12 +194,13 @@ hook, or a Switchboard before 0.7.8 started the run, the launcher's appended exi
|
|
|
180
194
|
leaseId, close: true}`, with the packet's body and `done[]` and `notes` added, no values in it. Without `workflowId` Observatory
|
|
181
195
|
reads the write as the start of a new workflow and refuses it; the `owner` is the one the accept named. The status is
|
|
182
196
|
`done` for `completed` and for a request step 3's reply answered; for
|
|
183
|
-
`failed_known` it is `
|
|
184
|
-
|
|
197
|
+
`failed_known` it is `blocked` with the reason in `notes` (Observatory takes `in_progress`, `done` and `blocked`;
|
|
198
|
+
OBS-323 shipped with no `failed`, FAA-215).
|
|
185
199
|
7. **End the run.** The one write left is the retro after work
|
|
186
200
|
(`observatory_lesson` and insight/1 lines in `insights/` (the project's data folder when it has one or the repository is public, else the repository; the data folder is the one its AGENTS.md names, else `<name>-data/` beside the main checkout), or one line that there is nothing; no values; ADR-0188's
|
|
187
201
|
`retro-after-work`); nothing more: no board sweep, no work claim, no
|
|
188
|
-
`watch-mail`, no further `fabric_question_ask` (step 3's gate question is the only one)
|
|
202
|
+
`watch-mail`, no further `fabric_question_ask` (step 3's gate question is the only one), no `agents-db.*` ask (a measured
|
|
203
|
+
win stays in its insight lines for a later session to contribute; FAA-277).
|
|
189
204
|
|
|
190
205
|
On an error the session cannot get past (a refused claim, a missing key, a tool that fails twice), it completes the
|
|
191
206
|
request `failed_known` with the reason (and `observed: true` after `com.effect_begin`) when it holds a fence for it,
|
|
@@ -201,7 +216,7 @@ unsettled request made the next dispatch fail at the take-back on every cycle (F
|
|
|
201
216
|
no live session (a live session holds the request), and after two runs with no answer Fabric shows the request to the
|
|
202
217
|
operator whatever its state. A workflow left open because another session holds the request is closed by Fabric once
|
|
203
218
|
that request settles, and a request a run's `com.enroll` left `outcome_unknown` shows in «На проверке» at once
|
|
204
|
-
(Fabric
|
|
219
|
+
(Fabric 0.3.17, `09ddb2fc`; FAA-213). Switchboard's appended exit contract says the same from
|
|
205
220
|
0.7.13 (SB-258); an earlier Switchboard tells the run to close the workflow whatever the request's state, and this
|
|
206
221
|
rule, the later one, is the one to follow.
|
|
207
222
|
|
|
@@ -221,6 +236,12 @@ and ends `timed-out`.
|
|
|
221
236
|
|
|
222
237
|
## A session inside the run
|
|
223
238
|
|
|
239
|
+
A session the run starts itself (`claude -p`, `codex exec`) runs with that runner's bypass flag, once: Claude Code
|
|
240
|
+
`--dangerously-skip-permissions`, Codex `--dangerously-bypass-approvals-and-sandbox`. Nobody is in its chat to answer a
|
|
241
|
+
permission prompt, so the task's limits (its folder, its tools, its time and spend) are set when the run launches it,
|
|
242
|
+
in its prompt and arguments, never asked for later (the operator's decision of 2026-11-07, relayed on board thread
|
|
243
|
+
`eefa7959`, message `aa7c14a9`; FAA-274). No adapter code launches a runner.
|
|
244
|
+
|
|
224
245
|
A coding agent the run starts itself (a `claude -p` or `codex exec` from its shell, a reviewer) inherits the
|
|
225
246
|
environment, and its prompt does not start with the line, so it is a helper; an Agent tool subagent runs in the same
|
|
226
247
|
process and gets no hook output at all. Every session under the environment gets the conditional contract at its
|
|
@@ -47,7 +47,8 @@ it is empty: say "the project board cannot be read", never "no mail".
|
|
|
47
47
|
Only through `com.submit`, addressed to a **Project id**: `kind: "request"` names the target Project and its
|
|
48
48
|
capability (a plain question may be `kind: "message"`), in a new thread whose participants are Project ids. An
|
|
49
49
|
agents-database ask follows [agents-database.md](agents-database.md) wherever it differs from this page: it asks no one,
|
|
50
|
-
never resubmits, adapts or escalates, and an unanswered one is
|
|
50
|
+
never resubmits, adapts or escalates, and an unanswered one is cancelled once the step it served is reported (an
|
|
51
|
+
accepted request never expires, C4).
|
|
51
52
|
|
|
52
53
|
- Never address a session, a process id or a folder, and put no estate, sender, principal or session field in the
|
|
53
54
|
arguments: the board takes identity from the authenticated session and refuses them (`invalid_arguments`).
|
|
@@ -120,7 +121,11 @@ A request to a Project whose owner is not working now would wait for nobody. Aft
|
|
|
120
121
|
change nothing.
|
|
121
122
|
2. **Live:** wait under a watcher (below) and work on independent tasks meanwhile; never touch files a live session
|
|
122
123
|
holds.
|
|
123
|
-
3. **Nobody live, and the change is small.**
|
|
124
|
+
3. **Nobody live, the machine is the operator's, and the change is small.** The machine's trust decides first
|
|
125
|
+
(ADR-0176): `fabric_whoami` names it as `protocol.machine` (`trust: mine | organization | unknown`, from Fabric
|
|
126
|
+
0.3.12, set by the person in Settings). Only `mine` (the operator's own computer, or a remote one of theirs) allows
|
|
127
|
+
this step; on `organization` or `unknown` never edit another Project: step 4. Before Fabric reports trust, the
|
|
128
|
+
operator's own computer reads as `mine`, any other as `unknown`. Small means all three:
|
|
124
129
|
- it carries out what the owner's own records already say (its contract, its specification, a backlog row, a
|
|
125
130
|
finding with its evidence) or fixes a defect with a test; it decides nothing new: no ADR or DEC, no change to
|
|
126
131
|
what a public interface means, nothing recorded as the owner's or the operator's decision;
|
|
@@ -130,32 +135,50 @@ A request to a Project whose owner is not working now would wait for nobody. Aft
|
|
|
130
135
|
Make it in your own worktree of the owner's repository by its AGENTS.md (its branch naming, its agent-sync leases,
|
|
131
136
|
its gate green, its handoff and a backlog row closed with the receipt), and integrate the pull request as its rules
|
|
132
137
|
allow. Then reply on the request's thread, one line of body text (not a `reaction`, which only an announcement's
|
|
133
|
-
reply carries): **adapted by** your Project, the merge commit, and what the owner should look at.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
reply carries): **adapted by** your Project, the merge commit, and what the owner should look at. Then cancel your own
|
|
139
|
+
request (`com.cancel`, the reason "done by the asker"): your reply as the asker does not answer it (only the target's
|
|
140
|
+
does), so without the cancel it stays queued and expires. The owner's next session reads the thread and may revert
|
|
141
|
+
the change. One such change per target repository at a time (the knowledge base's agents-first.md, "When nobody
|
|
142
|
+
answers", step 3).
|
|
143
|
+
4. **Not small** (a decision, a contract, a release, a large change), **or the machine is not the operator's**: the request stays queued for its owner until its
|
|
138
144
|
deadline (seven days by default); never take a decision that is the owner's, record the wait in one line (what it
|
|
139
145
|
waits on, which watcher acts) and go on with your own work. An expiry posts no message, so the mail watcher does
|
|
140
146
|
not see it: at each idle look, read your open request's state (`com.get`) before its deadline. If it expires and the
|
|
141
147
|
ask still stands, submit it again.
|
|
142
148
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
The rule is the operator's (2026-10-22); the knowledge base's agents-first.md, "When nobody answers", records it. The
|
|
150
|
+
decline below is the adapter's own.
|
|
151
|
+
|
|
152
|
+
**A decline from a run Fabric started** (adapter 0.68.0; the question to the operator since 0.69.0): where Fabric's
|
|
153
|
+
dispatcher runs, a request to a Project with no live session goes, after a two-minute grace, to a run Fabric starts.
|
|
154
|
+
From Fabric 0.3.17 (CO-506) that run's own Project is granted `board.respond` at `com.enroll`, so the run takes the
|
|
155
|
+
request. Before 0.3.17, and from it only where CO-506 grants nothing (a machine marked as an organization's, no live
|
|
156
|
+
credential, a standing denial, a full ask queue), its `com.enroll` is refused; when the capability's slot is `absent`
|
|
157
|
+
at generation 0 (no service of the project ever held it), the run declines the request once with the reason and what
|
|
158
|
+
opens it (before 0.3.17: a Fabric with CO-506), asks for a resend and asks the operator about the gate
|
|
159
|
+
(`fabric_question_ask`, `about: board.gate.<capability>`) (where the run carries this adapter's exit contract or Fabric
|
|
160
|
+
0.3.17's packet text; a run under Switchboard's or Fabric 0.3.16's own text alone has no such branch, and the request
|
|
161
|
+
may stay queued) ([on-demand session](on-demand-session.md), step 3).
|
|
150
162
|
The request is then `answered`: read the decline and `com.read_ack` it (unread, it shows in «На проверке»); it no
|
|
151
|
-
longer waits for its owner, so steps 1, 2 and 4 no longer apply to it, while a
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`cancelled`, submit it again with a new key, once: a second
|
|
155
|
-
|
|
156
|
-
below) is read the same way: `com.read_ack`, record the wait on the reason it names, one resend once that
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
longer waits for its owner, so steps 1, 2 and 4 no longer apply to it, while a small change may still be adapted under
|
|
164
|
+
step 3. Record the wait on the reason the decline names (what it waits on, which watcher acts); once that clears (an
|
|
165
|
+
upgrade to Fabric 0.3.17 or later, or the grant the reason names) and the ask still stands, cancel the answered request
|
|
166
|
+
(`com.cancel`) and, once the cancel's receipt reads it `cancelled`, submit it again with a new key, once: a second
|
|
167
|
+
decline goes to the person. A live session's answer that the request cannot be taken now (its own refused
|
|
168
|
+
`com.enroll`, below) is read the same way: `com.read_ack`, record the wait on the reason it names, one resend once that
|
|
169
|
+
clears.
|
|
170
|
+
|
|
171
|
+
**A gate question is moot once the request is answered or taken.** The usual end of a refusal at a slot a service held is that
|
|
172
|
+
service rejoining and taking the request; then the gate question a refused run or session filed stays open though
|
|
173
|
+
nothing waits on it (Fabric settles no question by its `about`, CO-519, and no Fabric through v0.3.19 lets an agent close
|
|
174
|
+
one). A run or session that later finds the request answered, or taken and completed, says so once, naming the question (`about:
|
|
175
|
+
board.gate.<capability>` and the `commandId` that `gate-id` prints), in its closing `notes` or to the person (FAA-248).
|
|
176
|
+
|
|
177
|
+
**A dispatched Claude Code run that sees only `fabric_whoami`** among Fabric's tools (a headless `claude -p` session
|
|
178
|
+
reads its list of tools once, and `fabric-mcp` offered the rest only after the call admitted it) is Fabric's CO-563,
|
|
179
|
+
fixed on Fabric's main at `780022af` and expected in the release after v0.3.19. Until then such a run cannot read or
|
|
180
|
+
answer the board: it says so in its exit report (an `in_progress` checkpoint naming CO-563 when it holds the lease, then the retro) and never
|
|
181
|
+
claims the board was read; the request waits for the next run or a live session (FAA-296).
|
|
159
182
|
|
|
160
183
|
## The project directory
|
|
161
184
|
|
|
@@ -177,11 +200,12 @@ The rule is the operator's (2026-10-22); the knowledge base's agents-first.md, "
|
|
|
177
200
|
|
|
178
201
|
Answering it as a responder is `com.enroll` then `com.claim`, under a `board.respond` grant: a service asks it with
|
|
179
202
|
`fabric.access.request` and the operator decides it in Fabric (Agent access); a coding session cannot ask it, and
|
|
180
|
-
gets its own Project's at `com.enroll`
|
|
203
|
+
gets its own Project's at `com.enroll` from Fabric 0.3.17 (CO-506). The request then moves with `com.accept` and `com.complete`. A product agent that answers
|
|
181
204
|
requests as a service uses the service kit's responder (building-fabric-services, `references/board.md`).
|
|
182
205
|
|
|
183
206
|
**A request whose thread holds an *adapted by* reply** was carried out by another Project under "When nobody answers".
|
|
184
|
-
Review the merged change; then reply on
|
|
207
|
+
Review the merged change; then reply on its thread: kept, or reverted and why (the asker cancels its request once it
|
|
208
|
+
adapted, so the reply informs; on a request still open, your reply as its target answers it).
|
|
185
209
|
|
|
186
210
|
**A participant reply answers the request** (DEC-0098, DEC-0102). Your Project's `com.reply` (any message of yours
|
|
187
211
|
whose `replyTo` is the request) moves a queued request to `answered`: no responder claims it any more and its
|
|
@@ -191,7 +215,9 @@ nearest date (Answering at once, below); the result then follows in the same thr
|
|
|
191
215
|
service responder should still do the work (`com.status`'s slot for the capability is `active` or `stale`, or `absent` at a
|
|
192
216
|
generation of 1 or more: a holder enrolled on it), do
|
|
193
217
|
not reply on the request: tell the person and leave it queued. A session whose own `com.enroll` is refused
|
|
194
|
-
`not_authorized` has no route to the grant before Fabric
|
|
218
|
+
`not_authorized` has no route to the grant (before Fabric 0.3.17 every session; from it, where CO-506 grants nothing:
|
|
219
|
+
an organization's machine, no live credential, a standing denial, a full ask queue): when its capability's slot is
|
|
220
|
+
`absent` at generation 0 (no service of the project ever held it),
|
|
195
221
|
it answers the request once that it cannot be taken now and why, does no work on it without the slot, then asks the
|
|
196
222
|
operator once (at any other slot state it sends no reply and still asks, FAA-241) with `fabric_question_ask` (`kind: access`, `about: board.gate.<capability>`, the `commandId` that
|
|
197
223
|
`python3 <skill-dir>/scripts/fabric_session.py gate-id <request>` prints, the same a run uses, and a `text` naming the request, the gate and what
|
|
@@ -199,7 +225,7 @@ opens it; without Python, no `commandId` can be made: tell the person only), tel
|
|
|
199
225
|
filed even when that is the operator: Fabric keeps its `about` and settles nothing by it yet, CO-519), and stops handling
|
|
200
226
|
that request without waiting (Fabric's `board.read-at-start` rule and `com.enroll`'s own tool description say the
|
|
201
227
|
same since main `4f3c742a`: the question since `603da12f` (CO-517) in the rule and `12d57bdd` in the description, the slot condition since
|
|
202
|
-
`dbfbfce4` and `12d57bdd
|
|
228
|
+
`dbfbfce4` and `12d57bdd`, all in Fabric 0.3.17;
|
|
203
229
|
on Fabric 0.3.16 this rule wins). Never write "it stays queued" in a reply: the reply itself ends the queue. A
|
|
204
230
|
run Fabric started on a board request has no person in its chat to tell: with the slot `absent` at generation 0 it declines,
|
|
205
231
|
asks for a resend and asks the operator with `fabric_question_ask`; otherwise it leaves the request queued and still asks ([on-demand session](on-demand-session.md), step 3).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Remote sessions: over SSH, and one Project from two machines
|
|
2
|
+
|
|
3
|
+
Read this when the session runs over SSH, or when the same repository is worked on from two computers (two Macs of
|
|
4
|
+
one operator, a laptop and a studio). Fabric's behaviour cited here is Fabric 0.3.20 (its CHANGELOG and the on-demand
|
|
5
|
+
sessions spec, §3.9 *Two machines, sleep, retries and memory*); verify it against the running Fabric's own rules from
|
|
6
|
+
`fabric_whoami` when they disagree (FAA-284).
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- [Over SSH](#over-ssh)
|
|
11
|
+
- [One Project from two machines](#one-project-from-two-machines)
|
|
12
|
+
- [A board run sent to the other machine](#a-board-run-sent-to-the-other-machine)
|
|
13
|
+
|
|
14
|
+
## Over SSH
|
|
15
|
+
|
|
16
|
+
Fabric is a desktop app in its user's GUI login session, and the lifecycle broker that starts it runs there too
|
|
17
|
+
(launchd's `gui/<uid>` domain). So the first question over SSH is whether that user has a GUI login on the machine:
|
|
18
|
+
|
|
19
|
+
| Check (macOS) | Answer | Means |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `stat -f %Su /dev/console` | your own user name | you are logged in at the screen: Fabric and the broker can run |
|
|
22
|
+
| `stat -f %Su /dev/console` | `root`, or another user | the login window, or someone else: no GUI session of yours |
|
|
23
|
+
| `launchctl print gui/$(id -u) >/dev/null 2>&1; echo $?` | not `0` (`Could not find domain`) | no GUI session of yours |
|
|
24
|
+
|
|
25
|
+
`SSH_CONNECTION` in the environment says the session came in over SSH; it says nothing about a GUI login.
|
|
26
|
+
|
|
27
|
+
**With your GUI session on that machine**, everything works as in a terminal at the screen: `hub.json` names Fabric's
|
|
28
|
+
loopback origin, `fabric-mcp` reaches it from the SSH session, and Step 1's broker calls work. One difference: a
|
|
29
|
+
consent prompt (`waiting for the operator to allow this coding agent`) is on that machine's screen, not yours. Tell the
|
|
30
|
+
person where it is, and call `fabric_whoami` again in a moment; never loop fast.
|
|
31
|
+
|
|
32
|
+
**With no GUI session** (a headless Mac after a FileVault restart, a machine nobody is logged in to), Fabric cannot
|
|
33
|
+
run there:
|
|
34
|
+
|
|
35
|
+
- No `hub.json`, or one whose `pid` is dead, is no Fabric (Step 0). Do not try to start it: no `open -a`, no
|
|
36
|
+
`launchctl bootstrap gui/<uid>`, no running the binary. A broker call that fails, times out or answers that the
|
|
37
|
+
target cannot start is that, not a reason to retry.
|
|
38
|
+
- Say once that Fabric is unreachable on this machine because its user has no GUI login, and name the human gate:
|
|
39
|
+
someone logs in at the screen (or over Screen Sharing) as that user. Skip `fabric_whoami` and the board, report
|
|
40
|
+
nothing to Fabric that did not reach it, and do the work that needs no Fabric.
|
|
41
|
+
- Leave the work where the next session finds it without the board: commit and push the branch, and write the
|
|
42
|
+
repository's handoff entry with the exact next task. The board learns of it once a session with Fabric reads it.
|
|
43
|
+
- A service of your own that must run with no login is a LaunchDaemon, not a LaunchAgent: building-fabric-services,
|
|
44
|
+
`references/lifecycle.md`, *The system domain*. Fabric itself is not one.
|
|
45
|
+
|
|
46
|
+
## One Project from two machines
|
|
47
|
+
|
|
48
|
+
Each Fabric keeps its board in its own store. Two machines each running Fabric have **two boards**: a Project, its
|
|
49
|
+
tasks, claims, board requests and messages on one are not on the other, even for the same repository.
|
|
50
|
+
|
|
51
|
+
- `fabric_whoami` on each machine names that machine's Project. Never carry a task id, claim id, request id or
|
|
52
|
+
message id from one machine's board to the other's: it does not exist there, and a call with it is refused or,
|
|
53
|
+
worse, names something else.
|
|
54
|
+
- Claim the work on the machine where you do it. A claim on the laptop's board does not stop a session on the studio:
|
|
55
|
+
what both machines see is Git. Before starting, `git fetch` and read the other machine's pushed branches and the
|
|
56
|
+
repository's handoff entries; where the repository coordinates agents with agent-sync, its leases are refs on
|
|
57
|
+
`origin` (`git ls-remote origin 'refs/agent-sync/leases/*'`), which both machines see.
|
|
58
|
+
- Finish as on one machine, plus one rule: push before you stop, so the other machine's next session sees the branch.
|
|
59
|
+
An unpushed worktree on one machine is invisible to the other.
|
|
60
|
+
- The session hooks spool and the watch-mail watcher are per machine and per session; nothing in them travels.
|
|
61
|
+
- **A shared board.** When two Fabrics read one external board (a stack named by `SUPABASE_URL`), only one machine may
|
|
62
|
+
dispatch: `"dispatchOnSharedBoard": true` in that machine's Fabric `settings.json`, and on no other. Two dispatchers
|
|
63
|
+
on one board would both start a run for one request; nothing on the board coordinates them yet (Fabric's CO-567).
|
|
64
|
+
|
|
65
|
+
## A board run sent to the other machine
|
|
66
|
+
|
|
67
|
+
Fabric can start a board request's run on the machine that holds the repository (the `dispatchTargets` key of its
|
|
68
|
+
`settings.json`, through Switchboard over SSH). Such a run has no board of the request's Fabric: its instructions carry
|
|
69
|
+
the request and its thread as text, and its answer goes back through the run's result, which the sending Fabric posts in
|
|
70
|
+
the request's thread.
|
|
71
|
+
|
|
72
|
+
- Use `com.*` for that request only when `fabric_whoami` on this machine names the request's own Project
|
|
73
|
+
(`fabric-project:<id>` as the instructions give it). A Fabric on this machine keeps a board of its own, where the
|
|
74
|
+
request does not exist: replying there answers nothing.
|
|
75
|
+
- Otherwise answer in the run's result, in the form its exit contract asks, and say what was done with its evidence.
|
|
@@ -27,21 +27,25 @@ session start and turn; FAA-183 tracks making the plugin find the interpreter it
|
|
|
27
27
|
|
|
28
28
|
| Event | When it acts | What it does |
|
|
29
29
|
|---|---|---|
|
|
30
|
-
| SessionStart (`startup`, `clear`; the plugin's matcher, and the script ignores `compact` and `resume` for every host) | Claude Code has an MCP server named `fabric` (local, project or user scope) | Fabric running, in a project that uses Fabric (below): adds the preamble: `fabric_whoami` first; `com.status` and `com.list`, `com.read_ack` only what was read, and what waits on the Project answered in the same turn with its state, the result later (FAA-205); `fabric_work_list`, then `fabric_work_claim`; `fabric_work_release` at the end, and, when a piece of work or the session ends, its retro (`observatory_lesson`, insight/1 lines in `insights/` (below), or one line that there is nothing; FAA-206); ask another Project only through `com.submit`. Fabric running in a folder with no such sign (a user-scope registration reaches every folder): one line pointing at `fabric_whoami`, which names the project when Fabric serves the folder anyway (FAA-60). Fabric down, in a project that uses Fabric: one line, skip `fabric_whoami` and the board, start Fabric through the lifecycle broker only if the task needs it. Fabric down anywhere else, or not registered: nothing. |
|
|
30
|
+
| SessionStart (`startup`, `clear`; the plugin's matcher, and the script ignores `compact` and `resume` for every host) | Claude Code has an MCP server named `fabric` (local, project or user scope, read from the `.claude.json` in `CLAUDE_CONFIG_DIR` when it is set, else `~/.claude.json`, where Claude Code reads it; FAA-218, FAA-234; a local entry is the one keyed by the folder's git root, or the folder itself outside git, never an ancestor's, as Claude Code applies it, FAA-235) | Fabric running, in a project that uses Fabric (below): adds the preamble: `fabric_whoami` first; `com.status` and `com.list`, `com.read_ack` only what was read, and what waits on the Project answered in the same turn with its state, the result later (FAA-205); `fabric_work_list`, then `fabric_work_claim`; `fabric_work_release` at the end, and, when a piece of work or the session ends, its retro (`observatory_lesson`, insight/1 lines in `insights/` (below), or one line that there is nothing; FAA-206; a measured win also to `agents-db.contribute`, FAA-277); ask another Project only through `com.submit`. Fabric running in a folder with no such sign (a user-scope registration reaches every folder): one line pointing at `fabric_whoami`, which names the project when Fabric serves the folder anyway (FAA-60). Fabric down, in a project that uses Fabric: one line, skip `fabric_whoami` and the board, start Fabric through the lifecycle broker only if the task needs it. Fabric down anywhere else, or not registered: nothing. On a Mac, a registered command that is an absolute path to another user's copy of `fabric-mcp` (outside the home, neither this user's nor root's; Claude Code's entry, Codex's `config.toml`, Kimi Code's `mcp.json`) gets one line first, also when nothing else is said, naming it and `fabric_session.py status`, which says how to register your own; a bare name is left to `status`, which looks it up on `PATH` (FAA-232). |
|
|
31
31
|
| Stop (each turn) | as for SessionStart, and the running Fabric's `hub.json` lists `"session-hooks/1"` in `features` | measures the turn's facts (HEAD, files changed, lines added and removed: counts and ids only, git sharing 1.5 s) and names the other Git repositories the turn touched (FAA-140, below: only once Fabric takes them), spools them in one entry and starts a detached worker that sends `fabric_session_observe {event: "turn", head, filesChanged, linesAdded, linesRemoved, …}` as stored (with `repositories` only where that tool's input schema lists the field as an array of items with host, owner and name requiring nothing else, at most its `maxItems`, each item meeting what the schema says of its fields or left out; a turn spooled with the mark and delivered to a Fabric whose schema no longer takes them goes without them, and the log says so once per session; with a Fabric that never took them nothing is read and nothing is logged), keeps the `nudges` its answer carries (Fabric 0.3.14: each `{kind, says, task_id?, request_id?}`, shown on a turn once, at most five; it stays for `fabric_work_list` and `fabric_heartbeat`, so it may come again there: act on it once) in `nudges.json`, then reads `com.status` and keeps the unread count in the session's spool (`board.json`) |
|
|
32
32
|
| UserPromptSubmit | the session's spool holds a count or nudges, and the session was not started on a board request (nor runs inside such a run) | adds ONE line, `N unread on the project board: com.list; answer what waits on you in this turn: ...` (FAA-205: its state now, the result later), only when the count rose since the last line (a fall or the same count says nothing), then each nudge's `says` once, as Fabric writes it (Fabric 0.3.14 starts each with `Fabric:`; any other line gets the tag `Fabric reminder: `), one line each, at most five lines of at most 400 characters; the nudges file is taken by renaming it away before they are printed (FAA-142). It reads, renames and writes small files in the session's folder and never calls Fabric. |
|
|
33
33
|
| SessionEnd | as for Stop | spools the end; the worker sends `fabric_work_release {claims: "session", outcome: "let-go", …}` (with `observedAt`, the time the hook fired, where the tool's input schema names it: Fabric 0.3.9 then refuses an end from before the session enrolled), then `fabric_session_observe {event: "end", reason}`, and removes the session's spool. Reason `clear` ends nothing: `/clear` keeps the agent process and its fabric-mcp, so no claim is released and no end observed; turns already spooled still go out (since 0.42.0). Their nudges stay in the old id's folder and are not printed here; Fabric 0.3.14 only shows a nudge on a turn's answer and keeps it for `fabric_work_list` and `fabric_heartbeat`, so the new session gets it there (board `b605a636`; decision of 0.51.2, FAA-142) |
|
|
34
|
-
| PostToolUse (the plugin's matcher: Fabric's `fabric_work_release` and `fabric_task_release` only) | the call released a claim as done (`how`, or `outcome` when `how` is absent, done; the older tool's `outcome` succeeded) and Fabric's answer did not refuse it or release nothing (`released: false`, Fabric's refusal; `already-released`; a `claims: "session"` release with no live claim, `claims: []`; a repeat, which Fabric answers with the first release's `at`; a claim the operator released first, `released-by-operator`; a repeat after a first answer the agent never saw (lost in the client, or `unconfirmed`), and a session release answered `partial` or `unconfirmed`; in each the session-end retro still stands; a payload over 4 MiB is read as nothing and gets no line, as on every hook (below); or `isError`; an answer the hook cannot read keeps the line) | adds ONE line naming the retro in this turn (`observatory_lesson`, or the turn's answer without it; insight/1 lines in `insights/` (below); or one line that there is nothing; FAA-206, Fabric's `retro-after-work`, since Fabric 0.3.16, `e1eecec0`); reads no file, calls nothing, also in a session Fabric launched |
|
|
34
|
+
| PostToolUse (the plugin's matcher: Fabric's `fabric_work_release` and `fabric_task_release` only) | the call released a claim as done (`how`, or `outcome` when `how` is absent, done; the older tool's `outcome` succeeded) and Fabric's answer did not refuse it or release nothing (`released: false`, Fabric's refusal; `already-released`; a `claims: "session"` release with no live claim, `claims: []`; a repeat, which Fabric answers with the first release's `at`; a claim the operator released first, `released-by-operator`; a repeat after a first answer the agent never saw (lost in the client, or `unconfirmed`), and a session release answered `partial` or `unconfirmed`; in each the session-end retro still stands; a payload over 4 MiB is read as nothing and gets no line, as on every hook (below); or `isError`; an answer the hook cannot read keeps the line) | adds ONE line naming the retro in this turn (`observatory_lesson`, or the turn's answer without it; insight/1 lines in `insights/` (below); or one line that there is nothing; a measured win also to `agents-db.contribute`, FAA-277; FAA-206, Fabric's `retro-after-work`, since Fabric 0.3.16, `e1eecec0`); reads no file, calls nothing, also in a session Fabric launched |
|
|
35
35
|
|
|
36
36
|
Every hook reads at most 4 MiB of its input (`HOOK_INPUT_CAP`) as UTF-8 bytes: a byte order mark is dropped, a byte
|
|
37
37
|
that is no UTF-8 reads as U+FFFD, and a payload over the cap is read as nothing (FAA-210, since 0.67.0; 0.64.0 to
|
|
38
38
|
0.66.0 counted 1 MiB of bytes and read a BOM as nothing).
|
|
39
39
|
|
|
40
|
-
An insight/1 line is one JSON object per line with
|
|
41
|
-
|
|
42
|
-
`
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
An insight/1 line is one JSON object per line with nine required fields, as the insights specification the estate's
|
|
41
|
+
collector reads states them (FAA-217): `schema` `"insight/1"`, `id` (`<agent>-<yyyymmdd>-<n>`), `at` (UTC), `agent`,
|
|
42
|
+
`kind` (`lesson`, `finding`, `metric`, `pattern`, `tool`, `market`), `title` (at most 160 characters), `body` (at most
|
|
43
|
+
1200 characters), `evidence` (a list of one or more: `file:line`, a command and its output, a link, a run id) and
|
|
44
|
+
`audience` (`internal`, `public-maybe` or `public`); optional `numbers` (each with what it measures and its source),
|
|
45
|
+
`tags`, `next` and `supersedes` (the id of a row it corrects; a row is never rewritten). An insight without evidence is
|
|
46
|
+
not written, and the collector counts a row without evidence or with an unknown kind as a problem. It never holds a
|
|
47
|
+
value, a person's name or a client's text. Fabric's ADR-0188 §5 names insight/1 without a shape; this is the shape
|
|
48
|
+
the specification's owner keeps. A session writes them in the project's `insights/` folder,
|
|
45
49
|
one JSONL file a month, the place Observatory named for its reader (OBS-331, board thread `f7758225`): in the
|
|
46
50
|
project's data folder when it has one or the repository is public, else in the repository, never inside a public one
|
|
47
51
|
(Observatory's own copy rule keeps `insights/` out of a public copy): a public repository with no data folder yet gets
|
|
@@ -77,16 +81,19 @@ backslash as an escape, so `cd C:\work` goes to `C:work`, which is left out); an
|
|
|
77
81
|
*touched*: a `cd` or a read-only `git -C <path> log` names one as much as an edit does. A subagent's entries are not
|
|
78
82
|
read, so a repository only a subagent touched is not named. Each
|
|
79
83
|
path is resolved with one `git rev-parse --show-toplevel` and its repository's `origin` with one `git config --get
|
|
80
|
-
remote.origin.url`, every call inside the same 1.5 s as the turn's facts (the facts first); a path outside every
|
|
84
|
+
remote.origin.url`, every call inside the same 1.5 s as the turn's facts (the facts first, and spooled before the walk starts, so a hook ended during it still leaves them; the folder checks run beside the hook, which waits for them no longer than the budget, then writes the entry again with the repositories it found, FAA-161); a path outside every
|
|
81
85
|
checkout costs no git call, and when the budget runs out the hook spools what it found. Each repository is named
|
|
82
86
|
`{host, owner, name}` from its `origin` (https, `ssh://`, `git@host:owner/name`; credentials, a port and `.git`
|
|
83
87
|
dropped; a nested group stays in `owner`, and on a self-hosted ssh remote `owner` is the folder path on that server
|
|
84
88
|
before the repository's name, which may hold a user name). The repository of the session's folder when the turn ends (the Stop payload's `cwd`; any checkout of it: a worktree, a clone), a repository whose `origin`
|
|
85
89
|
is missing or does not parse, and a second checkout of one repository are not named; at most 10. No local path leaves the hook; a
|
|
86
90
|
missing or unreadable transcript names none and is no error. The list can miss a repository (FAA-174): in a turn whose
|
|
87
|
-
entries pass 512 KB, an edit that large tool output pushed out of the tail is not named.
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
entries pass 512 KB, an edit that large tool output pushed out of the tail is not named. Claude Code keeps the Bash folder between calls while it
|
|
92
|
+
stays inside the session's folder, so each Bash call is read against the folder the one before it left the shell in
|
|
93
|
+
(FAA-184): an absolute `cd` inside the session's folder carries to the next call, and a folder outside it, or one not
|
|
94
|
+
known here (a relative `cd`, `popd`, a call past the cap), is the session's folder again. A `cd` into a folder Claude
|
|
95
|
+
Code allows outside the session's (an added working directory) is read as reset, so a relative `git -C` after it can
|
|
96
|
+
still name a wrong repository. Turn the read off (FAA-160)
|
|
90
97
|
with `FABRIC_SESSION_NO_REPOSITORIES=1` in the environment, or a file named `no-repositories` in the hooks' folder:
|
|
91
98
|
the turn's facts are still sent, without `repositories` (any value of the variable but empty or `0` turns it off; the
|
|
92
99
|
file sits in a cache folder a cleaner may empty, so the variable, set in Claude Code's settings `env`, is the lasting form:
|
|
@@ -96,6 +103,8 @@ repository's `docs/evidence/runs/2026-10-27-settings-env-reaches-hooks.md`; Wind
|
|
|
96
103
|
or not taking the repositories yet) or not done because `fabric` is not registered for the folder, as the environment
|
|
97
104
|
of the shell it runs in has it; its "Session hooks" line describes Fabric alone.
|
|
98
105
|
|
|
106
|
+
**`status`** (FAA-233, FAA-239) judges each registration it reads (Claude Code's, Codex's `[mcp_servers.fabric]` `command`, Kimi Code's `mcpServers.fabric`) for another user's copy and names the re-registration for that host. With `PASSIONCODE_APPLICATIONS` set, it names a value that is not an absolute path (read as unset, as the launcher reads it), a registration of a copy outside that folder (which the launcher does not update), the user's own copy in `~/Applications` it no longer takes, and a folder this user cannot enter, where it advises `~/Applications` with the variable unset. Native bridges (Hermes, OpenCode, OpenClaw, Pi) pass the hooks a boolean registration, not a command, so neither the session start nor `status` judges their copy.
|
|
107
|
+
|
|
99
108
|
**A project that uses Fabric** (`project_uses_fabric`), cheapest sign first: `fabric` registered at local or project
|
|
100
109
|
scope for the folder (user scope is every folder's, so it says nothing about this one); a `fabric-agent.json`,
|
|
101
110
|
`fabric-contract.lock.json` or `fabric.package.json` file from the folder up to its checkout's root (the folder alone
|
|
@@ -198,7 +207,13 @@ close. The prompt hook prints no unread line and no nudges there, nor in a sessi
|
|
|
198
207
|
`git config` with it; the Stop hook drops the credential in memory before anything is spooled, logged or sent.
|
|
199
208
|
- **Send content, or use more than it reads for.** A turn is the HEAD commit, the number of files that differ from it
|
|
200
209
|
(untracked included) and the lines added and removed against it, and, once Fabric takes them, the other repositories
|
|
201
|
-
it touched by their `origin` name (`{host, owner, name}`); no path,
|
|
210
|
+
it touched by their `origin` name (`{host, owner, name}`), and its branch (below); no path, commit message or diff
|
|
211
|
+
text. The branch (FAA-152, contract DEC-0140 PR-3; Fabric 0.3.16, CO-459) is `git symbolic-ref --short -q HEAD`,
|
|
212
|
+
one more git call inside the same 1.5 s, null on a detached head, and goes with a turn (never the end) only where
|
|
213
|
+
`fabric_session_observe` declares `branch`, null only where its schema allows it and a name outside its pattern
|
|
214
|
+
not at all; Fabric shows it on this machine's screens and never stores or shares it. Turn it off with
|
|
215
|
+
`FABRIC_SESSION_NO_BRANCH=1` (any value but empty or `0`): no branch is read or sent. The role PR-3 also defines
|
|
216
|
+
is not sent: Fabric's enrollment takes no role yet. The
|
|
202
217
|
transcript's tail is read, message text and tool output with it, and only tool names and path arguments are used; a sent turn's log
|
|
203
218
|
line counts its repositories, never names them. The board is read
|
|
204
219
|
as a count (`com.status`), never a message. Git runs with `--no-optional-locks`.
|
|
@@ -248,6 +263,6 @@ launched-session check exact.
|
|
|
248
263
|
| D1 | `fabric-mcp` enrolls with the runtime session id | for `--runner claude-code`, `runtimeSessionId` = env `CLAUDE_CODE_SESSION_ID` in `fabric.session.enroll {cwd, runtimeSessionId}` (Claude Code 2.1.296 gives MCP stdio servers and Bash commands that variable, equal to the hooks' `session_id`) |
|
|
249
264
|
| D2 | rejoin only | `fabric-mcp --runner claude-code --rejoin-only --runtime-session-id <id>` attaches to that runtime session's live enrolled session; never asks, never mints; otherwise only `fabric_whoami`, as not enrolled; Fabric 0.3.9 exits 3 at once when no session holds the id |
|
|
250
265
|
| D3 | the flag | `hub.json` `"features": ["session-hooks/1"]` once D1, D2, D4 and D5 ship |
|
|
251
|
-
| D4 | `fabric_session_observe` | `{event: "turn" or "end", runtime: "claude-code", runtimeSessionId, observedAt, head, filesChanged, linesAdded, linesRemoved, reason (end only), repositories (turn only, optional), idempotencyKey}` → `session.turn.observed@1`, `session.ended@1`; `repositories` (FAA-140, proposed to Fabric on `a8bdda7a`, accepted on `d3f9e6fb`) is at most 10 `{host, owner, name}` and is sent only where the tool's input schema lists it with items carrying host, owner and name, so a Fabric without it gets the call it gets today; from Fabric 0.3.14 a turn's answer is `{recorded, seq, nudges?}`, each nudge `{kind, says, task_id?, request_id?}` shown on a turn once, at most five, the same on a
|
|
266
|
+
| D4 | `fabric_session_observe` | `{event: "turn" or "end", runtime: "claude-code", runtimeSessionId, observedAt, head, filesChanged, linesAdded, linesRemoved, reason (end only), repositories (turn only, optional), branch (turn only, optional; Fabric 0.3.16), idempotencyKey}` → `session.turn.observed@1`, `session.ended@1`; `repositories` (FAA-140, proposed to Fabric on `a8bdda7a`, accepted on `d3f9e6fb`) is at most 10 `{host, owner, name}` and is sent only where the tool's input schema lists it with items carrying host, owner and name, so a Fabric without it gets the call it gets today; from Fabric 0.3.14 a turn's answer is `{recorded, seq, nudges?}`, each nudge `{kind, says, task_id?, request_id?}` shown on a turn once, at most five, the same (with `repeated: true`) on a repeat of the turn's `idempotencyKey` within an hour, and kept for `fabric_work_list` and `fabric_heartbeat` (FAA-142; re-read against the tag `v0.3.14`, `7e43dc03`, FAA-143: `workTools.ts` `fabric_session_observe`, `abandonedSweepRun.ts` `TURN_NUDGES_MAX` and `TURN_REPEAT_MS`, `shared/abandonedSweep.ts` `nudgeText`, which starts every `says` with `Fabric:`) |
|
|
252
267
|
| D5 | release a whole session | `fabric_work_release {claims: "session", outcome: "let-go", reason, idempotencyKey}`; `observedAt` too where its input schema names it (Fabric 0.3.9: `stale-end` for an end observed before the session enrolled) |
|
|
253
268
|
| D6 | a launched-session marker | `FABRIC_LAUNCHED_SESSION=1` in the environment of every session Fabric launches, whatever its adapter; until then, the inference above |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# The context of a task, settled before the first act on it
|
|
2
|
+
|
|
3
|
+
The operator's rule, relayed on the board by the contract's owner (message `bfc0e14e`, 2026-11-12, FAA-299).
|
|
4
|
+
The Fabric Agent Contract drafts it as CT-292, rules TC-1 to TC-6:
|
|
5
|
+
`docs/evidence/plans/2026-11-12-ct292-task-context.md`, at `753c5093` or later on its main branch. This page follows
|
|
6
|
+
that draft until its DEC lands (the contract's slice of 2026-11-21); then the DEC is the rule and this page cites it.
|
|
7
|
+
|
|
8
|
+
A context is `personal` or `org:<orgId>` (contract DEC-0082). It matters only when you can reach resources of more than
|
|
9
|
+
one: an account, a key, a grant, a board, another agent, a data store or a repository of an organization.
|
|
10
|
+
|
|
11
|
+
## Settle it before the first act (TC-1, TC-2)
|
|
12
|
+
|
|
13
|
+
Before your first act on such a resource, the task has exactly one context. Read each source that can name it; each
|
|
14
|
+
one that names a context is a claim:
|
|
15
|
+
|
|
16
|
+
1. the operator's words for this task that name an organization, its product or its flow;
|
|
17
|
+
2. the board request the task serves: the requesting Project's organization;
|
|
18
|
+
3. the folder's Project by folder attribution (DEC-0089) and that Project's organizations; a Project with no
|
|
19
|
+
organization claims `personal`;
|
|
20
|
+
4. the grant the act runs under, when it states `context` (DEC-0082);
|
|
21
|
+
5. the place, when it belongs to one organization (its space, its machine, its cloud).
|
|
22
|
+
|
|
23
|
+
**The context is settled when at least one source claims and every claim names the same context.** A Project listed
|
|
24
|
+
for two organizations claims both and settles nothing. Reading your own working folder needs no context.
|
|
25
|
+
|
|
26
|
+
## Unsettled: ask once (TC-3, TC-4)
|
|
27
|
+
|
|
28
|
+
When no source claims and you can reach more than one context, or two claims differ, **ask the operator once, naming
|
|
29
|
+
each candidate context, the flow or product in it, and what the task would do there**, each candidate an option.
|
|
30
|
+
**Never guess, never pick the "safest" candidate, and never split the task across contexts**: work that needs two
|
|
31
|
+
contexts is two tasks. The question is a step that waits: every part whose context is settled goes on meanwhile.
|
|
32
|
+
|
|
33
|
+
This is the one exception to the operator's rule that his word is done the first time without asking (FAA-207, CT-220):
|
|
34
|
+
an agent that reaches one context, or whose context is settled, asks nothing.
|
|
35
|
+
|
|
36
|
+
With a person in the session, ask in the chat. With nobody there (a dispatched run, a headless place), ask through
|
|
37
|
+
`fabric_question_ask` with the candidates as `options` and `about: "task.context"`, send `fabric_heartbeat` with
|
|
38
|
+
`waitingOn: {kind: "question", id}`, and name the open step in your report. A board request you cannot place is
|
|
39
|
+
answered with a `com.reply` naming the step, never worked in a guessed context.
|
|
40
|
+
|
|
41
|
+
## Record the answer (TC-5)
|
|
42
|
+
|
|
43
|
+
Record the answer as `{context, by: "operator", at, source}` in the handoff (and the task note), so the next agent and
|
|
44
|
+
the next session read a settled context and do not ask again. It holds for that task, or for the Project when the
|
|
45
|
+
operator says so; it changes no registry record.
|
|
46
|
+
|
|
47
|
+
## Keep the contexts apart (TC-6)
|
|
48
|
+
|
|
49
|
+
The question, its options and its answer go only to the operator's own session or question surface: **never into
|
|
50
|
+
either organization's board thread** and never to another organization's agent, since naming one membership to the
|
|
51
|
+
other links them. The credentials, data and grants of one context are never used for a task settled in another.
|
|
52
|
+
|
|
53
|
+
## The checklist
|
|
54
|
+
|
|
55
|
+
- [ ] Reached more than one context? If not, nothing to settle.
|
|
56
|
+
- [ ] Each source read; the context settled, or one question asked with every candidate and its effect.
|
|
57
|
+
- [ ] No guess, no "safest" pick, no split; settled parts went on.
|
|
58
|
+
- [ ] The answer recorded in the handoff as `{context, by, at, source}`; nothing about it on any board thread.
|