@synadia-ai/agent-fabric-claude-code 0.1.0 → 0.2.0

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.
@@ -1,10 +1,10 @@
1
1
  {
2
- "name": "agent-fabric",
3
- "version": "0.1.0",
4
- "description": "The Synadia Agent Fabric add-on for the NATS channel plugin, as a Claude Code plugin of its own: ScratchPad's skill and the hook that hands scripts/sp its environment.",
2
+ "name": "nats-channel",
3
+ "version": "0.6.0+fabric.554414d.bbd3128",
4
+ "description": "NATS channel plugin for Claude Code — bridges NATS messaging to Claude Code sessions, with the Synadia Agent Fabric add-on and ScratchPad",
5
5
  "author": {
6
6
  "name": "Synadia"
7
7
  },
8
- "repository": "https://github.com/synadia-ai/agent-fabric-sdk",
8
+ "repository": "https://github.com/synadia-ai/synadia-agents",
9
9
  "license": "Apache-2.0"
10
10
  }
package/.mcp.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "mcpServers": {
3
+ "nats": {
4
+ "command": "bun",
5
+ "args": [
6
+ "${CLAUDE_PLUGIN_ROOT}/runtime/server.js"
7
+ ],
8
+ "env": {
9
+ "CLAUDE_CWD": "${PWD}",
10
+ "SYNADIA_CLAUDE_CODE_EXTENSIONS": "${CLAUDE_PLUGIN_ROOT}/runtime/fabric.js"
11
+ }
12
+ }
13
+ }
14
+ }
package/BUILD ADDED
@@ -0,0 +1,4 @@
1
+ plugin nats-channel 0.6.0
2
+ synadia-agents 554414d
3
+ agent-fabric-sdk bbd3128
4
+ built 2026-10-11T08:01:50.287Z
package/LICENSE CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  Apache License
2
3
  Version 2.0, January 2004
3
4
  http://www.apache.org/licenses/
@@ -137,8 +138,8 @@
137
138
 
138
139
  6. Trademarks. This License does not grant permission to use the trade
139
140
  names, trademarks, service marks, or product names of the Licensor,
140
- except as required for describing the origin of the Work and
141
- reproducing the content of the NOTICE file.
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
142
143
 
143
144
  7. Disclaimer of Warranty. Unless required by applicable law or
144
145
  agreed to in writing, Licensor provides the Work (and each
@@ -162,16 +163,16 @@
162
163
  other commercial damages or losses), even if such Contributor
163
164
  has been advised of the possibility of such damages.
164
165
 
165
- 9. Accepting Warranty or Support. While redistributing the Work or
166
- Derivative Works thereof, You may choose to offer, and charge a fee
167
- for, acceptance of support, warranty, indemnity, or other liability
168
- obligations and/or rights consistent with this License. However, in
169
- accepting such obligations, You may act only on Your own behalf and
170
- on Your sole responsibility, not on behalf of any other Contributor,
171
- and only if You agree to indemnify, defend, and hold each Contributor
172
- harmless for any liability incurred by, or claims asserted against,
173
- such Contributor by reason of your accepting any such warranty or
174
- additional liability.
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
175
176
 
176
177
  END OF TERMS AND CONDITIONS
177
178
 
@@ -180,13 +181,13 @@
180
181
  To apply the Apache License to your work, attach the following
181
182
  boilerplate notice, with the fields enclosed by brackets "[]"
182
183
  replaced with your own identifying information. (Don't include
183
- the brackets!) The text should be enclosed in the appropriate
184
+ the brackets!) The text should be enclosed in the appropriate
184
185
  comment syntax for the file format. We also recommend that a
185
186
  file or class name and description of purpose be included on the
186
187
  same "printed page" as the copyright notice for easier
187
188
  identification within third-party archives.
188
189
 
189
- Copyright 2026 Synadia Communications
190
+ Copyright 2026 Anthropic, PBC
190
191
 
191
192
  Licensed under the Apache License, Version 2.0 (the "License");
192
193
  you may not use this file except in compliance with the License.
@@ -196,6 +197,6 @@
196
197
 
197
198
  Unless required by applicable law or agreed to in writing, software
198
199
  distributed under the License is distributed on an "AS IS" BASIS,
199
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
200
- implied. See the License for the specific language governing permissions
201
- and limitations under the License.
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md CHANGED
@@ -1,466 +1,77 @@
1
1
  # @synadia-ai/agent-fabric-claude-code
2
2
 
