@synadia-ai/agent-fabric-pi 0.1.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/CHANGELOG.md +17 -0
- package/LICENSE +201 -0
- package/README.md +281 -0
- package/dist/index.cjs +154 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -0
- package/skill/scratchpad/LICENSE +201 -0
- package/skill/scratchpad/SKILL.md +208 -0
- package/skill/scratchpad/THIRD_PARTY_NOTICES +1067 -0
- package/skill/scratchpad/bin/BUILD.json +367 -0
- package/skill/scratchpad/bin/COMMIT +1 -0
- package/skill/scratchpad/bin/sp-darwin-amd64 +0 -0
- package/skill/scratchpad/bin/sp-darwin-arm64 +0 -0
- package/skill/scratchpad/bin/sp-linux-amd64 +0 -0
- package/skill/scratchpad/bin/sp-linux-arm64 +0 -0
- package/skill/scratchpad/scripts/sp +231 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@synadia-ai/agent-fabric-pi` are recorded here. The
|
|
4
|
+
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
|
|
5
|
+
package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — unreleased
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- The extension module for the PI channel plugin, named in `SYNADIA_PI_EXTENSIONS`: `fabricTracing()` on the plugin's service and agent-tools client.
|
|
12
|
+
- The `X-Synadia-Thread-ID` and `X-Synadia-Root-ID` headers on PI's own model calls, with each served request's scope bound at PI's events.
|
|
13
|
+
- The `409` loop guard in front of PI's handler.
|
|
14
|
+
- The edge subject setting: `options.edgeSubject`, `SYNADIA_FABRIC_EDGE_SUBJECT`, or `TRACE.edges`.
|
|
15
|
+
- ScratchPad: the shared skill with the sp command line for darwin and linux, its variables for `scripts/sp`, references in and out, and grant requests.
|
|
16
|
+
|
|
17
|
+
[0.1.0]: https://github.com/synadia-ai/agent-fabric-sdk/tree/main/plugins/pi
|
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
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.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
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.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Synadia Communications
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# @synadia-ai/agent-fabric-pi
|
|
2
|
+
|
|
3
|
+
The Synadia Agent Fabric add-on for the PI channel plugin
|
|
4
|
+
(`@synadia-ai/nats-pi-channel`, `agents/pi` 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_PI_EXTENSIONS`, it puts the fabric package's tracing on the
|
|
8
|
+
plugin's service and on the client its agent tools prompt through, the
|
|
9
|
+
fabric's two headers on PI's own model calls, and the `409` loop guard in
|
|
10
|
+
front of PI's handler. It also ships ScratchPad's skill with the sp command
|
|
11
|
+
line, so PI's model reads, publishes and shares ScratchPad content through
|
|
12
|
+
its bash tool. Without it the plugin is the plain plugin. The contract it
|
|
13
|
+
fills is the plugin's `agents/EXTENSIONS.md`; the spec is
|
|
14
|
+
[`docs/plugins.md`](https://github.com/synadia-ai/agent-fabric-sdk/blob/main/docs/plugins.md) §2, §3.1, §3.2 and §9.
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
The factory, the module's default export, calls `fabricTracing()` once and
|
|
19
|
+
hands the plugin its three hooks, so PI is on the wire exactly what an
|
|
20
|
+
SDK-built agent is:
|
|
21
|
+
|
|
22
|
+
- **On the service, per prompt:** the envelope's `thread_id` and `root_id`
|
|
23
|
+
are adopted, or a root minted when neither is there; a half pair or a
|
|
24
|
+
malformed id is refused with `400` before the handler runs; the handler
|
|
25
|
+
runs inside the trace scope.
|
|
26
|
+
- **On the client PI's agent tools prompt through, per prompt:** the child
|
|
27
|
+
thread is minted and the pair put into the envelope, and the signed `edge`
|
|
28
|
+
record is published on `TRACE.edges` before the prompt goes out, with the
|
|
29
|
+
model's tool-call ID, which the plugin passes to `execute()`. A delegation
|
|
30
|
+
PI's model makes hangs under its tool call in the console.
|
|
31
|
+
- **On the heartbeat:** `records_published` and `records_dropped`.
|
|
32
|
+
|
|
33
|
+
The harness piece is the scope. PI's loop does not run in the handler's
|
|
34
|
+
async context, so the add-on keeps each served request's scope by the
|
|
35
|
+
plugin's request id and binds it where the plugin says the request is being
|
|
36
|
+
worked on:
|
|
37
|
+
|
|
38
|
+
| Event | What the add-on does |
|
|
39
|
+
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
40
|
+
| `promptAccepted(request)` | keeps `activeTrace()`, the scope tracing bound for this request, by `request.id` |
|
|
41
|
+
| `aroundInject(request, run)` | runs `run` inside `bindActiveTrace(scope, …)`, which replaces the ambient context: the plugin injects the next queued prompt from the previous turn's settle hook, whose context is the previous request's |
|
|
42
|
+
| `providerHeaders(request)` | returns `X-Synadia-Thread-ID` and `X-Synadia-Root-ID` for the request, so the proxy files PI's own model calls on the served thread; each call counts against the scope, so an edge PI's model spawns later says how many calls came before it, retries included |
|
|
43
|
+
| `aroundToolCall(request, tool, run)` | binds the request's scope around `run`, so the edge of a `prompt_agent` PI's model calls is filed under the served thread even if PI's tool runner did not inherit the injected context |
|
|
44
|
+
| `promptEnded(request)` | forgets the request |
|
|
45
|
+
|
|
46
|
+
A request the add-on never saw (no scope kept) is left alone: no headers,
|
|
47
|
+
`run` called as it is.
|
|
48
|
+
|
|
49
|
+
**The `409` loop guard.** PI serves one turn at a time and cannot serve a
|
|
50
|
+
prompt while its model waits on a call to another agent, so a prompt of a
|
|
51
|
+
tree PI is waiting in is refused at once with `409` instead of queueing
|
|
52
|
+
behind the wait it is part of. PI waits from the moment its model sends a
|
|
53
|
+
prompt until every call the served request has open has settled: a
|
|
54
|
+
blocking `prompt_agent` until it returns, a call started with `wait: false`
|
|
55
|
+
for as long as it is open. With no call open, a prompt of the tree is
|
|
56
|
+
admitted. The guard is two more interceptors and a wrapper: its request
|
|
57
|
+
interceptor, after tracing's, keeps the served request by the scope tracing
|
|
58
|
+
bound for the handler's duration and refuses an arriving prompt whose root
|
|
59
|
+
a served request is waiting in, before `next()`, with
|
|
60
|
+
`RequestRejectedError(409, …)`; its prompt interceptor, on the plugin's
|
|
61
|
+
client, runs in the tool call's context and marks that request as waiting;
|
|
62
|
+
its `aroundToolCall`, inside the add-on's, lifts the mark when a tool
|
|
63
|
+
result counts no open call and no other agent-tool call of the request
|
|
64
|
+
runs. A call that fails or expires while the model runs no agent tool keeps
|
|
65
|
+
the mark until the next agent-tool result or the served prompt's end. The
|
|
66
|
+
root is read from the scope tracing bound, never from the envelope. Stated
|
|
67
|
+
honestly: this also refuses a legitimate second prompt of the same tree
|
|
68
|
+
that arrives while PI waits in it, such as a sibling delegation issued with
|
|
69
|
+
`wait: false` by the same coordinator; PI would have queued it anyway, and
|
|
70
|
+
the guard turns the queue into a refusal the caller sees at once.
|
|
71
|
+
|
|
72
|
+
**What it does not do.** No `served` record: PI's calls carry the headers,
|
|
73
|
+
so they need none, and PI writes nothing to `TRACE.edges` but the edges of
|
|
74
|
+
its own delegations. No `stopping`: an edge is published before the prompt
|
|
75
|
+
it describes and counted as it goes, so there is nothing to flush.
|
|
76
|
+
|
|
77
|
+
## Naming it
|
|
78
|
+
|
|
79
|
+
Installed where PI runs, `npm install @synadia-ai/agent-fabric-pi`, and
|
|
80
|
+
named to the plugin by package name when that directory is the plugin's or
|
|
81
|
+
an ancestor of it, by absolute path otherwise (a package directory with
|
|
82
|
+
`dist/` built):
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
SYNADIA_PI_EXTENSIONS=/opt/fabric/node_modules/@synadia-ai/agent-fabric-pi pi …
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
or in `nats-channel.json`, where an entry may carry options:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"extensions": [
|
|
93
|
+
{ "module": "@synadia-ai/agent-fabric-pi", "options": { "edgeSubject": "TRACE.edges" } }
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`SYNADIA_PI_EXTENSIONS` wins over `SYNADIA_AGENT_EXTENSIONS`, which wins
|
|
99
|
+
over the config field. The plugin's connect line and `/nats-status` list
|
|
100
|
+
`extensions=agent-fabric` when the module is loaded.
|
|
101
|
+
|
|
102
|
+
## The one setting
|
|
103
|
+
|
|
104
|
+
The edge subject: the entry's `options.edgeSubject`, else the variable
|
|
105
|
+
`SYNADIA_FABRIC_EDGE_SUBJECT`, else `TRACE.edges`. `null` in the options is
|
|
106
|
+
propagate-only: lineage forwarded, no record published, no counts on the
|
|
107
|
+
heartbeat. A subject that can never be published to (empty, an empty token,
|
|
108
|
+
whitespace, a wildcard, not a string) stops the factory; the plugin logs
|
|
109
|
+
`extension "…" not loaded: …` and starts plain, so the mistake is visible at
|
|
110
|
+
start. There is no `tracing` setting: the add-on's presence is the switch.
|
|
111
|
+
|
|
112
|
+
## ScratchPad
|
|
113
|
+
|
|
114
|
+
The package carries ScratchPad's skill, adapted for an agent on the fabric,
|
|
115
|
+
in `skill/scratchpad/`: `SKILL.md`, `scripts/sp`, and `bin/` with the sp
|
|
116
|
+
command line for darwin and linux on arm64 and amd64. It is the one skill
|
|
117
|
+
every add-on ships, kept in `../scratchpad-skill` and installed here by
|
|
118
|
+
`npm run build:sp`. The model runs
|
|
119
|
+
`scripts/sp` through PI's bash tool; the harness's tools stay as the
|
|
120
|
+
operator configured them, and PI asks no permission for any command.
|
|
121
|
+
|
|
122
|
+
**The operator's step.** Start PI with the skill and the bash tool on:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
pi --mode rpc … \
|
|
126
|
+
-e <plugin>/extensions/nats-channel.ts \
|
|
127
|
+
--skill <add-on>/skill/scratchpad \
|
|
128
|
+
--tools bash,discover_agents,prompt_agent,answer_agent
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`--skill` loads the directory even under `--no-skills`. PI lists the skill
|
|
132
|
+
to the model only when its `bash` or `read` tool is on, so
|
|
133
|
+
`--no-builtin-tools` alone hides it; `--tools` naming `bash` and the
|
|
134
|
+
plugin's tools keeps them. Then name a volume for PI's identity, reserved
|
|
135
|
+
by the ScratchPad operator: the extension entry's `options.scratchpad`, or
|
|
136
|
+
the variable.
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{ "module": "@synadia-ai/agent-fabric-pi", "options": { "scratchpad": { "volume": "PiVol" } } }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Where the settings come from.** Once the service is up, the add-on
|
|
143
|
+
resolves each from the entry's `options.scratchpad` (`volume`, `dir`, `url`,
|
|
144
|
+
`creds`), else the variable (`SCRATCHPAD_VOLUME`, `SCRATCHPAD_DIR`,
|
|
145
|
+
`NATS_URL`, `NATS_CREDS`), else, for the URL and the credentials file, the
|
|
146
|
+
plugin's own NATS context (`NATS_CONTEXT`, or `context` in
|
|
147
|
+
`nats-channel.json`), which sp cannot read itself. The workspace defaults to
|
|
148
|
+
`scratchpad/` in the plugin's state directory (`~/.pi/agent/scratchpad`),
|
|
149
|
+
created if missing; keep it across restarts. Without a volume ScratchPad is
|
|
150
|
+
off; without a URL and a credentials file it is off with a warning.
|
|
151
|
+
|
|
152
|
+
**What `scripts/sp` gets.** The add-on sets these in PI's process, and PI's
|
|
153
|
+
bash tool passes them to every command (measured on PI 0.85.1):
|
|
154
|
+
|
|
155
|
+
| Variable | Set | `scripts/sp` adds |
|
|
156
|
+
| ------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
157
|
+
| `SP_NATS_URL`, `SP_NATS_CREDS` | at start | `--url`, `--creds`, every call |
|
|
158
|
+
| `SCRATCHPAD_DIR` | at start | `--dir`, every call |
|
|
159
|
+
| `SCRATCHPAD_VOLUME` | at start | `--volume`, on `init` only: a read refuses a volume other than the reference's |
|
|
160
|
+
| `SP_CREATOR` | at start: PI's prompt address | `--creator`, on `publish-file` |
|
|
161
|
+
| `SP_THREAD_ID`, `SP_ROOT_ID` | per served prompt, from its hand-over to PI's loop to its end | `--thread-id`, `--root-id`, every call |
|
|
162
|
+
| `SP_CALLER_ACCOUNT`, `SP_CALLER_USER` | the same, for a verified caller | `--grantee-account`, `--grantee-user`, on `grant` and after `publish-file` |
|
|
163
|
+
| `SP_GRANTS_FILE` | at start: `<workspace>.grants` | nothing: it records there each grant sp confirmed, for grant requests |
|
|
164
|
+
|
|
165
|
+
The connection has names of its own, not `NATS_URL` and `NATS_CREDS`, which
|
|
166
|
+
a plugin may read as its own connection. The model passes none of these
|
|
167
|
+
flags; `scripts/sp` refuses them, and refuses a `grant` when no served
|
|
168
|
+
prompt has a verified caller, so PI grants read on what it publishes to the
|
|
169
|
+
agent that prompted it and to no other. A `publish-file` during a served
|
|
170
|
+
prompt with a verified caller also makes that grant, as an SDK-built agent
|
|
171
|
+
grants the caller its reply: the result carries `granted_to_caller`, or
|
|
172
|
+
`caller_grant_error`. Models skip a separate grant step often enough that
|
|
173
|
+
the publication makes it (`docs/plugins.md` §9.12).
|
|
174
|
+
On a platform sp is not built for, `scripts/sp` says so, in sp's own error
|
|
175
|
+
shape with exit 2. `SCRATCHPAD_BIN` names another sp binary.
|
|
176
|
+
|
|
177
|
+
**References in and out.** With ScratchPad on, the add-on registers PI as
|
|
178
|
+
reading references (`scratchpad_refs_ok: "true"` through the extension's
|
|
179
|
+
`metadata`), so discovery shows `reads_scratchpad_references: true` and a
|
|
180
|
+
caller's `prompt_agent` hands them to PI. And the references PI's model puts
|
|
181
|
+
into its own `prompt_agent` go through the fabric package's ScratchPad
|
|
182
|
+
extension, as an SDK-built agent's do: a receiver that reads references is
|
|
183
|
+
granted read, first-hand, right before the prompt goes out; any other gets
|
|
184
|
+
the text without them and the call's result says how many were removed; a
|
|
185
|
+
reference PI was neither given nor made is refused. A reference into PI's
|
|
186
|
+
own volume counts as made by PI, since its model publishes with
|
|
187
|
+
`scripts/sp`, which the extension does not see. The extension's own grants
|
|
188
|
+
run the packaged sp directly, with the settings above.
|
|
189
|
+
|
|
190
|
+
**Grant requests.** A caller PI handed a reference to may hand it on
|
|
191
|
+
through its own agent tools; its ScratchPad extension then asks the creator,
|
|
192
|
+
PI, to grant the receiver read (the grant request, `grant-request.ts` in
|
|
193
|
+
the fabric package). The add-on answers on PI's service, by the rule every
|
|
194
|
+
SDK-built agent answers by: the reference must point into PI's volume, the
|
|
195
|
+
requester must hold a live read grant PI made it first-hand, the grantee
|
|
196
|
+
must be another agent, and the grant is one path, read only, ten minutes at
|
|
197
|
+
most, recorded as made on request, which entitles no one to ask again. The
|
|
198
|
+
grants PI's model makes go through `scripts/sp`, which the add-on does not
|
|
199
|
+
see, so `scripts/sp` records each one sp confirms in `SP_GRANTS_FILE`
|
|
200
|
+
(`<workspace>.grants`), and the add-on counts a live one as first-hand: it
|
|
201
|
+
went to the agent whose prompt PI was serving.
|
|
202
|
+
|
|
203
|
+
## Identity
|
|
204
|
+
|
|
205
|
+
Records are signed, so the plugin needs `senderIdentity: "signed"` with a
|
|
206
|
+
NATS context carrying PI's own credentials. With identity off the add-on
|
|
207
|
+
warns once at `started`, the fabric package warns once more when the first
|
|
208
|
+
record is due, every record owed counts as dropped on the heartbeat, and
|
|
209
|
+
PI's model calls still file under the caller's thread, since the headers
|
|
210
|
+
need no signature.
|
|
211
|
+
|
|
212
|
+
## What the console shows
|
|
213
|
+
|
|
214
|
+
PI's model calls on the child thread the caller's edge created, PI the
|
|
215
|
+
executor, its depth `full`; the caller's `asked` row names PI. A delegation
|
|
216
|
+
PI's model makes is a grandchild under its tool call, PI's edge signed and
|
|
217
|
+
verified. No outcome or duration from a pair: PI publishes none, and the
|
|
218
|
+
thread's calls carry its timing.
|
|
219
|
+
|
|
220
|
+
## Public API
|
|
221
|
+
|
|
222
|
+
| | |
|
|
223
|
+
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
224
|
+
| `default`, `fabricExtension(ctx, env?)` | the factory of the contract: resolves ScratchPad, then builds the extension; `env` is where the variables are read and ScratchPad's are set, `process.env` by default |
|
|
225
|
+
| `createFabricExtension(ctx, env?, scratchPad?)` | the same, synchronous, from settings already resolved |
|
|
226
|
+
| `resolveEdgeSubject(options, env?)` | the setting's precedence, as above |
|
|
227
|
+
| `SCRATCHPAD_OPTION`, `SP_VARS`, `ScratchPadHandOn`, `spBinary(env)` | ScratchPad's option key, the variables `scripts/sp` reads, the tool extension, the packaged sp's path |
|
|
228
|
+
| `EXTENSION_NAME`, `EDGE_SUBJECT_OPTION`, `EDGE_SUBJECT_VAR`, `IDENTITY_OFF_WARNING` | the constants |
|
|
229
|
+
| `loopGuard()`, `LOOP_GUARD_CODE`, `loopGuardDescription(rootId)` | the guard on its own: `requestInterceptor`, `promptInterceptor`, `aroundToolCall(scope, run)`, `serving(rootId)`, `waiting(rootId)` |
|
|
230
|
+
| `AgentExtensionContext`, `AgentExtension`, `HarnessEvents`, `ServedRequest`, `Outcome`, `AgentExtensionHandles`, `AgentExtensionSettings`, `Harness`, `AgentExtensionFactory` | the plugin's contract, declared structurally so this package imports nothing of the plugin or of PI |
|
|
231
|
+
|
|
232
|
+
The setting's precedence, the loop guard and the contract's types live in
|
|
233
|
+
`@synadia-ai/agent-fabric`, shared by every add-on under `plugins/`; this
|
|
234
|
+
package re-exports them, the contract's `PluginHarness` as `Harness`.
|
|
235
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
Built like the fabric package (tsup, ESM and CJS, vitest, eslint,
|
|
239
|
+
prettier). It links `@synadia-ai/agent-fabric` at `file:../../typescript`,
|
|
240
|
+
built first, and the two protocol SDK packages by the same `file:` links
|
|
241
|
+
the fabric package uses; its `package-lock.json` is committed, and a clean clone
|
|
242
|
+
installs with `npm ci`. The tests: unit, a fake of the plugin driving the events
|
|
243
|
+
(`test/support/fake-plugin.ts`); integration, the factory's hooks on a real
|
|
244
|
+
`AgentService` and `Agents` over a local `nats-server` in operator mode,
|
|
245
|
+
the edges on the wire validated against `test-fixtures/schemas/edge.json`.
|
|
246
|
+
|
|
247
|
+
`npm run build:sp` installs the skill from `../scratchpad-skill` into
|
|
248
|
+
`skill/scratchpad`. It downloads the four published binaries pinned in
|
|
249
|
+
`../scratchpad-skill/release.json` and verifies their archive and executable
|
|
250
|
+
hashes with Node 20+. `npm pack` runs it too (`prepack`). `skill/` is
|
|
251
|
+
gitignored. No Go compiler or service checkout is required. The
|
|
252
|
+
[binary guide](https://github.com/synadia-ai/agent-fabric-sdk/blob/main/docs/scratchpad-binaries.md) covers authenticated
|
|
253
|
+
downloads, offline archives, cache checks, and the included licenses.
|
|
254
|
+
`test/unit/scripts-sp.test.ts` drives the shared `scripts/sp` with fake
|
|
255
|
+
binaries and a fake `uname`, and runs the real binary for this machine
|
|
256
|
+
through the installed copy when it is built.
|
|
257
|
+
|
|
258
|
+
```sh
|
|
259
|
+
(cd ../../typescript && npm install && npm run build)
|
|
260
|
+
npm install
|
|
261
|
+
npm run build:sp
|
|
262
|
+
npm run typecheck && npm run lint && npm run format:check
|
|
263
|
+
npm test
|
|
264
|
+
npm run build
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
Apache-2.0, like the fabric package. See [LICENSE](https://github.com/synadia-ai/agent-fabric-sdk/blob/main/plugins/pi/LICENSE).
|
|
270
|
+
|
|
271
|
+
Short artifact references
|
|
272
|
+
-------------------------
|
|
273
|
+
|
|
274
|
+
The shared wrapper and grant records accept `sp-r1:` short tokens and existing `sp-ref:` tokens.
|
|
275
|
+
Publication uses the short form when the service and the selected client support it.
|
|
276
|
+
Short-token reads and resolution require a compatible `sp` binary selected through
|
|
277
|
+
`SCRATCHPAD_BIN`. The bundled CLI release is pinned independently; this change
|
|
278
|
+
does not advance that pin.
|
|
279
|
+
Copy the returned token unchanged into prompts and replies.
|
|
280
|
+
The token survives client restarts; each content read still requires current authorization.
|
|
281
|
+
See the [native client contract](https://github.com/synadia-ai/agent-fabric-sdk/blob/main/docs/scratchpad-native.md) for recovery and capability fallback.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var agentFabric = require('@synadia-ai/agent-fabric');
|
|
6
|
+
var url = require('url');
|
|
7
|
+
|
|
8
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
9
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
10
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
11
|
+
var SKILL_DIR = url.fileURLToPath(new URL("../skill/scratchpad", importMetaUrl));
|
|
12
|
+
function spBinary(env) {
|
|
13
|
+
return agentFabric.spBinary(env, SKILL_DIR);
|
|
14
|
+
}
|
|
15
|
+
function spPort(settings, creator, env) {
|
|
16
|
+
return agentFabric.spPort(settings, creator, env, SKILL_DIR);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/extension.ts
|
|
20
|
+
var EXTENSION_NAME = "agent-fabric";
|
|
21
|
+
var IDENTITY_OFF_WARNING = `${EXTENSION_NAME}: senderIdentity is off; trace records are signed, so none will be published and each counts as dropped on the heartbeat. Set senderIdentity to signed.`;
|
|
22
|
+
async function fabricExtension(ctx, env = process.env) {
|
|
23
|
+
return createFabricExtension(ctx, env, await agentFabric.resolveScratchPad(ctx, env));
|
|
24
|
+
}
|
|
25
|
+
function createFabricExtension(ctx, env = process.env, scratchPad) {
|
|
26
|
+
const edgeSubject = agentFabric.resolveEdgeSubject(ctx.options, env);
|
|
27
|
+
const tracing = agentFabric.fabricTracing({ edgeSubject });
|
|
28
|
+
const guard = agentFabric.loopGuard();
|
|
29
|
+
const log = ctx.logger;
|
|
30
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
31
|
+
const scopeOf = (request) => request === void 0 ? void 0 : scopes.get(request.id);
|
|
32
|
+
let warnedIdentityOff = false;
|
|
33
|
+
const scratchPadOn = scratchPad !== void 0;
|
|
34
|
+
const promptEnv = new agentFabric.ServedPromptEnv(env);
|
|
35
|
+
if (scratchPad !== void 0) agentFabric.exportScratchPad(scratchPad, env);
|
|
36
|
+
const handOn = scratchPad !== void 0 ? new agentFabric.ScratchPadHandOn() : void 0;
|
|
37
|
+
return {
|
|
38
|
+
name: EXTENSION_NAME,
|
|
39
|
+
// The service: tracing first, so the guard runs inside the scope tracing
|
|
40
|
+
// bound and a malformed pair is refused with 400 before the guard
|
|
41
|
+
// looks; the plugin appends its own (the tools' scope) after these.
|
|
42
|
+
// The ScratchPad extension's, last, remembers the references each
|
|
43
|
+
// served prompt brings, which PI may hand on.
|
|
44
|
+
requestInterceptors: [
|
|
45
|
+
tracing.requestInterceptor,
|
|
46
|
+
guard.requestInterceptor,
|
|
47
|
+
...handOn !== void 0 ? [handOn.requestInterceptor] : []
|
|
48
|
+
],
|
|
49
|
+
// The client PI's agent tools prompt through: the edge and the pair,
|
|
50
|
+
// then the waiting mark. Both read the ambient scope, so the order
|
|
51
|
+
// between them does not matter.
|
|
52
|
+
promptInterceptors: [tracing.promptInterceptor, guard.promptInterceptor],
|
|
53
|
+
heartbeatExtras: tracing.heartbeatExtras,
|
|
54
|
+
...handOn !== void 0 ? {
|
|
55
|
+
// References in PI's `prompt_agent`: granted to a receiver that
|
|
56
|
+
// reads them, removed with a note for any other.
|
|
57
|
+
toolExtensions: [handOn],
|
|
58
|
+
// So a caller's `prompt_agent` hands references to PI.
|
|
59
|
+
metadata: { [agentFabric.READS_REFERENCES_KEY]: "true" }
|
|
60
|
+
} : {},
|
|
61
|
+
// No `stopping`: an
|
|
62
|
+
// edge record is published before the prompt it describes and counted
|
|
63
|
+
// as it goes, so there is nothing to flush when the plugin stops.
|
|
64
|
+
started({ agents, service }) {
|
|
65
|
+
if (ctx.settings.senderIdentity !== "signed" && !warnedIdentityOff) {
|
|
66
|
+
warnedIdentityOff = true;
|
|
67
|
+
log.warn(IDENTITY_OFF_WARNING);
|
|
68
|
+
}
|
|
69
|
+
log.info(
|
|
70
|
+
`${EXTENSION_NAME}: tracing ${service.subject.prompt}` + (edgeSubject === null ? " (propagate-only: lineage forwarded, no edge record published)" : `, edge records on ${edgeSubject}`)
|
|
71
|
+
);
|
|
72
|
+
if (scratchPad !== void 0 && handOn !== void 0) {
|
|
73
|
+
env[agentFabric.SP_VARS.creator] = service.subject.prompt;
|
|
74
|
+
const port = spPort(scratchPad, service.subject.prompt, env);
|
|
75
|
+
if (port !== void 0) handOn.start({ scratchPad: port, agents, service, logger: log });
|
|
76
|
+
else
|
|
77
|
+
log.warn(
|
|
78
|
+
`${EXTENSION_NAME}: no sp for this platform (set SCRATCHPAD_BIN); references in prompt_agent are not handed on, and grant requests not answered`
|
|
79
|
+
);
|
|
80
|
+
log.info(
|
|
81
|
+
`${EXTENSION_NAME}: ScratchPad volume ${scratchPad.volume}` + (scratchPad.dir !== void 0 ? `, workspace ${scratchPad.dir}` : ", no workspace") + ", for the skill's scripts/sp"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
events: {
|
|
86
|
+
promptAccepted(request) {
|
|
87
|
+
const scope = agentFabric.activeTrace();
|
|
88
|
+
if (scope !== void 0) scopes.set(request.id, scope);
|
|
89
|
+
},
|
|
90
|
+
promptEnded(request) {
|
|
91
|
+
scopes.delete(request.id);
|
|
92
|
+
promptEnv.leave(request);
|
|
93
|
+
},
|
|
94
|
+
aroundInject(request, run) {
|
|
95
|
+
const scope = scopes.get(request.id);
|
|
96
|
+
if (scratchPadOn) promptEnv.enter(request, scope);
|
|
97
|
+
return scope === void 0 ? run() : agentFabric.bindActiveTrace(scope, run);
|
|
98
|
+
},
|
|
99
|
+
providerHeaders(request) {
|
|
100
|
+
const scope = scopes.get(request.id);
|
|
101
|
+
return scope === void 0 ? void 0 : agentFabric.bindActiveTrace(scope, agentFabric.traceHeaders);
|
|
102
|
+
},
|
|
103
|
+
aroundToolCall(request, _toolName, run) {
|
|
104
|
+
const scope = scopeOf(request);
|
|
105
|
+
return scope === void 0 ? run() : agentFabric.bindActiveTrace(scope, () => guard.aroundToolCall(scope, run));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Object.defineProperty(exports, "EDGE_SUBJECT_OPTION", {
|
|
112
|
+
enumerable: true,
|
|
113
|
+
get: function () { return agentFabric.EDGE_SUBJECT_OPTION; }
|
|
114
|
+
});
|
|
115
|
+
Object.defineProperty(exports, "EDGE_SUBJECT_VAR", {
|
|
116
|
+
enumerable: true,
|
|
117
|
+
get: function () { return agentFabric.EDGE_SUBJECT_VAR; }
|
|
118
|
+
});
|
|
119
|
+
Object.defineProperty(exports, "LOOP_GUARD_CODE", {
|
|
120
|
+
enumerable: true,
|
|
121
|
+
get: function () { return agentFabric.LOOP_GUARD_CODE; }
|
|
122
|
+
});
|
|
123
|
+
Object.defineProperty(exports, "SCRATCHPAD_OPTION", {
|
|
124
|
+
enumerable: true,
|
|
125
|
+
get: function () { return agentFabric.SCRATCHPAD_OPTION; }
|
|
126
|
+
});
|
|
127
|
+
Object.defineProperty(exports, "SP_VARS", {
|
|
128
|
+
enumerable: true,
|
|
129
|
+
get: function () { return agentFabric.SP_VARS; }
|
|
130
|
+
});
|
|
131
|
+
Object.defineProperty(exports, "ScratchPadHandOn", {
|
|
132
|
+
enumerable: true,
|
|
133
|
+
get: function () { return agentFabric.ScratchPadHandOn; }
|
|
134
|
+
});
|
|
135
|
+
Object.defineProperty(exports, "loopGuard", {
|
|
136
|
+
enumerable: true,
|
|
137
|
+
get: function () { return agentFabric.loopGuard; }
|
|
138
|
+
});
|
|
139
|
+
Object.defineProperty(exports, "loopGuardDescription", {
|
|
140
|
+
enumerable: true,
|
|
141
|
+
get: function () { return agentFabric.loopGuardDescription; }
|
|
142
|
+
});
|
|
143
|
+
Object.defineProperty(exports, "resolveEdgeSubject", {
|
|
144
|
+
enumerable: true,
|
|
145
|
+
get: function () { return agentFabric.resolveEdgeSubject; }
|
|
146
|
+
});
|
|
147
|
+
exports.EXTENSION_NAME = EXTENSION_NAME;
|
|
148
|
+
exports.IDENTITY_OFF_WARNING = IDENTITY_OFF_WARNING;
|
|
149
|
+
exports.createFabricExtension = createFabricExtension;
|
|
150
|
+
exports.default = fabricExtension;
|
|
151
|
+
exports.fabricExtension = fabricExtension;
|
|
152
|
+
exports.spBinary = spBinary;
|
|
153
|
+
//# sourceMappingURL=index.cjs.map
|
|
154
|
+
//# sourceMappingURL=index.cjs.map
|