@passioncode-ai/fabric-agent-adapter 0.73.0 → 0.75.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 +172 -0
- package/README.md +11 -7
- package/SECURITY.md +17 -0
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +6 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +3 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +16 -17
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/channel-agent.example.json +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +4 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +6 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +10 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +33 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +6 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +19 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +37 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +38 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +53 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +16 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +545 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +43 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +29 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +13 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +845 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +35 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +214 -9
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +8 -5
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +6 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/communication-agent-template.md +380 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +6 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +8 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +3 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/practices.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,177 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.75.0 (2026-11-07)
|
|
4
|
+
|
|
5
|
+
Guidance only: no kit module, probe rule or hook changes; the contract pin stays `18f8d487`.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **The Fabric communication agent template** (FAA-262, the operator's decision of 2026-11-07):
|
|
10
|
+
`creating-fabric-agents/references/communication-agent-template.md`, a prompt a coding agent builds a channel agent
|
|
11
|
+
from (support, sales and leads, community, inbox, store reviews, an internal assistant). It carries the inbound
|
|
12
|
+
pipeline as one estate measured it on 56 real questions and 19 written batches: a code prefilter, a triage at low
|
|
13
|
+
thinking that answers only a batch with no ask, one call that analyses, plans and drafts (with the injection check
|
|
14
|
+
and the product-only scope), the actions, a final pass only when an action returned new facts or an injection is
|
|
15
|
+
suspected, then the pre-send gates with a checker of another vendor. It also carries the business patterns, the
|
|
16
|
+
knowledge and data connections (the handoff queue's «a person answered» signal, the account state and version facts
|
|
17
|
+
as tools of every turn, retrieval over the whole conversation), leads handed to a sales agent of their own that owns
|
|
18
|
+
the CRM, the learning loop, the model lanes, the Fabric integration and the acceptance with 18 adversarial batches, and maps its sections to the parts of an organization agent template one file can carry (fabric-workspace
|
|
19
|
+
`knowledge/system/templates.md`, S2; S2's operating rules A1 to A12 apply by reference until the folder, FAA-265). `SKILL.md` links it beside `agents-that-answer-people.md`.
|
|
20
|
+
- A scenario eval (`a-channel-agent-starts-from-the-communication-template`) and a trigger query for it (measured with a
|
|
21
|
+
real client: creating-fabric-agents 28 of 31, the new query routed right;
|
|
22
|
+
[record](docs/evidence/evals/2026-11-07-trigger-run.md)), and `test/test_communication_template_prose.py`,
|
|
23
|
+
which pins the template phrases that scenario grades and checks every event kind it names against the kit's writer.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `references/models-and-providers.md`: a new agent's model key gets USD 100 a month unless the operator names a higher
|
|
28
|
+
one, and the agent's own stop sits a little under the key's limit (the operator's decision of 2026-11-07).
|
|
29
|
+
It also names the template's open-vendor rule (every route of a lane reading people's words, fallbacks included) as
|
|
30
|
+
narrower than DEC-0083, and excepts a channel agent's batch triage from the volume row.
|
|
31
|
+
- The template is linked from the creating-fabric-agents grill row «Does it answer people?» (the row «Who pays?» names
|
|
32
|
+
the USD 100 default), `agents-that-answer-people.md` and `practices.md`, and named in building-fabric-services
|
|
33
|
+
`SKILL.md` and the support-agent recipe's README, which now says how the recipe serves the template: the plan's draft carries `[KB:<id>]`
|
|
34
|
+
markers and the agent runs `check_reply` on it; the checker of another vendor stays the agent's own.
|
|
35
|
+
|
|
36
|
+
- `agents-that-answer-people.md` A1: every inbound message belongs to one turn; a burst the template batches is one
|
|
37
|
+
turn listing its message ids (it said every message is a turn). The support-agent recipe's README names what goes in
|
|
38
|
+
`check_reply`'s `given`: the ids placed in the prompt, never the plan's own list, with tool results as entries of their own.
|
|
39
|
+
- The operator channel's first step (`operator-channel-and-repository.md`, in adapting-projects-to-fabric and
|
|
40
|
+
creating-fabric-agents) says an agent that answers people on Telegram gives the operator channel a bot of its own,
|
|
41
|
+
and its example slot is now `EXAMPLE_AGENT_OPERATOR_BOT_TOKEN`.
|
|
42
|
+
- The support-agent recipe's `channel-agent.example.json` final text reads as the standard's final reply (6.6: «we
|
|
43
|
+
could not confirm this»), not as an answer.
|
|
44
|
+
- `docs/backlog.md`: FAA-263 (SKILL.md word headroom), FAA-264 (the readings' follow-ups that block nothing) and
|
|
45
|
+
FAA-265 (organization agent templates as folders, W0-Q8).
|
|
46
|
+
- building-fabric-services `references/usage.md`: the every-limit example calls `usage_report` over the ledger's
|
|
47
|
+
receipts, since the Python `ledger.report` takes no `budgets` (it showed a call that raised `TypeError` since 0.19.0);
|
|
48
|
+
`test/test_usage_reference_snippet.py` runs it.
|
|
49
|
+
|
|
50
|
+
### Upgrading
|
|
51
|
+
|
|
52
|
+
- An answering agent that adopts the template moves every route that reads people's text to an open-vendor model,
|
|
53
|
+
fallbacks included (template rule 6); one that does not adopt it keeps what DEC-0083 asks. Its monthly stop uses the
|
|
54
|
+
kit's OpenRouter client with `window="month"` (the default window is a day).
|
|
55
|
+
- Adopting the template starts a new series for `nobodyDropped.turns` (batched turns) and the outcome shares
|
|
56
|
+
(`customer_silent` up, `no_reply_owed` down); say so in the owner's report, never inside `support.quality_daily`.
|
|
57
|
+
- A declaration copied from the 0.74.0 support-agent example replaces its `texts.final`, which claimed an answer.
|
|
58
|
+
- An agent whose operator channel shares its answering bot moves the operator channel to a bot of its own (a new token
|
|
59
|
+
slot and link code), and binds the operator's id on the answering bot by its own link code (CA-8).
|
|
60
|
+
|
|
61
|
+
## 0.74.0 (2026-11-07)
|
|
62
|
+
|
|
63
|
+
The contract pin moves to `18f8d487` (`v0.1.0+2026-11-07.1`) from `eac1a594` (`v0.1.0+2026-11-06.1`); between them
|
|
64
|
+
only DEC-0154 (several OS accounts on one computer). `eac1a594` stays in `SUPPORTED_CONTRACT_COMMITS`, so bundles
|
|
65
|
+
issued under it stay valid; a new scaffold writes `18f8d487`.
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **The listener's owner before a credential** (FAA-226, the contract's DEC-0154 HA-2). Loopback is one per computer,
|
|
70
|
+
not one per OS account, so every kit client that sends a credential to `http://127.0.0.1:<port>` first tells the
|
|
71
|
+
owner of the server end of its own connection from the computer's socket table, and sends nothing to another OS
|
|
72
|
+
account's process, root's, SYSTEM's, or one whose owner cannot be told (`foreign-user`):
|
|
73
|
+
- a new layer-L0 module, `fabric_peer_owner` / `fabric-peer-owner.mjs`. The Python file is Project Observatory's
|
|
74
|
+
`peer_owner.py` 1.1.0, vendored by commit (`f86f0622`, the same licence), with `Owner` and `Row` as NamedTuples so
|
|
75
|
+
the module loads by its path and `Optional[X]` in its signatures for the Python 3.9 floor; it reads macOS through `sysctl net.inet.tcp.pcblist_n` (calibrated on a connected pair
|
|
76
|
+
of its own, else `netstat -anv`), Linux through `/proc/net/tcp` and `/proc/net/tcp6`, Windows through
|
|
77
|
+
`GetExtendedTcpTable` and the owner's SID. The Node twin reads `netstat -anv` and `ps` on macOS, the same `/proc`
|
|
78
|
+
files on Linux, `netstat -ano`, `whoami /user` and a PowerShell `GetOwnerSid` (SIDs, never names) on Windows; `checkedSocket` and `CheckedAgent` check a connection
|
|
79
|
+
before a byte is written. Both twins answer the same questions over `test/fixtures/peer-owner/peer-owner-cases.json`.
|
|
80
|
+
- `check_service.py`: a new rule for a local placement, `listener.owner`, the first that reads the port, from the server end of a connection
|
|
81
|
+
of the probe's own on which nothing is sent: `PASS` for this OS account's process,
|
|
82
|
+
`FAIL` `foreign-user` (then no rule reads the port and no token is sent), `NOT_RUN` when nothing listens. Every
|
|
83
|
+
local request goes on a checked connection.
|
|
84
|
+
- the hub client (`fabric_hub.post_mcp`, `postMcp`): `HubError` code `foreign-user`, retryable, before the bearer; the
|
|
85
|
+
live pid in `hub.json` stays a precondition and is never the proof. The Node client keeps a checked connection
|
|
86
|
+
alive (closed after 2 s idle), so a busy session reads the socket table once per connection.
|
|
87
|
+
- `fabric_http` / `fabric-http.mjs` for a plain `http://` loopback URL: `HttpError` kind `foreign-user`, never
|
|
88
|
+
retried; the Node twin sends such a request through `node:http` on a checked connection instead of `fetch`.
|
|
89
|
+
- `check_dashboard_layout.mjs` before it asks a local service for a login code (a copy of that file alone, without
|
|
90
|
+
the kit beside it, sends nothing to an origin that is not `https://`).
|
|
91
|
+
- the launchd installers' poll: a listener of another account is never a successful install; the error names
|
|
92
|
+
`foreign-user`.
|
|
93
|
+
|
|
94
|
+
### Upgrading
|
|
95
|
+
|
|
96
|
+
- `check_service.py` prints one more line for a local service (`listener.owner`), and a local service whose port is
|
|
97
|
+
held by another OS account's or root's process, or by one whose owner cannot be told (a sandbox without `sysctl` or
|
|
98
|
+
`netstat`, a socket launchd holds for a socket-activated job), now fails (exit 1) where 0.73.0 read its answer.
|
|
99
|
+
When the port's rules cannot be read (that FAIL, or a well-known document that gives no answer), one `port.rules`
|
|
100
|
+
`NOT_RUN` line names them, where 0.73.0 left them out of the report.
|
|
101
|
+
A socket-activated job's listening socket belongs to whoever created it: a systemd user unit's to this account, a
|
|
102
|
+
system unit's and launchd's to root (pid 1), so such a service binds its own listener or runs as a user unit.
|
|
103
|
+
- `fetch_well_known` (re-exported by `fabric_service`) answers `None` for a listener of another account, and
|
|
104
|
+
`launchd_install` / `launchdInstall` now raises, naming `foreign-user`, where 0.73.0 returned success when another
|
|
105
|
+
account's copy of the service answered on the port.
|
|
106
|
+
- The Node `fabric-http.mjs` sends a plain `http://` loopback request through `node:http` on a checked connection, not
|
|
107
|
+
`fetch`: a caller or test that stubs the global `fetch` for a loopback URL is no longer reached (pass `transport`).
|
|
108
|
+
- On a computer with more than one counted OS account a loopback caller with no token is not T0 until the door checks
|
|
109
|
+
its peer's account (the pinned HA-3 and DL-25; the kit's own peer check is FAA-227): pass `tier="T0"` to `gate` or
|
|
110
|
+
`server.handle` there only for a caller the descriptor token authenticated (`references/delegation.md`).
|
|
111
|
+
- Services that vendored 0.73.0 kit files keep sending to loopback unchecked until their copies are refreshed
|
|
112
|
+
(`fabric_kit.py status` names them `older`). `fabric-supervisors.mjs` exports `wellKnownOf` and `ForeignUserAnswer`
|
|
113
|
+
(new names), and `fabric_peer_owner` reports its version as `1.1.0+kit.1`.
|
|
114
|
+
- `fabric_http`, `fabric-http.mjs` and the hub client refuse a loopback listener of root or a service account too (a
|
|
115
|
+
rootful container proxy in front of a local model gateway, for one): use a rootless runtime or a forwarder of your own
|
|
116
|
+
account. The Python `fabric_http` no longer sends a loopback URL through a proxy from the environment (`http_proxy`).
|
|
117
|
+
- `fabric_peer_owner` is now imported by `check_service.py`, `fabric_hub`, `fabric_http`, `fabric_supervisors` and the
|
|
118
|
+
Node `fabric-hub.mjs`, `fabric-supervisors.mjs` and `fabric-http.mjs` (which imported no kit module before): a project
|
|
119
|
+
that refreshes vendored copies file by file takes each module's `kit.json` closure, or the import fails.
|
|
120
|
+
- A socket table over 4 MiB cannot be read (about 6,800 TCP sockets for the Python reader on macOS, 617 bytes each in
|
|
121
|
+
`pcblist_n`; about 19,000 for `netstat -anv`, the Node reader there; about 28,000 in Linux's `/proc/net/tcp`), so every loopback send fails closed as `foreign-user` on such a host.
|
|
122
|
+
- On Linux the server end of an accepted connection is told by its own row (the uid of the process that accepted it),
|
|
123
|
+
and of one not accept()ed yet, which `/proc` prints with uid 0 and inode 0, by the listener row on its local address
|
|
124
|
+
(state 0A; the exact address, else the wildcard), as Observatory's `peer_owner.py` 1.2.0 reads it; the contract's
|
|
125
|
+
CT-254 is settling HA-2's Linux row (FAA-258). A client row with inode 0 (a closed client) reads `unknown`. Several
|
|
126
|
+
listening rows of one owner (SO_REUSEPORT workers, an IPv4 and an IPv6 socket of one process) read as that owner;
|
|
127
|
+
rows of two owners stay `unknown`.
|
|
128
|
+
- A `/proc` read that sockets came and went under can print a row twice or skip one (Linux resumes it by position):
|
|
129
|
+
rows equal in every field count once, and a `/proc` question that finds no row or several reads the table once
|
|
130
|
+
more before it answers `unknown`. Without it a busy Linux host refused its own service now and then.
|
|
131
|
+
- The Python module imports `ctypes` only where it reads with it, so a CPython built without libffi still imports
|
|
132
|
+
every kit module; the owner check then reads `netstat` on macOS and fails closed on Windows.
|
|
133
|
+
- A client running as root or SYSTEM reads root's or SYSTEM's listener as its own account's (launchd's pid 1 stays
|
|
134
|
+
`system`): the vendored module put the system rule first, which would have refused a root container's own sidecar.
|
|
135
|
+
- The hub client and `fabric_http` refuse a loopback listener whose owner cannot be told (a socket table the process
|
|
136
|
+
cannot read): nothing is sent. Fail closed, as HA-2 asks.
|
|
137
|
+
- A copy of `check_dashboard_layout.mjs` alone refuses a `--service` whose origin is not `https://`; run it from the
|
|
138
|
+
kit's folder. Which way a request goes follows its origin, not the descriptor's `placement`: a descriptor that says
|
|
139
|
+
`remote` with an `http://` origin is asked through the checked transport too.
|
|
140
|
+
- A service's own `<tool> mcp-register` now establishes that the listener is this OS account's before it writes the
|
|
141
|
+
token (`references/surfaces-and-auth.md`: `fabric_peer_owner.server_end` on a connection it opens and sends nothing
|
|
142
|
+
on, as `listener.owner` does; `foreign-user` writes nothing). The kit ships no `mcp-register` helper: one written for
|
|
143
|
+
0.73.0 or earlier writes the token unchecked until its author adds the check.
|
|
144
|
+
- Every new checked loopback connection from the Node twin starts processes: on macOS `netstat -anv` and `ps` (about
|
|
145
|
+
20 to 35 ms), on Windows `netstat -ano` twice and one PowerShell `GetOwnerSid` (5 s cap each, not yet measured,
|
|
146
|
+
FAA-256). The hub client keeps its checked connection alive; `fabric-http.mjs` does not, so each loopback request
|
|
147
|
+
pays it.
|
|
148
|
+
- The Python `fetch_well_known` answers `None` both when nothing listens and for a `foreign-user` listener; a status
|
|
149
|
+
command that must report `foreign-user` (the order `down`, `foreign-user`, `foreign`, then `status`) asks
|
|
150
|
+
`fabric_peer_owner.server_end` itself, or uses the Node `wellKnownOf`, which answers `ForeignUserAnswer` (FAA-259).
|
|
151
|
+
- The Python `launchd_install` polls through the module's `_read_well_known`, no longer through `fetch_well_known`:
|
|
152
|
+
a test that faked the poll by patching `fabric_supervisors.fetch_well_known` now makes real connections and times out
|
|
153
|
+
(the Node `launchdInstall` still takes an injected `fetchWellKnown`; FAA-259 gives Python the same).
|
|
154
|
+
|
|
155
|
+
### Changed
|
|
156
|
+
|
|
157
|
+
- `references/protocol.md` gains "The listener's owner (DEC-0154 HA-2)"; `references/board.md`, `references/lifecycle.md`,
|
|
158
|
+
`references/kit-modules.md` and `SKILL.md` name the check. `references/surfaces-and-auth.md` gives `mcp-register` the
|
|
159
|
+
check, `references/interop.md`'s real-client gate waits for `listener.owner` PASS, and the README's MCP quick start
|
|
160
|
+
writes the client config only after the probe passes.
|
|
161
|
+
- The test fake hub keeps HTTP/1.1 connections with no Nagle delay, as a Node server does by default (whether Fabric's
|
|
162
|
+
own hub keeps idle connections past the Node client's 2 s is FAA-256).
|
|
163
|
+
- `dashboard-links.md` (all three copies) opens a raw loopback address in a browser only after the listener is this OS
|
|
164
|
+
account's (the pinned PL-3); the link gate enforcing it is FAA-251. `connectors.md` names the loopback owner check
|
|
165
|
+
and the kind `foreign-user`; the skill card's Network row names the socket table the kit now reads; `SKILL.md` and
|
|
166
|
+
`references/lifecycle.md` read a service's identity only from this account's listener, with a scenario eval for a
|
|
167
|
+
port another account holds; `references/delegation.md` says when a token-free loopback caller is T0 (HA-3);
|
|
168
|
+
`SECURITY.md` names the socket-table reads and the programs the kit now starts. Owed, each its own row: FAA-251 (the
|
|
169
|
+
link gate), FAA-252 (a running board session's reason), FAA-253 (the systemd and Task Scheduler installers' poll),
|
|
170
|
+
FAA-254 (working-with-fabric's "Fabric is up"), FAA-255 (the delegation key set read over loopback), FAA-256 (the Node
|
|
171
|
+
check's cost on Windows), FAA-257 (the OpenRouter connector reports a refused loopback gateway as no answer), FAA-258 (re-pinning
|
|
172
|
+
to CT-254 once it is tagged and reconciling with Observatory's `peer_owner.py` 1.2.0), FAA-259 (the Python
|
|
173
|
+
`fetch_well_known` telling `foreign-user` from `down`), FAA-260 (the readings' smaller follow-ups).
|
|
174
|
+
|
|
3
175
|
## 0.73.0 (2026-11-07)
|
|
4
176
|
|
|
5
177
|
### Added
|
package/README.md
CHANGED
|
@@ -70,7 +70,9 @@ installed into a service with `scripts/fabric_kit.py install` and checked with `
|
|
|
70
70
|
the dates API); connectors for outbound HTTP and OpenRouter (`fabric_http`, `fabric_openrouter`: one deadline, safe
|
|
71
71
|
retries, the agent's own key, a spend ceiling before every attempt, usage receipts, a fallback model;
|
|
72
72
|
[connectors](plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md)); the draft-and-check step of a support agent as a
|
|
73
|
-
[recipe](plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md)
|
|
73
|
+
[recipe](plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md)
|
|
74
|
+
(the whole agent starts from creating-fabric-agents'
|
|
75
|
+
[communication agent template](plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/communication-agent-template.md));
|
|
74
76
|
a complete `scripts/sample_service.py`; and `scripts/check_service.py`, a live probe. A service that answers
|
|
75
77
|
requests other Projects post to its Project on Fabric's project board uses the board responder
|
|
76
78
|
(`scripts/fabric_hub.py` and `scripts/fabric_board.py`, Node `fabric-hub.mjs` and `fabric-board.mjs`): the
|
|
@@ -188,8 +190,9 @@ KIT=plugins/fabric-agent-adapter/skills/building-fabric-services/scripts # or
|
|
|
188
190
|
DATA=$(mktemp -d); SERVICES=$(mktemp -d); WORK=$(mktemp -d)
|
|
189
191
|
python3 "$KIT/sample_service.py" serve --port 47190 --data-dir "$DATA" &
|
|
190
192
|
python3 "$KIT/sample_service.py" register --port 47190 --data-dir "$DATA" --services-dir "$SERVICES"
|
|
191
|
-
|
|
192
|
-
#
|
|
193
|
+
# The probe exits 0 (no FAIL); only then is the service registered for one client run (listener.owner proves the port
|
|
194
|
+
# is this account's, and the client cannot check it). The token goes in a header, never in argv.
|
|
195
|
+
python3 "$KIT/check_service.py" sample --services-dir "$SERVICES" && \
|
|
193
196
|
python3 - "$DATA/service.token" "$WORK/mcp.json" <<'EOF'
|
|
194
197
|
import json, os, sys
|
|
195
198
|
token = open(sys.argv[1]).read().strip()
|
|
@@ -203,9 +206,10 @@ EOF
|
|
|
203
206
|
kill %1
|
|
204
207
|
```
|
|
205
208
|
|
|
206
|
-
Verified 2026-
|
|
207
|
-
pointing at
|
|
208
|
-
the CLI connected to the sample over streamable HTTP, called `sample.echo` and printed `ping
|
|
209
|
+
Verified 2026-11-07 from the published npm package 0.74.0 (`npm pack` into a throwaway folder, `KIT`
|
|
210
|
+
pointing at its skill) with Claude Code 2.1.299: the probe reported 44 rules, 0 FAIL, `listener.owner` PASS;
|
|
211
|
+
the CLI connected to the sample over streamable HTTP, called `sample.echo` and printed `ping`
|
|
212
|
+
([record](docs/evidence/runs/2026-11-07-0.74.0-quick-start.md)). The probe prints one line per rule
|
|
209
213
|
(`PASS`, `FAIL` or `NOT_RUN` with its evidence) and exits 1 on any `FAIL`; run it against your
|
|
210
214
|
own service by its id once its installer has written the descriptor.
|
|
211
215
|
`FABRIC_REAL_CLIENT=1 python3 -m unittest discover -s test -p test_real_client.py` repeats the connection check
|
|
@@ -301,7 +305,7 @@ Scaffolding is non-destructive by default. It writes only the paths declared in
|
|
|
301
305
|
The default pin for new bundles is [`fabric-contract.lock.json`](fabric-contract.lock.json):
|
|
302
306
|
|
|
303
307
|
- version: `0.1.0`
|
|
304
|
-
- commit: `
|
|
308
|
+
- commit: `18f8d48762edc5bfb1433657cbfe1c16196df1a6`
|
|
305
309
|
|
|
306
310
|
Every other mention of the default contract revision (this section, the skills' metadata, the
|
|
307
311
|
skill card, `adapt_project.py`) must equal it; `python3 test/validate.py` fails a tree
|
package/SECURITY.md
CHANGED
|
@@ -32,6 +32,23 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
|
|
|
32
32
|
external APIs the service's author configures, such as OpenRouter, with keys the service
|
|
33
33
|
reads by name at run time; the kit itself calls no outside host on its own. A board
|
|
34
34
|
responder talks only to Fabric's hub on loopback.
|
|
35
|
+
- Before `check_service.py`, the hub client, `fabric_http` (a plain `http://` loopback URL),
|
|
36
|
+
`check_dashboard_layout.mjs` (a local login code) and the launchd installers' poll send
|
|
37
|
+
anything to `127.0.0.1:<port>`, they read the computer's socket table to tell which OS
|
|
38
|
+
account owns the listener (DEC-0154 HA-2, `fabric_peer_owner`), and send nothing to
|
|
39
|
+
another account's, root's, SYSTEM's or an untellable listener. They read, never change:
|
|
40
|
+
macOS `sysctl net.inet.tcp.pcblist_n` in the process, or the programs `/usr/sbin/netstat
|
|
41
|
+
-anv -p tcp` and `/bin/ps -o uid= -p <pid>` (the Node twin always); Linux
|
|
42
|
+
`/proc/net/tcp` and `/proc/net/tcp6`; Windows `GetExtendedTcpTable` and the process
|
|
43
|
+
token (Python), or `netstat -ano`, `whoami /user` and one PowerShell `GetOwnerSid` query
|
|
44
|
+
per new connection (Node). A token-free loopback door is not yet protected against
|
|
45
|
+
another account's process (HA-3, FAA-227). An MCP client (Claude Code, Codex) sends the
|
|
46
|
+
token it was registered with on every later connection without this check (the contract's
|
|
47
|
+
residue "a coding agent's bearer"): when the port may have changed hands, rotate the token
|
|
48
|
+
and re-run `mcp-register` (one written as `references/surfaces-and-auth.md` says checks the
|
|
49
|
+
listener first; the kit ships none). On Linux `/proc` names a socket's creator and
|
|
50
|
+
no pid, so a listening socket root created and handed to another account (systemd
|
|
51
|
+
socket activation with `User=`) reads as root's: a client running as root sends to it.
|
|
35
52
|
- The working-with-fabric PostToolUse hook (FAA-206) reads only the release call Claude Code gives it on stdin (the
|
|
36
53
|
tool's name, its input, which can carry the agent's note, and Fabric's answer), in memory: it writes, logs and sends
|
|
37
54
|
nothing and prints one fixed line or nothing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/fabric-agent-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.75.0",
|
|
4
4
|
"description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "fabric-agent-adapter",
|
|
4
4
|
"displayName": "Fabric Agent Adapter",
|
|
5
5
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.75.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
5
5
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.75.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
|
-
contract-commit: "
|
|
10
|
+
contract-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Adapting projects to Fabric
|
|
@@ -76,7 +76,7 @@ Use exactly:
|
|
|
76
76
|
|
|
77
77
|
- contract version `0.1.0`;
|
|
78
78
|
- repository `https://github.com/passioncode-ai/fabric-agent-contract`;
|
|
79
|
-
- commit `
|
|
79
|
+
- commit `18f8d48762edc5bfb1433657cbfe1c16196df1a6`.
|
|
80
80
|
|
|
81
81
|
This is the default for new bundles. When checking an issued legacy bundle, preserve its
|
|
82
82
|
explicit supported lock and use that exact clean checkout. Never silently upgrade or
|
|
@@ -62,8 +62,12 @@ the fabric-dashboards MCP (`list_services`, never guessed) and hand over the
|
|
|
62
62
|
`open_link` its `link` tool returns for the page's path; to open it, call the
|
|
63
63
|
host's `open` with `fallback=never`. Never `open <url>`, `open -a Safari`,
|
|
64
64
|
`xdg-open`, `start <url>`, `webbrowser.open` or `shell.openExternal` on this
|
|
65
|
-
agent's pages. A browser only when `host_status` confirms `not_installed
|
|
66
|
-
|
|
65
|
+
agent's pages. A browser only when `host_status` confirms `not_installed`, and
|
|
66
|
+
a raw `http://127.0.0.1:<port>` address only after the listener on that port is
|
|
67
|
+
this OS account's (DEC-0154 PL-3 and HA-2; the adapter's `building-fabric-services/scripts/fabric_peer_owner.py`: `server_end` of a connection it opens and sends nothing on; `check_service.py <id>.<instance>` prints it as `listener.owner`): a
|
|
68
|
+
`foreign-user` listener is reported, never opened (the link gate below does not
|
|
69
|
+
read the owner yet, FAA-251). An installed host that failed
|
|
70
|
+
to open is reported, not replaced by HTTP. Links
|
|
67
71
|
carry no token or login code. Every completion and notification action passes
|
|
68
72
|
the Fabric Agent Adapter's link gate before delivery: `check_dashboard_link.py`
|
|
69
73
|
in its `building-fabric-services/scripts`, run from that skill's directory;
|
|
@@ -12,9 +12,10 @@ its stops with buttons: `fabric-operator-channel/0.1` (contract DEC-0034,
|
|
|
12
12
|
`docs/specification/operator-channel.md`), whose rule OC-11 this follows. Give the operator's four
|
|
13
13
|
steps:
|
|
14
14
|
|
|
15
|
-
1. create a bot with @BotFather
|
|
15
|
+
1. create a bot with @BotFather (for an agent that answers people on Telegram, a bot of its own, never
|
|
16
|
+
the bot that answers people: the contract's channel-agent.md);
|
|
16
17
|
2. put its token into the operator's secret store (Project Observatory where it is installed) under
|
|
17
|
-
the name the agent reads (for example `
|
|
18
|
+
the name the agent reads (for example `EXAMPLE_AGENT_OPERATOR_BOT_TOKEN`), never into a chat, a
|
|
18
19
|
file or the bundle;
|
|
19
20
|
3. run the agent's link command;
|
|
20
21
|
4. type the code it shows in the chat with the bot.
|
package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py
CHANGED
|
@@ -22,10 +22,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
|
|
|
22
22
|
|
|
23
23
|
CONTRACT_VERSION = "0.1.0"
|
|
24
24
|
CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
|
|
25
|
-
CONTRACT_COMMIT = "
|
|
25
|
+
CONTRACT_COMMIT = "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
26
26
|
# #region supported-contract-revisions — docs: README.md#contract-pin
|
|
27
27
|
# Only this declaration may name a legacy revision in live files.
|
|
28
|
-
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "3830a3353923e20f826d466610e1c479f120bec0", "00e050ba7429c0c60261cec871779e0fd6aeeb28", "1197a90dc4b57152cabcc71ad17db7559f35a27f", "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
28
|
+
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "eac1a594aa131851f079da8bb7b2149e1951f35f", "3830a3353923e20f826d466610e1c479f120bec0", "00e050ba7429c0c60261cec871779e0fd6aeeb28", "1197a90dc4b57152cabcc71ad17db7559f35a27f", "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
29
29
|
# #endregion supported-contract-revisions
|
|
30
30
|
INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
|
|
31
31
|
MCP_REVISION = "2026-07-28"
|
|
@@ -14,10 +14,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
14
14
|
compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
|
|
15
15
|
metadata:
|
|
16
16
|
author: PassionCode.ai
|
|
17
|
-
version: "0.
|
|
17
|
+
version: "0.75.0"
|
|
18
18
|
contract-version: "0.1.0"
|
|
19
19
|
extension: "fabric-service/0.1"
|
|
20
|
-
extension-commit: "
|
|
20
|
+
extension-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
# Building Fabric services
|
|
@@ -71,7 +71,7 @@ Answer before writing code, and record the answers in the project README:
|
|
|
71
71
|
|
|
72
72
|
| Caller | Surface | Why |
|
|
73
73
|
|---|---|---|
|
|
74
|
-
| Another agent on this machine | MCP `2026-07-28`, `streamable-http`, at `/mcp` on the service origin, token in a header | one process serves every session
|
|
74
|
+
| Another agent on this machine | MCP `2026-07-28`, `streamable-http`, at `/mcp` on the service origin, token in a header | one process serves every session; `mcp-register` checks the listener (HA-2) first |
|
|
75
75
|
| The operator or a script | a CLI: `<tool> service status\|start\|stop\|restart`, `<tool> dashboard` (opens only in Fabric Dashboards, below), `<tool> doctor --json` | every verb delegates to launchd and the well-known document, never to its own process table |
|
|
76
76
|
| A remote agent | A2A `1.0` over HTTPS, or MCP behind an authenticated gateway | loopback reachability is not authorization |
|
|
77
77
|
| An A2A peer on this computer | `surfaces.a2a` names a public Agent Card, served without the token; its interface URLs are loopback (DEC-0122); the kits read it, serving one is FAA-10 | the card carries no credential, process member or required Fabric extension |
|
|
@@ -168,8 +168,8 @@ handler.
|
|
|
168
168
|
Calling out: [the connectors reference](references/connectors.md) (`fabric_http` with one
|
|
169
169
|
deadline, safe retries and a size cap; `fabric_openrouter` with the agent's own key, a
|
|
170
170
|
spend ceiling before every attempt, receipts and a fallback). The draft-and-check step of a
|
|
171
|
-
support agent on the kit: [`recipes/support-agent/`](recipes/support-agent/README.md);
|
|
172
|
-
creating-fabric-agents' `references/agents-that-answer-people.md
|
|
171
|
+
support agent on the kit: [`recipes/support-agent/`](recipes/support-agent/README.md); the whole agent starts from
|
|
172
|
+
creating-fabric-agents' `references/communication-agent-template.md` (parts: `agents-that-answer-people.md`).
|
|
173
173
|
|
|
174
174
|
Startup order is the part that goes wrong; keep it exactly:
|
|
175
175
|
|
|
@@ -220,7 +220,7 @@ The installer, not the service, owns the plist and the descriptor. Sequence:
|
|
|
220
220
|
(`launchd_uninstall`, `remove_descriptor`) as soon as its check is done, not left running.
|
|
221
221
|
2. `launchd_plist(...)` then `launchd_install(...)`: writes and lints the plist,
|
|
222
222
|
`bootout` and waits for the unload, `bootstrap` with retries on the transient I/O
|
|
223
|
-
error, then polls the well-known document until
|
|
223
|
+
error, then polls the well-known document until **this** identity answers on this OS account's listener (HA-2).
|
|
224
224
|
A label the operator disabled stays disabled: only a first install enables.
|
|
225
225
|
3. Uninstall: `launchd_uninstall` (waits for the unload, removes the plist, resets the
|
|
226
226
|
override; `purge=True` removes data only when the operator asks), `remove_descriptor`.
|
|
@@ -292,14 +292,14 @@ directory, restores only on a fresh database and never overwrites. Read
|
|
|
292
292
|
|
|
293
293
|
## Step 6: verify
|
|
294
294
|
|
|
295
|
-
Run the probe against the live service
|
|
296
|
-
|
|
295
|
+
Run the probe against the live service; it checks every rule it can reach and names
|
|
296
|
+
those it could not:
|
|
297
297
|
|
|
298
298
|
```bash
|
|
299
299
|
python3 <skill-dir>/scripts/check_service.py <id>[.<instance>]
|
|
300
300
|
```
|
|
301
301
|
|
|
302
|
-
It reports `PASS`, `FAIL` or `NOT_RUN` per rule (descriptor, port claim, `descriptor.source-repository`,
|
|
302
|
+
It reports `PASS`, `FAIL` or `NOT_RUN` per rule (descriptor, port claim, `descriptor.source-repository`, `listener.owner` (HA-2), `port.rules`,
|
|
303
303
|
`well-known.connections`, well-known
|
|
304
304
|
shape, identity, latency, Host/Origin/cross-site guards, loopback bind, token file,
|
|
305
305
|
events auth and shape, single-use login, state outside code, instance lock, plist,
|
|
@@ -409,28 +409,27 @@ this estate needs.
|
|
|
409
409
|
- **A desktop app with no supervisor** (DEC-0069): its descriptor says `manager: "person"` with `lifecycle.app`, no
|
|
410
410
|
origin, `installedBy {by: "person", role, tool}`, `source.repository` (the lifecycle reference, *An app's own
|
|
411
411
|
descriptor and the node*).
|
|
412
|
-
- **No Python:** use the Node kit; run the probe
|
|
413
|
-
|
|
412
|
+
- **No Python:** use the Node kit; run the probe where Python reaches the port, or mark it `NOT_RUN`
|
|
413
|
+
with the reason.
|
|
414
414
|
- **Neither kit fits the framework:** implement the four routes and the lock by hand
|
|
415
415
|
against [the protocol reference](references/protocol.md); the probe is the judge.
|
|
416
416
|
- **No contract checkout:** the protocol reference is pinned to the extension commit
|
|
417
417
|
in this skill's metadata; do not reconstruct fields from memory.
|
|
418
|
-
- **Fabric Dashboards not installed:** nothing changes; the
|
|
419
|
-
without a host; the descriptor waits for one.
|
|
418
|
+
- **Fabric Dashboards not installed:** nothing changes; the descriptor waits for a host.
|
|
420
419
|
|
|
421
420
|
## Gotchas
|
|
422
421
|
|
|
423
422
|
- `lsof` shows `*:<port>` or `0.0.0.0` → the bind is wrong even if the Host check works.
|
|
424
|
-
-
|
|
425
|
-
`
|
|
423
|
+
- HTTP 200 alone accepts any program on the port: compare `service.id` and `service.instance`, read only from
|
|
424
|
+
this OS account's listener. On `listener.owner` FAIL (`foreign-user`) send that port nothing.
|
|
426
425
|
- `launchctl bootstrap` right after `bootout` fails with I/O error 5: wait for the unload, then retry.
|
|
427
426
|
- Under launchd `PATH` is bare. Pin the interpreter by absolute path; set `PATH`
|
|
428
427
|
explicitly in the plist; never copy the whole shell environment into it.
|
|
429
428
|
- A plist pointing at a worktree or checkout breaks when it moves: point it at a release.
|
|
430
429
|
- `http.server.HTTPServer` resolves its FQDN between `bind()` and `listen()`: with a slow
|
|
431
430
|
resolver (a macOS CI runner, a Mac offline) the port is bound but silent. Use `fs.LoopbackHTTPServer`.
|
|
432
|
-
- `logging.basicConfig` called twice: the second is a no-op
|
|
433
|
-
|
|
431
|
+
- `logging.basicConfig` called twice: the second is a no-op and the log stays empty. Configure logging once, in
|
|
432
|
+
the entry point.
|
|
434
433
|
- Hand-made `.plist.bak` files in `~/Library/LaunchAgents` are never cleaned up; keep
|
|
435
434
|
backups elsewhere.
|
|
436
435
|
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
# Recipe: the draft-and-check step of a support agent
|
|
2
2
|
|
|
3
3
|
A service answers one inbound question from its own knowledge file, or hands it to a person. It is a step of an
|
|
4
|
-
agent that answers people, not that agent: see the paragraph after the table.
|
|
4
|
+
agent that answers people, not that agent: see the paragraph after the table. Under the creating-fabric-agents
|
|
5
|
+
template (`communication-agent-template.md`) the plan-and-draft call writes the draft, not this recipe's `answer()`:
|
|
6
|
+
the draft carries a `[KB:<id>]` marker for each knowledge id the plan used, and the agent runs `check_reply(draft,
|
|
7
|
+
question, prompt_ids, max_chars=..., sources=...)` on it for the code checks. `prompt_ids` are the ids of the items
|
|
8
|
+
placed in the plan's prompt, never the plan's own list (an id the plan names outside them is held as a suspected
|
|
9
|
+
injection); each tool result of the turn enters both under an id of its own, with its system-of-record fields only, never one
|
|
10
|
+
a customer wrote (`tool.order`; the citation takes letters,
|
|
11
|
+
digits, dot, dash and underscore, no colon; knowledge ids too, so map one that does not). Every template turn that went through the plan runs
|
|
12
|
+
`check_reply` on its final text, except an off-topic plan, a plan whose only actions are ask_clarification or handoff,
|
|
13
|
+
and a triage reply: the route decides, never the draft's markers. The final pass keeps the markers. A handle
|
|
14
|
+
(`@name`) is not one of `check_reply`'s claims: it is the agent's own gate. Repair of a plan draft, and the
|
|
15
|
+
checker of another vendor, stay the agent's own (agents-that-answer-people.md): the recipe's `repair=` reruns only its
|
|
16
|
+
own `answer()` draft, and it has no checker. Built only on the service
|
|
5
17
|
kit: `fabric_openrouter` for the model, `fabric_usage` for receipts, `fabric_paths` for private, atomic state.
|
|
6
18
|
Tests: `test/test_support_agent_recipe.py` (a fake model; no network, no key).
|
|
7
19
|
|
|
@@ -130,7 +142,7 @@ FAC-SEM-162 to 165 (rule `channel-agent.declaration`).
|
|
|
130
142
|
| `stateDir` | the decisions (`decisions.jsonl`), the handoff outbox (`handoffs.jsonl`) and the usage ledger (`usage.jsonl`) |
|
|
131
143
|
| `model`, `fallback` | two models of different vendors; a lane that reads what customers write runs on an open-vendor model (DEC-0083) |
|
|
132
144
|
| `keyEnv` | the environment variable that holds the agent's own OpenRouter key; never a value in the file |
|
|
133
|
-
| `ceilingUsd`, `window` | the spend ceiling per UTC `day` or `month` |
|
|
145
|
+
| `ceilingUsd`, `window` | the spend ceiling per UTC `day` or `month` (`month` under the communication agent template, under the key's monthly limit) |
|
|
134
146
|
| `prices` or `catalog` | USD per token per model. The example's prices are placeholders, not OpenRouter's: take them from `GET https://openrouter.ai/api/v1/models` (a saved catalog file named in `catalog` is read with `prices_from_catalog`). A model without a price is never called |
|
|
135
147
|
| `maxTokens`, `maxChars` | the output budget per call and the reply's length limit |
|
|
136
148
|
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"en": "Thank you, we are looking into it and will answer here."
|
|
60
60
|
},
|
|
61
61
|
"final": {
|
|
62
|
-
"en": "
|
|
62
|
+
"en": "We could not confirm this. Please write to the support address on our site."
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -35,7 +35,10 @@ operator gives in Fabric (Agents & access): callee `fabric`, capability `board.r
|
|
|
35
35
|
binding credential, shown once, goes into `credential_file` (0600, atomic) and never into the answer, a log or argv.
|
|
36
36
|
Keep the file in the service's data directory or the operator's vault.
|
|
37
37
|
3. Each connection joins with `fabric.board.join {projectId}` (`join_board` / `joinBoard`). It re-reads `hub.json`,
|
|
38
|
-
checks the pid it names is alive,
|
|
38
|
+
checks the pid it names is alive, tells the owner of the door's listener from the socket table (DEC-0154 HA-2:
|
|
39
|
+
another OS account's process, or one whose owner cannot be told, is `HubError` code `foreign-user`, retryable, with
|
|
40
|
+
nothing sent), and only then sends the credential (a session already running maps that refusal, as any transport
|
|
41
|
+
failure, to `board.down` with reason `unreachable` until FAA-252). A missing grant is `HubError` code
|
|
39
42
|
`not_authorized` whose `data.ask` holds the arguments to ask with.
|
|
40
43
|
|
|
41
44
|
## Wiring it into the service
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md
CHANGED
|
@@ -37,6 +37,7 @@ const answer = await request('GET', 'https://api.example/v1/items', { headers, d
|
|
|
37
37
|
| The body is read to one byte past `max_bytes`; over it is an error | 8 MiB | a runaway answer never fills memory |
|
|
38
38
|
| Redirects are answered, never followed | | a redirect must not carry the `Authorization` header to another host |
|
|
39
39
|
| Plain `http://` is refused unless the host is loopback or `insecure=True`; credentials in a URL are refused | | a token never crosses a network in clear, nor sits in a URL |
|
|
40
|
+
| Plain `http://` to a loopback host goes on a connection whose server end is this OS account's process, told from the socket table before a byte is sent and never through a proxy from the environment (`fabric_peer_owner`); another account's listener, root's, or one whose owner cannot be told is kind `foreign-user`, nothing sent, never retried. The Node twin sends such a request through `node:http` on a checked connection, not `fetch` | | DEC-0154 HA-2: loopback is one per computer, not one per OS account |
|
|
40
41
|
| Header names must be tokens; a value with a line break or NUL is refused | | header injection |
|
|
41
42
|
| A JSON body is written as `JSON.stringify` writes it: a lone surrogate escaped, NaN and Infinity refused | | both twins send the same bytes |
|
|
42
43
|
| Whatever the transport throws is a connection error, in both languages; a timeout is a timeout | | |
|
|
@@ -44,7 +45,7 @@ const answer = await request('GET', 'https://api.example/v1/items', { headers, d
|
|
|
44
45
|
| `Response.json()` reads as `JSON.parse` does: a byte order mark skipped, `NaN` and `Infinity` refused | | |
|
|
45
46
|
|
|
46
47
|
Every answer comes back as a `Response`, whatever its status; `HttpError` means no usable answer came, with `kind`
|
|
47
|
-
`connection`, `timeout`, `too-large` or `
|
|
48
|
+
`connection`, `timeout`, `too-large`, `refused` or `foreign-user`, the status when there was one, and the URL redacted to scheme,
|
|
48
49
|
host, port and path (`redact_url`); a URL that is not http or https keeps only its scheme (`htps:<redacted>`, and `ssh:<redacted>`
|
|
49
50
|
for `ssh://h:99999/r` too), and an http, https, ws, wss or ftp URL that does not parse (Node reads it with `new URL`;
|
|
50
51
|
Python reads its own special-scheme form and prints what it finds, never the user information), or a value with no
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md
CHANGED
|
@@ -62,8 +62,12 @@ the fabric-dashboards MCP (`list_services`, never guessed) and hand over the
|
|
|
62
62
|
`open_link` its `link` tool returns for the page's path; to open it, call the
|
|
63
63
|
host's `open` with `fallback=never`. Never `open <url>`, `open -a Safari`,
|
|
64
64
|
`xdg-open`, `start <url>`, `webbrowser.open` or `shell.openExternal` on this
|
|
65
|
-
agent's pages. A browser only when `host_status` confirms `not_installed
|
|
66
|
-
|
|
65
|
+
agent's pages. A browser only when `host_status` confirms `not_installed`, and
|
|
66
|
+
a raw `http://127.0.0.1:<port>` address only after the listener on that port is
|
|
67
|
+
this OS account's (DEC-0154 PL-3 and HA-2; the adapter's `building-fabric-services/scripts/fabric_peer_owner.py`: `server_end` of a connection it opens and sends nothing on; `check_service.py <id>.<instance>` prints it as `listener.owner`): a
|
|
68
|
+
`foreign-user` listener is reported, never opened (the link gate below does not
|
|
69
|
+
read the owner yet, FAA-251). An installed host that failed
|
|
70
|
+
to open is reported, not replaced by HTTP. Links
|
|
67
71
|
carry no token or login code. Every completion and notification action passes
|
|
68
72
|
the Fabric Agent Adapter's link gate before delivery: `check_dashboard_link.py`
|
|
69
73
|
in its `building-fabric-services/scripts`, run from that skill's directory;
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md
CHANGED
|
@@ -52,7 +52,9 @@ service's own local floor (the descriptor token, the Host guard) has authenticat
|
|
|
52
52
|
the same owner; **T2** everyone else, and any call relayed onto loopback from off the machine (a tunnel, a proxy, the
|
|
53
53
|
hub forwarding a T2 caller). DL-26: a T0 or T1 caller is never required to present a token; pass the tier,
|
|
54
54
|
`gate(tool, headers, tier="T0")` or `server.handle(message, headers, tier="T0")`, and the call passes with no
|
|
55
|
-
delegation.
|
|
55
|
+
delegation. On a computer with more than one counted OS account, a loopback caller with no token is T0 only after the
|
|
56
|
+
door checks the peer's account (the pinned DEC-0154 HA-3 and DL-25; the kit's own peer check is FAA-227), so pass
|
|
57
|
+
`tier="T0"` there only for a caller the descriptor token authenticated. A token such a caller presents is still verified and refused when invalid. A T2 caller takes the whole
|
|
56
58
|
profile. Money limits and human gates bind at every tier.
|
|
57
59
|
|
|
58
60
|
**The family and the agent network** (DEC-0130, wording settled by DEC-0137). The operator's family is every agent
|
|
@@ -77,7 +77,9 @@ untraced work carries no trace pair.
|
|
|
77
77
|
**Check with a real client**, never only an SDK: `FABRIC_REAL_CLIENT=1 python3 -m unittest
|
|
78
78
|
test.test_real_client` in the adapter repository, or by hand: a throwaway directory, a
|
|
79
79
|
temporary `--mcp-config` (mode 600, the token in a header) and `--strict-mcp-config`, so
|
|
80
|
-
the operator's own configuration is not touched
|
|
80
|
+
the operator's own configuration is not touched. Write that config only after `check_service.py <id>` reads
|
|
81
|
+
`listener.owner` PASS: the client cannot tell the port's owner, so the side that hands it the token does (DEC-0154
|
|
82
|
+
HA-2):
|
|
81
83
|
|
|
82
84
|
```bash
|
|
83
85
|
claude -p "Reply with the single word OK." --strict-mcp-config --mcp-config "$TMP/mcp.json" \
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md
CHANGED
|
@@ -59,6 +59,7 @@ A module imports only modules listed before it in `kit.json`, so the kit has no
|
|
|
59
59
|
| L0 | `fabric_time` | strict RFC 3339, UTC days, formatting, day windows, the clock ([dates](#dates-and-times)) |
|
|
60
60
|
| L0 | `fabric_identifiers` | identifier equality by normal form (DEC-0076) |
|
|
61
61
|
| L0 | `fabric_secrets` | the contract's one credential detector (DEC-0044) |
|
|
62
|
+
| L0 | `fabric_peer_owner` | which OS account owns each end of a loopback connection, from the computer's socket table (DEC-0154 HA-2, FAA-226): `listener`, `server_end`, `peer`, `CheckedHTTPConnection`; the Python file is Project Observatory's `peer_owner.py` 1.1.0 vendored from commit `f86f0622` with the readings its header names (`1.1.0+kit.1`), the Node twin reads netstat, `/proc` and the owner SID where Python reads `sysctl`, `/proc` and `GetExtendedTcpTable` |
|
|
62
63
|
| L1 | `fabric_crypto` | Ed25519 signing (a Montgomery ladder that does not branch on the key) and verification, P-256 verification, base64url, sha256 |
|
|
63
64
|
| L1 | `fabric_base` | the protocol name, `ServiceError`, small shared helpers |
|
|
64
65
|
| L1 | `fabric_platform` | Windows: named pipes, owner-only files (DEC-0032) |
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md
CHANGED
|
@@ -48,10 +48,12 @@ refuses a port another instance declares), the service's directories, and comman
|
|
|
48
48
|
carry a credential. Only `doctor` and `update` are commands a host may run.
|
|
49
49
|
|
|
50
50
|
**Started is not ready.** Readiness is the well-known document, `GET /.well-known/fabric-service`: answered without
|
|
51
|
-
authentication, from memory, in under 100 ms, with `service.id` and `service.instance` equal to the descriptor's
|
|
51
|
+
authentication, from memory, in under 100 ms, with `service.id` and `service.instance` equal to the descriptor's,
|
|
52
|
+
on a listener of this OS account (DEC-0154 HA-2: another account's listener is `foreign-user` whatever it answers).
|
|
52
53
|
`status` is `starting`, `ready`, `degraded` or `stopping`; `degraded` is always present, and an empty list asserts
|
|
53
54
|
full health. A process that cannot serve does not answer, and a host derives `down`. An install or upgrade polls
|
|
54
|
-
that document until `service.id` matches (at most 40 s) before it calls the service started
|
|
55
|
+
that document until `service.id` matches (at most 40 s) before it calls the service started (the launchd installers;
|
|
56
|
+
systemd and Task Scheduler do not poll yet, FAA-253).
|
|
55
57
|
|
|
56
58
|
| Id | Rule | Here | Kit (Python / Node) |
|
|
57
59
|
|---|---|---|---|
|
|
@@ -87,11 +89,15 @@ that document until `service.id` matches (at most 40 s) before it calls the serv
|
|
|
87
89
|
1. Build or unpack the release into `~/.local/share/<id>/releases/<version>-<sha12>/`
|
|
88
90
|
(a venv or `node_modules` inside it). Never edit a release after it is written.
|
|
89
91
|
2. `write_descriptor` (port and id claims are checked here, and a new descriptor without `source.repository` is
|
|
90
|
-
refused; a rewrite of an installed one warns, FAA-198).
|
|
92
|
+
refused; a rewrite of an installed one warns, FAA-198). "Port claim" here is the kit's older sense, a port no other
|
|
93
|
+
descriptor declares; the pinned contract's HA-1 claim file per OS account, and a port free on the whole computer,
|
|
94
|
+
are not written or checked yet (FAA-225, after the contract's CT-228).
|
|
91
95
|
3. `launchd_install` (Node: `await launchdInstall(...)`, the same steps): write + `plutil -lint`, read `launchctl print-disabled gui/<uid>`,
|
|
92
96
|
`enable` **only when no override is recorded** (a first install), `bootout` and wait
|
|
93
97
|
until `launchctl print gui/<uid>/<label>` fails, `bootstrap` with up to five retries,
|
|
94
|
-
then poll the well-known document until it answers with this `id.instance
|
|
98
|
+
then poll the well-known document until it answers with this `id.instance` on a listener of this OS account
|
|
99
|
+
(DEC-0154 HA-2: another account's listener, or one whose owner cannot be told, is `foreign-user` and never a
|
|
100
|
+
successful install; the error names it).
|
|
95
101
|
4. Upgrade = new release, rewrite the plist with the new path, `launchd_install`
|
|
96
102
|
again, then `prune_releases(releases_dir, current=<new release>)`: the running release
|
|
97
103
|
and the one before it stay for rollback, older ones go (LC-11, LC-15).
|