@passioncode-ai/passioncode 0.2.8 → 0.2.10
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 +16 -0
- package/family.json +1 -1
- package/package.json +1 -1
- package/payload/.claude-plugin/marketplace.json +2 -2
- package/payload/manifest.json +8 -8
- package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +12 -1
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/resilience-and-autonomy.md +254 -0
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +2 -2
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +3 -2
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +6 -2
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +41 -3
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/hosts/hermes/__init__.py +9 -3
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.10 - 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Fabric Agent Adapter is pinned at `v0.35.3`** (it was `v0.35.2`). `creating-fabric-agents` asks in its grill
|
|
8
|
+
whether a new agent writes text people read, and if so gives it the no-em-dash rule (U+2014) in its prompt and a
|
|
9
|
+
test that fails on it; the resilience reference gains its third pass.
|
|
10
|
+
|
|
11
|
+
## 0.2.9 - 2026-10-18
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Fabric Agent Adapter is pinned at `v0.35.2`** (it was `v0.35.0`). 0.35.1 names the Codex hook groups a removal
|
|
16
|
+
moves, stops the tests and the session hooks' spool from accumulating files, adds the `creating-fabric-agents`
|
|
17
|
+
resilience-and-autonomy reference and fixes the Hermes adapter losing a helper's answer; 0.35.2 extends the reference.
|
|
18
|
+
|
|
3
19
|
## 0.2.8 - 2026-10-16
|
|
4
20
|
|
|
5
21
|
### Changed
|
package/family.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/passioncode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Set up PassionCode.ai on a computer with one command — the apps from their verified releases, their MCP servers for every coding agent and the PassionCode.ai skills — and check and repair it with the same command or `doctor`.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"passioncode": "bin/passioncode.js"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"name": "fabric-agent-adapter",
|
|
12
12
|
"displayName": "Fabric Agent Adapter",
|
|
13
13
|
"source": "./plugins/fabric-agent-adapter",
|
|
14
|
-
"version": "0.35.
|
|
14
|
+
"version": "0.35.3",
|
|
15
15
|
"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.",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "PassionCode.ai",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"name": "passioncode",
|
|
36
36
|
"displayName": "PassionCode.ai",
|
|
37
37
|
"source": "./plugins/passioncode",
|
|
38
|
-
"version": "0.2.
|
|
38
|
+
"version": "0.2.10",
|
|
39
39
|
"description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
40
40
|
"author": {
|
|
41
41
|
"name": "PassionCode.ai",
|
package/payload/manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"family": "passioncode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"release": true,
|
|
5
5
|
"members": [
|
|
6
6
|
{
|
|
7
7
|
"name": "fabric-agent-adapter",
|
|
8
8
|
"displayName": "Fabric Agent Adapter",
|
|
9
9
|
"repo": "passioncode-ai/fabric-agent-adapter",
|
|
10
|
-
"ref": "v0.35.
|
|
11
|
-
"commit": "
|
|
12
|
-
"version": "0.35.
|
|
10
|
+
"ref": "v0.35.3",
|
|
11
|
+
"commit": "5657241426db468e4f51f9ef72788efb98eab9f9",
|
|
12
|
+
"version": "0.35.3",
|
|
13
13
|
"via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
|
|
14
14
|
"skills": [
|
|
15
15
|
"adapting-projects-to-fabric",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"legacyMarketplaces": [
|
|
24
24
|
"fabric-agent-adapter"
|
|
25
25
|
],
|
|
26
|
-
"contentHash": "sha256:
|
|
26
|
+
"contentHash": "sha256:392cd0dd9bf29f450346b9d02af7d6b4cb19922da13aed46dba7fb9b65715e8e",
|
|
27
27
|
"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.",
|
|
28
28
|
"author": {
|
|
29
29
|
"name": "PassionCode.ai",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"name": "passioncode",
|
|
63
63
|
"displayName": "PassionCode.ai",
|
|
64
64
|
"repo": "passioncode-ai/passioncode",
|
|
65
|
-
"ref": "v0.2.
|
|
65
|
+
"ref": "v0.2.10",
|
|
66
66
|
"commit": null,
|
|
67
|
-
"version": "0.2.
|
|
67
|
+
"version": "0.2.10",
|
|
68
68
|
"via": "this repository",
|
|
69
69
|
"skills": [
|
|
70
70
|
"setting-up-passioncode",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
],
|
|
73
73
|
"legacyPluginIds": [],
|
|
74
74
|
"legacyMarketplaces": [],
|
|
75
|
-
"contentHash": "sha256:
|
|
75
|
+
"contentHash": "sha256:a34b46fa6572410a232b9c1442eb6df00dc92037b342fb7539b5a378e9c06dac",
|
|
76
76
|
"description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
77
77
|
"author": {
|
|
78
78
|
"name": "PassionCode.ai",
|
|
@@ -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.35.
|
|
6
|
+
"version": "0.35.3",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -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.35.
|
|
8
|
+
version: "0.35.3"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "d8146cfb0a1c74fc6432284c4356bb0c864b37b5"
|
|
11
11
|
---
|
|
@@ -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.35.
|
|
21
|
+
version: "0.35.3"
|
|
22
22
|
contract-version: "0.1.0"
|
|
23
23
|
extension: "fabric-service/0.1"
|
|
24
24
|
extension-commit: "d8146cfb0a1c74fc6432284c4356bb0c864b37b5"
|
|
@@ -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.35.
|
|
8
|
+
version: "0.35.3"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "d8146cfb0a1c74fc6432284c4356bb0c864b37b5"
|
|
11
11
|
---
|
|
@@ -53,6 +53,7 @@ No file is created until every row has an answer. An answer of "later" fails the
|
|
|
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
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
|
| **Which tenancy?** | record whether the agent assumes one operator; the assumption is cheap to write down and expensive to excavate |
|
|
56
|
+
| **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*) |
|
|
56
57
|
|
|
57
58
|
## Step 1 — the knowledge intake
|
|
58
59
|
|
|
@@ -201,6 +202,16 @@ the pinned `schemas/operator-channel-status.schema.json`; the channel's runtime
|
|
|
201
202
|
probe rule here yet, so report them `NOT_RUN`. An agent with neither stops nor notify
|
|
202
203
|
events skips this step.
|
|
203
204
|
|
|
205
|
+
## Resilience and autonomy
|
|
206
|
+
|
|
207
|
+
Before the first stage that calls a model or spends money, read
|
|
208
|
+
[resilience and autonomy](references/resilience-and-autonomy.md) and design its answers in:
|
|
209
|
+
a limit pauses and resumes, every model answer passes the call site's own acceptance, fallbacks
|
|
210
|
+
and checkers cross vendors, spend is a persisted ledger with idempotency keys, open questions
|
|
211
|
+
become recorded decisions, every fix has a test seen failing, and text a person reads carries no
|
|
212
|
+
em dash (U+2014), held by its prompt and a test. Report its checklist with a
|
|
213
|
+
file and a test per line, or `NOT_RUN` where the agent has no such stage.
|
|
214
|
+
|
|
204
215
|
## Completion format
|
|
205
216
|
|
|
206
217
|
Report: capability, consumer, profile with the step-0 grill answers; the knowledge pack
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Resilience and autonomy — what kept agents working
|
|
2
|
+
|
|
3
|
+
Read when designing an agent's loop, its model calls, its spend and its stops — before the first
|
|
4
|
+
stage that calls a model or spends money. These rules come from a retrospective of about twenty
|
|
5
|
+
production agents (2026-10-17): each rule names the failure that taught it, generalized, and the
|
|
6
|
+
mechanism that held after the fix. Every rule is cheaper to build in than to retrofit.
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- Limits are pauses
|
|
11
|
+
- Model answers are untrusted until accepted
|
|
12
|
+
- Money is a ledger
|
|
13
|
+
- Autonomy: decide, record, continue
|
|
14
|
+
- Verification you can believe
|
|
15
|
+
- Operations that survive restarts
|
|
16
|
+
- Secrets and confinement
|
|
17
|
+
- Text for people
|
|
18
|
+
- Checklist
|
|
19
|
+
|
|
20
|
+
## Limits are pauses
|
|
21
|
+
|
|
22
|
+
1. **A provider or subscription limit pauses the job; it never fails it.** Store a typed pause
|
|
23
|
+
(`quota_exhausted`, `retryable: true`, the reset time when known) and resume from the same
|
|
24
|
+
stage. Failing it cost callers whole jobs and resubmits.
|
|
25
|
+
2. **Resume first.** Every worker reads its own state — checkpoint, worktree, branches, open
|
|
26
|
+
pull requests, notes — before it acts, and continues from there. Relaunching from scratch
|
|
27
|
+
after a limit recreated work over committed work and re-issued credentials already held.
|
|
28
|
+
3. **A silent worker is a limit until proven otherwise.** A watcher that sees no progress for N
|
|
29
|
+
minutes, or limit text in the last output, resumes the run with the *same* run id.
|
|
30
|
+
4. **Keep prompt cache keys stable across a resume.** One changed argument that every prompt
|
|
31
|
+
carries (today's date) re-keyed a finished stage and paid for it again.
|
|
32
|
+
5. **Count only the job's own failures toward its retry limit.** A lost lease, a busy lock or a
|
|
33
|
+
provider outage is bounded by a time budget instead; five infrastructure stops a day ended a
|
|
34
|
+
healthy phase as FAILED.
|
|
35
|
+
6. **Match concurrency to the account's rate, and cool down after a 429** per provider door: ten
|
|
36
|
+
parallel workers met a 429 about hourly and each lost a whole batch; three to five heavy ones,
|
|
37
|
+
resumed in batches, did not. A "busy" or "still starting" dependency is retried a bounded number
|
|
38
|
+
of times with spacing (a handful, seconds apart, only on refusals classified as transient) —
|
|
39
|
+
never once, never forever.
|
|
40
|
+
7. **Inside a fallback chain, do not retry a provider that answered 429 or timed out** — move to
|
|
41
|
+
the next one. Retries there stalled a waiting user. Stagger many resumes; bound each worker's
|
|
42
|
+
working set (a narrow time window, a prefix), so one stop does not become a burst.
|
|
43
|
+
|
|
44
|
+
8. **Move a refused request to another account at most once, and only before any byte reached the
|
|
45
|
+
client** — a replay after streaming began duplicated output. Persist a provider's `Retry-After`
|
|
46
|
+
across restarts; a check inside the hold answers the stored observation instead of probing.
|
|
47
|
+
|
|
48
|
+
## Model answers are untrusted until accepted
|
|
49
|
+
|
|
50
|
+
1. **"Answered" is not "usable".** Each call site has its own `accept(text)` — the parser the
|
|
51
|
+
result must pass. Empty content, a refusal, a truncation (`finish_reason: length`) or an
|
|
52
|
+
unparseable answer is a failure, and only an accepted answer may be cached. A cached
|
|
53
|
+
unreadable answer once served "0 labelled" for hours.
|
|
54
|
+
2. **Fallbacks cross vendors.** A chain is an ordered list of models from different vendors; a
|
|
55
|
+
same-vendor fallback refuses the same inputs. Build the chain where a test can refuse a lane
|
|
56
|
+
with no other-vendor route, and act on a new fallback only after measuring it.
|
|
57
|
+
3. **The checker is never the drafter's vendor.** Choose it per turn from the model that
|
|
58
|
+
*actually* drafted (a fallback may have), in the router, not in configuration. No checker
|
|
59
|
+
left, or a checker outage, means hold or stay silent — never ship unchecked.
|
|
60
|
+
4. **Size the output budget for the answer, not the input.** Reasoning tokens eat `max_tokens`:
|
|
61
|
+
a judge capped at 800 tokens returned no JSON in 8 of 14 calls. Give judges room or turn
|
|
62
|
+
reasoning off for that route; on `length`, retry once with a larger cap.
|
|
63
|
+
5. **Run code checks before model checks**, and degrade to a fixed safe reply with a reason code.
|
|
64
|
+
6. **A model's word verifies nothing.** A fact is verified by stored source text (or two truly
|
|
65
|
+
independent sources); an effect by the system of record it lands in — never by the page, a
|
|
66
|
+
flag the model set, or its "done".
|
|
67
|
+
7. **A coding agent runs only on a model it knows.** One CLI agent on an unfamiliar vendor's model
|
|
68
|
+
read its instructions and ended its turn with nothing written; measure each (agent, model)
|
|
69
|
+
pair live before making it a default.
|
|
70
|
+
8. **Pin what the agent says about itself.** A short block of true facts (what it can see, its
|
|
71
|
+
pipeline, its limits) lives in code; a test fails when a fact drifts. Forbid promises the
|
|
72
|
+
pipeline cannot keep.
|
|
73
|
+
|
|
74
|
+
## Money is a ledger
|
|
75
|
+
|
|
76
|
+
1. **Unknown is unknown, never zero** — in cost, in verdicts, in health — and nothing acts on it.
|
|
77
|
+
A missing price is a high estimate, a missing field is `NOT_RUN`, never a pass; a probe that
|
|
78
|
+
timed out is "unreadable", not "credential rejected"; a cooldown that could not be read is not
|
|
79
|
+
"never sent". An owner's refusal inside a successful response (`ok: false` over a 200) is a
|
|
80
|
+
refusal, never an empty result.
|
|
81
|
+
2. **Persist the attempt before dispatch** — its route and a hash of its prompt — and finalize it
|
|
82
|
+
in `finally`; spend is Σ actual + Σ estimate for attempts without an actual. In-memory counters
|
|
83
|
+
reset on every resume. A call that broke before the provider answered is reconciled against
|
|
84
|
+
the provider's own history by that route and hash, never guessed; one provider request id
|
|
85
|
+
belongs to exactly one ledger row.
|
|
86
|
+
3. **Check the cap before every HTTP attempt, retries included**, with one stop rule; record the
|
|
87
|
+
provider's own reported cost per call; count reserved or held tokens as spent.
|
|
88
|
+
4. **A resend of an unknown outcome reuses its idempotency key — or does not happen.** Derive
|
|
89
|
+
keys from the intent (action and entity, or run and step), never from a clock or a random
|
|
90
|
+
value, and make them unique in storage: time-and-random keys let redelivered requests charge
|
|
91
|
+
again. A write whose effect is uncertain stops as `outcome_unknown`; a new key there pays twice.
|
|
92
|
+
5. **A client error at submit is a rejection, not a lost task.** Settle it with known usage (zero
|
|
93
|
+
if none), release the reserved money in the same transaction, and never resend the identical
|
|
94
|
+
request; a 4xx while polling is still a lost task.
|
|
95
|
+
6. **A failed outer job cancels the paid work it started**, and a cancel is a row the worker reads
|
|
96
|
+
at every claim and checkpoint — never an in-memory flag that a restart forgets.
|
|
97
|
+
7. **Reuse what was paid for.** A restarted stage looks up the job's successful call with the
|
|
98
|
+
same route, prompt version and inputs before calling again (one restart made 117 paid calls
|
|
99
|
+
where 49 sufficed). Cache keys are content plus definition, never an identity alone.
|
|
100
|
+
8. **A rerun after an interruption calls read-only tools until its open outcomes are reconciled**;
|
|
101
|
+
an idempotency key covers the content as well as the run, so a retried run that asks a new
|
|
102
|
+
question is not refused as a conflict.
|
|
103
|
+
9. **Deny spending tools by enumerated name, and test the list**: a provider's newer tools were
|
|
104
|
+
missing from a deny list while older ones were denied.
|
|
105
|
+
|
|
106
|
+
## Autonomy: decide, record, continue
|
|
107
|
+
|
|
108
|
+
1. **An open question is a decision to make.** Decide from the recorded decisions, then the
|
|
109
|
+
goal, then the safest reversible option; write the decision and its source where the work
|
|
110
|
+
lives; batch the questions only a person can answer (sign-in, consent, payment, a typed
|
|
111
|
+
secret) and keep working around them.
|
|
112
|
+
2. **Nothing in a user's path waits for a human approval.** Send now or not at all; when unsure,
|
|
113
|
+
say so, open a recorded question, and answer again automatically when the knowledge changes.
|
|
114
|
+
Drafts awaiting approval waited for days.
|
|
115
|
+
3. **Every wait lives in durable storage**, rebuilt on start from a "debt" query — timers in
|
|
116
|
+
process memory died with every restart. A wait that must outlive a session (a credential that
|
|
117
|
+
has not landed yet) is an operating-system job, not a session's background task.
|
|
118
|
+
4. **Stop at the third round.** Tuning wording or patching one heuristic a third time means
|
|
119
|
+
splitting the change or replacing the mechanism with one invariant and a property test.
|
|
120
|
+
Write a review's exit criterion before its rounds start, and block only on what breaks a
|
|
121
|
+
documented promise; the rest becomes a board row with its evidence.
|
|
122
|
+
5. **Find the real cause before telling a person what to do** — a full disk once read as "the
|
|
123
|
+
service stopped" while the service was healthy; check free space and the service's own health
|
|
124
|
+
first.
|
|
125
|
+
6. **Background chores never hold a finished command open**, unattended runs skip them, and an
|
|
126
|
+
unattended `--yes` never reverses a "no" a person saved.
|
|
127
|
+
|
|
128
|
+
7. **When a peer waits on your decision, make it**, mark it reversible, record it where the work
|
|
129
|
+
lives and reply naming the record; a platform once built nothing for days waiting on a
|
|
130
|
+
definition. Release a coordination lease in the same step as the commit that used it, the
|
|
131
|
+
failure path included.
|
|
132
|
+
8. **Hand work over only while it is alive**, and claim each hand-over exactly once by an exclusive
|
|
133
|
+
create; a forgotten workflow was handed over twice.
|
|
134
|
+
|
|
135
|
+
## Verification you can believe
|
|
136
|
+
|
|
137
|
+
1. **A test counts once it was watched failing against a planted defect** (clear bytecode caches
|
|
138
|
+
first — stale bytecode reported mutants killed); a mutation battery
|
|
139
|
+
asserts its own reach ("no tests ran" is not "all plants survived"). Exercise every guard
|
|
140
|
+
with a must-catch and a must-pass case.
|
|
141
|
+
2. **Write expectations out; never compute them from the code under test**, and run a reference
|
|
142
|
+
function and its schema on the same inputs in one test — the most common defect a first reading
|
|
143
|
+
found was a function returning what its own schema refuses. A wall-clock bound sits far above a
|
|
144
|
+
loaded machine and far below the defect it guards.
|
|
145
|
+
3. **Test against the real deployed instance behind its platform**, not a sample the same run
|
|
146
|
+
wrote: a probe passed the shipped sample and failed the first real service behind a router.
|
|
147
|
+
4. **After each wave, three blind readings** — the changed units, everything that reaches them,
|
|
148
|
+
and the documented behaviour — then one fix round. Green suites hid six to ten real breaks.
|
|
149
|
+
5. **Read a gate's exit code directly**, never through a pipe in the same command (`| tail` hid a
|
|
150
|
+
failed gate as success): write the output to a file, take the status, then read the file.
|
|
151
|
+
6. **"Could not measure" is its own outcome.** An overloaded machine makes a gate refuse, and a
|
|
152
|
+
timeout under load is unmeasured, not failed or passed.
|
|
153
|
+
7. **Measure noise before calling a regression**; release a retrieval change only with zero new
|
|
154
|
+
confident wrong answers against the previous version.
|
|
155
|
+
8. **Compute a status from the whole group**, never incrementally; verify a release by the
|
|
156
|
+
commit it serves, from a fresh checkout of that commit.
|
|
157
|
+
9. **Probe live after every deploy on every consumer channel**, not the default one; read the
|
|
158
|
+
nightly full-suite result first when resuming work, and release from a full local run, not a
|
|
159
|
+
selected subset.
|
|
160
|
+
10. **Port a protocol from the contract's shared vectors, never its prose** — vendored with a hash
|
|
161
|
+
per file and run case by case; a prose port accepted a year-0000 date the vectors refused.
|
|
162
|
+
11. **Read an external contract from its raw documentation or a recorded live snapshot**, never
|
|
163
|
+
from a summarising fetch; fail closed on a field nobody documented.
|
|
164
|
+
|
|
165
|
+
## Operations that survive restarts
|
|
166
|
+
|
|
167
|
+
1. **One owner per run, named.** Only the holder of the run's lock changes its lifecycle, and each
|
|
168
|
+
concurrent worker has its own identity; a refused claim names the run that holds the task, never
|
|
169
|
+
a guessed race against a lease that lapsed long ago; a copy that a
|
|
170
|
+
supervisor started backs off on a held lock instead of exiting into a respawn loop. A failed
|
|
171
|
+
lease renewal is not a lost lease — stop only when the remaining margin runs out.
|
|
172
|
+
2. **Health treats "never finished" as a fault.** Age each phase from its last completion; a
|
|
173
|
+
phase that only ever yielded looked healthy for fifteen days.
|
|
174
|
+
3. **Everything a run creates carries its owner** (run id, pid); the next start sweeps what a dead
|
|
175
|
+
owner left — markers, test databases, temp roots, container volumes. Nothing a long job needs
|
|
176
|
+
lives in a temp directory; a cleaner removed a test database mid-deploy.
|
|
177
|
+
4. **Check disk and load before heavy work**, and serialize heavy suites behind a lock held for
|
|
178
|
+
the whole run. A disk cleanup never deletes files version control tracks.
|
|
179
|
+
5. **State that must survive a restart or a second instance lives in the shared store**, never in
|
|
180
|
+
process memory or on one instance's disk; a per-process cache is a cache with a TTL.
|
|
181
|
+
6. **Every scheduled job leaves a trace when it dies, stalls or never starts**: a heartbeat at the
|
|
182
|
+
start of each tick, a queryable error row, re-arming in `finally`, an alert when quiet for two
|
|
183
|
+
intervals. Serialize every use of a connection threads share, reads included.
|
|
184
|
+
7. **Bound every wait on a shared lock and survive a lost connection.** A lock timeout (a few
|
|
185
|
+
seconds) answers a retryable "busy", never a crash or a deadlock; every pooled connection has
|
|
186
|
+
an error listener, and readiness reports the store while liveness reports the process, so a
|
|
187
|
+
database blip takes the instance out of traffic instead of restarting it.
|
|
188
|
+
8. **Every accumulating output has a cap in the code that writes it** — logs, receipts, caches —
|
|
189
|
+
with both bounds tested.
|
|
190
|
+
9. **Name the account and context on every cloud CLI call** and verify the account before a write;
|
|
191
|
+
a saved default context once pointed a correct token at another team.
|
|
192
|
+
10. **Log by error name and code**, never a driver's raw message, which can carry connection
|
|
193
|
+
details. A failed command logs its cause — deadline, signal, exit code, missing program — never a
|
|
194
|
+
bare "command failed"; a watched dependency logs once when it fails and once when it recovers,
|
|
195
|
+
never on every poll.
|
|
196
|
+
11. **Admit work only when the machine can carry it** — free disk, swap, a cap on concurrent
|
|
197
|
+
sessions — and say why when you defer.
|
|
198
|
+
|
|
199
|
+
12. **Keep blocking work off the workers that answer requests**, never let a read wait behind a long
|
|
200
|
+
transaction, and log a transaction held long with the step that held it. Every blocking network
|
|
201
|
+
wait is interruptible from outside, with its deadline on the socket, not between frames.
|
|
202
|
+
|
|
203
|
+
## Secrets and confinement
|
|
204
|
+
|
|
205
|
+
1. **A secret never enters an environment, argv, URL or log a child can read.** Hand it over a
|
|
206
|
+
pipe or file descriptor, or through a vault runner per command; never print a configuration
|
|
207
|
+
dump, and never filter one yourself.
|
|
208
|
+
2. **Confine what an agent runs by the operating system**, per run (filesystem, network, ports),
|
|
209
|
+
and treat prompt rules and command guards as tripwires, not boundaries.
|
|
210
|
+
3. **Fail closed.** When a reduced view or a scope cannot be built, answer an error, never the
|
|
211
|
+
wider view; a new matching rule fires only when every neighbouring input is on its allow-list.
|
|
212
|
+
4. **Run the private-name gate chained before every public push** (`<gate> && git push`), not only
|
|
213
|
+
before merge: a `;` once let a private path reach a public branch.
|
|
214
|
+
5. **Freeze every input at acceptance**, nested references included: copy it by hash and work on
|
|
215
|
+
the copy, so a file swapped between acceptance and use is never read.
|
|
216
|
+
6. **Pin every tool's input schema to the domain schema with a test.** A save tool that silently
|
|
217
|
+
dropped unknown fields once stored an agent set to send without the guards it was given.
|
|
218
|
+
7. **Treat what a run wrote as untrusted** before the service copies, reads or renders it: a
|
|
219
|
+
regular file whose real path is inside the run's own tree.
|
|
220
|
+
8. **An artefact missing a secret refuses to start**, never runs unauthenticated: validation rejects
|
|
221
|
+
placeholder-shaped values, and a test renders the configuration without the value and expects
|
|
222
|
+
an error.
|
|
223
|
+
9. **Widen an access configuration first, code second**, so nothing is over-scoped even briefly.
|
|
224
|
+
|
|
225
|
+
10. **Bound every recursive parse of untrusted input** (a depth limit, tested at the limit and one
|
|
226
|
+
above). Revoke or retire by a content-addressed reference, never a reusable name: a re-keyed
|
|
227
|
+
machine keeps its key id. Never refresh a person's own credentials as a side effect; renew only
|
|
228
|
+
what you own.
|
|
229
|
+
|
|
230
|
+
## Text for people
|
|
231
|
+
|
|
232
|
+
1. **Text a person reads carries no em dash** (U+2014) and no substitute for it: not `--`, not a
|
|
233
|
+
spaced hyphen used as a dash. The operator reads it as the mark of machine-written text. Use a
|
|
234
|
+
full stop, a comma, a colon or parentheses, or rewrite the sentence. This covers replies,
|
|
235
|
+
messages, posts, mail, reports, captions and UI strings, in every language.
|
|
236
|
+
2. **Exceptions are exact, never stylistic:** exact quotations, code, commands, paths and
|
|
237
|
+
historical records keep the character as they are.
|
|
238
|
+
3. **The rule lives in the prompt and in a test.** The prompt of every lane that writes for people
|
|
239
|
+
states it; a test feeds that lane's output (and every fixed template) through a check that
|
|
240
|
+
fails on U+2014, for example `assert "\u2014" not in text`, and is seen failing on a planted
|
|
241
|
+
dash. A spaced hyphen cannot be told from a range or a minus by a pattern: the prompt and a
|
|
242
|
+
reviewer carry that half, the check does not guess.
|
|
243
|
+
4. **A model's slip is rewritten or refused, never shipped.** When an accepted answer still holds U+2014, the
|
|
244
|
+
call site's `accept` rewrites it or retries once, then refuses; it never sends the dash on.
|
|
245
|
+
|
|
246
|
+
## Checklist
|
|
247
|
+
|
|
248
|
+
Before the first release, answer each with a file and a test: where a limit pauses and resumes; what
|
|
249
|
+
`accept` each model call has; which vendors its fallback and checker use; where the attempt ledger
|
|
250
|
+
and idempotency keys live; what happens to an unknown outcome; which waits are durable; who owns the
|
|
251
|
+
run lock; what the next start sweeps; how a cancel reaches paid inner work; which outputs are
|
|
252
|
+
capped; how a secret reaches a child; which test was seen failing for each fix; and, for an agent
|
|
253
|
+
that writes text for people, where its prompt says no em dash and which test fails on U+2014 in its
|
|
254
|
+
output.
|
|
@@ -19,7 +19,7 @@ compatibility: >-
|
|
|
19
19
|
this plugin; Codex/Kimi use command hooks; Hermes/OpenCode/OpenClaw/Pi use native context plugins after opt-in.
|
|
20
20
|
metadata:
|
|
21
21
|
author: PassionCode.ai
|
|
22
|
-
version: "0.35.
|
|
22
|
+
version: "0.35.3"
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
# Working with Fabric
|
|
@@ -105,7 +105,7 @@ and its answer is an error that names what Fabric waits for — never a fake suc
|
|
|
105
105
|
|---|---|---|
|
|
106
106
|
| Fabric is not running, or its hub is closed | no live hub | Step 1; Settings → Agent access in Fabric says why a hub is closed |
|
|
107
107
|
| Fabric's hub could not be verified | foreign protocol, dead pid, token outside Fabric's folder | send nothing; ask the person to open Fabric |
|
|
108
|
-
| the folder is not a project | no project holds this folder | the
|
|
108
|
+
| the folder is not a project | no project holds this folder yet | Fabric with automatic project admission creates or attaches the Project during enrollment; an older Fabric needs the person to open it (+ Project → Open a project) |
|
|
109
109
|
| waiting for the operator to allow this coding agent | the consent prompt is on their screen | tell the person; call again in a moment; never loop fast |
|
|
110
110
|
| denied | the operator said no | stop; the denial stands until they clear it in Agent access |
|
|
111
111
|
| connecting | enrollment in progress | call again in a moment |
|
package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md
CHANGED
|
@@ -134,8 +134,9 @@ personal data or private path in a body or an artifact reference: name a vault s
|
|
|
134
134
|
- Never enroll a second session from a hook or a script: a `fabric-mcp` started outside the agent's own connection
|
|
135
135
|
enrolls a new session, and Fabric ends a binding's oldest live session past eight — the agent's own. The plugin's
|
|
136
136
|
hooks and `watch-mail` only rejoin (`--rejoin-only`).
|
|
137
|
-
- Never attach a folder
|
|
138
|
-
|
|
137
|
+
- Never attach a folder or create a Project through a side channel to make enrollment succeed. Fabric's own
|
|
138
|
+
enrollment admits an eligible Git folder and records the Project it creates or attaches. If the answer is still
|
|
139
|
+
"the folder is not a project", report it; never write another product's registry or improvise a second admission path.
|
|
139
140
|
|
|
140
141
|
## Not yet available
|
|
141
142
|
|
package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md
CHANGED
|
@@ -43,7 +43,9 @@ would add — never a value already in the file — and the `--apply --expect-sh
|
|
|
43
43
|
backs the file up (`<file>.<UTC stamp>.bak`, never overwriting an earlier backup), keeps the file's mode, refuses a
|
|
44
44
|
file whose shape is not `{"hooks": {"<Event>": [{"hooks": [...]}]}}` (Codex) or an array of `[[hooks]]` tables
|
|
45
45
|
(Kimi Code, read back with `tomllib`: Python 3.11+), and refuses one that changed since the dry run. `--remove` takes
|
|
46
|
-
the entry out again. Codex may ask the person to trust a new hook (`/hooks`) before it runs.
|
|
46
|
+
the entry out again. Codex may ask the person to trust a new hook (`/hooks`) before it runs. Codex keys that trust by event, group and index, so
|
|
47
|
+
`--remove` names the SessionStart groups after the removed one that it moves, which Codex asks the person to approve
|
|
48
|
+
again. The hook runs the
|
|
47
49
|
`fabric_session.py` of the skills install (`~/.agents/skills/working-with-fabric/`) when there is one, else this
|
|
48
50
|
script's own path; a plugin cache path changes with each update, so pass `--script <a stable path>` there.
|
|
49
51
|
|
|
@@ -93,7 +95,9 @@ only variable names are looked at, never a value (two of them carry the session'
|
|
|
93
95
|
One line per event that did something, never content: `~/Library/Caches/ai.passioncode.fabric-agent-adapter/hooks.log`
|
|
94
96
|
(macOS), `$XDG_CACHE_HOME/fabric-agent-adapter/hooks.log` (Linux, default `~/.cache`),
|
|
95
97
|
`%LOCALAPPDATA%\fabric-agent-adapter\hooks.log` (Windows). Mode 0600, rotated at 1 MB to `hooks.log.1`. The
|
|
96
|
-
per-session spool is `sessions/<session id>/` beside it (turn entries capped at 50, `board.json`, `board.shown
|
|
98
|
+
per-session spool is `sessions/<session id>/` beside it (turn entries capped at 50, `board.json`, `board.shown`; a
|
|
99
|
+
folder untouched for 7 days, whose worker failed or whose session ended without SessionEnd, is removed by the next
|
|
100
|
+
spooling hook),
|
|
97
101
|
removed at the session's end.
|
|
98
102
|
|
|
99
103
|
Turn them off with Claude Code's own switch (`/hooks`, or `"disableAllHooks": true` in settings), or remove the
|
package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py
CHANGED
|
@@ -53,6 +53,9 @@ PS_TIMEOUT_S = 0.5
|
|
|
53
53
|
LOG_CAP_BYTES = 1_000_000
|
|
54
54
|
SPOOL_CAP = 50
|
|
55
55
|
LOCK_STALE_S = 30.0
|
|
56
|
+
#: A session folder untouched this long belongs to a session that will not spool again: its worker failed, or the
|
|
57
|
+
#: session ended without SessionEnd. Nothing retries it, so the next spooling hook removes it (FAA-50).
|
|
58
|
+
SESSION_STALE_S = 7 * 86400
|
|
56
59
|
READ_CAP = 64 * 1024
|
|
57
60
|
CONFIG_CAP = 32 * 1024 * 1024
|
|
58
61
|
WATCH_MIN_INTERVAL_S = 10
|
|
@@ -60,7 +63,7 @@ WATCH_MAX_BACKOFF_S = 900
|
|
|
60
63
|
SESSION_ID = re.compile(r"[A-Za-z0-9._:-]{1,128}")
|
|
61
64
|
LOOPBACK_ORIGIN = re.compile(r"http://127\.0\.0\.1:(\d{1,5})")
|
|
62
65
|
LOCAL_ORIGIN = re.compile(r'"origin": "http://(?:127\.0\.0\.1|localhost)')
|
|
63
|
-
VERSION = "0.35.
|
|
66
|
+
VERSION = "0.35.3"
|
|
64
67
|
HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",
|
|
65
68
|
"hermes": "Hermes", "opencode": "OpenCode", "openclaw": "OpenClaw", "pi": "Pi"}
|
|
66
69
|
NATIVE_PLUGIN_HOSTS = frozenset(("hermes", "opencode", "openclaw", "pi"))
|
|
@@ -413,6 +416,30 @@ def spool(directory: Path, entry: Dict[str, Any]) -> Path:
|
|
|
413
416
|
return path
|
|
414
417
|
|
|
415
418
|
|
|
419
|
+
def prune_stale_sessions(env: Optional[Dict[str, str]] = None, now: Optional[float] = None,
|
|
420
|
+
keep: Optional[str] = None) -> int:
|
|
421
|
+
"""Remove session folders whose newest file is older than SESSION_STALE_S (a running worker's lock is a fresh file,
|
|
422
|
+
so its folder is never stale); never the folder of the session `keep`. Returns how many were removed. Never
|
|
423
|
+
raises."""
|
|
424
|
+
now = time.time() if now is None else now
|
|
425
|
+
removed = 0
|
|
426
|
+
try:
|
|
427
|
+
folders = list((state_root(env=env) / "sessions").iterdir())
|
|
428
|
+
except OSError:
|
|
429
|
+
return 0
|
|
430
|
+
for folder in folders:
|
|
431
|
+
try:
|
|
432
|
+
if folder.name == keep or not SESSION_ID.fullmatch(folder.name) or not folder.is_dir():
|
|
433
|
+
continue
|
|
434
|
+
newest = max([folder.stat().st_mtime] + [f.stat().st_mtime for f in folder.iterdir()])
|
|
435
|
+
if now - newest > SESSION_STALE_S:
|
|
436
|
+
_remove_tree(folder)
|
|
437
|
+
removed += 1
|
|
438
|
+
except OSError:
|
|
439
|
+
continue
|
|
440
|
+
return removed
|
|
441
|
+
|
|
442
|
+
|
|
416
443
|
def lock_held(directory: Path) -> bool:
|
|
417
444
|
lock = directory / "worker.lock"
|
|
418
445
|
try:
|
|
@@ -528,6 +555,7 @@ def hook_spool(payload: Dict[str, Any], event: str, env: Optional[Dict[str, str]
|
|
|
528
555
|
return "skipped: launched by Fabric"
|
|
529
556
|
entry = {"event": event, "runtimeSessionId": session_id, "cwd": str(cwd),
|
|
530
557
|
"observedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}
|
|
558
|
+
prune_stale_sessions(env, keep=session_id)
|
|
531
559
|
if event == "end" and isinstance(payload.get("reason"), str):
|
|
532
560
|
entry["reason"] = payload["reason"][:64]
|
|
533
561
|
spool(directory, entry)
|
|
@@ -1105,6 +1133,7 @@ def install_host_hooks_cli(argv: Sequence[str], env: Optional[Dict[str, str]] =
|
|
|
1105
1133
|
else:
|
|
1106
1134
|
path = _home(env) / ".kimi-code" / "config.toml"
|
|
1107
1135
|
command = hook_command(str(args.script or default_hook_script(env)), args.host)
|
|
1136
|
+
shifted = 0 # Codex hook groups after ours, whose position a removal moves
|
|
1108
1137
|
try:
|
|
1109
1138
|
data = _read_bytes(path)
|
|
1110
1139
|
digest = _digest(data)
|
|
@@ -1118,11 +1147,15 @@ def install_host_hooks_cli(argv: Sequence[str], env: Optional[Dict[str, str]] =
|
|
|
1118
1147
|
if not present:
|
|
1119
1148
|
out("install-host-hooks: no Fabric preamble hook in %s; nothing to change" % path)
|
|
1120
1149
|
return 0
|
|
1121
|
-
groups = []
|
|
1122
|
-
for group in doc["hooks"]["SessionStart"]:
|
|
1150
|
+
groups, dropped = [], None
|
|
1151
|
+
for index, group in enumerate(doc["hooks"]["SessionStart"]):
|
|
1123
1152
|
kept = [h for h in group.get("hooks", []) if not _is_ours(h.get("command"), args.host)]
|
|
1124
1153
|
if kept or not group.get("hooks"):
|
|
1125
1154
|
groups.append(dict(group, hooks=kept) if kept != group.get("hooks") else group)
|
|
1155
|
+
elif dropped is None:
|
|
1156
|
+
dropped = index # a group that held only our hook goes; the groups after it move up
|
|
1157
|
+
if dropped is not None:
|
|
1158
|
+
shifted = len(doc["hooks"]["SessionStart"]) - dropped - 1
|
|
1126
1159
|
if groups:
|
|
1127
1160
|
doc["hooks"]["SessionStart"] = groups
|
|
1128
1161
|
else:
|
|
@@ -1187,6 +1220,11 @@ def install_host_hooks_cli(argv: Sequence[str], env: Optional[Dict[str, str]] =
|
|
|
1187
1220
|
else "added the Fabric preamble hook to", path,
|
|
1188
1221
|
"(backup: %s)" % backup if backup else "(new file)"))
|
|
1189
1222
|
out("Undo: fabric_session.py install-host-hooks --host %s %s" % (args.host, "--apply" if args.remove else "--remove"))
|
|
1223
|
+
if shifted:
|
|
1224
|
+
# Codex keys a hook's trust by its event, group and index: the groups after ours moved up one, so Codex asks
|
|
1225
|
+
# the person to trust them again in /hooks before they run.
|
|
1226
|
+
out("Codex trusts a hook by its position: %d SessionStart hook group(s) after the removed one moved, and "
|
|
1227
|
+
"Codex will ask you to approve them again in /hooks." % shifted)
|
|
1190
1228
|
return 0
|
|
1191
1229
|
except Refused as exc:
|
|
1192
1230
|
err("install-host-hooks: refused — %s" % exc)
|
|
@@ -53,8 +53,11 @@ def _capture(command, payload):
|
|
|
53
53
|
reader = threading.Thread(target=read_output, daemon=True)
|
|
54
54
|
reader.start()
|
|
55
55
|
try:
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
try:
|
|
57
|
+
process.stdin.write(payload)
|
|
58
|
+
process.stdin.close()
|
|
59
|
+
except BrokenPipeError:
|
|
60
|
+
pass # FAA-51: a helper may exit without reading its input; its output still counts
|
|
58
61
|
process.wait(timeout=max(0.001, deadline - time.monotonic()))
|
|
59
62
|
reader.join(timeout=max(0, deadline - time.monotonic()))
|
|
60
63
|
if reader.is_alive() or invalid.is_set() or process.returncode != 0:
|
|
@@ -69,7 +72,10 @@ def _capture(command, payload):
|
|
|
69
72
|
reader.join(timeout=0.2)
|
|
70
73
|
if not reader.is_alive():
|
|
71
74
|
process.stdout.close()
|
|
72
|
-
|
|
75
|
+
try:
|
|
76
|
+
process.stdin.close()
|
|
77
|
+
except BrokenPipeError:
|
|
78
|
+
pass
|
|
73
79
|
|
|
74
80
|
|
|
75
81
|
def fabric_registered(config=None):
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "passioncode",
|
|
4
4
|
"displayName": "PassionCode.ai",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.10",
|
|
6
6
|
"description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check at session start that keeps the PassionCode.ai skill set current.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|