@passioncode-ai/fabric-agent-adapter 0.39.0 → 0.39.1
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 +47 -1
- package/README.md +16 -14
- 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 +41 -41
- 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/references/board.md +12 -12
- 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 +10 -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/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 +5 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +20 -45
- 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-board.mjs +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +28 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +34 -14
- 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/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 +6 -6
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py
CHANGED
|
@@ -18,6 +18,7 @@ import hashlib
|
|
|
18
18
|
import hmac
|
|
19
19
|
import http.server
|
|
20
20
|
import json
|
|
21
|
+
import math
|
|
21
22
|
import ntpath
|
|
22
23
|
import os
|
|
23
24
|
from pathlib import Path, PurePath
|
|
@@ -46,7 +47,7 @@ EXTENSION_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/service
|
|
|
46
47
|
# The embedded dashboard declaration (fabric-dashboard/0.1, DEC-0036 ED-9), under the well-known
|
|
47
48
|
# document's `extensions`. The kit is assets/dashboard-kit; its version is the one in kit.json.
|
|
48
49
|
DASHBOARD_EXTENSION_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1"
|
|
49
|
-
DASHBOARD_KIT = "fabric-dashboard-kit@1.1.
|
|
50
|
+
DASHBOARD_KIT = "fabric-dashboard-kit@1.1.1"
|
|
50
51
|
REFERENCE_THEMES = ("graphite", "fabric", "paper", "forest", "ocean", "ember", "contrast")
|
|
51
52
|
EXIT_ALREADY_RUNNING = 75
|
|
52
53
|
# Lifecycle rules: references/lifecycle.md, the rule for this kit's users, which implements the public
|
|
@@ -1734,11 +1735,21 @@ _DAY_MS = 86_400_000
|
|
|
1734
1735
|
|
|
1735
1736
|
|
|
1736
1737
|
def _usage_count(value: Any) -> int:
|
|
1737
|
-
|
|
1738
|
+
"""The contract's `count`: Number.isSafeInteger and not negative, so 5.0 (which JavaScript reads as 5) counts."""
|
|
1739
|
+
import fabric_estate # noqa: E402
|
|
1740
|
+
return int(value) if not isinstance(value, bool) and fabric_estate._safe_integer(value) and value >= 0 else 0
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
def _usage_money(value: Any) -> Optional[float]:
|
|
1744
|
+
"""A JSON number as JavaScript holds it (an integer beyond a double is Infinity), or None for anything else."""
|
|
1745
|
+
import fabric_estate # noqa: E402
|
|
1746
|
+
return fabric_estate._double(value) if isinstance(value, (int, float)) and not isinstance(value, bool) else None
|
|
1738
1747
|
|
|
1739
1748
|
|
|
1740
1749
|
def _usage_cost(value: Any) -> float:
|
|
1741
|
-
|
|
1750
|
+
"""The contract's `cost`: a known, finite, non-negative cost, or 0."""
|
|
1751
|
+
money = _usage_money(value)
|
|
1752
|
+
return money if money is not None and math.isfinite(money) and money >= 0 else 0.0
|
|
1742
1753
|
|
|
1743
1754
|
|
|
1744
1755
|
def _exceeds(a: float, b: float) -> bool:
|
|
@@ -1797,8 +1808,7 @@ def usage_findings(report: Any) -> List[Dict[str, str]]:
|
|
|
1797
1808
|
previous = ""
|
|
1798
1809
|
for i, day in enumerate(days):
|
|
1799
1810
|
at = "/days/%d" % i
|
|
1800
|
-
date = day.get("date")
|
|
1801
|
-
date = "" if date is None else (date if isinstance(date, str) else json.dumps(date))
|
|
1811
|
+
date = fabric_estate._show("" if day.get("date") is None else day["date"]) # the contract's show(day.date ?? "")
|
|
1802
1812
|
if date <= previous:
|
|
1803
1813
|
flag(at + "/date", "days must run forward without a repeat (%s after %s)" % (date, previous))
|
|
1804
1814
|
previous = date
|
|
@@ -1806,19 +1816,29 @@ def usage_findings(report: Any) -> List[Dict[str, str]]:
|
|
|
1806
1816
|
models = _usage_list(day.get("byModel"))
|
|
1807
1817
|
for j, m in enumerate(models):
|
|
1808
1818
|
findings.extend(_priced_row(m, "%s/byModel/%d" % (at, j)))
|
|
1819
|
+
# The contract reads every count as `toNumber(x ?? 0)` and writes it with `show`: a string count is read as a
|
|
1820
|
+
# number, and what is no number (an object, "abc") is NaN, which equals no sum, so it is a finding.
|
|
1821
|
+
number = lambda row, f: fabric_estate._js_to_number(row.get(f)) # noqa: E731 - None and absent read as 0
|
|
1809
1822
|
if not models:
|
|
1810
|
-
spent = any((day
|
|
1811
|
-
|
|
1823
|
+
spent = any(number(day, f) > 0 for f in ("calls", "inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens")) \
|
|
1824
|
+
or number(day, "costUsd") > 0
|
|
1812
1825
|
if spent:
|
|
1813
1826
|
flag(at + "/byModel", "a day with calls, tokens or cost names the models they belong to")
|
|
1814
1827
|
continue
|
|
1815
1828
|
for field in ("calls", "unpricedCalls", "unmeasuredCalls", "inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"):
|
|
1816
|
-
total =
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1829
|
+
total = 0.0
|
|
1830
|
+
for m in models:
|
|
1831
|
+
total += number(m, field) # in order, in doubles, as JavaScript adds
|
|
1832
|
+
if number(day, field) != total:
|
|
1833
|
+
shown = fabric_estate._show(0 if day.get(field) is None else day[field])
|
|
1834
|
+
flag("%s/%s" % (at, field), "the day's %s is not the sum of its models (%s ≠ %s)" % (field, shown, fabric_estate._js_string(total)))
|
|
1835
|
+
known = 0.0
|
|
1836
|
+
for m in models:
|
|
1837
|
+
money = _usage_money(m.get("costUsd"))
|
|
1838
|
+
known += money if money is not None else 0.0
|
|
1839
|
+
day_cost = _usage_money(day.get("costUsd"))
|
|
1840
|
+
# The contract's !close(day, known): Infinity or NaN is never close, so it is a finding.
|
|
1841
|
+
if day_cost is not None and not abs(day_cost - known) <= 0.000001 + 1e-9 * max(abs(day_cost), abs(known)):
|
|
1822
1842
|
flag(at + "/costUsd", "the day's cost is not the sum of its models' known costs")
|
|
1823
1843
|
# The window.
|
|
1824
1844
|
if days:
|
|
@@ -1865,7 +1885,7 @@ def usage_findings(report: Any) -> List[Dict[str, str]]:
|
|
|
1865
1885
|
run_priced += max(0, _usage_count(run.get("calls")) - _usage_count(run.get("unpricedCalls")))
|
|
1866
1886
|
findings.extend(_priced_row(run, "%s/taskRuns/%d" % (at, r)))
|
|
1867
1887
|
project_priced = max(0, _usage_count(project.get("calls")) - _usage_count(project.get("unpricedCalls")))
|
|
1868
|
-
known_cost =
|
|
1888
|
+
known_cost = _usage_cost(project.get("costUsd")) == _usage_money(project.get("costUsd")) # as JavaScript holds it
|
|
1869
1889
|
if run_calls > _usage_count(project.get("calls")):
|
|
1870
1890
|
flag(at + "/taskRuns", "a project's task runs hold more calls than the project (%d > %d)" % (run_calls, _usage_count(project.get("calls"))))
|
|
1871
1891
|
elif run_priced > project_priced:
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py
CHANGED
|
@@ -258,7 +258,7 @@ def make_handler(svc: Service):
|
|
|
258
258
|
if url.path == "/":
|
|
259
259
|
if not self._session_ok():
|
|
260
260
|
return self._send(401, "Open this dashboard from Fabric Dashboards.", content_type="text/plain")
|
|
261
|
-
rows = "".join("<li>%s
|
|
261
|
+
rows = "".join("<li>%s: %s</li>" % (html.escape(e["at"]), html.escape(e["text"]))
|
|
262
262
|
for e in reversed(svc.log.fetch(None, 20)))
|
|
263
263
|
return self._send(200, PAGE.format(name=html.escape(svc.name), agent=html.escape("%s.%s" % (svc.id, svc.instance)), status="ready", rows=rows),
|
|
264
264
|
content_type="text/html; charset=utf-8")
|
|
@@ -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. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.39.
|
|
8
|
+
version: "0.39.1"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "c55f2d5a11c9a0197e958e87212fca7ae1154769"
|
|
11
11
|
---
|
|
@@ -14,60 +14,60 @@ metadata:
|
|
|
14
14
|
|
|
15
15
|
Design a new agent so that Fabric compatibility is a property of its first commit, not a
|
|
16
16
|
retrofit. The sibling skill `adapting-projects-to-fabric` wraps an interface that already
|
|
17
|
-
exists; this one runs the stages that come *before* an interface exists
|
|
18
|
-
grill and the knowledge intake
|
|
17
|
+
exists; this one runs the stages that come *before* an interface exists (the intake
|
|
18
|
+
grill and the knowledge intake), then hands the scaffolding and verification to the same
|
|
19
19
|
pinned machinery, so both doors lead into one pipeline.
|
|
20
20
|
|
|
21
21
|
## Boundary
|
|
22
22
|
|
|
23
23
|
Use this skill to design and build a new provider. Do not use it to:
|
|
24
24
|
|
|
25
|
-
- adapt a project that already has a stable API, MCP/A2A surface, or CLI
|
|
25
|
+
- adapt a project that already has a stable API, MCP/A2A surface, or CLI: that is
|
|
26
26
|
`adapting-projects-to-fabric`, and entering there skips nothing;
|
|
27
27
|
- design the Fabric host, registry, scheduler, admission service, or binding runtime;
|
|
28
|
-
- build a generic agent with no Fabric requirement
|
|
28
|
+
- build a generic agent with no Fabric requirement: nothing here helps a project that
|
|
29
29
|
will never be a provider;
|
|
30
30
|
- claim admission: the deliverable is an admission-ready provider bundle, never a
|
|
31
31
|
connected provider.
|
|
32
32
|
|
|
33
33
|
## Handing off to neighbouring skills
|
|
34
34
|
|
|
35
|
-
- The agent's own loop
|
|
35
|
+
- The agent's own loop (tool calls, routing and fallback, memory, sub-agents) is
|
|
36
36
|
designed with `agent-orchestrator` (agent-stack).
|
|
37
|
-
- Its eval suite beyond the step-2 observables
|
|
38
|
-
fixtures from traces
|
|
37
|
+
- Its eval suite beyond the step-2 observables (trajectories, judges, regression
|
|
38
|
+
fixtures from traces) is built with `agent-evals` (agent-stack).
|
|
39
39
|
- Packaging the agent as an Agent Skill or Claude Code plugin goes through `make-skill`.
|
|
40
40
|
|
|
41
41
|
When one of them is not installed, do that part by hand against its own documentation
|
|
42
42
|
and say in the report which skill was missing.
|
|
43
43
|
|
|
44
|
-
## Step 0
|
|
44
|
+
## Step 0: the intake grill
|
|
45
45
|
|
|
46
46
|
No file is created until every row has an answer. An answer of "later" fails the grill.
|
|
47
47
|
|
|
48
48
|
| Question | The gate |
|
|
49
49
|
|---|---|
|
|
50
50
|
| **What capability?** | one capability, named as `domain.action` (optionally `@surface`), with input and output shapes statable now |
|
|
51
|
-
| **Who calls it?** | **a named consumer
|
|
52
|
-
| **Workflow or agent?** | if every step can be named now, build a deterministic workflow behind the capability and skip the autonomy
|
|
51
|
+
| **Who calls it?** | **a named consumer**: the project, schedule, or workflow that will actually invoke this capability. No consumer, no agent: a role that "would be useful" is a catalogue entry, not a build order |
|
|
52
|
+
| **Workflow or agent?** | if every step can be named now, build a deterministic workflow behind the capability and skip the autonomy; an agent buys flexibility with latency, cost, and a new failure class, and that price needs a reason |
|
|
53
53
|
| **Which profile?** | `mcp` when Fabric owns planning and retries and the capability has a schema; `a2a` when the provider owns its task lifecycle end to end; `local-runner` when it is a terminal agent Fabric drives. When unsure, the sibling skill's profile-selection reference is the decision record |
|
|
54
|
-
| **What effects?** | declare anything money-, deletion-, or publication-adjacent now
|
|
54
|
+
| **What effects?** | declare anything money-, deletion-, or publication-adjacent now; the host's floor and grants are designed against these declarations, and one discovered late invalidates the admission |
|
|
55
55
|
| **Where does it run, for whom?** | this machine, the person's cloud or an organization's, and in which contexts (`personal`, `org:<id>`): one instance per context, its secrets, spend and journal never crossing into another. Record which capabilities need the person's machine and what they answer when it is off (references/where-agents-run.md) |
|
|
56
56
|
| **Which models?** | per lane (planning, replies, volume work, media): a primary, a fallback of another vendor, an open-vendor model where it reads what users send, the input kinds each reads, declared as classes in the profile (references/models-and-providers.md) |
|
|
57
57
|
| **Who pays?** | one key per agent and context with its own ceiling, from the estate's key manager, read from the environment; checked before every call; a personal key never pays for an organization |
|
|
58
58
|
| **How does it learn?** | decisions recorded with reasons, one trace per turn, every failure a planted fixture first, history replayed, learnings proposed and never self-approved (references/orchestration-and-learning.md) |
|
|
59
59
|
| **Text for people?** | if it writes anything a person reads (a reply, a message, a post, a report, a UI string), its prompt forbids the em dash (U+2014) and any substitute for it, and a test fails on U+2014 in new output, from the first commit (the resilience reference, *Text for people*) |
|
|
60
60
|
|
|
61
|
-
## Step 1
|
|
61
|
+
## Step 1: the knowledge intake
|
|
62
62
|
|
|
63
|
-
If the estate holds prior art
|
|
64
|
-
failures, a retro
|
|
63
|
+
If the estate holds prior art (a project that did this job, an audit that names its
|
|
64
|
+
failures, a retro), distil it before writing code:
|
|
65
65
|
|
|
66
66
|
1. Name the sources: repositories, `docs/`, audits, retrospectives, with references.
|
|
67
67
|
2. Extract **patterns** (what worked, each citing its origin) and **traps** (recorded
|
|
68
68
|
failures and dead ends).
|
|
69
69
|
3. **Convert every trap into a planted eval fixture.** The new agent is not done until
|
|
70
|
-
it has been watched rejecting the exact defects its predecessors were burned by
|
|
70
|
+
it has been watched rejecting the exact defects its predecessors were burned by:
|
|
71
71
|
knowledge transfers as a check, not as prose.
|
|
72
72
|
4. Record the pack in the new project as `docs/knowledge-pack.md` with its sources, so
|
|
73
73
|
the provenance of every borrowed decision survives the person who borrowed it.
|
|
@@ -90,10 +90,10 @@ prior art of its own:
|
|
|
90
90
|
- [orchestration-and-learning.md](references/orchestration-and-learning.md): the work graph,
|
|
91
91
|
calling other agents, stops for a person, learning loops, the loop guard.
|
|
92
92
|
|
|
93
|
-
## Step 2
|
|
93
|
+
## Step 2: the project skeleton
|
|
94
94
|
|
|
95
95
|
Create the minimal project: repository, `README.md` naming the capability and its
|
|
96
|
-
consumer, and the eval **observables
|
|
96
|
+
consumer, and the eval **observables**: for each requirement, the pass/fail criterion
|
|
97
97
|
that would show it met, written now, before the implementation. The input corpus is NOT
|
|
98
98
|
authored now: it grows from real traces once the provider runs, and the step-1 fixtures
|
|
99
99
|
are its seed because a source project's recorded failures count as production.
|
|
@@ -122,9 +122,9 @@ workflow names the keys it needs in its checkpoint (`credentials`), never their
|
|
|
122
122
|
org-index `scripts/check_format.py` reports 0 findings for it before it is called done. **An agent
|
|
123
123
|
someone builds for themselves is not a PassionCode.ai repository:** its licence is its owner's
|
|
124
124
|
choice, nothing about it is published or listed by the organization, and none of these files is
|
|
125
|
-
required of it
|
|
125
|
+
required of it, though the verified MCP quick start is still how anyone learns to drive it.
|
|
126
126
|
|
|
127
|
-
## Step 3
|
|
127
|
+
## Step 3: scaffold the provider bundle
|
|
128
128
|
|
|
129
129
|
Reuse the sibling skill's scaffolder against the new skeleton with the profile chosen at
|
|
130
130
|
step 0:
|
|
@@ -145,7 +145,7 @@ Pin exactly contract `0.1.0` at commit `c55f2d5a11c9a0197e958e87212fca7ae1154769
|
|
|
145
145
|
read the pinned guide before implementing protocol details. If this skill is installed
|
|
146
146
|
without its sibling, the scaffolder is absent: create the bundle by hand from the pinned
|
|
147
147
|
contract's `docs/guides/connecting-compatible-agents.md` and mark the structural check
|
|
148
|
-
`NOT_RUN
|
|
148
|
+
`NOT_RUN`; do not reconstruct normative rules from memory.
|
|
149
149
|
|
|
150
150
|
This default accepts underscore capability names; a declared communications name does
|
|
151
151
|
not grant COM access or prove runtime admission. Existing bundles retain their explicit
|
|
@@ -160,10 +160,10 @@ tool of its name, and carry the caller's trace (the `building-fabric-services` i
|
|
|
160
160
|
reference). An agent that is not a service is announced with a provider entry
|
|
161
161
|
(`adapting-projects-to-fabric`, `scripts/fabric_provider.py`).
|
|
162
162
|
|
|
163
|
-
## Step 4
|
|
163
|
+
## Step 4: implement against the observables
|
|
164
164
|
|
|
165
|
-
If the agent keeps running on the operator's computer
|
|
166
|
-
long jobs or shows a dashboard
|
|
165
|
+
If the agent keeps running on the operator's computer (it answers other agents, runs
|
|
166
|
+
long jobs or shows a dashboard), build it as a service with the sibling skill
|
|
167
167
|
`building-fabric-services`: its instance lock, launchd plist, descriptor, well-known
|
|
168
168
|
document and events feed are part of this step, not a later retrofit. If that skill is
|
|
169
169
|
absent, apply the `fabric-service/0.1` rules from the pinned contract's
|
|
@@ -179,18 +179,18 @@ provider output is untrusted until schemas and semantic assertions pass. The ste
|
|
|
179
179
|
observables are the definition of done; an observable attached after the code exists
|
|
180
180
|
lets the output decide what counts as success.
|
|
181
181
|
|
|
182
|
-
## Step 5
|
|
182
|
+
## Step 5: check, report, and set the canary expectation
|
|
183
183
|
|
|
184
184
|
Run the sibling skill's `check` (structural, then exact when the contract checkout is
|
|
185
185
|
available) and complete `fabric/FABRIC-CONFORMANCE.md` with dated receipts, exactly as
|
|
186
|
-
its workflow steps 5–7 prescribe
|
|
186
|
+
its workflow steps 5–7 prescribe; this skill adds no second verification path on
|
|
187
187
|
purpose. Then record one expectation the report must carry:
|
|
188
188
|
|
|
189
|
-
> When a Fabric host admits this provider, it enters under a **canary binding**
|
|
190
|
-
> checker on its output and a budget cap
|
|
189
|
+
> When a Fabric host admits this provider, it enters under a **canary binding** (a
|
|
190
|
+
> checker on its output and a budget cap), regardless of who wrote it. Unsupervised
|
|
191
191
|
> operation is a later, recorded promotion citing eval results and run history.
|
|
192
192
|
|
|
193
|
-
## Step 6
|
|
193
|
+
## Step 6: propose the operator channel
|
|
194
194
|
|
|
195
195
|
If the agent has a human stop (a choice, an approval, a confirmation) or emits `notify: true`
|
|
196
196
|
events, propose its **operator channel** to the operator once, now. The channel is the agent's
|
|
@@ -206,11 +206,11 @@ operator's four steps:
|
|
|
206
206
|
4. type the code it shows in the chat with the bot.
|
|
207
207
|
|
|
208
208
|
In a group shared with other bots the channel follows OC-14…OC-17 (DEC-0035): it stays silent
|
|
209
|
-
unless addressed
|
|
210
|
-
bare `/command` in a group is not addressed
|
|
209
|
+
unless addressed (`/command@ThisBot`, a reply to its own message or a press of its own button; a
|
|
210
|
+
bare `/command` in a group is not addressed), and a channel nobody receives on is `degraded`
|
|
211
211
|
(`receiver-missing`). Say so in the proposal when the operator means to bind a shared group.
|
|
212
212
|
|
|
213
|
-
Record the answer
|
|
213
|
+
Record the answer (`accepted`, `declined` or `later`) in the agent's state and in the
|
|
214
214
|
report, and do not ask again; an agent that serves `surfaces.operatorChannel` also shows it as
|
|
215
215
|
`proposal` in its status document. Creating the bot, storing the token and enabling the channel are the
|
|
216
216
|
operator's action: never create the bot, ask for or store the token's value, or enable the
|
|
@@ -237,7 +237,7 @@ table with `PASS`, `FAIL`, `NOT_RUN`, or `NOT_VERIFIED`; remaining placeholders;
|
|
|
237
237
|
operator channel answer (step 6); and the exact next command. Never summarize the outcome as
|
|
238
238
|
"Fabric-compatible" unless every required admission gate passed against the real provider.
|
|
239
239
|
|
|
240
|
-
## Dashboard handoff
|
|
240
|
+
## Dashboard handoff: also for consumers
|
|
241
241
|
|
|
242
242
|
When handing a registered service dashboard to a person, or writing the generated
|
|
243
243
|
agent's completion/notification instructions, read and apply
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Resilience and autonomy
|
|
1
|
+
# Resilience and autonomy: what kept agents working
|
|
2
2
|
|
|
3
|
-
Read when designing an agent's loop, its model calls, its spend and its stops
|
|
3
|
+
Read when designing an agent's loop, its model calls, its spend and its stops, before the first
|
|
4
4
|
stage that calls a model or spends money. These rules come from a retrospective of about twenty
|
|
5
5
|
production agents (2026-10-17): each rule names the failure that taught it, generalized, and the
|
|
6
6
|
mechanism that held after the fix. Every rule is cheaper to build in than to retrofit.
|
|
@@ -27,8 +27,8 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
27
27
|
`retryable: false` when a person must act, and `resetsAt` (UTC, `T` and `Z`, at most three
|
|
28
28
|
fraction digits) only when `retryable: true` (contract DEC-0087, `FAC-SEM-119`). Failing a job a
|
|
29
29
|
limit only paused cost callers whole jobs and resubmits.
|
|
30
|
-
2. **Resume first.** Every worker reads its own state
|
|
31
|
-
pull requests, notes
|
|
30
|
+
2. **Resume first.** Every worker reads its own state (checkpoint, worktree, branches, open
|
|
31
|
+
pull requests, notes) before it acts, and continues from there. Relaunching from scratch
|
|
32
32
|
after a limit recreated work over committed work and re-issued credentials already held.
|
|
33
33
|
3. **A silent worker is a limit until proven otherwise.** A watcher that sees no progress for N
|
|
34
34
|
minutes, or limit text in the last output, resumes the run with the *same* run id.
|
|
@@ -40,19 +40,19 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
40
40
|
6. **Match concurrency to the account's rate, and cool down after a 429** per provider door: ten
|
|
41
41
|
parallel workers met a 429 about hourly and each lost a whole batch; three to five heavy ones,
|
|
42
42
|
resumed in batches, did not. A "busy" or "still starting" dependency is retried a bounded number
|
|
43
|
-
of times with spacing (a handful, seconds apart, only on refusals classified as transient)
|
|
43
|
+
of times with spacing (a handful, seconds apart, only on refusals classified as transient):
|
|
44
44
|
never once, never forever.
|
|
45
|
-
7. **Inside a fallback chain, do not retry a provider that answered 429 or timed out
|
|
45
|
+
7. **Inside a fallback chain, do not retry a provider that answered 429 or timed out**: move to
|
|
46
46
|
the next one. Retries there stalled a waiting user. Stagger many resumes; bound each worker's
|
|
47
47
|
working set (a narrow time window, a prefix), so one stop does not become a burst.
|
|
48
48
|
|
|
49
49
|
8. **Move a refused request to another account at most once, and only before any byte reached the
|
|
50
|
-
client
|
|
50
|
+
client**: a replay after streaming began duplicated output. Persist a provider's `Retry-After`
|
|
51
51
|
across restarts; a check inside the hold answers the stored observation instead of probing.
|
|
52
52
|
|
|
53
53
|
## Model answers are untrusted until accepted
|
|
54
54
|
|
|
55
|
-
1. **"Answered" is not "usable".** Each call site has its own `accept(text)
|
|
55
|
+
1. **"Answered" is not "usable".** Each call site has its own `accept(text)`: the parser the
|
|
56
56
|
result must pass. Empty content, a refusal, a truncation (`finish_reason: length`) or an
|
|
57
57
|
unparseable answer is a failure, and only an accepted answer may be cached. A cached
|
|
58
58
|
unreadable answer once served "0 labelled" for hours.
|
|
@@ -61,13 +61,13 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
61
61
|
with no other-vendor route, and act on a new fallback only after measuring it.
|
|
62
62
|
3. **The checker is never the drafter's vendor.** Choose it per turn from the model that
|
|
63
63
|
*actually* drafted (a fallback may have), in the router, not in configuration. No checker
|
|
64
|
-
left, or a checker outage, means hold or stay silent
|
|
64
|
+
left, or a checker outage, means hold or stay silent, never ship unchecked.
|
|
65
65
|
4. **Size the output budget for the answer, not the input.** Reasoning tokens eat `max_tokens`:
|
|
66
66
|
a judge capped at 800 tokens returned no JSON in 8 of 14 calls. Give judges room or turn
|
|
67
67
|
reasoning off for that route; on `length`, retry once with a larger cap.
|
|
68
68
|
5. **Run code checks before model checks**, and degrade to a fixed safe reply with a reason code.
|
|
69
69
|
6. **A model's word verifies nothing.** A fact is verified by stored source text (or two truly
|
|
70
|
-
independent sources); an effect by the system of record it lands in
|
|
70
|
+
independent sources); an effect by the system of record it lands in, never by the page, a
|
|
71
71
|
flag the model set, or its "done".
|
|
72
72
|
7. **A coding agent runs only on a model it knows.** One CLI agent on an unfamiliar vendor's model
|
|
73
73
|
read its instructions and ended its turn with nothing written; measure each (agent, model)
|
|
@@ -78,19 +78,19 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
78
78
|
|
|
79
79
|
## Money is a ledger
|
|
80
80
|
|
|
81
|
-
1. **Unknown is unknown, never zero**
|
|
81
|
+
1. **Unknown is unknown, never zero** (in cost, in verdicts, in health), and nothing acts on it.
|
|
82
82
|
A missing price is a high estimate, a missing field is `NOT_RUN`, never a pass; a probe that
|
|
83
83
|
timed out is "unreadable", not "credential rejected"; a cooldown that could not be read is not
|
|
84
84
|
"never sent". An owner's refusal inside a successful response (`ok: false` over a 200) is a
|
|
85
85
|
refusal, never an empty result.
|
|
86
|
-
2. **Persist the attempt before dispatch**
|
|
86
|
+
2. **Persist the attempt before dispatch** (its route and a hash of its prompt) and finalize it
|
|
87
87
|
in `finally`; spend is Σ actual + Σ estimate for attempts without an actual. In-memory counters
|
|
88
88
|
reset on every resume. A call that broke before the provider answered is reconciled against
|
|
89
89
|
the provider's own history by that route and hash, never guessed; one provider request id
|
|
90
90
|
belongs to exactly one ledger row.
|
|
91
91
|
3. **Check the cap before every HTTP attempt, retries included**, with one stop rule; record the
|
|
92
92
|
provider's own reported cost per call; count reserved or held tokens as spent.
|
|
93
|
-
4. **A resend of an unknown outcome reuses its idempotency key
|
|
93
|
+
4. **A resend of an unknown outcome reuses its idempotency key, or does not happen.** Derive
|
|
94
94
|
keys from the intent (action and entity, or run and step), never from a clock or a random
|
|
95
95
|
value, and make them unique in storage: time-and-random keys let redelivered requests charge
|
|
96
96
|
again. A write whose effect is uncertain ends `failed` with the code `outcome-unknown`; only an
|
|
@@ -102,7 +102,7 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
102
102
|
if none), release the reserved money in the same transaction, and never resend the identical
|
|
103
103
|
request; a 4xx while polling is still a lost task.
|
|
104
104
|
6. **A failed outer job cancels the paid work it started**, and a cancel is a row the worker reads
|
|
105
|
-
at every claim and checkpoint
|
|
105
|
+
at every claim and checkpoint, never an in-memory flag that a restart forgets.
|
|
106
106
|
7. **Reuse what was paid for.** A restarted stage looks up the job's successful call with the
|
|
107
107
|
same route, prompt version and inputs before calling again (one restart made 117 paid calls
|
|
108
108
|
where 49 sufficed). Cache keys are content plus definition, never an identity alone.
|
|
@@ -121,14 +121,14 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
121
121
|
2. **Nothing in a user's path waits for a human approval.** Send now or not at all; when unsure,
|
|
122
122
|
say so, open a recorded question, and answer again automatically when the knowledge changes.
|
|
123
123
|
Drafts awaiting approval waited for days.
|
|
124
|
-
3. **Every wait lives in durable storage**, rebuilt on start from a "debt" query
|
|
124
|
+
3. **Every wait lives in durable storage**, rebuilt on start from a "debt" query; timers in
|
|
125
125
|
process memory died with every restart. A wait that must outlive a session (a credential that
|
|
126
126
|
has not landed yet) is an operating-system job, not a session's background task.
|
|
127
127
|
4. **Stop at the third round.** Tuning wording or patching one heuristic a third time means
|
|
128
128
|
splitting the change or replacing the mechanism with one invariant and a property test.
|
|
129
129
|
Write a review's exit criterion before its rounds start, and block only on what breaks a
|
|
130
130
|
documented promise; the rest becomes a board row with its evidence.
|
|
131
|
-
5. **Find the real cause before telling a person what to do
|
|
131
|
+
5. **Find the real cause before telling a person what to do**: a full disk once read as "the
|
|
132
132
|
service stopped" while the service was healthy; check free space and the service's own health
|
|
133
133
|
first.
|
|
134
134
|
6. **Background chores never hold a finished command open**, unattended runs skip them, and an
|
|
@@ -144,17 +144,17 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
144
144
|
## Verification you can believe
|
|
145
145
|
|
|
146
146
|
1. **A test counts once it was watched failing against a planted defect** (clear bytecode caches
|
|
147
|
-
first
|
|
147
|
+
first: stale bytecode reported mutants killed); a mutation battery
|
|
148
148
|
asserts its own reach ("no tests ran" is not "all plants survived"). Exercise every guard
|
|
149
149
|
with a must-catch and a must-pass case.
|
|
150
150
|
2. **Write expectations out; never compute them from the code under test**, and run a reference
|
|
151
|
-
function and its schema on the same inputs in one test
|
|
151
|
+
function and its schema on the same inputs in one test; the most common defect a first reading
|
|
152
152
|
found was a function returning what its own schema refuses. A wall-clock bound sits far above a
|
|
153
153
|
loaded machine and far below the defect it guards.
|
|
154
154
|
3. **Test against the real deployed instance behind its platform**, not a sample the same run
|
|
155
155
|
wrote: a probe passed the shipped sample and failed the first real service behind a router.
|
|
156
|
-
4. **After each wave, three blind readings**
|
|
157
|
-
and the documented behaviour
|
|
156
|
+
4. **After each wave, three blind readings** (the changed units, everything that reaches them,
|
|
157
|
+
and the documented behaviour), then one fix round. Green suites hid six to ten real breaks.
|
|
158
158
|
5. **Read a gate's exit code directly**, never through a pipe in the same command (`| tail` hid a
|
|
159
159
|
failed gate as success): write the output to a file, take the status, then read the file.
|
|
160
160
|
6. **"Could not measure" is its own outcome.** An overloaded machine makes a gate refuse, and a
|
|
@@ -166,7 +166,7 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
166
166
|
9. **Probe live after every deploy on every consumer channel**, not the default one; read the
|
|
167
167
|
nightly full-suite result first when resuming work, and release from a full local run, not a
|
|
168
168
|
selected subset.
|
|
169
|
-
10. **Port a protocol from the contract's shared vectors, never its prose
|
|
169
|
+
10. **Port a protocol from the contract's shared vectors, never its prose**, vendored with a hash
|
|
170
170
|
per file and run case by case; a prose port accepted a year-0000 date the vectors refused.
|
|
171
171
|
11. **Read an external contract from its raw documentation or a recorded live snapshot**, never
|
|
172
172
|
from a summarising fetch; fail closed on a field nobody documented.
|
|
@@ -177,11 +177,11 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
177
177
|
concurrent worker has its own identity; a refused claim names the run that holds the task, never
|
|
178
178
|
a guessed race against a lease that lapsed long ago; a copy that a
|
|
179
179
|
supervisor started backs off on a held lock instead of exiting into a respawn loop. A failed
|
|
180
|
-
lease renewal is not a lost lease
|
|
180
|
+
lease renewal is not a lost lease; stop only when the remaining margin runs out.
|
|
181
181
|
2. **Health treats "never finished" as a fault.** Age each phase from its last completion; a
|
|
182
182
|
phase that only ever yielded looked healthy for fifteen days.
|
|
183
183
|
3. **Everything a run creates carries its owner** (run id, pid); the next start sweeps what a dead
|
|
184
|
-
owner left
|
|
184
|
+
owner left: markers, test databases, temp roots, container volumes. Nothing a long job needs
|
|
185
185
|
lives in a temp directory; a cleaner removed a test database mid-deploy.
|
|
186
186
|
4. **Check disk and load before heavy work**, and serialize heavy suites behind a lock held for
|
|
187
187
|
the whole run. A disk cleanup never deletes files version control tracks.
|
|
@@ -194,16 +194,16 @@ mechanism that held after the fix. Every rule is cheaper to build in than to ret
|
|
|
194
194
|
seconds) answers a retryable "busy", never a crash or a deadlock; every pooled connection has
|
|
195
195
|
an error listener, and readiness reports the store while liveness reports the process, so a
|
|
196
196
|
database blip takes the instance out of traffic instead of restarting it.
|
|
197
|
-
8. **Every accumulating output has a cap in the code that writes it**
|
|
197
|
+
8. **Every accumulating output has a cap in the code that writes it** (logs, receipts, caches),
|
|
198
198
|
with both bounds tested.
|
|
199
199
|
9. **Name the account and context on every cloud CLI call** and verify the account before a write;
|
|
200
200
|
a saved default context once pointed a correct token at another team.
|
|
201
201
|
10. **Log by error name and code**, never a driver's raw message, which can carry connection
|
|
202
|
-
details. A failed command logs its cause
|
|
202
|
+
details. A failed command logs its cause (deadline, signal, exit code, missing program), never a
|
|
203
203
|
bare "command failed"; a watched dependency logs once when it fails and once when it recovers,
|
|
204
204
|
never on every poll.
|
|
205
|
-
11. **Admit work only when the machine can carry it**
|
|
206
|
-
sessions
|
|
205
|
+
11. **Admit work only when the machine can carry it** (free disk, swap, a cap on concurrent
|
|
206
|
+
sessions) and say why when you defer.
|
|
207
207
|
|
|
208
208
|
12. **Keep blocking work off the workers that answer requests**, never let a read wait behind a long
|
|
209
209
|
transaction, and log a transaction held long with the step that held it. Every blocking network
|