@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.
- package/.claude-plugin/plugin.json +4 -4
- package/.mcp.json +14 -0
- package/BUILD +4 -0
- package/LICENSE +18 -17
- package/README.md +52 -441
- package/hooks/hooks.json +31 -0
- package/hooks/session-event.js +11 -0
- package/package.json +23 -72
- package/runtime/chunk-prncr0vg.js +38 -0
- package/runtime/fabric.js +7 -0
- package/runtime/server.js +63 -0
- package/skills/configure/SKILL.md +190 -0
- package/skills/configure/list-contexts.sh +22 -0
- package/CHANGELOG.md +0 -18
- package/dist/index.cjs +0 -277
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -63
- package/dist/index.d.ts +0 -63
- package/dist/index.js +0 -248
- package/dist/index.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
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/
|
|
8
|
+
"repository": "https://github.com/synadia-ai/synadia-agents",
|
|
9
9
|
"license": "Apache-2.0"
|
|
10
10
|
}
|
package/.mcp.json
ADDED
package/BUILD
ADDED
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
|
|
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
|
|
166
|
-
Derivative Works thereof, You may choose to offer,
|
|
167
|
-
for, acceptance of support, warranty, indemnity,
|
|
168
|
-
obligations and/or rights consistent with this
|
|
169
|
-
accepting such obligations, You may act only
|
|
170
|
-
on Your sole responsibility, not on behalf
|
|
171
|
-
and only if You agree to indemnify,
|
|
172
|
-
|
|
173
|
-
|
|
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!)
|
|
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
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
4
|
-
(`nats-channel
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
##
|
|
10
|
+
## Install
|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
|
416
|
-
the
|
|
417
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
+
## The fabric add-on
|
|
455
72
|
|
|
456
|
-
|
|
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
|
-
|
|
460
|
-
|
|
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.
|