@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.
Files changed (105) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/README.md +14 -7
  3. package/bin/fabric-agent-adapter.js +26 -4
  4. package/package.json +1 -1
  5. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  6. package/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +6 -4
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +4 -4
  9. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +42 -7
  10. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +13 -3
  11. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +3 -3
  12. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +12 -4
  13. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +23 -8
  14. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +6 -2
  15. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +2 -1
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +4 -2
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +6 -3
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +34 -8
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/README.md +67 -0
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/dashboard_service.py +252 -0
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.mjs +168 -0
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/dashboard-only/install.py +179 -0
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/README.md +57 -0
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.mjs +181 -0
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/key-delivery-door/door.py +237 -0
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +4 -4
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +11 -6
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +42 -7
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +6 -2
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +12 -1
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +26 -3
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +4 -2
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +14 -4
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +6 -2
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +44 -3
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +12 -4
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +7 -5
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +10 -5
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +1 -0
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +5 -0
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +2 -0
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +15 -6
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +3 -2
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +37 -5
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +30 -2
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +48 -5
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +9 -3
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +16 -2
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +344 -0
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +54 -20
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +4 -0
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +8 -2
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +4 -1
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -0
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +55 -14
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +8 -1
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +6 -0
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +3 -0
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +16 -7
  64. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +4 -2
  65. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +97 -3
  66. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +50 -8
  67. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -4
  68. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +49 -3
  69. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +9 -2
  70. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +15 -2
  71. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +2 -2
  72. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +358 -0
  73. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +41 -1
  74. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +6 -0
  75. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +17 -7
  76. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +6 -2
  77. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +24 -0
  78. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +110 -16
  79. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +12 -1
  80. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +543 -37
  81. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +4 -1
  82. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +12 -5
  83. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +4 -4
  84. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +42 -7
  85. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +6 -3
  86. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +13 -3
  87. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +13 -4
  88. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +32 -0
  89. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/working-rules.md +54 -0
  90. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +12 -4
  91. package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
  92. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +27 -26
  93. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/agents-database.md +4 -4
  94. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/cleanup.md +30 -0
  95. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +59 -38
  96. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +52 -26
  97. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/remote-sessions.md +75 -0
  98. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +28 -13
  99. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/task-context.md +58 -0
  100. package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/waits.md +36 -0
  101. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +14 -0
  102. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +169 -46
  103. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +146 -19
  104. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +78 -3
  105. 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.
