@passioncode-ai/fabric-agent-adapter 0.62.0 → 0.64.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 +136 -0
- package/README.md +9 -5
- package/SECURITY.md +6 -1
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/hooks/hooks.json +13 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +6 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +48 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/channel-agent.example.json +103 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +141 -17
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +183 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +4 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +5 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/practices.md +52 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +2 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +21 -20
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/native-host-hooks.md +7 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +209 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +34 -6
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +39 -6
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +4 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +455 -9
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +35 -6
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_hosts.py +4 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,141 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.64.0 (2026-11-05)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- working-with-fabric: the retro after work (FAA-206 part 1; Fabric's obligation `retro-after-work`, its
|
|
8
|
+
`fabric_whoami` since `e1eecec0`, ADR-0188 §5). A Claude Code `PostToolUse` hook on Fabric's release tools adds one
|
|
9
|
+
line of context when a claim is released as done (`fabric_work_release` with `how` or `outcome` done, or the older
|
|
10
|
+
`fabric_task_release` with `outcome` succeeded): write the retro in this turn, `observatory_lesson` with one class
|
|
11
|
+
key per lesson for what went wrong, insight/1 lines for what was learned about the product, or one line that there
|
|
12
|
+
is nothing, no values in either (without `observatory_lesson`, in the turn's answer). The ending is read as Fabric
|
|
13
|
+
reads it (`how`, then `outcome`). Any other release, any other tool, a release Fabric refused (its answer says
|
|
14
|
+
`released: false`, which is how Fabric refuses one, `already-released`, or carries `isError`), a session release
|
|
15
|
+
that released nothing (`claims: []`) and a repeat (Fabric answers it with the first release's `at`) print nothing; an answer the hook cannot read keeps the line. Fabric's
|
|
16
|
+
`retro-after-work` is on its `main` (`e1eecec0`) and in no release yet, so neither the line nor the preamble sends
|
|
17
|
+
the agent to `fabric_whoami` for it. It says it in a session Fabric
|
|
18
|
+
launched too, the one hook that speaks there besides a dispatched run's exit contract and the prompt hook's verdict on it. No hook can speak to the
|
|
19
|
+
model when a session ends, so the session-start preamble names the retro itself (it does not rely on `fabric_whoami`, since no released Fabric carries `retro-after-work` yet) for the end of the work or the session.
|
|
20
|
+
Measured through the real CLI under a load average of about 68: median 155 ms a call against 50 ms for a bare
|
|
21
|
+
`python3 -S`, 1006 ms at most over 20 calls ([raw](docs/reports/2026-11-05-faa-206-retro/raw/hook-timing.log)).
|
|
22
|
+
- creating-fabric-agents and adapting-projects-to-fabric: an agent built or adapted with the kit closes each piece of
|
|
23
|
+
work with the same retro (practices, *Reliability*).
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- A dispatched run's exit contract (step 7) and `references/on-demand-session.md` no longer say `fabric_whoami` asks
|
|
28
|
+
the retro: no released Fabric carries `retro-after-work` (0.63.0 shipped that wording).
|
|
29
|
+
- The session hooks read their input as UTF-8 bytes, so a hook payload in Cyrillic (a release's note) is no longer
|
|
30
|
+
dropped on Windows before Python 3.15, where the text stream decodes with the locale's code page.
|
|
31
|
+
|
|
32
|
+
### Upgrading
|
|
33
|
+
|
|
34
|
+
- The plugin's `hooks.json` gains a `PostToolUse` entry whose matcher names Fabric's two release tools on the `fabric`
|
|
35
|
+
server (or a plugin's `plugin_<name>_fabric`) only; nothing to configure. Codex and Kimi get no post-tool hook from `install-host-hooks` in this release.
|
|
36
|
+
- working-with-fabric SKILL.md, to stay within its word budget, no longer repeats five things its
|
|
37
|
+
`references/session-hooks.md` keeps (README keeps the first three too, SKILL-CARD and SECURITY.md the first two): that the
|
|
38
|
+
transcript's tail can hold a secret the session displayed, the off switch `FABRIC_SESSION_NO_REPOSITORIES=1`, that
|
|
39
|
+
session start ignores compact and resume, how a reminder Fabric gave is tagged, and where a turn `watch-mail`
|
|
40
|
+
delivered prints it.
|
|
41
|
+
|
|
42
|
+
## 0.63.0 (2026-11-05)
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- working-with-fabric: a session Fabric started on a board request (FAA-201, Fabric's on-demand spec at `1329b60f`).
|
|
47
|
+
A session is the run only when its own prompt's first line `fabric-dispatch/1 workflow=wf_<16 hex> handoff=<id>
|
|
48
|
+
request=<uuid>` names the same three as `FABRIC_DISPATCH_WORKFLOW`, `FABRIC_DISPATCH_HANDOFF` and
|
|
49
|
+
`FABRIC_DISPATCH_REQUEST` (Switchboard's `dispatch` sets all three and writes the line, SB-247, released in
|
|
50
|
+
Switchboard 0.7.8 on 2026-11-04); the environment alone makes no run, since a reader or a subshell inherits it, and a
|
|
51
|
+
line that disagrees, or an environment without all three, makes none either: that session says so in the request's
|
|
52
|
+
thread and stops (Fabric's spec 3.5 on its main at `deaf51fe`, agreed on the project board, 2026-11-05). A line with no workflow in the
|
|
53
|
+
environment is quoted text. The session-start hook, which sees no prompt yet, prints the exit contract on that
|
|
54
|
+
condition in place of the preamble (naming the session's own `sessionId` when it is a UUID, and after a `/clear`
|
|
55
|
+
the session id the clear replaced, which a replayed accept needs for its `leaseId` and a pending handoff is accepted
|
|
56
|
+
under, or the new id when the replaced one is unknown), a retro before the run ends, also in a session
|
|
57
|
+
Fabric launched: `fabric_whoami`, the packet
|
|
58
|
+
(`observatory_checkpoint_latest`), the request and its thread (`com.get`, `com.list`), the lease
|
|
59
|
+
(`observatory_handoff_accept` with its owner, key and the executor the launcher names), `com.status` and the claim,
|
|
60
|
+
a request an earlier run left accepted or in progress (`com.responder_replace`, `com.reconcile`), in_progress
|
|
61
|
+
checkpoints, renewals about every 20 s (work that outlasts the run gives its state in a `com.progress` note and is left accepted for the next run,
|
|
62
|
+
which Fabric's dispatcher starts since its main at `fff4b6d9`, in no release yet, Fabric itself moving the open
|
|
63
|
+
Observatory workflow to that run, as its spec at `c560d97e` says (sections 3.4 and 3.6; its code does not yet); when the request's deadline is not after
|
|
64
|
+
run-ends-at plus 10 minutes, since no next run starts that close to it (`RUN_END_TO_DEADLINE_MS` on Fabric's main at
|
|
65
|
+
`afe28a60`, in no release yet), it fails known instead), the reply and `com.complete`, and the closing checkpoint with its
|
|
66
|
+
`workflowId` (`done`, or for `failed_known` `failed` once Observatory takes it, OBS-323, until then `blocked` with
|
|
67
|
+
the reason). The prompt hook, which sees the line, tells the session it is the run (or, for a line that disagrees,
|
|
68
|
+
that it is none), and prints no unread line or nudge in such a session. Only the
|
|
69
|
+
first session whose line names the dispatch holds it: another one gets one line
|
|
70
|
+
telling it not to accept, claim, complete or close (the dispatch is told by its `FABRIC_DISPATCH_ID` when the
|
|
71
|
+
launcher sets one in the form `[A-Za-z0-9._-]{1,64}`, as Switchboard 0.7.8 does), and a `/clear` in the holding session keeps the contract (the
|
|
72
|
+
hooks keep one record per run under `dispatch/`, pruned after 7 days by the Claude Code turn and end hooks and by the next session start on a board request). Every
|
|
73
|
+
checkpoint the session writes repeats the goal, constraints, artifacts and credentials of the packet's
|
|
74
|
+
`checkpoint.body`, since Observatory keeps only the latest body; the session takes the request from the environment
|
|
75
|
+
or the first line and its thread from `com.get`, since Observatory's write redacts the packet's board references. A
|
|
76
|
+
Codex run gets the hook only where its `CODEX_HOME` trusts it (Switchboard's isolated homes carry the person's trust since 0.7.8); a host whose hook gets no session id (the native bridges) is always told
|
|
77
|
+
the conditional contract and writes no record; on Windows, where each hook runs under a new Git Bash, a `/clear` holds
|
|
78
|
+
nothing (it cannot tell the holder from a helper), so the cleared session is told it runs inside a run that holds it,
|
|
79
|
+
stays a helper and Fabric takes the workflow over after the run. On any system, a `/clear` while another session
|
|
80
|
+
holds the dispatch (a nested session's clear) is told the same.
|
|
81
|
+
`watch-mail` refuses to start in a dispatched session, and the dashboard-kit notice says nothing there. `references/on-demand-session.md` holds the steps and every argument; the
|
|
82
|
+
hooks tell a dispatched session from this version on.
|
|
83
|
+
- working-with-fabric: whatever waits on the Project is answered in the same turn (FAA-205, the operator's rule of
|
|
84
|
+
2026-11-04, board thread `11646774`): the session-start preamble, the unread line (`N unread on the project board:
|
|
85
|
+
com.list; answer what waits on you in this turn: ...`, its prefix unchanged for watchers) and the skill's board step
|
|
86
|
+
say to answer a request, an announcement or a question with its state now (done, where, when, what blocks) and send
|
|
87
|
+
the result later in a second message; `references/project-board.md` gains "Answering at once".
|
|
88
|
+
- The owner's word is done the first time (FAA-207, the operator's rule of 2026-11-04 relayed on the board, thread
|
|
89
|
+
`a7a9d05d`; the contract's CT-220): working-with-fabric's Step 5 and creating-fabric-agents' practices say the agent
|
|
90
|
+
does it itself, hands the person no command to run, asks one short question on a risk and then acts, and passes on
|
|
91
|
+
only a human gate with its reason; money limits and secrets stay.
|
|
92
|
+
- The channel-agent standard v1 at its round 4 (FAA-197): creating-fabric-agents' practices gain a section of the
|
|
93
|
+
standard's rules for agents on a channel people write to (the ticket as the unit, one outcome per ticket, answer
|
|
94
|
+
rather than route, repair before hand-off, declared sources with a maximum age, hand-off queues with their answered
|
|
95
|
+
signal, text from a channel as data (the contract's proposed CA-8), metadata only, the declaration's form, with a
|
|
96
|
+
holding reply owing a final text, FAC-SEM-164). The support-agent recipe:
|
|
97
|
+
- a hand-off its checks made carries the refused `draft` in the returned decision and to an `on_handoff` the agent
|
|
98
|
+
passes, never into a file (`decisions.jsonl` or the default outbox), so a repeat of the id returns it without; `answer(new_id, text, repair=decision)` rewrites it once with the refusal's reasons: a pass is
|
|
99
|
+
a reply with `repaired: true`, and a repair that ends otherwise (a second refusal, a model `HANDOFF`, no knowledge on
|
|
100
|
+
the retry; not a `model:*` outage) a hand-off with `fallback: true` (the standard's 4.4 and its ticket's
|
|
101
|
+
`fallback`), which `repair=` then refuses, as it refuses a repair under the refused decision's own message id;
|
|
102
|
+
- `KnowledgeSource` (or `knowledgeMaxAgeSeconds` in the config, which reads the file at once) reads the knowledge
|
|
103
|
+
file as a source: read again when it changes (its time, size, inode or status-change time, the last a creation time on Windows), the last good version kept when a read fails or the file has the
|
|
104
|
+
wrong shape (nested too deep included), `state()` with the age of that version, from its file's modification
|
|
105
|
+
time;
|
|
106
|
+
- `Knowledge.load` refuses a file that is not JSON, one nested deeper than 32 levels (on every Python), a file that is not an object with an `entries` list, an entry whose tags are not a list
|
|
107
|
+
of text or whose question is not text, each as a `RecipeError`;
|
|
108
|
+
- `channel-agent.example.json`, one project's declaration under
|
|
109
|
+
`https://fabric.passioncode.ai/agent-contract/extensions/channel-agent/0.1` in the round 4 form, held to it and to
|
|
110
|
+
the example config's maximum age by `test/test_channel_agent_example.py`.
|
|
111
|
+
|
|
112
|
+
### Upgrading
|
|
113
|
+
|
|
114
|
+
- A recopied `support_agent.py` refuses at start (exit 1, a `RecipeError` naming the entry or the file's shape) a knowledge file 0.62.0
|
|
115
|
+
took in other ways, so an agent that ran on 0.62.0 can stop at start now. Per shape, 0.62.0: a file that is not an
|
|
116
|
+
object with an `entries` list (`[]`, `{}`, a string) loaded as an empty set and handed every message off as
|
|
117
|
+
`no-knowledge`; `{"entries": null}` or `{"entries": <number>}` stopped it at start with a `TypeError`; an entry with `tags: null`
|
|
118
|
+
failed each message with a `TypeError`; an entry whose `tags` is a string or holds something other than text, or
|
|
119
|
+
whose `question` is not text, was read and answered from. A file nested deeper than 32 levels, which 0.62.0
|
|
120
|
+
loaded, is refused too. Fix the file before upgrading.
|
|
121
|
+
- A custom `on_handoff` that stores the row it is given now receives the refused `draft`, which can echo what the
|
|
122
|
+
customer wrote: drop `draft` before storing it (the default outbox does).
|
|
123
|
+
- A session carrying `FABRIC_DISPATCH_WORKFLOW` now gets the exit contract at session start, on the condition that its
|
|
124
|
+
own prompt starts with the dispatch line naming the same three, even when Fabric launched it; with the workflow
|
|
125
|
+
set but the handoff or the request missing or malformed, it gets one line that no session there is a run. Its prompt hook prints no unread line or reminder (the nudges file
|
|
126
|
+
stays), and `watch-mail` refuses to start there. A launcher that sets the workflow alone starts no run any more.
|
|
127
|
+
- The live docs name a closed module neutrally, as the organization edition (`references/package.md` in
|
|
128
|
+
building-fabric-services and two backlog rows), at its owner's request; dated reports and handoffs keep their words.
|
|
129
|
+
- A session that does a board request with no person in its chat stops before a production step and names it for a
|
|
130
|
+
person, unless the operator's own words, relayed verbatim with their date and source from one of his own projects (provenance `trusted`) and with a scope naming the step, approve it (working-with-fabric `references/project-board.md`; board threads `1add3c03` and `24e0c88e`; Fabric's board rule at `108239c0`).
|
|
131
|
+
|
|
132
|
+
### Fixed
|
|
133
|
+
|
|
134
|
+
- Every annotation in the shipped scripts (the kit, the skills' scripts, the recipe and the Hermes adapter) resolves
|
|
135
|
+
with `typing.get_type_hints` (FAA-203, a board finding, thread `9fd84d47`): `fabric_estate.py` and four working-with-fabric modules import the `typing` names they use, and
|
|
136
|
+
`LoginCodes`' `token` is written `Optional[Union[str, Callable[[], str]]]`, which Python 3.9 reads. A vendored copy
|
|
137
|
+
of `fabric_estate.py` or `fabric_auth.py` reads as older; it worked at run time before.
|
|
138
|
+
|
|
3
139
|
## 0.62.0 (2026-11-04)
|
|
4
140
|
|
|
5
141
|
A new install names the repository of its code, and a service says what it uses, so a map of the installed agents can
|
package/README.md
CHANGED
|
@@ -23,7 +23,8 @@ The skills are `adapting-projects-to-fabric`, `creating-fabric-agents`,
|
|
|
23
23
|
`working-with-fabric` is for the other side: a coding agent (Claude Code, Codex, Kimi Code, Kilo, Hermes) opened in
|
|
24
24
|
a terminal that works in a project through Fabric. It finds Fabric (`hub.json`, its `pid`), has the machine's
|
|
25
25
|
lifecycle broker start it when it is down, registers `fabric-mcp` once with no secret, calls `fabric_whoami`
|
|
26
|
-
first, reads the project board (`com.status`, `com.list`, `com.read_ack` only what was read)
|
|
26
|
+
first, reads the project board (`com.status`, `com.list`, `com.read_ack` only what was read), answers what waits on its
|
|
27
|
+
Project in the same turn with its state (the result later; a request a responder should take stays queued), and asks other
|
|
27
28
|
Projects only through `com.submit`, asks who is working on what and claims its task before it starts
|
|
28
29
|
(`fabric_work_list`, `fabric_work_claim`, Fabric v0.3.5 and later), links tasks to backlog rows instead of copying them, and
|
|
29
30
|
releases at the end (`fabric_work_release`). `scripts/fabric_session.py status` prints where Fabric stands and one
|
|
@@ -33,16 +34,19 @@ next action:
|
|
|
33
34
|
python3 plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py status
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
In Claude Code the plugin adds
|
|
37
|
+
In Claude Code the plugin adds five session hooks: SessionStart (at startup and `/clear`, never on compact or
|
|
37
38
|
resume) puts that preamble into the session when the MCP server `fabric` is registered, Fabric runs and the project
|
|
38
39
|
uses Fabric (one line pointing at `fabric_whoami` in a folder with no sign of it); when Fabric
|
|
39
40
|
is down it adds one line (skip `fabric_whoami`, start Fabric through the broker if the task needs it), and only in a
|
|
40
|
-
project that uses Fabric;
|
|
41
|
+
project that uses Fabric; in a session Fabric started on a board request (`FABRIC_DISPATCH_WORKFLOW` set) it prints the
|
|
42
|
+
exit contract instead, on the condition that the session's own prompt starts with the dispatch line naming the same
|
|
43
|
+
three, and UserPromptSubmit, which sees that line, confirms the run or says it is none (FAA-201); Stop records each turn's facts (HEAD, files changed, lines added and removed, never content)
|
|
41
44
|
and the board's unread count, and, once the running Fabric takes them (unless `FABRIC_SESSION_NO_REPOSITORIES=1`), the other repositories the turn touched (it then
|
|
42
45
|
reads the last 512 KB of the session transcript (message text and tool output included, which can hold a secret the session displayed; read in memory only), uses only tool names and path arguments, and sends host/owner/name, never a
|
|
43
46
|
local path or text); UserPromptSubmit adds one line only when that count rose, and each reminder Fabric gave on a turn once (a turn `watch-mail` delivered prints there instead); SessionEnd releases what the
|
|
44
|
-
session left claimed.
|
|
45
|
-
|
|
47
|
+
session left claimed. After a call to Fabric's release tools that releases a claim as done, PostToolUse adds one
|
|
48
|
+
line naming the retro (FAA-206; Fabric's `retro-after-work`, on its `main`, in no release yet). Stop, UserPromptSubmit and SessionEnd spool and call Fabric only when the running Fabric
|
|
49
|
+
advertises `session-hooks/1`; Stop and SessionEnd always prune their own stale spool folders. All are silent in a session Fabric launched (except the retro line after a release of finished work, and the exit contract in one started on a board request, where the prompt hook prints no unread line or reminder), never block the session, read no Fabric token (an `origin` URL's embedded credential is dropped in memory, never
|
|
46
50
|
kept or sent) and always
|
|
47
51
|
exit 0 once `python3` starts (on Windows, Claude Code runs hooks in Git Bash, or PowerShell without it, where `python3`
|
|
48
52
|
must name Python 3.9 or later; FAA-183). `fabric_session.py watch-mail` waits for board mail under a background watcher, and `install-host-hooks
|
package/SECURITY.md
CHANGED
|
@@ -32,6 +32,9 @@ 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
|
+
- The working-with-fabric PostToolUse hook (FAA-206) reads only the release call Claude Code gives it on stdin (the
|
|
36
|
+
tool's name, its input, which can carry the agent's note, and Fabric's answer), in memory: it writes, logs and sends
|
|
37
|
+
nothing and prints one fixed line or nothing.
|
|
35
38
|
- The working-with-fabric session hooks run as the person: Stop and SessionEnd run `git`
|
|
36
39
|
in the session's folder (counts only, at most 1.5 s; with the mark below, also that folder's own `origin`, only to leave its repository out), read the process table to tell a
|
|
37
40
|
session Fabric launched. Stop also, only once the running Fabric's `fabric_session_observe` takes
|
|
@@ -47,7 +50,9 @@ bounded and non-publishing until explicit scoped grants exist in a project bindi
|
|
|
47
50
|
remote, `owner` is the folder path on that server before the repository's name). The mark is
|
|
48
51
|
removed only by a worker that sees a schema without the field, so after Fabric is downgraded
|
|
49
52
|
the turns that end before a worker next reaches the running Fabric (about a minute; longer while
|
|
50
|
-
rejoins fail) are still read; none of their names is sent. They write their spool and log under the user's cache folder (0600),
|
|
53
|
+
rejoins fail) are still read; none of their names is sent. They write their spool and log under the user's cache folder (0600), and, in a session
|
|
54
|
+
started on a board request (FAA-201), one record of which session holds that dispatch (the holding session's id and, after a `/clear`, the id it replaced, the agent's process id and a time, 0600,
|
|
55
|
+
pruned after 7 days),
|
|
51
56
|
and the delivery worker starts `fabric-mcp` in its own process group and ends that group
|
|
52
57
|
(`killpg`, or `taskkill /T /F` on Windows) at its 10 s deadline. They read no Fabric token. An
|
|
53
58
|
`origin` URL that embeds a credential (`https://user:token@host/...`) is read by `git config`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/fabric-agent-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.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.64.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count, and, once the running Fabric's fabric_session_observe takes them (unless FABRIC_SESSION_NO_REPOSITORIES=1), the other repositories the turn touched (the last 512 KB of the session transcript, message text and tool output included (a secret the session displayed can be there; read in memory only), is then read and only the current turn's tool names and path arguments are used; host/owner/name leave the machine, never a local path or text) and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). All silent in a session Fabric launched. Every hook reads no Fabric token (a credential embedded in an origin URL is read by git config and dropped in memory, never spooled or sent), prints at most one block, always exits 0 once python3 starts (on Windows it must name Python in Git Bash, FAA-183), and hands any Fabric call to a detached worker with a 10 s deadline.",
|
|
2
|
+
"description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count, and, once the running Fabric's fabric_session_observe takes them (unless FABRIC_SESSION_NO_REPOSITORIES=1), the other repositories the turn touched (the last 512 KB of the session transcript, message text and tool output included (a secret the session displayed can be there; read in memory only), is then read and only the current turn's tool names and path arguments are used; host/owner/name leave the machine, never a local path or text) and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). All silent in a session Fabric launched but the retro line below. Every hook reads no Fabric token (a credential embedded in an origin URL is read by git config and dropped in memory, never spooled or sent), prints at most one block, always exits 0 once python3 starts (on Windows it must name Python in Git Bash, FAA-183), and hands any Fabric call to a detached worker with a 10 s deadline. In a session Fabric started on a board request (FABRIC_DISPATCH_WORKFLOW), session start prints the exit contract instead of the preamble on the condition that the session's own prompt starts with the dispatch line naming the same three, launched or not, and the prompt hook, which sees that line, confirms the run or says it is none, launched or not, with no unread line or nudge, and the dashboard-kit notice says nothing (FAA-201). After a call to Fabric's fabric_work_release or fabric_task_release that releases a claim as done, PostToolUse adds one line naming the retro (FAA-206); after any other call, or a release Fabric's answer refuses (released: false, already-released) or that released nothing, nothing.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -55,6 +55,18 @@
|
|
|
55
55
|
}
|
|
56
56
|
]
|
|
57
57
|
}
|
|
58
|
+
],
|
|
59
|
+
"PostToolUse": [
|
|
60
|
+
{
|
|
61
|
+
"matcher": "^mcp__(fabric|plugin_[A-Za-z0-9_-]+_fabric)__fabric_(work|task)_release$",
|
|
62
|
+
"hooks": [
|
|
63
|
+
{
|
|
64
|
+
"type": "command",
|
|
65
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook post-tool-use",
|
|
66
|
+
"timeout": 5
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
58
70
|
]
|
|
59
71
|
}
|
|
60
72
|
}
|
|
@@ -5,7 +5,7 @@ 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.64.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "3830a3353923e20f826d466610e1c479f120bec0"
|
|
11
11
|
---
|
|
@@ -248,6 +248,11 @@ shared reference above. An agent someone builds for themselves is not a PassionC
|
|
|
248
248
|
its licence is its owner's choice and none of those files is required of it (an agent built new with
|
|
249
249
|
creating-fabric-agents carries a `LICENSE` naming its holder; an existing one keeps what it has).
|
|
250
250
|
|
|
251
|
+
**An adapted agent closes each piece of work with a retro**, as Fabric's `retro-after-work` (on its `main`, in no release yet) asks every session:
|
|
252
|
+
`observatory_lesson` for what went wrong (one class key per lesson), insight/1 lines for what it learned about the
|
|
253
|
+
product, or one line that there is nothing; a resident service does it when a job ends, and without Project
|
|
254
|
+
Observatory it writes the lines to its own retrospective file (creating-fabric-agents practices, *Reliability*).
|
|
255
|
+
|
|
251
256
|
## Completion format
|
|
252
257
|
|
|
253
258
|
Report:
|
|
@@ -14,7 +14,7 @@ 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.64.0"
|
|
18
18
|
contract-version: "0.1.0"
|
|
19
19
|
extension: "fabric-service/0.1"
|
|
20
20
|
extension-commit: "3830a3353923e20f826d466610e1c479f120bec0"
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md
CHANGED
|
@@ -22,9 +22,14 @@ service that calls it owns its channel (mail, a chat, a store review) and its re
|
|
|
22
22
|
This recipe is the draft-and-check step of an agent that answers people, not that agent, and used alone it falls short
|
|
23
23
|
of one. Every hand-off goes to `on_handoff`, by default the outbox a person reads, at the first refusal: the practices
|
|
24
24
|
name that as the failing pattern (P2). An agent that repairs passes its own `on_handoff=` so a failed check reaches
|
|
25
|
-
its repair step rather than a person; the
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
its repair step rather than a person; a hand-off the checks made carries the failed checks in `reasons` and the
|
|
26
|
+
refused `draft`, and the repair step passes that decision back as `answer(new_id, text, repair=decision)`: the model
|
|
27
|
+
gets its draft and the reasons, and a pass comes back as a reply with `repaired: true`, a second refusal as a hand-off
|
|
28
|
+
with `fallback: true` (the channel-agent standard's 4.4 and its ticket's `fallback`, FAA-197). It calls under a new message id, since the stored decision answers any repeat of the same id (`repair=` under the
|
|
29
|
+
refused decision's own id is refused with a `RecipeError`); `repair=` takes only a hand-off the checks
|
|
30
|
+
made (not `no-knowledge`, `model-unsure` or `model:*`), and never a repeat: a repeat of the id returns the stored
|
|
31
|
+
decision, which has no draft, so the repair step keeps the decision it was handed and passes each first decision to
|
|
32
|
+
`repair=` once. A repair the model declines (`HANDOFF`) or that finds no knowledge is marked `fallback: true` too: it marks a spent repair; a `model:*` outage is not one. `on_handoff` runs under the agent's lock, before the decision is saved, so it queues the
|
|
28
33
|
hand-off and returns: since 0.55.0 a call to `answer` from inside it, on the same thread, is refused with
|
|
29
34
|
`ReentryError` (a `RecipeError`), and unless `on_handoff` catches it the first call raises it too, its draft paid and
|
|
30
35
|
nothing saved or delivered. A call it hands to another thread and waits for waits for the lock forever, so it never
|
|
@@ -42,8 +47,8 @@ are not counted. The recipe hands off nothing for `policy`: the calling service
|
|
|
42
47
|
handle (refunds, unbans, account recovery) before it calls the recipe, or a model's `HANDOFF` on them is counted as a
|
|
43
48
|
false knowledge gap. Count its hand-offs by cause: a failed check is a refusal for the agent to repair, research and
|
|
44
49
|
hold, and only a hold on the material counts as `inability` (a hold after an outage or at the research time cap is
|
|
45
|
-
`infrastructure`); `unsupported:amount` is a money reason, never researched, and so is `unsupported:number`
|
|
46
|
-
|
|
50
|
+
`infrastructure`); `unsupported:amount` is a money reason, never researched, and so is `unsupported:number` unless the
|
|
51
|
+
refused `draft` shows the number is not a price; `no-knowledge` and `model-unsure` are `inability`;
|
|
47
52
|
every `inability` opens a gap; `model:*` is `infrastructure`, and `model:ceiling` and `model:payment` are also the
|
|
48
53
|
turn's `limit` outcome. The rest of the agent's parts with checks come from [agents that answer
|
|
49
54
|
people](../../../creating-fabric-agents/references/agents-that-answer-people.md): the turn record, the daily
|
|
@@ -85,6 +90,34 @@ cp <adapter>/plugins/fabric-agent-adapter/skills/building-fabric-services/recipe
|
|
|
85
90
|
python3 <adapter>/.../fabric_kit.py status <service>/support # current, older or modified, per file
|
|
86
91
|
```
|
|
87
92
|
|
|
93
|
+
## The knowledge file as a source
|
|
94
|
+
|
|
95
|
+
The channel-agent standard declares every source with its owner, how it is read, its cadence and its maximum age, and a source that fails
|
|
96
|
+
keeps its last good version and says so (its 7.1 and 7.4). `KnowledgeSource(path, max_age_s=...)` is the knowledge
|
|
97
|
+
file read that way: it reads the file again when it changes, keeps the last good version when a read fails, and
|
|
98
|
+
`state()` gives `{id, age_s, max_age_s, stale, degraded}`. The age is the last good version's, from its file's
|
|
99
|
+
modification time: a file nobody rebuilt grows old however often it is read, and a restart does not make it new.
|
|
100
|
+
The standard ages a source from its last finished sync, so the knowledge build writes or touches the file at every
|
|
101
|
+
successful rebuild, unchanged or not, and never keeps the source's time (`cp -p`, `rsync -t`). Pass it as `knowledge=`, or set
|
|
102
|
+
`knowledgeMaxAgeSeconds` in the config, which reads the file at once (a file never read well fails there). The calling service shows `state()`
|
|
103
|
+
on its page and opens one alert when `stale` turns true: the recipe has no page and sends no alert itself.
|
|
104
|
+
|
|
105
|
+
## Declaring the channel-agent standard
|
|
106
|
+
|
|
107
|
+
An agent that answers people on a channel declares what it follows under
|
|
108
|
+
`https://fabric.passioncode.ai/agent-contract/extensions/channel-agent/0.1` (the contract's proposed DEC-0150, not
|
|
109
|
+
merged yet, so the key and the form may change), in its `fabric-agent.json` or its
|
|
110
|
+
well-known `extensions` (the standard's 11 and 13.6; the doctrine: creating-fabric-agents
|
|
111
|
+
[practices](../../../creating-fabric-agents/references/practices.md#agents-on-a-channel-people-write-to)).
|
|
112
|
+
`channel-agent.example.json` is one project's declaration in the standard's round 4 form: a required member that is
|
|
113
|
+
not there yet reads `{"none": "not_yet", "until": "<date>"}`, one not used `{"none": "not_used"}`, never null except
|
|
114
|
+
where nothing is decided (a channel's `decision`); a final text never `not_used` beside a holding reply with replies
|
|
115
|
+
on (FAC-SEM-164); `recommendation` only with `answered_signal: "none"`, the id of the
|
|
116
|
+
board thread where the queue's owner was told; a channel's `items_seen_unit` one of `mails`, `updates`, `messages`,
|
|
117
|
+
`reviews`; durations in ISO 8601; the knowledge file as a source with its
|
|
118
|
+
maximum age. `test/test_channel_agent_example.py` holds the example to that form; `check_service.py` reads the
|
|
119
|
+
declaration once the contract publishes its schema.
|
|
120
|
+
|
|
88
121
|
## Configure
|
|
89
122
|
|
|
90
123
|
`config.example.json`, paths relative to the config's folder:
|
|
@@ -92,6 +125,7 @@ python3 <adapter>/.../fabric_kit.py status <service>/support # current, old
|
|
|
92
125
|
| Key | Meaning |
|
|
93
126
|
|---|---|
|
|
94
127
|
| `knowledge` | the knowledge file: `{"entries": [{"id", "question", "answer", "tags"}]}`, ids unique |
|
|
128
|
+
| `knowledgeMaxAgeSeconds` | optional: the knowledge file is then a source with an age and a last good version (above) |
|
|
95
129
|
| `stateDir` | the decisions (`decisions.jsonl`), the handoff outbox (`handoffs.jsonl`) and the usage ledger (`usage.jsonl`) |
|
|
96
130
|
| `model`, `fallback` | two models of different vendors; a lane that reads what customers write runs on an open-vendor model (DEC-0083) |
|
|
97
131
|
| `keyEnv` | the environment variable that holds the agent's own OpenRouter key; never a value in the file |
|
|
@@ -111,7 +145,11 @@ the store's runner, which puts the named key into this one process's environment
|
|
|
111
145
|
`python3 "$(project-observatory full-path)/tools/use_secret.py" run --vault-only <project> OPENROUTER_API_KEY --
|
|
112
146
|
python3 support_agent.py answer ...`); a long-running
|
|
113
147
|
service gets it the same way from its service manager. The decision
|
|
114
|
-
prints as JSON: `{"messageId", "action": "reply"|"handoff", "reply"?, "citations", "reasons", "costUsd", "repeated"}
|
|
148
|
+
prints as JSON: `{"messageId", "action": "reply"|"handoff", "reply"?, "citations", "reasons", "costUsd", "repeated"}`,
|
|
149
|
+
with `"draft"` on a hand-off the checks made, `"repaired": true` on a reply after `repair=` and `"fallback": true` on
|
|
150
|
+
a repair that ended in a second refusal, a declined repair (`HANDOFF`) or no knowledge; a `model:*` outage during a
|
|
151
|
+
repair is a plain hand-off without a draft. Two meanings of the word: the config's `fallback` is the second model; a decision's `fallback: true`
|
|
152
|
+
is the channel-agent standard's fallback, a spent repair (a second refusal, a declined repair or no knowledge on the retry, never a `model:*` outage). `repair=` refuses a decision that is already `fallback: true`.
|
|
115
153
|
Exit 1 on a broken config or knowledge file. A missing key is not a crash: the message is handed off with
|
|
116
154
|
`model:config`.
|
|
117
155
|
|
|
@@ -126,7 +164,10 @@ Every handoff row in `handoffs.jsonl` holds the customer's own message, which ca
|
|
|
126
164
|
number or a card number. It stays until it is pruned. Choose a retention the product's privacy policy states, at most
|
|
127
165
|
30 days for an agent that answers people (the standard's A1). A1 also asks for the text redacted: the outbox keeps it
|
|
128
166
|
as written, and `prune` takes any `--keep-days`, so such an agent passes an `on_handoff` that redacts what it stores,
|
|
129
|
-
which replaces the outbox, and keeps 30 days or fewer.
|
|
167
|
+
which replaces the outbox, and keeps 30 days or fewer. A refused `draft`, which can echo what the customer wrote,
|
|
168
|
+
reaches the returned decision and an `on_handoff` the agent passes, never a file: neither `decisions.jsonl` nor the
|
|
169
|
+
default outbox stores it. A capability handler returns the decision without it: a kit job's result is written to
|
|
170
|
+
the job's file (`fabric_interop`'s job store), so pass `draft` only to the repair step. Prune on a schedule:
|
|
130
171
|
|
|
131
172
|
```bash
|
|
132
173
|
python3 support_agent.py prune --config config.json --keep-days 30
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"https://fabric.passioncode.ai/agent-contract/extensions/channel-agent/0.1": {
|
|
3
|
+
"standard": "channel-agent-standard/1",
|
|
4
|
+
"projects": [
|
|
5
|
+
{
|
|
6
|
+
"project": "project:example-shop",
|
|
7
|
+
"side": "example-side",
|
|
8
|
+
"lead_agent": "example-agent",
|
|
9
|
+
"adopted_at": "2026-11-10",
|
|
10
|
+
"taxonomy": {
|
|
11
|
+
"version": "2026-11-04",
|
|
12
|
+
"codes": [
|
|
13
|
+
{
|
|
14
|
+
"code": "orders.status",
|
|
15
|
+
"label": "Where is my order"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"code": "payments.refund",
|
|
19
|
+
"label": "A refund"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"tiers": [
|
|
24
|
+
{
|
|
25
|
+
"code": "premium",
|
|
26
|
+
"label": "Premium",
|
|
27
|
+
"rule": "open_orders > 10",
|
|
28
|
+
"escalate": "at_once"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"question_route": {
|
|
32
|
+
"capability": "operator.ask",
|
|
33
|
+
"target": "00000000-0000-4000-8000-000000000001",
|
|
34
|
+
"daily_cap": 10,
|
|
35
|
+
"days": "mon-fri",
|
|
36
|
+
"tz": "Europe/Warsaw",
|
|
37
|
+
"send_at": "09:00",
|
|
38
|
+
"expires": "P7D"
|
|
39
|
+
},
|
|
40
|
+
"gap_registry": {
|
|
41
|
+
"none": "not_yet",
|
|
42
|
+
"until": "2026-11-30"
|
|
43
|
+
},
|
|
44
|
+
"channels": [
|
|
45
|
+
{
|
|
46
|
+
"id": "mail",
|
|
47
|
+
"kind": "mail",
|
|
48
|
+
"replies_enabled": true,
|
|
49
|
+
"decision": null,
|
|
50
|
+
"final_limit": "P7D",
|
|
51
|
+
"first_reply_limit": "PT24H",
|
|
52
|
+
"holding_reply": true,
|
|
53
|
+
"clarifying_question": true,
|
|
54
|
+
"quiet_window": null,
|
|
55
|
+
"late_answer_window": null,
|
|
56
|
+
"items_seen_unit": "mails",
|
|
57
|
+
"texts": {
|
|
58
|
+
"holding": {
|
|
59
|
+
"en": "Thank you, we are looking into it and will answer here."
|
|
60
|
+
},
|
|
61
|
+
"final": {
|
|
62
|
+
"en": "Here is the answer to your question. Write again if anything is still unclear."
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"sources": [
|
|
68
|
+
{
|
|
69
|
+
"id": "knowledge",
|
|
70
|
+
"owner": "example-agent",
|
|
71
|
+
"read": "file",
|
|
72
|
+
"cadence": "PT1H",
|
|
73
|
+
"max_age": "PT2H"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"declined_sources": [],
|
|
77
|
+
"handoff_queues": [
|
|
78
|
+
{
|
|
79
|
+
"id": "support-mailbox",
|
|
80
|
+
"owner": "example-team",
|
|
81
|
+
"limit": "P2D",
|
|
82
|
+
"answered_signal": "none",
|
|
83
|
+
"recommendation": "5d1f0a3c-8b2e-4c7a-9e6f-1a2b3c4d5e6f"
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"evals": {
|
|
87
|
+
"suite": "test/evals/support",
|
|
88
|
+
"judge_budget_usd": 0,
|
|
89
|
+
"replay_budget_usd": 0,
|
|
90
|
+
"spend_record": null
|
|
91
|
+
},
|
|
92
|
+
"tickets": {
|
|
93
|
+
"outbox": "state/tickets-outbox.jsonl",
|
|
94
|
+
"token_slot": "example-shop/prod/SIGNAL_TICKETS_TOKEN",
|
|
95
|
+
"pd_check": {
|
|
96
|
+
"none": "not_used"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"recorded_decisions": []
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|