@passioncode-ai/fabric-agent-adapter 0.39.0 → 0.40.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 +126 -1
- package/README.md +25 -17
- package/bin/fabric-agent-adapter.js +6 -6
- 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 +13 -13
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +4 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +56 -45
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/demo.html +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +3 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +64 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +15 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/knowledge.example.json +13 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +264 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +12 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +93 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +4 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard.md +4 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +13 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +11 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/events-and-notifications.md +6 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +8 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +128 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +26 -26
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/migrating-a-service.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/profiles.md +10 -10
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +13 -13
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +4 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/settings-backups.md +3 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +5 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +7 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +11 -209
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +204 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/dashboard_kit.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +286 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +327 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +47 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +79 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +840 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +340 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +42 -1237
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +1 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +41 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +15 -46
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +387 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +8 -67
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +52 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +231 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +67 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +202 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +136 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +335 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +3 -13
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +69 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +164 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -32
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +122 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +177 -2521
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +1 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +400 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +46 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +458 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +402 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +399 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +78 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +64 -1398
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +116 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +902 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +457 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +36 -38
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +289 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +16 -181
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +586 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +39 -400
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +137 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +402 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +1 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +310 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_kit.py +243 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +308 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +202 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +431 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +22 -44
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +102 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +338 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +31 -38
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +173 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +298 -3430
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +8 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +412 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +120 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +697 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +1526 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +1 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +33 -33
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +1 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +3 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +27 -27
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +31 -31
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +9 -9
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +8 -8
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +116 -1392
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +270 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +508 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_hosts.py +265 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +155 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +335 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +1 -0
|
@@ -3,7 +3,7 @@ name: building-fabric-services
|
|
|
3
3
|
description: >-
|
|
4
4
|
Use when handing out or opening a Fabric service dashboard, building a local agent service on a
|
|
5
5
|
macOS machine, making an ONLINE agent or dashboard (https origin, the remote placement) a
|
|
6
|
-
Fabric service, or letting a service answer a Project's board requests
|
|
6
|
+
Fabric service, or letting a service answer a Project's board requests: «сделай агенту дашборд»,
|
|
7
7
|
«локальный сервис агента», «подключи агента к Fabric Dashboards», «отвечать на запросы с доски проекта»,
|
|
8
8
|
"make this agent a local service", "make an online dashboard a Fabric service", "add the
|
|
9
9
|
well-known endpoint", «расходы агента на модели». Covers fabric-service/0.1:
|
|
@@ -18,7 +18,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
18
18
|
compatibility: Python 3.9+ or Node.js 20+ for the kits; 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.
|
|
19
19
|
metadata:
|
|
20
20
|
author: PassionCode.ai
|
|
21
|
-
version: "0.
|
|
21
|
+
version: "0.40.0"
|
|
22
22
|
contract-version: "0.1.0"
|
|
23
23
|
extension: "fabric-service/0.1"
|
|
24
24
|
extension-commit: "c55f2d5a11c9a0197e958e87212fca7ae1154769"
|
|
@@ -29,7 +29,7 @@ metadata:
|
|
|
29
29
|
A **service** is an agent that keeps running on the operator's computer: it holds
|
|
30
30
|
state, answers other agents, and shows a dashboard. This skill makes one that is
|
|
31
31
|
always alive, comes back after a restart, never runs twice, keeps its state through a
|
|
32
|
-
reinstall, and appears in Fabric Dashboards by itself
|
|
32
|
+
reinstall, and appears in Fabric Dashboards by itself: the `fabric-service/0.1`
|
|
33
33
|
extension of the Fabric Agent Contract (DEC-0015).
|
|
34
34
|
|
|
35
35
|
Every rule below exists because a real service on this estate broke it. Treat them as
|
|
@@ -41,15 +41,15 @@ re-deriving a rule.
|
|
|
41
41
|
Use it to create a service, to bring an existing local server under the protocol, or
|
|
42
42
|
to review one. Do not use it for:
|
|
43
43
|
|
|
44
|
-
- a script, a cron job or a one-shot CLI
|
|
44
|
+
- a script, a cron job or a one-shot CLI: nothing stays running, so there is nothing
|
|
45
45
|
to supervise; a launchd `StartInterval` job needs no descriptor;
|
|
46
46
|
- a hosted product with no agent behind it. An online agent or dashboard that should appear in
|
|
47
|
-
Fabric IS in scope: it is the remote placement
|
|
48
|
-
- the provider manifest and admission bundle
|
|
47
|
+
Fabric IS in scope: it is the remote placement; read [Online services](#online-services-the-remote-placement);
|
|
48
|
+
- the provider manifest and admission bundle: that is `adapting-projects-to-fabric`
|
|
49
49
|
(a service that is also a provider does both);
|
|
50
50
|
- the Fabric Dashboards app itself.
|
|
51
51
|
|
|
52
|
-
## Step 0
|
|
52
|
+
## Step 0: is it a service?
|
|
53
53
|
|
|
54
54
|
It is a service when at least one is true: other agents call it while the operator is
|
|
55
55
|
away; it runs jobs longer than one request; it owns a store other tools read; the
|
|
@@ -65,7 +65,7 @@ Answer before writing code, and record the answers in the project README:
|
|
|
65
65
|
| What it stores | the store, where it lives (Step 2, rule 5), and what survives uninstall |
|
|
66
66
|
| What it tells the operator | the events worth a notification (Step 5) |
|
|
67
67
|
|
|
68
|
-
## Step 1
|
|
68
|
+
## Step 1: choose the surfaces
|
|
69
69
|
|
|
70
70
|
| Caller | Surface | Why |
|
|
71
71
|
|---|---|---|
|
|
@@ -103,14 +103,14 @@ responder in its own process: [`scripts/fabric_hub.py`](scripts/fabric_hub.py) f
|
|
|
103
103
|
every step and stops on `fenced`, `lease_expired` or `authority_changed` (Node: `fabric-hub.mjs`, `fabric-board.mjs`).
|
|
104
104
|
The request body is data behind the service's own input gate. Read [the board reference](references/board.md).
|
|
105
105
|
|
|
106
|
-
## Step 2
|
|
106
|
+
## Step 2: the non-negotiables
|
|
107
107
|
|
|
108
108
|
1. **Loopback only.** Bind `127.0.0.1`. Refuse any `Host` other than
|
|
109
109
|
`127.0.0.1:<port>`, `localhost:<port>`, `[::1]:<port>`; refuse a foreign `Origin` and
|
|
110
|
-
`Sec-Fetch-Site: cross-site
|
|
110
|
+
`Sec-Fetch-Site: cross-site`, on every path, the well-known one included.
|
|
111
111
|
*(A throwaway `python -m http.server` bound to every interface served a source tree to the local network.)*
|
|
112
112
|
2. **One copy, locked before any side effect.** Take the exclusive lock on
|
|
113
|
-
`<data>/service.lock` first
|
|
113
|
+
`<data>/service.lock` first, before resuming jobs, starting a scheduler, migrating
|
|
114
114
|
a store or even creating a token. Held → print one sentence naming the holder's pid
|
|
115
115
|
and exit **75**. Binding the port is not a lock. Under launchd the kit backs off
|
|
116
116
|
in-process first (up to 300 s), so a duplicate is a few starts an hour, not a
|
|
@@ -127,11 +127,11 @@ The request body is data behind the service's own input gate. Read [the board re
|
|
|
127
127
|
5. **State outside code.** Data and config in `~/Library/Application Support/<id>/`,
|
|
128
128
|
logs in `~/Library/Logs/<id>/`, cache in `~/Library/Caches/<id>/`; the service's own
|
|
129
129
|
access token in a 0600 file it generates (principle 6). **Every provider credential the
|
|
130
|
-
service uses comes from Project Observatory, by name
|
|
130
|
+
service uses comes from Project Observatory, by name**: the plist starts the service
|
|
131
131
|
with `use_secret.py serve --consumer <label> <project> <NAME>[,<NAME>] -- <command>`, which
|
|
132
132
|
reads the vault only and records the consumer so a rotation names it; never a copy of a
|
|
133
133
|
key in a `.env`, a config file or the plist. Never inside the service's own code checkout or a release directory; a
|
|
134
|
-
repository that versions the data itself is a store and is fine
|
|
134
|
+
repository that versions the data itself is a store and is fine; declare
|
|
135
135
|
`source.repository` so the probe can tell them apart. *(Deleting a checkout left a
|
|
136
136
|
plist launchd retried every 10 s, and the data went with it.)*
|
|
137
137
|
6. **Tokens stay in files and headers.** Mode 0600, owner-checked, never a symlink;
|
|
@@ -139,25 +139,36 @@ The request body is data behind the service's own input gate. Read [the board re
|
|
|
139
139
|
one-time login code, never the token.
|
|
140
140
|
7. **`degraded` is always present.** An empty list asserts full health; `ready` with a
|
|
141
141
|
non-empty list is a lie the kit corrects to `degraded`.
|
|
142
|
-
8. **Atomic writes.** Temporary file, fsync, rename
|
|
142
|
+
8. **Atomic writes.** Temporary file, fsync, rename, for the store's side files, the
|
|
143
143
|
descriptor and the token.
|
|
144
144
|
|
|
145
|
-
## Step 3
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
## Step 3: build it with the kit
|
|
146
|
+
|
|
147
|
+
Install the kit into the service with its manifest: `python3 scripts/fabric_kit.py install
|
|
148
|
+
<service-dir> --module fabric_service --module fabric_interop` (Python: the service and its MCP
|
|
149
|
+
surface, with every module they need; add `--module fabric_board` for a board responder, as
|
|
150
|
+
the sample has) or `--lang node` with the same names in `-`;
|
|
151
|
+
`fabric_kit.py status <service-dir>` says per file whether a copy is current, older or edited,
|
|
152
|
+
and what is missing. The kit is layered modules behind the `fabric_service` /
|
|
153
|
+
`fabric-service.mjs` facades, which keep every 0.39 import working; read
|
|
154
|
+
[the kit modules reference](references/kit-modules.md) for the map, the dates API and
|
|
155
|
+
installing more (`--module fabric_openrouter`). Both languages share file formats, and
|
|
150
156
|
their locks exclude each other. The complete worked example is
|
|
151
|
-
[`scripts/sample_service.py`](scripts/sample_service.py)
|
|
157
|
+
[`scripts/sample_service.py`](scripts/sample_service.py); read it before writing a
|
|
152
158
|
handler.
|
|
153
159
|
|
|
160
|
+
Calling out: [the connectors reference](references/connectors.md) (`fabric_http` with one
|
|
161
|
+
deadline, safe retries and a size cap; `fabric_openrouter` with the agent's own key, a
|
|
162
|
+
spend ceiling before every attempt, receipts and a fallback). A ready support agent on the
|
|
163
|
+
kit: [`recipes/support-agent/`](recipes/support-agent/README.md).
|
|
164
|
+
|
|
154
165
|
Startup order is the part that goes wrong; keep it exactly:
|
|
155
166
|
|
|
156
167
|
```python
|
|
157
168
|
import fabric_service as fs
|
|
158
169
|
|
|
159
170
|
dirs = fs.service_dirs("example-agent")
|
|
160
|
-
lock = fs.hold_single_instance(dirs["data"]) # 1. lock
|
|
171
|
+
lock = fs.hold_single_instance(dirs["data"]) # 1. lock: exits 75 if held (backs off first under launchd)
|
|
161
172
|
token = fs.ensure_token(dirs["data"] / "service.token") # 2. only now touch state
|
|
162
173
|
log = fs.JsonlEventLog(dirs["data"] / "events.jsonl") # (or a view over your own log)
|
|
163
174
|
codes = fs.LoginCodes(dirs["data"] / "auth")
|
|
@@ -172,12 +183,12 @@ On every request call `check_request(port, host, origin, sec_fetch_site)` and an
|
|
|
172
183
|
|
|
173
184
|
| Route | Auth | Kit |
|
|
174
185
|
|---|---|---|
|
|
175
|
-
| `GET /.well-known/fabric-service` | none, guard still applies | `build_well_known(...)
|
|
186
|
+
| `GET /.well-known/fabric-service` | none, guard still applies | `build_well_known(...)`, from memory, under 100 ms |
|
|
176
187
|
| `GET /fabric/v1/events?after=&limit=` | service token | `events_page(fetch, after, parse_limit(limit))` |
|
|
177
188
|
| `POST /fabric/v1/login-code` | service token | `LoginCodes.issue()` |
|
|
178
189
|
| `GET /fabric/v1/login?code=` | the code | `LoginCodes.redeem(code)` → `Set-Cookie: session_cookie_header(...)`, 302 to the dashboard |
|
|
179
190
|
|
|
180
|
-
`SIGTERM` drains in-flight work, then exits
|
|
191
|
+
`SIGTERM` drains in-flight work, then exits: 10 s at most, inside `ExitTimeOut`
|
|
181
192
|
(`fs.Drain` / `new Drain()`: `with drain.work():` refuses new work once a stop began);
|
|
182
193
|
interrupted work resumes on the next start. Log through `RotatingLog` (5 × 5 MB, 0600)
|
|
183
194
|
and call `cap_stdout_log` once at start.
|
|
@@ -187,18 +198,18 @@ in Python; jobs live in `fabric_interop.JobStore(dirs["data"] / "jobs")`, create
|
|
|
187
198
|
the lock like every other piece of state. Add `"mcp": {"path": "/mcp", "transport":
|
|
188
199
|
"streamable-http", "capabilities": [...]}` to the well-known surfaces, and point the
|
|
189
200
|
descriptor's `fabricManifest` at a manifest whose service key names this
|
|
190
|
-
`<id>.<instance
|
|
201
|
+
`<id>.<instance>`; `sample_service.py register` shows both.
|
|
191
202
|
|
|
192
|
-
## Step 4
|
|
203
|
+
## Step 4: install it
|
|
193
204
|
|
|
194
205
|
The installer, not the service, owns the plist and the descriptor. Sequence:
|
|
195
206
|
|
|
196
|
-
1. `write_descriptor(descriptor)
|
|
197
|
-
claims. A preview or branch copy is a second **instance**, never a second id
|
|
207
|
+
1. `write_descriptor(descriptor)`: refuses a port or `id.instance` another descriptor
|
|
208
|
+
claims. A preview or branch copy is a second **instance**, never a second id, and it is
|
|
198
209
|
one more service the operator sees, probes and gets notified by. Verify a release with its
|
|
199
210
|
own doctor or self-check before it replaces `default`; a temporary instance is uninstalled
|
|
200
211
|
(`launchd_uninstall`, `remove_descriptor`) as soon as its check is done, not left running.
|
|
201
|
-
2. `launchd_plist(...)` then `launchd_install(...)
|
|
212
|
+
2. `launchd_plist(...)` then `launchd_install(...)`: writes and lints the plist,
|
|
202
213
|
`bootout` and waits for the unload, `bootstrap` with retries on the transient I/O
|
|
203
214
|
error, then polls the well-known document until it answers with **this** identity.
|
|
204
215
|
A label the operator disabled stays disabled: only a first install enables.
|
|
@@ -217,7 +228,7 @@ rewrites the plist and restarts. Read [the lifecycle reference](references/lifec
|
|
|
217
228
|
for the plist fields, the release layout, log rotation, behaving under a host lifecycle broker
|
|
218
229
|
and the Linux and Windows supervisors.
|
|
219
230
|
|
|
220
|
-
## Step 5
|
|
231
|
+
## Step 5: the dashboard and the events
|
|
221
232
|
|
|
222
233
|
The dashboard is a same-origin page: no inline script, strict CSP, polling that pauses
|
|
223
234
|
while the operator types or a dialog is open, one panel's failure never blanks the
|
|
@@ -235,10 +246,10 @@ and declare it with `extensions=fs.dashboard_extension()`. Read
|
|
|
235
246
|
declaration and how to upgrade a dashboard built before the kit: an upgrade you may make yourself,
|
|
236
247
|
which the plugin's session-start notice offers in a repository that owns the service.
|
|
237
248
|
|
|
238
|
-
The events feed is a **view over the log you already keep**
|
|
239
|
-
journal
|
|
249
|
+
The events feed is a **view over the log you already keep** (a jobs table, a JSONL
|
|
250
|
+
journal), not a second store. Each event is one sentence a person reads; set
|
|
240
251
|
`notify: true` only when the operator must decide, or something failed or degrades their
|
|
241
|
-
work
|
|
252
|
+
work, once per episode, with a `subject` and a `link` to the page that resolves it. A
|
|
242
253
|
question's kind says so (`*.awaiting_*`, `*.approval_*`, `human_step.opened`). The service
|
|
243
254
|
raises no banners of its own: the host is the one channel. Read
|
|
244
255
|
[the events reference](references/events-and-notifications.md) for mapping an existing
|
|
@@ -253,9 +264,9 @@ A service whose store holds what the operator decided keeps **settings backups**
|
|
|
253
264
|
directory, restores only on a fresh database and never overwrites. Read
|
|
254
265
|
[the settings backups reference](references/settings-backups.md).
|
|
255
266
|
|
|
256
|
-
## Step 6
|
|
267
|
+
## Step 6: verify
|
|
257
268
|
|
|
258
|
-
Run the probe against the live service
|
|
269
|
+
Run the probe against the live service: it reads the descriptor, then checks every
|
|
259
270
|
rule it can reach and says which it could not:
|
|
260
271
|
|
|
261
272
|
```bash
|
|
@@ -268,7 +279,7 @@ events auth and shape, single-use login, state outside code, instance lock, plis
|
|
|
268
279
|
launchd pid equals answering pid, `dashboard.declared` (ED-9), and the `interop.*` rules:
|
|
269
280
|
manifest link, capability list, tools equal to the manifest, job tools, unknown job, trace
|
|
270
281
|
propagation, event trace pairs, and `board.responder` for a service that declares the project board) and exits 1 on
|
|
271
|
-
any `FAIL`. A `NOT_RUN` is not a pass
|
|
282
|
+
any `FAIL`. A `NOT_RUN` is not a pass;
|
|
272
283
|
name it in the report. A service with a dashboard also runs the layout probe,
|
|
273
284
|
`node <skill-dir>/scripts/check_dashboard_layout.mjs --service <id>` (WebKit at 360, 480, 768
|
|
274
285
|
and 1280 px; needs Playwright, else `NOT_RUN`).
|
|
@@ -277,7 +288,7 @@ Then prove the lock: start a second copy by hand against the same data directory
|
|
|
277
288
|
must exit 75 while the first keeps serving. Prove recovery: `kill -9` the launchd pid;
|
|
278
289
|
the well-known document must answer again with a new pid within about 15 seconds.
|
|
279
290
|
|
|
280
|
-
## Packing and signing it
|
|
291
|
+
## Packing and signing it: `fabric-package/1`
|
|
281
292
|
|
|
282
293
|
To hand an agent to another machine or an organization, pack it as one folder (contract DEC-0050, DEC-0051). Write the
|
|
283
294
|
author's `fabric.package.json` (the descriptive fields; for an agent `resultSignal` and `evals` with the receipt of the
|
|
@@ -313,11 +324,11 @@ first commit:
|
|
|
313
324
|
org-index `scripts/check_format.py` reports 0 findings for it before it is called done. **An agent
|
|
314
325
|
someone builds for themselves is not a PassionCode.ai repository:** its licence is its owner's
|
|
315
326
|
choice, nothing about it is published or listed by the organization, and none of these files is
|
|
316
|
-
required of it
|
|
327
|
+
required of it, though the verified MCP quick start is still how anyone learns to drive it.
|
|
317
328
|
|
|
318
|
-
## Online services
|
|
329
|
+
## Online services: the remote placement
|
|
319
330
|
|
|
320
|
-
An agent or a dashboard that runs online
|
|
331
|
+
An agent or a dashboard that runs online (on a platform, a server, a hosted app) becomes a
|
|
321
332
|
Fabric service with the same four routes and one descriptor on the operator's computer
|
|
322
333
|
(`placement: "remote"`, DEC-0019). Nothing about launchd, the lock or loopback applies; three
|
|
323
334
|
things change and the kits implement each:
|
|
@@ -328,7 +339,7 @@ things change and the kits implement each:
|
|
|
328
339
|
| Well-known | only for the token; otherwise `401` with an **empty** body | `wellKnownAllowed('remote', …)` / `well_known_allowed` |
|
|
329
340
|
| Session | `__Host-fabric_session`, `Secure`; codes in memory, the key from a platform secret so sessions survive a deploy | `remoteSessionCookieHeader`, `new LoginCodes(null, 120, { store: new MemoryCodeStore(), key })` |
|
|
330
341
|
|
|
331
|
-
1. **Serve the four routes** in the app that already exists
|
|
342
|
+
1. **Serve the four routes** in the app that already exists, not beside it. The complete worked
|
|
332
343
|
example is [`scripts/sample-remote-service.mjs`](scripts/sample-remote-service.mjs): it runs
|
|
333
344
|
with its own TLS or behind a platform router.
|
|
334
345
|
2. **Secrets live on the platform:** the service token and the session key (32 bytes) are
|
|
@@ -336,7 +347,7 @@ things change and the kits implement each:
|
|
|
336
347
|
remote service needs is issued and kept in Project Observatory, pushed to the platform, and
|
|
337
348
|
the movement recorded with `vault.py moved --at <provider>`, so the platform's copy is
|
|
338
349
|
known rather than invisible.
|
|
339
|
-
3. **Register it on the operator's computer
|
|
350
|
+
3. **Register it on the operator's computer**, the installer side, run there:
|
|
340
351
|
`registerRemote({ id, name, origin, token })` / `register_remote(...)` writes the token file
|
|
341
352
|
(0600) and the descriptor; pass the token from a file or stdin, never in argv. Public names
|
|
342
353
|
only in public artifacts: a private service is registered by a local descriptor and nowhere
|
|
@@ -350,7 +361,7 @@ behaviour a host shows, rotation, a multi-process platform, and what a host will
|
|
|
350
361
|
|
|
351
362
|
## Migrating an existing service
|
|
352
363
|
|
|
353
|
-
Read [the migration reference](references/migrating-a-service.md)
|
|
364
|
+
Read [the migration reference](references/migrating-a-service.md): the order of
|
|
354
365
|
changes that keeps the service answering throughout, and what each existing service on
|
|
355
366
|
this estate needs.
|
|
356
367
|
|
|
@@ -367,14 +378,14 @@ this estate needs.
|
|
|
367
378
|
against [the protocol reference](references/protocol.md); the probe is the judge.
|
|
368
379
|
- **No contract checkout:** the protocol reference is pinned to the extension commit
|
|
369
380
|
in this skill's metadata; do not reconstruct fields from memory.
|
|
370
|
-
- **Fabric Dashboards not installed:** nothing changes
|
|
381
|
+
- **Fabric Dashboards not installed:** nothing changes; the service is complete
|
|
371
382
|
without a host; the descriptor waits for one.
|
|
372
383
|
|
|
373
384
|
## Gotchas
|
|
374
385
|
|
|
375
386
|
- `lsof` shows `*:<port>` or `0.0.0.0` → the bind is wrong even if the Host check works.
|
|
376
387
|
- A health probe that only checks HTTP 200 accepts any program on the port. Compare
|
|
377
|
-
`service.id` and `service.instance
|
|
388
|
+
`service.id` and `service.instance`: two services here once defaulted to ports
|
|
378
389
|
another service already held.
|
|
379
390
|
- `launchctl bootstrap` right after `bootout` fails with I/O error 5 until the unload
|
|
380
391
|
finishes; wait, then retry.
|
|
@@ -397,7 +408,7 @@ the probe table verbatim with every `NOT_RUN` explained; the lock and kill-9 res
|
|
|
397
408
|
with pids; and the one next command. Never call a service conformant while the probe
|
|
398
409
|
reports a `FAIL`.
|
|
399
410
|
|
|
400
|
-
## Dashboard handoff
|
|
411
|
+
## Dashboard handoff: also for consumers
|
|
401
412
|
|
|
402
413
|
When handing a registered service dashboard to a person, or writing the generated
|
|
403
414
|
agent's completion/notification instructions, read and apply
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/demo.html
CHANGED
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
<tbody>
|
|
66
66
|
<tr><td class="fd-mono">job-7f3a91</td><td>example.summarize</td><td><span class="fd-chip fd-s-info"><span class="fd-dot"></span>Running</span></td><td><time>2026-10-11 17:02</time></td><td class="fd-num">12 480</td><td class="fd-num">$0.031</td><td class="fd-mono">4bf92f3577b34da6a3ce929d0e0e4736</td></tr>
|
|
67
67
|
<tr><td class="fd-mono">job-7f3a90</td><td>example.translate</td><td><span class="fd-chip fd-s-ok"><span class="fd-dot"></span>Done</span></td><td><time>2026-10-11 16:40</time></td><td class="fd-num">8 002</td><td class="fd-num">$0.019</td><td class="fd-mono">00f067aa0ba902b74bf92f3577b34da6</td></tr>
|
|
68
|
-
<tr><td class="fd-mono">job-7f3a8f</td><td>example.review</td><td><span class="fd-chip fd-s-warn"><span class="fd-dot"></span>Needs you</span></td><td><time>2026-10-11 15:12</time></td><td class="fd-num">2 210</td><td class="fd-num"
|
|
68
|
+
<tr><td class="fd-mono">job-7f3a8f</td><td>example.review</td><td><span class="fd-chip fd-s-warn"><span class="fd-dot"></span>Needs you</span></td><td><time>2026-10-11 15:12</time></td><td class="fd-num">2 210</td><td class="fd-num">none</td><td class="fd-mono">a3ce929d0e0e47364bf92f3577b34da6</td></tr>
|
|
69
69
|
</tbody>
|
|
70
70
|
</table>
|
|
71
71
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.1 · Fabric's button (Fabric Agent Contract DEC-0040, FB-1…FB-8, FL-1…FL-6)
|
|
2
2
|
Load it same-origin, after fabric-dashboard.css, with the service it opens Fabric on:
|
|
3
3
|
<script src="fabric-button.js" defer data-service="example-agent.default" data-service-name="Example Agent"
|
|
4
4
|
data-project="project-a" data-project-name="Project A"></script>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
(function (root) {
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
-
var VERSION = '1.1.
|
|
13
|
+
var VERSION = '1.1.1';
|
|
14
14
|
var COOKIE = 'fabric_button';
|
|
15
15
|
var GET_FABRIC = 'https://passioncode.ai/fabric';
|
|
16
16
|
var ABSENT_AFTER_MS = 1500;
|
|
@@ -212,7 +212,7 @@
|
|
|
212
212
|
return a;
|
|
213
213
|
});
|
|
214
214
|
var note = el(doc, 'p', { 'class': 'fd-fabric-button-note', role: 'note' });
|
|
215
|
-
note.appendChild(doc.createTextNode('Fabric is not installed
|
|
215
|
+
note.appendChild(doc.createTextNode('Fabric is not installed. Get it at '));
|
|
216
216
|
note.appendChild(el(doc, 'a', { href: GET_FABRIC, target: '_blank', rel: 'noopener', 'class': 'fd-fabric-button-get' }, 'passioncode.ai/fabric'));
|
|
217
217
|
note.hidden = !absent;
|
|
218
218
|
menu.appendChild(note);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.1 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
2
|
A Fabric service dashboard shown inside a host pane: usable from 360 CSS px up, navigation always
|
|
3
3
|
reachable, wide content in its own scroll box, one vertical scroller, appearance from the host.
|
|
4
4
|
Copy this file next to fabric-dashboard.js; do not edit it in place. Put the page's own rules in
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* fabric-dashboard-kit@1.1.
|
|
1
|
+
/* fabric-dashboard-kit@1.1.1 · fabric-dashboard/0.1 (Fabric Agent Contract DEC-0036)
|
|
2
2
|
Load it same-origin with <script src="fabric-dashboard.js" defer></script>: no inline script, no
|
|
3
3
|
eval, nothing from a CDN. It applies the host's appearance (ED-6, ED-8), folds navigation that
|
|
4
4
|
does not fit into a More menu (ED-3), makes wide boxes keyboard-reachable (ED-4) and keeps
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
var KIT = 'fabric-dashboard-kit';
|
|
11
|
-
var VERSION = '1.1.
|
|
11
|
+
var VERSION = '1.1.1';
|
|
12
12
|
/* fabric-dashboard-kit:theme-ids:begin */
|
|
13
13
|
var THEMES = ['graphite', 'fabric', 'paper', 'forest', 'ocean', 'ember', 'contrast'];
|
|
14
14
|
/* fabric-dashboard-kit:theme-ids:end */
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabric-dashboard-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"profile": "fabric-dashboard/0.1",
|
|
5
5
|
"decision": "DEC-0036",
|
|
6
6
|
"declarationKey": "https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"contrast"
|
|
16
16
|
],
|
|
17
17
|
"files": {
|
|
18
|
-
"fabric-dashboard.css": "
|
|
19
|
-
"fabric-dashboard.js": "
|
|
20
|
-
"fabric-button.js": "
|
|
18
|
+
"fabric-dashboard.css": "a8847c8e2f386572318842ec1d1613fcb29e4aec060e033c060d8dba6b7c6f02",
|
|
19
|
+
"fabric-dashboard.js": "d0fc41144a6bd413383bd53bbfa6e01a29547c7ec9c01a3927d69fb36cd71a7d",
|
|
20
|
+
"fabric-button.js": "f5599fabfeadfb5c69d30f7ea500846521a166470078a736291c7753014bc25c",
|
|
21
21
|
"themes.json": "511839f1a1a83eb37fadfd132791474497d5c28c5091700b9aa31ad076e359b5"
|
|
22
22
|
},
|
|
23
23
|
"history": {
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
"fabric-dashboard.css": "498571903fac6dfd2d6645283da2f703fa945f410f2fafa269a8c9c322735c3b",
|
|
30
30
|
"fabric-dashboard.js": "100f55d940d4ed60aa8511cee4c65219c93114eb7d7a66639b0e4e4d7aa05f22",
|
|
31
31
|
"fabric-button.js": "4f1e100936ae65af5539594681e66092ae1834a4952da547a67d0558c9fadbbe"
|
|
32
|
+
},
|
|
33
|
+
"1.1.1": {
|
|
34
|
+
"fabric-dashboard.css": "a8847c8e2f386572318842ec1d1613fcb29e4aec060e033c060d8dba6b7c6f02",
|
|
35
|
+
"fabric-dashboard.js": "d0fc41144a6bd413383bd53bbfa6e01a29547c7ec9c01a3927d69fb36cd71a7d",
|
|
36
|
+
"fabric-button.js": "f5599fabfeadfb5c69d30f7ea500846521a166470078a736291c7753014bc25c"
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
}
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Recipe: a support agent
|
|
2
|
+
|
|
3
|
+
A service answers one inbound question from its own knowledge file, or hands it to a person. Built only on the service
|
|
4
|
+
kit: `fabric_openrouter` for the model, `fabric_usage` for receipts, `fabric_paths` for private, atomic state.
|
|
5
|
+
Tests: `test/test_support_agent_recipe.py` (a fake model; no network, no key).
|
|
6
|
+
|
|
7
|
+
## What it does with one message
|
|
8
|
+
|
|
9
|
+
| Step | Rule | When it fails |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Repeat | the decision already made for this message id is returned, with no model call | |
|
|
12
|
+
| Lookup | entries sharing words with the question (its question, tags and answer), best three | handoff `no-knowledge` |
|
|
13
|
+
| Draft | `fabric_openrouter.Client.chat`: own key, spend ceiling before every attempt, a usage receipt per call, a fallback of another vendor, temperature 0 | handoff `model:<kind>` (`ceiling`, `failed`, `auth`, `payment`, `config`, `unpriced`) |
|
|
14
|
+
| Model unsure | the model may answer exactly `HANDOFF` | handoff `model-unsure` |
|
|
15
|
+
| Checks, proven by code | at least one `[KB:<id>]` citation; every cited id was given to the model; no em dash (U+2014), `--` or a spaced hyphen or en dash between words; the reply's script is the question's; at most `maxChars` | handoff, each failed check named (`no-citation`, `unknown-citation:<ids>`, `dash`, `script:<got>-not-<wanted>`, `too-long`, `empty`) |
|
|
16
|
+
| Record | the decision, 0600, written atomically; a handoff goes to the outbox first, so a crash repeats a handoff and never loses one | |
|
|
17
|
+
|
|
18
|
+
A reply comes back without its citation markers, with `citations` beside it. The agent never sends the reply: the
|
|
19
|
+
service that calls it owns its channel (mail, a chat, a store review) and its response time.
|
|
20
|
+
|
|
21
|
+
## Install into a service
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python3 <adapter>/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_kit.py \
|
|
25
|
+
install <service>/support --lang py --module fabric_openrouter --module fabric_usage --module fabric_paths
|
|
26
|
+
cp <adapter>/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py <service>/support/
|
|
27
|
+
python3 <adapter>/.../fabric_kit.py status <service>/support # current, older or modified, per file
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configure
|
|
31
|
+
|
|
32
|
+
`config.example.json`, paths relative to the config's folder:
|
|
33
|
+
|
|
34
|
+
| Key | Meaning |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `knowledge` | the knowledge file: `{"entries": [{"id", "question", "answer", "tags"}]}`, ids unique |
|
|
37
|
+
| `stateDir` | decisions, the handoff outbox and the usage ledger (`usage.jsonl`) |
|
|
38
|
+
| `model`, `fallback` | two models of different vendors; a lane that reads what customers write runs on an open-vendor model (DEC-0083) |
|
|
39
|
+
| `keyEnv` | the environment variable that holds the agent's own OpenRouter key; never a value in the file |
|
|
40
|
+
| `ceilingUsd`, `window` | the spend ceiling per UTC `day` or `month` |
|
|
41
|
+
| `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 |
|
|
42
|
+
| `maxTokens`, `maxChars` | the output budget per call and the reply's length limit |
|
|
43
|
+
|
|
44
|
+
## Run
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
OPENROUTER_API_KEY=... python3 support_agent.py answer --config config.json --message-id m-1 --text "How do I reset my password?"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The key comes from the environment the service manager gives the agent, never typed on a command line. The decision
|
|
51
|
+
prints as JSON: `{"messageId", "action": "reply"|"handoff", "reply"?, "citations", "reasons", "costUsd", "repeated"}`.
|
|
52
|
+
Exit 1 on a broken config or knowledge file. A missing key is not a crash: the message is handed off with
|
|
53
|
+
`model:config`.
|
|
54
|
+
|
|
55
|
+
From a service's capability handler, build the agent once with `support_agent.from_config(path)` and call
|
|
56
|
+
`agent.answer(message_id, text, task_run_id=...)`; pass `on_handoff=` to `SupportAgent` to route handoffs to the
|
|
57
|
+
operator's channel instead of the outbox file.
|
|
58
|
+
|
|
59
|
+
## Before you call it finished
|
|
60
|
+
|
|
61
|
+
- Replace the example knowledge with the product's own, and run real past questions through it ([practices](../../../creating-fabric-agents/references/practices.md#judges-and-evals), *Judges
|
|
62
|
+
and evals*): count handoffs and read a sample of replies.
|
|
63
|
+
- Show spend: `fabric_usage.usage_report` over the ledger, or `fabric_openrouter.totals`.
|
|
64
|
+
- One decision per message id holds in one process; a service runs one instance per context under its instance lock.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"knowledge": "knowledge.example.json",
|
|
3
|
+
"stateDir": "state",
|
|
4
|
+
"model": "deepseek/deepseek-v4.1-flash",
|
|
5
|
+
"fallback": "qwen/qwen3.8-flash",
|
|
6
|
+
"keyEnv": "OPENROUTER_API_KEY",
|
|
7
|
+
"ceilingUsd": 1.0,
|
|
8
|
+
"window": "day",
|
|
9
|
+
"maxTokens": 600,
|
|
10
|
+
"maxChars": 1200,
|
|
11
|
+
"prices": {
|
|
12
|
+
"deepseek/deepseek-v4.1-flash": {"prompt": 0.0000002, "completion": 0.0000008},
|
|
13
|
+
"qwen/qwen3.8-flash": {"prompt": 0.0000001, "completion": 0.0000004}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entries": [
|
|
3
|
+
{"id": "password-reset", "question": "How do I reset my password?",
|
|
4
|
+
"answer": "Open Settings, then Account, then Reset password. A link arrives by email within five minutes.",
|
|
5
|
+
"tags": ["password", "login", "account"]},
|
|
6
|
+
{"id": "refund-window", "question": "Can I get a refund?",
|
|
7
|
+
"answer": "Purchases can be refunded within 14 days through the store you bought from.",
|
|
8
|
+
"tags": ["refund", "money", "purchase"]},
|
|
9
|
+
{"id": "password-reset-ru", "question": "Как сбросить пароль?",
|
|
10
|
+
"answer": "Откройте Настройки, затем Аккаунт, затем Сбросить пароль. Ссылка придёт на почту в течение пяти минут.",
|
|
11
|
+
"tags": ["пароль", "вход", "аккаунт"]}
|
|
12
|
+
]
|
|
13
|
+
}
|