@@ -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`. "Fabric's main" below is 0.3.16 with CO-409 on top (`babb2dc1`, `09ddb2fc`, in no release yet); the hooks recognise a dispatched session from
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. Then `observatory_checkpoint_latest {workflowId}`. Its `goal`
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>`; Observatory's write redacts the three board
61
- ones (and a UUID in `goal`) today, so take the request from the environment or the first line, never from the
62
- packet. No message body travels in the packet: read the request with `com.get {message: <request>}` (a request's id
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 does no work: on Fabric 0.3.16 a
80
- session has no route to that grant (`fabric.access.request` is the door's and a binding's, never exposed by
81
- `fabric-mcp`, and Fabric's Agent access only decides a request already waiting), and work without a slot has no
82
- fence, no `com.progress` and a live session may take the request in parallel. When `com.get` reads the request
83
- `queued` and `com.status`'s entry for the request's capability reads `absent` at generation 0 (no holder of this
84
- Project ever enrolled on that slot; `com.status` lists the capability of every open request, so the entry is there
85
- whenever the board can be read: a missing entry or an unavailable board is the error rule),
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: this Project cannot take it until Fabric ships CO-506, and the requester sends
88
- it again then (the reply ends the queue, so nothing takes it later; never say it waits). Since only the operator
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 (on Fabric before CO-506: a Fabric
95
- release with CO-506, which grants it at `com.enroll`, and at a slot a holder held, the Project's service enrolling again,
96
- which takes the request itself without opening this gate; on Fabric 0.3.16 nothing else does, since Agent access only
97
- decides a request a service filed; once CO-506 shipped, at every slot state, the reason Fabric's `board.own-respond`
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 after CO-506 a session; one that stopped cleanly reads `absent`, and a service takes the
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; after CO-506 such a refusal comes from the reason `board.own-respond` names (such as an organization's machine, no live
110
- credential, a standing denial, a full ask queue), so it is never silent: FAA-241, Fabric main `4f3c742a`; a question that fails is named in the
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 until CO-506.
119
- Fabric's dispatcher counts an answered request as settled and starts no second run for it. Once CO-506 ships
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, main `dbfbfce4`, in no release
125
- yet);
126
- Fabric's `com.enroll` description in 0.3.16 tells a session to ask the grant, which it cannot, and 0.3.16's own
127
- `not_authorized` refusal says to reply with the answer; step 3 wins over both (Fabric's `main` gives `com.enroll`'s
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 `failed` once Observatory takes it (OBS-323; Observatory takes `in_progress`, `done` and
184
- `blocked` today), and until then `blocked` with the reason in `notes`.
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's main since `09ddb2fc`, in no release yet; FAA-213). Switchboard's appended exit contract says the same from
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 left to expire.
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.** Small means all three:
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. The owner's next
134
- session reads it and may revert it. One such change per target repository at a time. Your own reply does not answer
135
- your own request: a queued one stays on the board, on its thread and on Fabric's home ("requests no agent answers
136
- now"), for the owner to see until its deadline; do not cancel it.
137
- 4. **Not small** (a decision, a contract, a release, a large change): the request stays queued for its owner until its
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
- **A decline from a run Fabric started** (adapter 0.68.0; the question to the operator since 0.69.0): where Fabric's dispatcher runs, a request to a Project with no live
144
- session and whose capability slot no service ever held (`absent` at generation 0), whose session holds no
145
- `board.respond` grant (every coding-session Project before Fabric ships CO-506), is answered within minutes (after a
146
- two-minute grace) by a run that declines it until CO-506, asks for a resend and asks the operator about the gate
147
- (`fabric_question_ask`, `about: board.gate.<capability>`) (where the run carries this adapter's exit
148
- contract; a run under Switchboard's or Fabric 0.3.16's own text alone has no such branch, and the request may stay
149
- queued) ([on-demand session](on-demand-session.md), step 3).
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
- small change may still be adapted under step 3. Record the wait on CO-506 (what it waits on, which watcher acts); once
153
- it ships and the ask still stands, cancel the answered request (`com.cancel`) and, once the cancel's receipt reads it
154
- `cancelled`, submit it again with a new key, once: a second decline after CO-506 shipped (a machine CO-506 grants
155
- nothing on) goes to the person. A live session's answer that the request cannot be taken now (its own refused `com.enroll`,
156
- below) is read the same way: `com.read_ack`, record the wait on the reason it names, one resend once that clears.
157
-
158
- The rule is the operator's (2026-10-22); the knowledge base's agents-first.md, "When nobody answers", records it.
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` once Fabric ships CO-506. The request then moves with `com.accept` and `com.complete`. A product agent that answers
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 the request (that answers it): kept, or reverted and why.
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's CO-506: when its capability's slot is `absent` at generation 0,
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`; in no Fabric release yet, and
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 seven required fields, `schema` `"insight/1"`, `id`, `at` (UTC),
41
- `agent`, `kind` (in use: `lesson`, `finding`, `metric`, `pattern`, `tool`), `title` and `body`, and usually
42
- `evidence` (a list), `tags`, `audience`, `numbers` and `next`; it never holds a value. Fabric's ADR-0188 §5 names
43
- insight/1 without a shape; this is the shape in use across the estate's insight files (one JSONL file a month),
44
- stated here provisionally until ADR-0188's owner fixes it. A session writes them in the project's `insights/` folder,
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. Until FAA-184 lands it can also
88
- name a wrong one: Claude Code keeps the Bash folder between calls, and a relative `git -C` in a later call is read
89
- against the session's folder, not the folder an earlier call moved to. Turn the read off (FAA-160)
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, branch name, commit message or diff text. The
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 repeated turn, and kept for `fabric_work_list` and `fabric_heartbeat` (FAA-142) |
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.