3
- The Synadia Agent Fabric add-on for the Claude Code channel plugin
4
- (`nats-channel`, `agents/claude-code` in
5
- [`synadia-agents`](https://github.com/synadia-ai/synadia-agents)). One
6
- extension module in the shape the plugin loads: named in
7
- `SYNADIA_CLAUDE_CODE_EXTENSIONS`, it puts the fabric package's tracing on the
8
- plugin's service and on the client its agent tools prompt through, binds
9
- each served prompt to the Claude Code session it was delivered to with the
10
- signed `served` pair, its `end` at the turn's `Stop` so the closing model
11
- call falls inside the window, and puts the `409` loop guard in front of the
12
- channel's handler. With a ScratchPad volume configured it also gives
13
- Claude Code's model ScratchPad, through a skill this package carries as a
14
- Claude Code plugin of its own (see "ScratchPad"). Without it the plugin is
15
- the plain plugin. The contract it fills is the plugin's
16
- `agents/EXTENSIONS.md`; the spec is
17
- [`docs/plugins.md`](https://github.com/synadia-ai/agent-fabric-sdk/blob/main/docs/plugins.md) §2, §3.1, §3.4 and §9.13.
3
+ Claude Code on the Synadia Agent Fabric, in one plugin: the NATS channel
4
+ plugin for Claude Code (`nats-channel`), the fabric's tracing and
5
+ ScratchPad's skill. The Claude Code session becomes an agent on the fabric:
6
+ other agents find it and prompt it, its model calls join the prompt's
7
+ thread, and its own prompts to other agents hang under the tool call that
8
+ sent them.
18
9
 
19
- ## What it does
10
+ ## Install
20
11
 
21
- The factory, the module's default export, calls `fabricTracing()` once and
22
- hands the plugin its three hooks, so the session is on the wire exactly what
23
- an SDK-built agent is:
24
-
25
- - **On the service, per prompt:** the envelope's `thread_id` and `root_id`
26
- are adopted, or a root minted when neither is there; a half pair or a
27
- malformed id is refused with `400` before the handler runs; the handler
28
- runs inside the trace scope.
29
- - **On the client Claude Code's agent tools prompt through, per prompt:**
30
- the child thread is minted and the pair put into the envelope, and the
31
- signed `edge` record is published on `TRACE.edges` before the prompt goes
32
- out, with the model's tool-call ID, which the plugin takes from its
33
- `PreToolUse` hook and passes to `execute()`. A delegation Claude Code's
34
- model makes hangs under its tool call in the console.
35
- - **On the heartbeat:** `records_published` and `records_dropped`, the
36
- `served` records counted with the edges.
37
-
38
- The harness piece is the join of Claude Code's own model calls. Claude Code
39
- sends `X-Claude-Code-Session-ID` on every model request, and the proxy files
40
- the calls as `harness: claude`, the session id their `harness_thread_id`.
41
- The plugin is an MCP server inside the session and cannot touch those
42
- headers, so the add-on publishes the binding:
43
-
44
- | Event | What the add-on does |
45
- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
46
- | `sessionStarted(session, source)` | remembers the session Claude Code is in now, as the plugin's `SessionStart` hook reported it (also once at start, for the session recorded before the server came up) |
47
- | `promptAccepted(request)` | keeps `activeTrace()`, the scope tracing bound for this request, by `request.id`; the session is `request.sessionId`, else the remembered one; when it is a Claude Code session id, `beginTurn(scope, "claude")` and `bind(session)` on the fabric package's served publisher, which publishes `start` stamped with the arrival |
48
- | `aroundToolCall(request, tool, run)` | binds the request's scope around `run`, so the edge of a `prompt_agent` Claude Code's model calls is filed under the served thread; the plugin already re-enters the handler's context through a snapshot before it calls this, so it is a no-op rebind |
49
- | `promptEnded(request, outcome, at, reason?)` | `error` and `timeout`: `settle(outcome, at, reason)` at once. `ok` with the reason `final_text`, which the plugin reports when the model never called `reply` and the plugin sent the turn's final text as the reply at the turn's `Stop`: at once too, the turn being over. Any other `ok`, which the plugin reports when `reply` completed the request: the turn is not over, since Claude Code makes at least one more model call after the reply to write its closing text, so the pair stays open until a `Stop` after `at`, or for at most `stopWaitMs` (120 s), then ends at `at`. The plugin's `reason` goes on `end` as the record's `reason` (version 2) |
50
- | `turnStopped(session, at)` | the `Stop` hook ran: every turn of that session answered before `at` ends with `ok` at `at`, the oldest first. So two prompts answered in one Claude Code turn share its end, and a prompt answered after this stop waits for the next |
51
-
52
- A prompt that arrives while no session is known gets no pair and one log
53
- line (`NO_SESSION_LINE`); its scope is still kept, so its model's
54
- delegations are still filed under the served thread. The session is bound
55
- at arrival: a `/clear` during a turn does not move the binding.
56
-
57
- The pair goes through the fabric package's `servedPublisher()`, made at
58
- `started` from the plugin's client: signed as the plugin's identity with the
59
- record id as the nonce and the `Nats-Msg-Id`, on the edge subject, in the
60
- order the records were due, so a pair's `end` never overtakes its `start`.
61
- `stopping` ends every open turn — an answered one at its reply's time, one
62
- still open now with `error` and the reason `shutdown` — and flushes. The plugin calls it before it
63
- fails the requests still open, and no `Stop` comes after it.
64
-
65
- **The `409` loop guard.** Claude Code cannot serve a prompt while its model
66
- waits on a call to another agent, so a prompt of a tree it is waiting in is
67
- refused at once with `409` instead of waiting behind the wait it is part of.
68
- It waits from the moment its model sends a prompt until every call the
69
- served request has open has settled: a blocking `prompt_agent` until it
70
- returns, a call started with `wait: false` for as long as it is open. Once
71
- none is open, a prompt of the tree is admitted, though the turn goes on.
72
- The guard is the fabric package's `loopGuard()`, two more interceptors
73
- after tracing's and its `aroundToolCall` inside the add-on's; the PI and
74
- OpenClaw add-ons return the same.
75
-
76
- **What it does not do.** No `toolExtensions`: a harness reads no ScratchPad
77
- reference in the MVP. No header on Claude Code's calls: the plugin cannot
78
- add one, which is why the pair exists. Nothing with the tool-call ID: the
79
- plugin's `PreToolUse` hook gives it to the tools, and tracing writes it.
80
-
81
- ## Naming it
82
-
83
- Installed where Claude Code runs, `npm install @synadia-ai/agent-fabric-claude-code`,
84
- and named to the plugin by absolute path (a package directory with `dist/`
85
- built); a package name resolves from the plugin's own directory upward,
86
- which for a plugin in Claude Code's plugin cache is rarely where the add-on
87
- is:
88
-
89
- ```sh
90
- SYNADIA_CLAUDE_CODE_EXTENSIONS=/opt/fabric/node_modules/@synadia-ai/agent-fabric-claude-code claude …
91
- ```
92
-
93
- Claude Code hands its environment to the plugin's MCP server, so the
94
- variable set where `claude` starts reaches the plugin. Or in the channel's
95
- `config.json` (`~/.claude/channels/nats/`, or `NATS_STATE_DIR`), where an
96
- entry may carry options:
12
+ Claude Code installs plugins from a marketplace. This package is the
13
+ plugin; a marketplace names it with an npm source:
97
14
 
98
15
  ```json
99
16
  {
100
- "extensions": [
101
- {
102
- "module": "/opt/fabric/node_modules/@synadia-ai/agent-fabric-claude-code",
103
- "options": { "edgeSubject": "TRACE.edges" }
104
- }
17
+ "name": "agent-fabric",
18
+ "owner": { "name": "Synadia" },
19
+ "plugins": [
20
+ { "name": "nats-channel", "source": { "source": "npm", "package": "@synadia-ai/agent-fabric-claude-code", "version": "^0.2.0" } }
105
21
  ]
106
22
  }
107
23
  ```
108
24
 
109
- `SYNADIA_CLAUDE_CODE_EXTENSIONS` wins over `SYNADIA_AGENT_EXTENSIONS`, which
110
- wins over the config field. The channel's `service registered` log line and
111
- its `request_info` tool list `agent-fabric` when it is loaded.
112
-
113
- ## The settings
114
-
115
- - **The edge subject:** the entry's `options.edgeSubject`, else the variable
116
- `SYNADIA_FABRIC_EDGE_SUBJECT`, else `TRACE.edges`. The `served` pair goes
117
- on the same subject. `null` in the options is propagate-only: lineage
118
- forwarded, no record of either kind published. A subject that can never
119
- be published to stops the factory; the plugin logs
120
- `extension "…" not loaded: …` and starts plain.
121
- - **`options.stopWaitMs`:** how long an answered turn waits for the `Stop`
122
- hook, 120000 by default. A value that is not a number of ms ≥ 0 stops the
123
- factory the same way.
124
-
125
- There is no `tracing` setting: the add-on's presence is the switch.
126
-
127
- ## Identity
128
-
129
- Records are signed, so the plugin needs `senderIdentity: "signed"` with a
130
- NATS context carrying Claude Code's own credentials. With identity off the
131
- add-on warns once at `started`, the served publisher warns once more when
132
- the first record is due, every record owed counts as dropped on the
133
- heartbeat, and Claude Code's calls stand as a harness thread of their own,
134
- unbound.
135
-
136
- ## Running Claude Code on the fabric
137
-
138
- Measured on Claude Code 2.1.281 (2026-09-24).
139
-
140
- 1. **Claude Code and bun.** `claude` on `PATH`, and `bun`: the plugin's
141
- server and hooks run on it.
142
- 2. **The add-on, installed and named.** Install it from npm, `dist/`
143
- included:
144
-
145
- ```sh
146
- npm install @synadia-ai/agent-fabric-claude-code
147
- ```
148
-
149
- and set `SYNADIA_CLAUDE_CODE_EXTENSIONS` to the installed package's
150
- absolute path, `<prefix>/node_modules/@synadia-ai/agent-fabric-claude-code`,
151
- in the environment `claude` starts in (see "Naming it"). Export `PWD`
152
- too: the channel plugin's `.mcp.json` hands it to its server. The same
153
- directory is the Claude Code plugin `agent-fabric`, which Claude Code
154
- loads with a second `--plugin-dir` for ScratchPad (see "ScratchPad").
155
- To develop the add-on, name this repository's `plugins/claude-code`
156
- instead, with `dist/` built (see "Development").
157
-
158
- 3. **The channel plugin, from the checkout until it is published.** This
159
- loads it for the session as `nats-channel@inline`, hooks included; its
160
- bundle is committed, nothing is built there:
161
-
162
- ```sh
163
- claude --plugin-dir <synadia-agents>/agents/claude-code \
164
- --dangerously-load-development-channels plugin:nats-channel@inline
165
- ```
166
-
167
- 4. **The identity.** The channel's `config.json` with a NATS context of
168
- Claude Code's own and `senderIdentity: "signed"` (see "Identity"), and
169
- `permissions.allow: ["mcp__plugin_nats-channel_nats"]` in Claude Code's
170
- `settings.json`, so a turn driven from NATS does not stop at an approval
171
- nobody sees.
172
- 5. **A virtual key naming Anthropic as its provider.** Claude Code is
173
- Anthropic's product, its documented gateway shape is a proxy in front of
174
- Anthropic, and an Anthropic key means Anthropic bills its owner; the
175
- fabric adds the trace, the identity and the usage record, and never
176
- routes Claude Code to another model provider. So the key is issued for
177
- Claude Code's identity with the proxy's built-in `anthropic` provider
178
- (on a test rig where that name is taken, a provider of another name at
179
- `https://api.anthropic.com` on the Anthropic shape), the tenant's
180
- Anthropic API key as the provider key in the record, and the harness
181
- `claude`, without which the proxy files each call as a thread of its own.
182
- 6. **Two keys and the base URL.** Both are set, and Claude Code sends both:
183
- - `ANTHROPIC_API_KEY`: an Anthropic API key of the user's own, sent as
184
- `x-api-key`. Claude Code turns its channel on only when a feature flag
185
- says so, and it fetches its flags from Anthropic directly, never
186
- through the base URL, and only with an Anthropic credential; with the
187
- virtual key alone the channel is ignored. It may be
188
- the same key as the provider key of step 5, or another of the
189
- tenant's.
190
- - `ANTHROPIC_AUTH_TOKEN`: the virtual key, sent as
191
- `Authorization: Bearer` next to it. The proxy reads the virtual key
192
- from `Authorization` first and deletes both headers before it sets the
193
- provider's own, so the Anthropic key reaches no provider.
194
- - `ANTHROPIC_BASE_URL`: the proxy's bare origin, no `/api`; Claude Code
195
- appends `/v1/messages` and the proxy forwards it to Anthropic as it is.
196
-
197
- Claude Code warns at start that both keys are set and "auth may not work
198
- as expected". That is expected: sending both is the point.
199
-
200
- 7. **No model variables.** Claude Code runs its own default models, the
201
- main loop and the side calls, every call through the proxy, recorded
202
- with Anthropic's usage, cache reads and writes included.
203
- 8. **A home outside any git repository.** `HOME`, `CLAUDE_CONFIG_DIR` and
204
- the channel's `NATS_STATE_DIR` in a directory of the instance's own,
205
- never `~/.claude`, and not inside a git working tree: from inside one,
206
- Claude Code sends that repository's `CLAUDE.md` files and its git status
207
- to the model with every call. Outside one it offers the
208
- model the skills in the `.claude/skills` of every directory above its
209
- working directory, the user's own included; `--disable-slash-commands`
210
- removes them, and with them the Skill tool, so not with
211
- ScratchPad.
212
- 9. **A terminal, or a pseudo-terminal.** Interactive only: `claude -p`
213
- registers on the bus and turns no prompt into a turn.
214
- Answer the development-channels warning once and the channel is on;
215
- from a script, `tmux` with one Enter does the same.
25
+ Save it as `.claude-plugin/marketplace.json` in a folder of your choosing,
26
+ then:
216
27
 
217
- ## What the console shows
218
-
219
- The served thread with the session's model calls joined through the pair,
220
- the closing call inside the window, the thread's outcome and its duration
221
- from `start` to `end`, `harness: claude` at depth `full` with both halves;
222
- a subagent's calls as children of the session's thread. A delegation
223
- Claude Code's model makes: it discovers and prompts another agent, whose
224
- thread sits under its `prompt_agent` tool call with Claude Code its
225
- verified caller. Everything the session
226
- does between the two records is filed under the prompt: local typing
227
- during a NATS-driven turn, and a second NATS prompt answered in the same
228
- turn, share the window.
229
-
230
- One call can fall outside the pair. At the end of each turn Claude Code
231
- asks its model what the user might type next; when that call's model
232
- reaches for a tool, Claude Code calls it once more after the turn's `Stop`,
233
- and the add-on's `end`. The console shows that call as a root of its own
234
- on the session id, with no caller. The pair is the turn, and
235
- the reader infers nothing beyond it: a limit, not a bug.
236
-
237
- ## Stated honestly
238
-
239
- - **Without the plugin's hooks** (`disableAllHooks`, or `bun` not on the
240
- hooks' `PATH`) there is no `SessionStart`, so the session comes from
241
- `CLAUDE_CODE_SESSION_ID` in the server's environment if at all, and no
242
- `Stop`, so `end` carries the reply's time after the bound and the closing
243
- call falls outside the window.
244
- - **The bound delays `end`**: with no `Stop`, the pair stays open for
245
- `stopWaitMs` after the reply.
246
- - **A `Stop` names the turn it ended by time only.** Two prompts answered in
247
- one turn share its end; a prompt answered after a stop that the server's
248
- poll saw late (250 ms) could take that stop's time. Claude Code gives
249
- nothing finer.
250
- - **The two keys are what Claude Code's warning calls unsupported.** Both
251
- sent together, the virtual key as `Bearer` and the Anthropic key as
252
- `x-api-key`, was measured on 2.1.281; Claude Code does not
253
- document it. Re-check it on a release that touches channels or gateways.
254
- - **An Anthropic credential on the machine.** The channel is behind a flag
255
- only an Anthropic credential fetches, so every Claude Code on the fabric
256
- carries an Anthropic API key of its own, and could call Anthropic with it
257
- directly. Its model calls are traced because it is pointed at the proxy,
258
- by configuration, not by anything the add-on enforces.
259
- - **An interactive session.** No headless mode serves a prompt; a script
260
- drives a terminal.
261
- - **The context Claude Code sends.** Whatever its working directory
262
- offers goes to the model with every call: a repository's `CLAUDE.md` and
263
- git status from inside one, the skills of the directories above it from
264
- outside one. The home's place decides it.
265
- - **ScratchPad's grant is per turn.** Claude Code applies a skill's
266
- `allowed-tools` in the turn that loaded the skill, and clears it with the
267
- next message. A later prompt whose turn runs `scripts/sp` without loading
268
- the skill again sends the caller a question for each call. The model
269
- loaded it in every measured turn; Claude Code gives no rule that lasts
270
- the session short of the operator's own `allow`.
271
- - **ScratchPad without the hook.** With hooks off, the Bash tool gets no
272
- `SP_ENV_FILE`, and `scripts/sp` refuses to run unconfigured, unless the
273
- operator sets the variables in the environment `claude` starts in, which
274
- gives the connection but no served prompt's trace or caller.
275
-
276
- ## ScratchPad
277
-
278
- The package is also a Claude Code plugin, `agent-fabric`
279
- (`.claude-plugin/plugin.json`), with two pieces: ScratchPad's skill, the
280
- one every add-on ships, in `skills/scratchpad/` (`SKILL.md`, `scripts/sp`,
281
- and `bin/` with the sp command line for darwin and linux on arm64 and
282
- amd64, installed from `../scratchpad-skill` by `npm run build:sp`), and a
283
- `SessionStart` hook. Claude Code loads the skill as
284
- `agent-fabric:scratchpad`; the model runs `scripts/sp` through its Bash
285
- tool. Nothing restricts Bash or any other tool.
286
-
287
- **The operator's steps.** Besides the steps above:
288
-
289
- 1. The package loaded as a plugin too, a second `--plugin-dir`:
290
-
291
- ```sh
292
- claude --plugin-dir <synadia-agents>/agents/claude-code --plugin-dir <add-on> \
293
- --dangerously-load-development-channels plugin:nats-channel@inline
294
- ```
295
-
296
- 2. Permission questions to the caller, and the skill allowed, in the
297
- channel's `config.json` and Claude Code's `settings.json`:
298
-
299
- ```json
300
- { "permissions": { "mode": "query" } }
301
- ```
302
-
303
- ```json
304
- {
305
- "permissions": {
306
- "defaultMode": "default",
307
- "allow": ["mcp__plugin_nats-channel_nats", "Skill(agent-fabric:scratchpad)"]
308
- }
309
- }
310
- ```
311
-
312
- `defaultMode: "default"` matters: Claude Code 2.1.283 can start in auto
313
- mode, where its own classifier approves or blocks a tool call, so no
314
- permission question ever reaches the caller.
315
-
316
- 3. A volume reserved for Claude Code's identity by the ScratchPad operator,
317
- in the extension's entry in the channel's `config.json`, or as
318
- `SCRATCHPAD_VOLUME` in the environment `claude` starts in:
319
-
320
- ```json
321
- "extensions": [{ "module": "<add-on>", "options": { "scratchpad": { "volume": "CcVol" } } }]
322
- ```
323
-
324
- Keep `Bash` and `Skill` out of the settings' `deny`, and do not start
325
- Claude Code with `--disable-slash-commands`, which removes the Skill tool.
326
-
327
- **The first prompt of a session.** Claude Code can serve a session's first
328
- prompt before it lists the session's skills to the model. An instruction
329
- that says "use ScratchPad only when the skill is listed" therefore fails
330
- once: the model answers that it cannot read a reference. Tell the model to
331
- call the skill by name (`agent-fabric:scratchpad`) whenever it holds a
332
- reference, and to ask for the text only if the skill does not exist.
333
-
334
- **Permission questions.** The skill's copy here has one line ScratchPad's
335
- own skill has and the shared one does not:
336
- `allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/sp *)`. It restricts
337
- nothing; it lets the model run `scripts/sp` by its absolute path without a
338
- question, in the turn that loaded the skill. Claude Code asks before it
339
- loads a skill that carries such a line, and `Skill(agent-fabric:scratchpad)`
340
- in the settings' `allow` answers that once for all. Every other command
341
- follows the operator's rules: with `permissions.mode: "query"` the plugin
342
- sends each question to the caller of the served prompt, who answers yes
343
- or no. The skill tells the model to run `scripts/sp` as a command of its
344
- own, since a command that wraps it (a shell variable for its path, a pipe
345
- out of it, `;` or `&&`) is a different command, and asks.
346
-
347
- **Where the settings come from.** The factory resolves each from the
348
- entry's `options.scratchpad` (`volume`, `dir`, `url`, `creds`), else the
349
- variable (`SCRATCHPAD_VOLUME`, `SCRATCHPAD_DIR`, `NATS_URL`, `NATS_CREDS`),
350
- else, for the URL and the credentials file, the plugin's NATS context. The
351
- workspace defaults to `scratchpad/` in the channel's state directory
352
- (`$NATS_STATE_DIR/scratchpad`), created if missing; keep it across
353
- restarts, since it holds the volume's journal. Without a volume ScratchPad
354
- is off.
355
-
356
- **How `scripts/sp` gets them.** The add-on runs in the plugin's MCP server,
357
- a sibling of the Bash tool's processes, so it cannot set their
358
- environment. It writes the variables to
359
- `$NATS_STATE_DIR/agent-fabric/<CLAUDE_PID>.env` (mode 600, rewritten whole
360
- on every change, removed when the server stops), and the package's
361
- `SessionStart` hook names that file to the Bash tool as `SP_ENV_FILE`,
362
- through `CLAUDE_ENV_FILE`. `scripts/sp` reads it on every call:
363
-
364
- | Variable | Written | `scripts/sp` adds |
365
- | ------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------- |
366
- | `SP_NATS_URL`, `SP_NATS_CREDS` | at start | `--url`, `--creds`, every call |
367
- | `SCRATCHPAD_DIR` | at start | `--dir`, every call |
368
- | `SCRATCHPAD_VOLUME` | at start | `--volume`, on `init` only |
369
- | `SP_CREATOR` | at start: Claude Code's prompt address | `--creator`, on `publish-file` |
370
- | `SP_THREAD_ID`, `SP_ROOT_ID` | per served prompt, from its arrival to its end | `--thread-id`, `--root-id`, every call |
371
- | `SP_CALLER_ACCOUNT`, `SP_CALLER_USER` | the same, for a verified caller | `--grantee-account`, `--grantee-user`, on `grant` and after `publish-file` |
372
- | `SP_GRANTS_FILE` | at start: `<workspace>.grants` | nothing: it records there each grant sp confirmed, for grant requests |
373
-
374
- A `publish-file` during a served prompt with a verified caller also grants
375
- that caller read on what it published; a `grant` goes to that caller and no
376
- other. When a second prompt arrives in the same turn, the file names it
377
- until it ends, then the first again.
378
-
379
- **References in and out.** With ScratchPad on, the add-on registers Claude
380
- Code as reading references (`scratchpad_refs_ok: "true"` through the
381
- extension's `metadata`), and the references its model puts into
382
- `prompt_agent` go through the fabric package's ScratchPad extension, as an
383
- SDK-built agent's do: granted to a receiver that reads references, removed
384
- with a note for any other.
385
-
386
- **Grant requests.** A caller Claude Code handed a reference to may hand it on
387
- through its own agent tools; its ScratchPad extension then asks the creator,
388
- Claude Code, to grant the receiver read (the grant request, `grant-request.ts` in
389
- the fabric package). The add-on answers on Claude Code's service, by the rule every
390
- SDK-built agent answers by: the reference must point into Claude Code's volume, the
391
- requester must hold a live read grant Claude Code made it first-hand, the grantee
392
- must be another agent, and the grant is one path, read only, ten minutes at
393
- most, recorded as made on request, which entitles no one to ask again. The
394
- grants Claude Code's model makes go through `scripts/sp`, which the add-on does not
395
- see, so `scripts/sp` records each one sp confirms in `SP_GRANTS_FILE`
396
- (`<workspace>.grants`), and the add-on counts a live one as first-hand: it
397
- went to the agent whose prompt Claude Code was serving.
398
-
399
- **Measured** on Claude Code 2.1.283 on 2026-09-27 (`docs/plugins.md`
400
- §9.13): given a reference, the model read it through the skill, published
401
- its own file, which the caller read, and handed that file to another agent
402
- through `prompt_agent`, which read it; no permission question reached the
403
- caller.
28
+ ```sh
29
+ claude plugin marketplace add <that folder>
30
+ claude plugin install nats-channel@agent-fabric
31
+ ```
404
32
 
405
- ## Public API
33
+ The plugin's server and hooks run on [Bun](https://bun.sh), which must be on
34
+ `PATH`.
406
35
 
407
- | | |
408
- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
409
- | `default`, `fabricExtension(ctx, env?)` | the factory of the contract: resolves ScratchPad, then builds the extension; `env` is where the variables are read, `process.env` by default |
410
- | `createFabricExtension(ctx, env?, scratchPad?)` | the same, synchronous, from settings already resolved |
411
- | `SCRATCHPAD_OPTION`, `SP_VARS`, `SP_ENV_FILE_VAR`, `ScratchPadHandOn`, `SpEnvFile`, `spEnvFilePath(stateDir, env)`, `spBinary(env)` | ScratchPad's option key, the variables `scripts/sp` reads, the file they go to and the variable that names it, the tool extension, the packaged sp's path |
412
- | `EXTENSION_NAME`, `IDENTITY_OFF_WARNING`, `NO_SESSION_LINE`, `SERVED_HARNESS`, `DEFAULT_STOP_WAIT_MS` | the constants |
413
- | `isClaudeSessionId(v)` | the session-id rule the pair is published under, the pre-split plugin's rule, header-safe |
36
+ ## Connect
414
37
 
415
- The contract's types, the setting's precedence (`resolveEdgeSubject`) and
416
- the loop guard (`loopGuard`) are `@synadia-ai/agent-fabric`'s, shared by
417
- every add-on under `plugins/`.
38
+ The fabric's console creates the agent and hands out its NATS
39
+ credentials. Claude Code and the channel read the rest from the
40
+ environment:
418
41
 
419
- ## Development
42
+ | Variable | What |
43
+ | --- | --- |
44
+ | `NATS_URL` | the fabric's NATS server |
45
+ | `NATS_CREDS` | the agent's credentials file: its identity |
46
+ | `NATS_SENDER_IDENTITY` | `signed`: the agent signs what it sends, which the fabric verifies |
47
+ | `SYNADIA_CLAUDE_CODE_NAME` | the agent's name, the last token of its address `agents.prompt.cc.<owner>.<name>` |
48
+ | `SYNADIA_OWNER` | the owner, the 4th token |
49
+ | `ANTHROPIC_BASE_URL` | the fabric's model proxy |
50
+ | `ANTHROPIC_AUTH_TOKEN` | the agent's model key: Claude Code's model calls go through the proxy with it |
51
+ | `ANTHROPIC_API_KEY` | your own Anthropic API key: Claude Code turns channels on only with it |
52
+ | `SCRATCHPAD_VOLUME`, `SCRATCHPAD_DIR` | optional: the agent's ScratchPad volume and its private workspace |
420
53
 
421
- Built like the fabric package (tsup, ESM and CJS, vitest, eslint,
422
- prettier). It links `@synadia-ai/agent-fabric` at `file:../../typescript`,
423
- built first, and the two protocol SDK packages by the same `file:` links
424
- the fabric package uses; its `package-lock.json` is committed, and a clean clone
425
- installs with `npm ci`. Nothing of Claude Code is a dependency. The tests: unit, a
426
- fake of the plugin driving the events in the MCP server's order
427
- (`test/support/fake-plugin.ts`: the handler open until the reply, a tool
428
- call re-entered through the handler's snapshot); integration, the factory's
429
- hooks on a real `AgentService` and `Agents` over a local `nats-server` in
430
- operator mode, the pair and the edges on the wire validated against
431
- `test-fixtures/schemas/served.json` and `edge.json`, signed, one id three
432
- ways, `end` at the stop; ScratchPad's file, the hook and the plugin's
433
- manifest (unit), and the references handed on (integration).
54
+ `NATS_CONTEXT`, a NATS CLI context, wins over `NATS_URL` and `NATS_CREDS`.
434
55
 
435
- `npm run build:sp` installs the skill from `../scratchpad-skill` into
436
- `skills/scratchpad`, with the `allowed-tools` line. It downloads the four
437
- published binaries pinned in `../scratchpad-skill/release.json` and verifies
438
- their archive and executable hashes with Node 20+. `npm pack` runs it too.
439
- `skills/` is gitignored. No Go compiler or service checkout is required.
440
- The [binary guide](https://github.com/synadia-ai/agent-fabric-sdk/blob/main/docs/scratchpad-binaries.md) covers authenticated
441
- downloads, offline archives, cache checks, and the included licenses.
56
+ Start it with the channel, which is not on Claude Code's list of channels,
57
+ so it loads as a development channel, with the warning Claude Code shows
58
+ for it:
442
59
 
443
60
  ```sh
444
- (cd ../../typescript && npm install && npm run build)
445
- npm install
446
- npm run build:sp
447
- npm run typecheck && npm run lint && npm run format:check
448
- npm test
449
- npm run build
61
+ claude --dangerously-load-development-channels plugin:nats-channel@agent-fabric
450
62
  ```
451
63
 
452
- ## License
64
+ So that a prompt from the fabric is answered without a question in the
65
+ terminal, allow the channel's tools in Claude Code's settings
66
+ (`"permissions": { "allow": ["mcp__plugin_nats-channel_nats", "Skill(nats-channel:scratchpad)"] }`),
67
+ and send the questions Claude Code asks to the prompt's caller with the
68
+ channel's configuration, `{"permissions": {"mode": "query"}}` in
69
+ `~/.claude/channels/nats/config.json` (or `/nats-channel:configure permissions query`).
453
70
 
454
- Apache-2.0, like the fabric package. See [LICENSE](https://github.com/synadia-ai/agent-fabric-sdk/blob/main/plugins/claude-code/LICENSE).
71
+ ## The fabric add-on
455
72
 
456
- Short artifact references
457
- -------------------------
73
+ The plugin's `.mcp.json` names the bundled add-on (`runtime/fabric.js`) in
74
+ `SYNADIA_CLAUDE_CODE_EXTENSIONS`. The package's main export is the add-on.
458
75
 
459
- The shared wrapper and grant records accept `sp-r1:` short tokens and existing `sp-ref:` tokens.
460
- Publication uses the short form when the service and the selected client support it.
461
- Short-token reads and resolution require a compatible `sp` binary selected through
462
- `SCRATCHPAD_BIN`. The bundled CLI release is pinned independently; this change
463
- does not advance that pin.
464
- Copy the returned token unchanged into prompts and replies.
465
- The token survives client restarts; each content read still requires current authorization.
466
- See the [native client contract](https://github.com/synadia-ai/agent-fabric-sdk/blob/main/docs/scratchpad-native.md) for recovery and capability fallback.
76
+ `BUILD` names the channel's version and the commits it was built from.
77
+ Apache-2.0.