@oneie/claude 0.8.0 → 0.10.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/.claude-plugin/plugin.json +1 -1
- package/agents/abm-strategist.md +67 -1
- package/agents/ads-meta.md +67 -1
- package/agents/analyst.md +67 -1
- package/agents/animator.md +108 -0
- package/agents/architect.md +269 -20
- package/agents/brand-guardian.md +67 -1
- package/agents/brand-strategist.md +67 -1
- package/agents/campaign-content.md +67 -1
- package/agents/campaign-email.md +67 -1
- package/agents/campaign-sms.md +67 -1
- package/agents/campaign-social.md +67 -1
- package/agents/cco.md +83 -2
- package/agents/ceo.md +108 -11
- package/agents/chairman.md +197 -0
- package/agents/cmo.md +82 -2
- package/agents/community-greeter.md +67 -1
- package/agents/community-moderator.md +67 -1
- package/agents/compliance.md +67 -1
- package/agents/copywriter.md +67 -1
- package/agents/creative-strategist.md +67 -1
- package/agents/cro.md +81 -1
- package/agents/cto.md +266 -28
- package/agents/customer-interviewer.md +67 -1
- package/agents/customer-researcher.md +67 -1
- package/agents/customer-success-manager.md +67 -1
- package/agents/customer-trainer.md +67 -1
- package/agents/cxo.md +82 -1
- package/agents/demand-creator.md +67 -1
- package/agents/demo-mover.md +67 -1
- package/agents/demo-specialist.md +67 -1
- package/agents/demo-thai-family-law.md +67 -1
- package/agents/designer.md +67 -1
- package/agents/discovery-caller.md +67 -1
- package/agents/doctor.md +269 -0
- package/agents/educate-coach.md +67 -1
- package/agents/elevate-tutor.md +67 -1
- package/agents/email-lifecycle-marketer.md +67 -1
- package/agents/engage-specialist.md +67 -1
- package/agents/events-coordinator.md +67 -1
- package/agents/foundation-builder.md +67 -1
- package/agents/funnel-architect.md +67 -1
- package/agents/gift-creator.md +67 -1
- package/agents/google-ads.md +67 -1
- package/agents/guide.md +67 -1
- package/agents/helpdesk-dispatcher.md +67 -1
- package/agents/hook-specialist.md +67 -1
- package/agents/identify-optimizer.md +67 -1
- package/agents/implementer.md +313 -45
- package/agents/incident-commander.md +67 -1
- package/agents/insights-lead.md +87 -1
- package/agents/journey-runner.md +67 -1
- package/agents/linkedin-ads.md +67 -1
- package/agents/live-sales-chat.md +67 -1
- package/agents/market-researcher.md +67 -1
- package/agents/media-buyer.md +67 -1
- package/agents/memory-keeper.md +195 -0
- package/agents/movers-customer-researcher.md +67 -1
- package/agents/movers-foundation-builder.md +67 -1
- package/agents/movers-market-researcher.md +67 -1
- package/agents/movers-pricing-strategist.md +67 -1
- package/agents/nurture-architect.md +67 -1
- package/agents/offer-architect.md +67 -1
- package/agents/onboarder.md +67 -1
- package/agents/onboarding-specialist.md +67 -1
- package/agents/operations-dashboard.md +87 -1
- package/agents/perf-engineer.md +333 -37
- package/agents/playbook-writer.md +67 -1
- package/agents/plg-strategist.md +67 -1
- package/agents/positioning-architect.md +67 -1
- package/agents/press-officer.md +67 -1
- package/agents/pricing-strategist.md +67 -1
- package/agents/privacy-officer.md +67 -1
- package/agents/referral-manager.md +67 -1
- package/agents/refine-analyst.md +67 -1
- package/agents/release-manager.md +446 -39
- package/agents/renewals-upsell-rep.md +67 -1
- package/agents/review-engineer.md +319 -45
- package/agents/rewards-steward.md +67 -1
- package/agents/sales-call-coach.md +67 -1
- package/agents/sales-closer.md +67 -1
- package/agents/security-auditor.md +343 -48
- package/agents/sell-closer.md +67 -1
- package/agents/share-amplifier.md +67 -1
- package/agents/social-media-manager.md +67 -1
- package/agents/storyteller.md +301 -0
- package/agents/strategist.md +67 -1
- package/agents/strategy-aligner.md +67 -1
- package/agents/support-agent.md +67 -1
- package/agents/tagger.md +327 -0
- package/agents/tech-writer.md +195 -22
- package/agents/test-engineer.md +398 -29
- package/agents/tiktok-ads.md +67 -1
- package/agents/tracking-engineer.md +67 -1
- package/agents/trailkeeper.md +181 -0
- package/agents/upsell-strategist.md +67 -1
- package/agents/voice.md +67 -1
- package/agents/w1-recon.md +1 -1
- package/agents/w2-decide.md +1 -1
- package/agents/w3-edit.md +8 -2
- package/agents/w4-verify.md +13 -0
- package/agents/workflow-optimiser.md +81 -1
- package/commands/close.md +916 -160
- package/commands/deploy.md +102 -724
- package/commands/do.md +58 -2
- package/commands/sweep.md +159 -0
- package/commands/tasks.md +222 -0
- package/hooks/scripts/dev-only.sh +135 -0
- package/hooks/scripts/git-add-guard.sh +37 -2
- package/hooks/scripts/session-start.sh +32 -4
- package/package.json +1 -1
- package/rules/scripts.md +85 -0
- package/scripts/CLAUDE.md +315 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agent-actor-parity.sh +129 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/chrome.mjs +18 -0
- package/scripts/close-metrics.sh +587 -0
- package/scripts/close-owner.sh +326 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-emit.sh +311 -0
- package/scripts/deploy-gate-check.sh +155 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy-schema-check.sh +58 -0
- package/scripts/deploy.sh +393 -243
- package/scripts/do-auto.sh +127 -26
- package/scripts/do-board.sh +429 -0
- package/scripts/do-close.sh +1184 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-fleet.sh +8 -2
- package/scripts/do-plan-json.mjs +110 -12
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +86 -10
- package/scripts/do-rank.py +200 -3
- package/scripts/do-reconcile.sh +73 -12
- package/scripts/do-signal.sh +101 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-w4-gates.sh +11 -1
- package/scripts/do-world-check.sh +153 -0
- package/scripts/download-stats.sh +172 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +18 -1
- package/scripts/factory-close-check.sh +257 -0
- package/scripts/factory-emit.sh +211 -0
- package/scripts/factory-executor-check.mjs +353 -0
- package/scripts/factory-peak.sh +301 -0
- package/scripts/factory-repo.sh +71 -0
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/flywheel-outcome.sh +63 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +9 -0
- package/scripts/gate-watchdog.sh +619 -0
- package/scripts/gc-content-check.sh +142 -0
- package/scripts/gh-traffic-capture.sh +153 -0
- package/scripts/govern-order-check.sh +202 -0
- package/scripts/governor-doors-check.sh +86 -5
- package/scripts/health.sh +448 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/incident.sh +212 -0
- package/scripts/land.sh +755 -45
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/livekit-ratchet.sh +18 -1
- package/scripts/machine-check.sh +1 -1
- package/scripts/memory-index-budget.sh +79 -0
- package/scripts/npm-downloads.sh +109 -0
- package/scripts/one-agents.mjs +204 -8
- package/scripts/one-resume.sh +31 -3
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/redirect-lint.sh +169 -0
- package/scripts/release.sh +40 -6
- package/scripts/resume-lost-sessions.sh +68 -0
- package/scripts/shoot-pages.mjs +140 -0
- package/scripts/signal-meta-backfill.ts +451 -0
- package/scripts/signal-watch.sh +63 -6
- package/scripts/speed-cache-check.sh +12 -2
- package/scripts/sweep.sh +426 -0
- package/scripts/task-titles-dump.ts +101 -0
- package/scripts/test-cached.sh +47 -10
- package/scripts/test-lanes.sh +14 -0
- package/scripts/thread-name-backfill.ts +215 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +155 -8
- package/scripts/typedb-flake-check.sh +3 -1
- package/scripts/urls-lint.sh +8 -0
- package/scripts/verify-board-doors.sh +80 -0
- package/scripts/verify-fast.sh +159 -6
- package/scripts/worktree-up.sh +21 -3
- package/skills/astro/SKILL.md +9 -3
- package/skills/astro/optimize-performance.md +3 -2
- package/skills/cloudflare/SKILL.md +3 -2
- package/skills/cloudflare-security-audit/AI-AND-LLM.md +83 -0
- package/skills/cloudflare-security-audit/ATTACK-CLASSES.md +130 -0
- package/skills/cloudflare-security-audit/CLIENT-SIDE.md +83 -0
- package/skills/cloudflare-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
- package/skills/cloudflare-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
- package/skills/cloudflare-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
- package/skills/cloudflare-security-audit/HUNTING.md +251 -0
- package/skills/cloudflare-security-audit/LICENSE +21 -0
- package/skills/cloudflare-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
- package/skills/cloudflare-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
- package/skills/cloudflare-security-audit/PROVENANCE.md +78 -0
- package/skills/cloudflare-security-audit/RECONNAISSANCE.md +156 -0
- package/skills/cloudflare-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
- package/skills/cloudflare-security-audit/SKILL.md +192 -0
- package/skills/cloudflare-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
- package/skills/cloudflare-security-audit/VALIDATION-AND-REPORTING.md +186 -0
- package/skills/cloudflare-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
- package/skills/cloudflare-security-audit/report-schema.json +461 -0
- package/skills/cloudflare-security-audit/validate-coverage-ledger.cjs +872 -0
- package/skills/cloudflare-security-audit/validate-coverage-ledger.test.cjs +740 -0
- package/skills/cloudflare-security-audit/validate-findings.cjs +773 -0
- package/skills/cloudflare-security-audit/validate-findings.test.cjs +652 -0
- package/skills/deploy/REFERENCE.md +713 -0
- package/skills/deploy/SKILL.md +140 -0
- package/skills/fleet-audit/SKILL.md +58 -0
- package/skills/meeting/SKILL.md +220 -0
- package/skills/planning/SKILL.md +256 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/typedb/SKILL.md +7 -0
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/templates/template-agent.md +7 -1
- package/templates/template-feature.md +9 -0
- package/templates/template-todo.md +29 -0
package/commands/close.md
CHANGED
|
@@ -1,179 +1,935 @@
|
|
|
1
1
|
# /close
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Close the loop. One bounded pass, then a row for anything it could not close.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
bash .claude/scripts/do-close.sh <slug> [--tid <tid>] [--composite 0.NN] [--dims k=v,…]
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
**That script is the authority.** It carries the leg table, the budgets, the
|
|
10
|
+
credential ladder and the remediation commands; this file does not restate them.
|
|
11
|
+
`--help` prints the flags, `--self-test` proves the red halves.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# The close is a conversation, not a status flip
|
|
16
|
+
|
|
17
|
+
A close that only writes `status=done` is a closed loop the substrate can read and
|
|
18
|
+
**no person can**. Since 2026-09-09 every close runs as a real thread in
|
|
19
|
+
`/u/one/in`, spoken by the agent that owns the work and answered by the agent it
|
|
20
|
+
reports to. The thread IS the record: an operator who was not here opens one room
|
|
21
|
+
and learns what was done, what it cost, what it disproved, and what is next.
|
|
22
|
+
|
|
23
|
+
The acts below run in order. None is optional; each names the door it uses.
|
|
6
24
|
|
|
7
|
-
##
|
|
25
|
+
## One close, many rooms
|
|
8
26
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| *(no arg)* | mark() + tick | session report — record all outcomes | L2, L6 |
|
|
16
|
-
| `--todo <slug> --wave N` | emit `do:close` | wave close (soft gate) — append learnings.md; next wave proceeds if skipped | L1, L6 |
|
|
17
|
-
| `--todo <slug> --cycle N` | emit `do:close` | cycle close (**hard gate**) — verify learnings grew; block next cycle if missing | L1, L2, L6 |
|
|
18
|
-
| `--todo <slug> --cycle N --wave N` | emit `do:close` | specific wave of a cycle — used for re-running a failed close | L1 |
|
|
27
|
+
**A close that touches eight things belongs in eight conversations, not one.** This
|
|
28
|
+
is not extra work — it is already how the store is built. `tasks:comment` writes
|
|
29
|
+
the SAME D1 message store the inbox reads, keyed ``getOrCreateThread(env.DB, {
|
|
30
|
+
slug, agentId: `task:${tid}` })`` (`resolvers/tasks.ts:1815`). So a comment on a
|
|
31
|
+
task **is** a conversation in `/u/<slug>/in`, titled by that task. There is no
|
|
32
|
+
second store and no second step.
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
Two levels, and they carry different things:
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
| Room | Holds | Door |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| **the close thread** (`close-<slug>`) | the spine — what was closed as a whole, the superior's verdict, the rubric | `thread:append` |
|
|
39
|
+
| **each thing's own thread** (`task:<tid>`) | what THIS row's close means for THIS row, in the place its watchers already read | `tasks:comment` |
|
|
25
40
|
|
|
26
|
-
|
|
41
|
+
Post the whole close only to the spine, and post to each thing's room the one
|
|
42
|
+
paragraph that matters **there**. A watcher who follows one task must not have to
|
|
43
|
+
read a session-wide close to learn what happened to it — and a `@mention` in that
|
|
44
|
+
comment INSERTs a `follows` row (`tasks.ts:1686-1693`), so speaking in a room is
|
|
45
|
+
also how the next person gets subscribed to it.
|
|
27
46
|
|
|
28
|
-
The
|
|
47
|
+
The failure this prevents is measured: agents post without `replyTo` — 0 of 33 on
|
|
48
|
+
the launch board — and the board was invisible for two days that way. One lump
|
|
49
|
+
thread nobody is watching is the same defect wearing a tidier shape.
|
|
29
50
|
|
|
51
|
+
## Act 0 — the room exists before anyone speaks
|
|
52
|
+
|
|
53
|
+
Open the thread FIRST, then let the agents talk into it. A room minted after the
|
|
54
|
+
fact is a transcript; a room minted first is a place.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
K=$(grep -m1 '^GATEWAY_API_KEY=' one.ie/web/.dev.vars | cut -d= -f2- | tr -d '"')
|
|
58
|
+
curl -s -X POST https://one.ie/api/ask/thread:append \
|
|
59
|
+
-H "Authorization: Bearer $K" -H 'Content-Type: application/json' \
|
|
60
|
+
-d '{"data":{"slug":"one","group":"close-<slug>","role":"user",
|
|
61
|
+
"content":"<what is being closed, in one paragraph>"}}'
|
|
30
62
|
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
|
|
64
|
+
Four traps, each one measured, each one silent when you hit it:
|
|
65
|
+
|
|
66
|
+
| Trap | What happens |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `.env`'s `GATEWAY_API_KEY` | prod REFUSES it. The working service key is in **`.dev.vars`** |
|
|
69
|
+
| python `urllib` | **403** from prod. Use `curl` |
|
|
70
|
+
| field named `text` | `validation … field: content`. The field is **`content`** |
|
|
71
|
+
| omitting `sender` | `validation … field: sender`. It is **required**, even for `role:"user"` |
|
|
72
|
+
| no `data` envelope | `envelope_missing`. Every payload is `{"data":{…}}` |
|
|
73
|
+
|
|
74
|
+
**`ok:true` is not proof.** This door can answer `ok` with nothing durable. Read
|
|
75
|
+
the row back before believing it:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx wrangler d1 execute one-owners --remote --json --command \
|
|
79
|
+
"SELECT role, substr(content_json,1,120), ts FROM messages
|
|
80
|
+
WHERE thread_id='<tid>' ORDER BY ts DESC LIMIT 5;"
|
|
35
81
|
```
|
|
36
82
|
|
|
37
|
-
|
|
38
|
-
|
|
83
|
+
`chat:context` answers **forbidden** here — the service key is not a workspace
|
|
84
|
+
member. D1 is the read.
|
|
85
|
+
|
|
86
|
+
## Act 1 — every task has an owner, or the close assigns one
|
|
87
|
+
|
|
88
|
+
An unassigned task cannot report to anyone, so the close **fills the gap rather
|
|
89
|
+
than skipping it**. Resolution order, and it never invents a name — the roster is
|
|
90
|
+
`ls one.ie/ai/agents/`:
|
|
91
|
+
|
|
92
|
+
Resolution is a **script, not a judgment** — `bash .claude/scripts/close-owner.sh
|
|
93
|
+
--tags "<task tags>"` answers owner + the full chain above them, and
|
|
94
|
+
`--self-test` proves it can go red. Run it; do not reason it out.
|
|
95
|
+
|
|
96
|
+
1. **Match the task's tags against `subscribes:`** in each `one.ie/ai/agents/*/agent.md`.
|
|
97
|
+
Bare words only — `marketing`, never `lifecycle:marketing`; a namespaced tag
|
|
98
|
+
matches zero signals (root `CLAUDE.md`). Measured 2026-09-09: **56** agents,
|
|
99
|
+
48 declare `subscribes:` — but only **30 carry a bare stake**, so 26 cannot be
|
|
100
|
+
tag-matched at all and reach an owner only through the ladder below.
|
|
101
|
+
2. **Ties break DOWNWARD** — the lowest tier that matches wins. Sending a director
|
|
102
|
+
to do specialist work is how a director stops being available for the judgment
|
|
103
|
+
only they can make.
|
|
104
|
+
3. **No match → `world:route`**, which walks the staked tag→receiver paths and
|
|
105
|
+
answers `reason:'ceo'` for a workspace with no history.
|
|
106
|
+
4. **Still nothing → the CEO.** Repo law: the CEO is the default receiver, always
|
|
107
|
+
listening via a standing workflow.
|
|
108
|
+
|
|
109
|
+
Assign with `tasks:reassign` (or `tasks:claim` if the agent is taking it itself),
|
|
110
|
+
and say so in the room. An assignment nobody announced is an assignment nobody acts on.
|
|
111
|
+
|
|
112
|
+
**The owner does not have to be in this workspace.** `tasks:everywhere` admits a row
|
|
113
|
+
tagged `@actor` with **no workspace check** (`subscriptions.ts:81`, rule a), so an
|
|
114
|
+
assignment crosses the tenant line and lands on that actor's `/tasks` and `/plan`.
|
|
115
|
+
Use it when the right owner lives elsewhere — the ladder fallback exists for when
|
|
116
|
+
nobody fits, not for when the person who fits is simply somewhere else.
|
|
117
|
+
|
|
118
|
+
Two constraints that follow: the `@` tag is writable **only** through
|
|
119
|
+
create/claim/reassign (`tasks:tag` admits plain words only — `resolvers/tasks.ts:112`),
|
|
120
|
+
and each of those takes identity from `ctx`, never the payload. So you can reach
|
|
121
|
+
anyone, and the row always carries who sent it.
|
|
122
|
+
|
|
123
|
+
## Act 2 — the owner analyses, and closes what it can prove
|
|
124
|
+
|
|
125
|
+
The owning agent reads the work and answers four questions in the thread, in its
|
|
126
|
+
own name (`role:"assistant"`, `sender:"<agent>"`):
|
|
127
|
+
|
|
128
|
+
- **What was asked**, quoted from the task's own `notes`, not paraphrased.
|
|
129
|
+
- **What was done**, as file:line or a receipt — never "implemented X".
|
|
130
|
+
- **What it disproved.** The most valuable line in any close. A close that
|
|
131
|
+
disproved nothing usually measured nothing.
|
|
132
|
+
- **What it did NOT do**, and why. Scope declined on purpose is a result;
|
|
133
|
+
scope silently dropped is a defect.
|
|
134
|
+
|
|
135
|
+
Then it acts on the board, and every act is a door that already exists:
|
|
136
|
+
|
|
137
|
+
| Act | Door |
|
|
138
|
+
|---|---|
|
|
139
|
+
| close a task it can prove | `tasks:status` → `done` |
|
|
140
|
+
| leave a comment on the task | `tasks:comment` — writes the SAME message store as the inbox thread |
|
|
141
|
+
| tag the row so it routes | `tasks:tag` (bare words) |
|
|
142
|
+
| file what it found | `tasks:subtask` — with `notes` AND `blockedBy` in the SAME call |
|
|
143
|
+
| rank it | `tasks:priority` |
|
|
144
|
+
|
|
145
|
+
**Never close a task on inference.** A task closed because it "looks done" is worse
|
|
146
|
+
than one left open — the board stops being a record of what is true. If the proof
|
|
147
|
+
is not in hand, comment what is missing and leave it open.
|
|
148
|
+
|
|
149
|
+
## Act 2b — the blast radius: what ELSE touches what you just closed
|
|
150
|
+
|
|
151
|
+
A thing is never closed alone. Sweep the four surfaces that reference it, and
|
|
152
|
+
**say what you found in each one's own room**. A close that names no neighbours
|
|
153
|
+
either touched nothing or did not look.
|
|
154
|
+
|
|
155
|
+
| Surface | The question | How to answer it |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| **tasks** | what did this unblock, and what is now orphaned? | `blockers-of($t)` · `ready-tasks($plan)` · `orphans($plan)` — a row with no `parent` is unreachable from any plan and the factory's walk will never find it |
|
|
158
|
+
| **workflows** | does a step still name what I changed? | a step's receiver is a literal string; a renamed receiver dissolves at run time as `unknown_receiver`, silently. Grep `trigger_source` and step receivers for the old name |
|
|
159
|
+
| **pages** | which surfaces render it? | Puck blocks + `PAGE_CHAT_META`. A block registered but never rendered is inert, and a page that names a dead receiver fails only when a person clicks |
|
|
160
|
+
| **agents** | who staked on these tags? | the bare words in `subscribes:` — those actors should hear it. `world:announce` fans it; silence is not routing |
|
|
161
|
+
|
|
162
|
+
Three substrate funs are the close's own auditors. Run them on what you touched:
|
|
163
|
+
|
|
164
|
+
- **`overclaimed()`** — an asserted hypothesis carrying more confidence than its
|
|
165
|
+
evidence. If your close created one, you did not close, you claimed.
|
|
166
|
+
- **`well-made-wrong()`** — high craft, `rubric-fit 0.0`. The most expensive
|
|
167
|
+
failure there is: it looks finished and it solved the wrong problem.
|
|
168
|
+
- **`unproven($plan)` / `covered-unproven($plan)`** — a deliverable that shipped
|
|
169
|
+
and was never proven. Shipping is not proving.
|
|
170
|
+
|
|
171
|
+
## Act 3b — will anyone ever know if this was any good?
|
|
172
|
+
|
|
173
|
+
**This is the act most closes skip, and it is the one that decides whether the
|
|
174
|
+
world can learn.** Rule 1 says every signal closes with `mark`, `warn` or
|
|
175
|
+
`dissolve`. But a thing that emits no signal can never be marked or warned — so no
|
|
176
|
+
path to it ever moves, no outcome ever re-ranks it, and the substrate is blind to
|
|
177
|
+
it forever. Verification > presence: "it shipped" is a claim about the past.
|
|
178
|
+
|
|
179
|
+
So before writing `done`, answer: **what future event tells us this worked?**
|
|
180
|
+
|
|
181
|
+
| What shipped | What must emit |
|
|
182
|
+
|---|---|
|
|
183
|
+
| a UI affordance | `emitClick('ui:<surface>:<action>')` — every onClick is a signal |
|
|
184
|
+
| a page or block | a view event, and a lane if it is a choice — a block with no lane is scenery |
|
|
185
|
+
| money or a commitment | `recordConversion` with a real `ConversionStage` (`event-vocabulary.ts`) — the server narrates a purchase, never the client |
|
|
186
|
+
| a receiver | it closes with `mark`/`warn`, never a silent return |
|
|
187
|
+
| an agent's work | an attempt that closes with a rubric, so `craft-composite` has something to read |
|
|
188
|
+
|
|
189
|
+
If the honest answer is **nothing will emit** — say so in the room and file the
|
|
190
|
+
tracking rung. Do not launder it into `done`. An unmeasurable feature is a feature
|
|
191
|
+
the world cannot rank, and a substrate whose whole thesis is that outcomes re-rank
|
|
192
|
+
routing has just been handed a permanent blind spot.
|
|
193
|
+
|
|
194
|
+
Two traps worth naming because both read as success: a stage in `MARK_STAGES` that
|
|
195
|
+
excludes awareness means **reaching** something is deliberately not a `mark` — do
|
|
196
|
+
not upgrade a view into evidence. And a cron handler whose expression is missing
|
|
197
|
+
from `[triggers] crons` is **dark** — registered in code, never fired, and nothing
|
|
198
|
+
says so.
|
|
199
|
+
|
|
200
|
+
## Act 3 — the follow-ups are filed before the close, not after
|
|
201
|
+
|
|
202
|
+
Anything the analysis surfaced becomes a row **in the same act**, or it evaporates.
|
|
203
|
+
Use `tasks:subtask` and pass `notes` + `blockedBy` in the one call: between a bare
|
|
204
|
+
`tasks:create` and a later `tasks:notes`, the row sits on the board claimable, with
|
|
205
|
+
an empty body and no ordering, and the factory will take it.
|
|
206
|
+
|
|
207
|
+
Every row lands with its metadata filled — no bare titles:
|
|
208
|
+
|
|
209
|
+
| Field | Rule |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `notes` | the goal in prose: what done looks like, and why. **A row with no notes is the row that sits untouched.** |
|
|
212
|
+
| `tags` | bare routing words that will actually reach a subscriber |
|
|
213
|
+
| `priority` | **0..1, a fraction** — `tasks:create`'s zod is `z.number().min(0).max(1)` and `55` is rejected outright as `validation … field: priority`. The registry is the spec; this table said `1–100` until 2026-09-15 and every row filed from it bounced. The rubric that decides the number: money/security bleeding NOW ≫ decisions blocking others ≫ critical path ≫ backlog |
|
|
214
|
+
| `parent` | omit it and the row is an orphan, unreachable from any plan |
|
|
215
|
+
| `blockedBy` | what stops ten siblings going ready at once |
|
|
216
|
+
| `dueAt` | **only where a real date exists.** Never invent one — a fabricated deadline is a lie the Today lens repeats every morning |
|
|
217
|
+
|
|
218
|
+
## Act 4 — it reports to its superior, and the superior answers
|
|
219
|
+
|
|
220
|
+
The chain is declared, not guessed: `reports_to:` in the agent's own frontmatter,
|
|
221
|
+
resolved by `close-owner.sh --owner <agent>`. Measured 2026-09-09 — **39 of 56**
|
|
222
|
+
agents carry it, so a third of the roster needs the fallback, and the fallback is
|
|
223
|
+
a ladder, not a default:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
reports_to: in agent.md → use it
|
|
227
|
+
else specialist → the director of its own `domain:`
|
|
228
|
+
director → ceo
|
|
229
|
+
ceo → chairman
|
|
230
|
+
chairman → the human, via human:notify
|
|
231
|
+
else ceo (repo law: the CEO is the standing receiver)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The superior does not rubber-stamp. It reads the thread and answers with one of
|
|
235
|
+
three verdicts, posted in its own name:
|
|
236
|
+
|
|
237
|
+
- **CLOSED** — the work is proven. The loop ends.
|
|
238
|
+
- **OPEN, with a named gap** — what is missing, as a task it files itself.
|
|
239
|
+
- **REFUTED** — the claim does not hold; the owner's close is reversed and the
|
|
240
|
+
row goes back to open with the reason.
|
|
241
|
+
|
|
242
|
+
**The loop runs until the superior closes it, not until the owner is finished.**
|
|
243
|
+
Bound it at 3 rounds: the owner answers the gap, the superior re-reads. Still open
|
|
244
|
+
after 3 → escalate one rung and say so, rather than looping forever. A superior
|
|
245
|
+
that has not answered is not a close — it is an unrun gate, and an unrun gate is
|
|
246
|
+
never a pass.
|
|
247
|
+
|
|
248
|
+
**A verdict is not an account.** CLOSED ends the loop; it does not tell anyone
|
|
249
|
+
what happened. Act 4b is where the work is assessed and said out loud.
|
|
250
|
+
|
|
251
|
+
## Act 4b — the director assesses, and posts what the world reads
|
|
252
|
+
|
|
253
|
+
Act 4 answers **"is this proven?"** — a gate, and its three verdicts decide only
|
|
254
|
+
whether the loop may end. It does not answer **"does anyone outside this thread
|
|
255
|
+
know, and should they care?"** A close that stops at a verdict is legible to the
|
|
256
|
+
loop and invisible to the company: the superior said CLOSED, the row went green,
|
|
257
|
+
and nobody who was not in the room learned anything.
|
|
258
|
+
|
|
259
|
+
So the close hands the finished work to a **director**, who assesses it and posts
|
|
260
|
+
about it in its own name.
|
|
261
|
+
|
|
262
|
+
**The director is resolved, never chosen.** The same script Act 4 already runs
|
|
263
|
+
returns the whole chain; the DIRECTOR rung of it is the one that posts:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# COMMA-separated. Spaces do not split — "deploy release ship" is ONE tag named
|
|
267
|
+
# "deploy release ship", it matches nobody, and the answer is an entirely ordinary
|
|
268
|
+
# `hits=0 confidence=none` that reads exactly like a genuinely unowned area.
|
|
269
|
+
bash .claude/scripts/close-owner.sh --tags "deploy,release,ship"
|
|
270
|
+
# owner=release-manager via=subscribes hits=2 specific=2 contenders=1 confidence=high
|
|
271
|
+
# chain=cto ceo chairman
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**The first entry of `chain=` is the director** — `cto` above. When the owner is
|
|
275
|
+
itself a director the chain starts one rung higher (`--owner cmo` → `chain=ceo
|
|
276
|
+
chairman`) and the director who posts is the OWNER; do not walk past it to the
|
|
277
|
+
CEO, or every close in the company arrives at one desk.
|
|
278
|
+
|
|
279
|
+
Two answers are NOT a director, and they are different — the script says which:
|
|
280
|
+
|
|
281
|
+
| It prints | Means | Who posts |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `owner=REFUSED … confidence=low` + a `fallback=ceo` line | several agents staked equally — genuine ambiguity | the CEO decides who speaks, and says why |
|
|
284
|
+
| `owner=ceo via=fallback(no-tag-match) … confidence=none` | **nothing staked on these tags at all** | the CEO posts, and files the missing stake as a row |
|
|
285
|
+
|
|
286
|
+
The second is the interesting one: it is not a routing failure, it is the world
|
|
287
|
+
telling you a whole area has no owner. Closing it silently under the CEO hides
|
|
288
|
+
that. File the stake.
|
|
289
|
+
|
|
290
|
+
And the route follows the world rather than the org chart's fiat: emit
|
|
291
|
+
`signal("world", { tags })` and the director who **staked** on those tags gets it
|
|
292
|
+
(root `CLAUDE.md § the universal router`). Bare words only — a namespaced tag
|
|
293
|
+
matches zero subscribers, so `marketing`, never `lifecycle:marketing`. The CEO is
|
|
294
|
+
the receiver of LAST resort, for genuine ambiguity (no clear staker), not the
|
|
295
|
+
default addressee.
|
|
296
|
+
|
|
297
|
+
**Assessment is a different question from review.** Act 6's reviewer asks whether
|
|
298
|
+
the work is correct; the director asks whether it was worth doing and what it
|
|
299
|
+
changes. Four questions, answered in the post or explicitly declined:
|
|
300
|
+
|
|
301
|
+
| Question | What a non-answer looks like |
|
|
302
|
+
|---|---|
|
|
303
|
+
| **Who is better off, and at what?** | "improved the panel" — no persona, no verb |
|
|
304
|
+
| **What did it cost?** | no cycles, no tokens, no wall clock |
|
|
305
|
+
| **What did it disprove?** | every close that discovers nothing is a close that tested nothing |
|
|
306
|
+
| **What is now possible that was not?** | no next rung, or a rung with no owner |
|
|
307
|
+
|
|
308
|
+
Then it posts — one message, into `/u/one/in`, through the door the operator
|
|
309
|
+
already reads:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# space:post → the workspace inbox + every connected SSE listener
|
|
313
|
+
signal("chat:send", { space: "one", content: "<the assessment>", replyTo: "<the close thread>" })
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Five rules for that post, each one a defect this repo has already shipped
|
|
317
|
+
|
|
318
|
+
1. **Name the lane.** A fast pass is never reported as a full pass. `verify:fast`
|
|
319
|
+
and `bun run verify` are different claims and the reader cannot tell them apart
|
|
320
|
+
afterwards.
|
|
321
|
+
2. **Name where it landed.** "on `dev`" is not "in production". A post that says
|
|
322
|
+
*shipped* about a branch is the same lie as a green cycle with an unrun gate,
|
|
323
|
+
and it is the one a reader acts on.
|
|
324
|
+
3. **Name what did NOT ship.** A close post with no open items is a marketing
|
|
325
|
+
post wearing a record's clothes. What was left out, and why, is the half a
|
|
326
|
+
future reader needs — they already assume the rest worked.
|
|
327
|
+
4. **Quote a measurement, not a claim.** One number that was actually read —
|
|
328
|
+
`816px 560px`, `998 tests`, `0 errors` — beats a paragraph of adjectives, and
|
|
329
|
+
it is what makes the post checkable a month later.
|
|
330
|
+
5. **It does not post OUTWARD.** Customer-facing copy — blog, social, changelog —
|
|
331
|
+
is a row the director FILES, never a thing the close publishes. `brand-guardian`
|
|
332
|
+
holds a hard veto on creative, and a close that publishes has just routed
|
|
333
|
+
around it.
|
|
334
|
+
|
|
335
|
+
**`ok:true` is not proof here either.** Read the row back (Act 0's D1 query) before
|
|
336
|
+
believing the post landed.
|
|
337
|
+
|
|
338
|
+
The failure this prevents is measured, in this repo, twice. Agents post without
|
|
339
|
+
`replyTo` — **0 of 33** on the launch board — and the board was invisible for two
|
|
340
|
+
days. And a thread only holds the top of the inbox while it is LIVE
|
|
341
|
+
(`LIVE_WINDOW_MS`, `lib/in/live-pin.ts`): a close nobody posts is not merely quiet,
|
|
342
|
+
it is unreachable within the hour, sitting under every task row that spoke more
|
|
343
|
+
recently.
|
|
344
|
+
|
|
345
|
+
## Act 5 — the docs end true
|
|
346
|
+
|
|
347
|
+
The propagate matrix below (§ Loop Close) already lists which doc each trigger
|
|
348
|
+
touches. Two rules the conversation adds:
|
|
349
|
+
|
|
350
|
+
- **The promise is the first row reconciled**, before any derived doc.
|
|
351
|
+
- **A doc that still says the thing is unbuilt, after you built it, is a defect** —
|
|
352
|
+
the W4 shipped-status check exists because this drifts on 3 of 3 runs.
|
|
353
|
+
|
|
354
|
+
## Act 6 — the rubric, and who reviews it
|
|
355
|
+
|
|
356
|
+
Score the **cycle** rubric. Weights and axis definitions live in
|
|
357
|
+
`.claude/scripts/rubric-weights.json` (`cycle` block) — read them there, **never
|
|
358
|
+
restate them here**, and never substitute `w4-rubric.ts`'s `task-composite=`
|
|
359
|
+
(five axes, no goal-fit, so the `goal-fit ≥ 0.50` gate becomes unevaluable).
|
|
360
|
+
|
|
361
|
+
Gate, all four conjuncts: `composite ≥ 0.65` **AND** `goal-fit ≥ 0.50` **AND**
|
|
362
|
+
no adversarial > 0.5 **AND** `delta_tsc ≤ 0`.
|
|
363
|
+
|
|
364
|
+
**Who reviews is decided by size × importance, and both are measured, not felt:**
|
|
365
|
+
|
|
366
|
+
| Size (`do-tier.sh`) × importance (task priority) | Model |
|
|
367
|
+
|---|---|
|
|
368
|
+
| the mechanical closes — status flips, comments, tag writes, doc-truth greps | **haiku**, fanned out in parallel, one per task |
|
|
369
|
+
| the analysis and the report | **sonnet** — the default |
|
|
370
|
+
| FEATURE, or priority ≥ 0.90 | **opus** reviews before the superior answers |
|
|
371
|
+
| SCHEMA, or it touched money / auth / custody / a locked name | **fable** reviews — apex judgment, once, fed a context pack |
|
|
372
|
+
|
|
373
|
+
The fan-out is real parallelism and it is where the cost goes: N tasks closing is
|
|
374
|
+
N haiku calls in ONE message, never a serial walk. The review is the opposite —
|
|
375
|
+
one call, at the end, on the whole thread.
|
|
376
|
+
|
|
377
|
+
**A reviewer defaults to REFUTED and must quote the line that changed its mind.**
|
|
378
|
+
No quotable line ⇒ not proven.
|
|
379
|
+
|
|
380
|
+
## Act 6b — the chain, when the promise was minted
|
|
381
|
+
|
|
382
|
+
A close that settles a **promise** settles it twice: off-chain, which runs the
|
|
383
|
+
loop, and on-chain, which holds the state and enforces the ratchet.
|
|
384
|
+
|
|
385
|
+
`.claude/scripts/do-promise-settle.sh <slug> --composite <n>` is the one door. It
|
|
386
|
+
re-runs the promise's own `proof:` and, when the promise carries an `object_id:`
|
|
387
|
+
in its `contract:` block, submits the generated `settle_promise` on the same run —
|
|
388
|
+
kept/broken, amount = strength×1000 bps. The Move object is **generated from
|
|
389
|
+
`schema/sui.tql`** by `schema/codegen/targets/move.ts`, never hand-written.
|
|
390
|
+
|
|
391
|
+
Read the exit code, not the vibe:
|
|
392
|
+
|
|
393
|
+
| Exit | Meaning | Verb |
|
|
394
|
+
|---|---|---|
|
|
395
|
+
| 0 | proof green | `mark` on `promise:<slug>→proof` + `world:announce` |
|
|
396
|
+
| **3** | **UNVERIFIABLE — a leg could not reach its substrate** | **neither. The promise stays open. Re-run later** |
|
|
397
|
+
| other non-zero | proof red | `warn` |
|
|
398
|
+
| no observable | dissolved — reported, never silent |
|
|
399
|
+
|
|
400
|
+
**Exit 3 is not a close.** A `proof:` is an `&&`-join, so one unreachable leg stops
|
|
401
|
+
the chain. Folding that into `warn` deposits resistance on a proven path for a
|
|
402
|
+
cluster blip — a promise recorded BROKEN for a reason its maker cannot fix.
|
|
403
|
+
|
|
404
|
+
**Measured 2026-09-09, and this is why the chain leg is usually a no-op today: the
|
|
405
|
+
mint door is sealed.** `NETWORK` is absent from `one.ie/web/wrangler.toml` (grep
|
|
406
|
+
count **0**) and the testnet gateway's `worker_public_key` is empty on-chain, so
|
|
407
|
+
nothing can be minted and therefore nothing can settle. The mechanism is wired and
|
|
408
|
+
the schema is real (`Promise` appears 6× in `schema/sui.tql`); the door is shut.
|
|
409
|
+
Arming it is one of Tony's open calls, and it sits deliberately BEHIND the three
|
|
410
|
+
custody decisions — arming a mint before deciding whether a master may derive an
|
|
411
|
+
agent's key is backwards.
|
|
412
|
+
|
|
413
|
+
So: attempt the chain leg, report what it answered, and **never report a sealed
|
|
414
|
+
door as a settled promise.** An unarmed chain degrades cleanly; the off-chain
|
|
415
|
+
settle stands on its own.
|
|
416
|
+
|
|
417
|
+
## Act 7 — what to do next is COMPUTED, not suggested
|
|
418
|
+
|
|
419
|
+
A close ends by naming the next work, and that name is read out of the graph
|
|
420
|
+
rather than invented. `schema/factory.tql` carries 43 funs; these answer "next":
|
|
421
|
+
|
|
422
|
+
| Question | Fun |
|
|
423
|
+
|---|---|
|
|
424
|
+
| what is now claimable? | `ready-tasks($plan)` · `ready-in-wave` |
|
|
425
|
+
| what did this close unblock? | `blockers-of($t)` — run it BEFORE and AFTER |
|
|
426
|
+
| what is stuck with nothing to wait on? | `deadlocked()` |
|
|
427
|
+
| what has no plan above it? | `orphans($plan)` |
|
|
428
|
+
| what is promised and not built? | `uncovered($plan)` · `incomplete()` |
|
|
429
|
+
| what is built and not proven? | `unproven($plan)` · `covered-unproven($plan)` |
|
|
430
|
+
| what did we settle wrongly? | `mis-settled()` |
|
|
431
|
+
| the single next thing | `next($plan)` |
|
|
432
|
+
|
|
433
|
+
**A recommendation the model composed is a guess wearing a receipt.** The rule is
|
|
434
|
+
the one the plan parser already enforces: any deterministic fact a close needs gets
|
|
435
|
+
a query, and the model executes it rather than substituting for it. Where a fun
|
|
436
|
+
answers, quote its answer. Where none does — a judgment call about sequencing,
|
|
437
|
+
say — mark it plainly as judgment and say what would settle it.
|
|
438
|
+
|
|
439
|
+
Report next work as **rows, not prose**: each one filed with `notes`, `tags`,
|
|
440
|
+
`priority`, `parent` and `blockedBy` per Act 3, so the recommendation IS the board
|
|
441
|
+
rather than a paragraph someone must re-type into it.
|
|
442
|
+
|
|
443
|
+
## The loop runs until CLOSED or a human is notified — never until tired
|
|
444
|
+
|
|
445
|
+
A close is a loop, and a loop needs a termination condition that is not fatigue.
|
|
446
|
+
There are exactly two exits:
|
|
447
|
+
|
|
448
|
+
| Exit | When | Door |
|
|
449
|
+
|---|---|---|
|
|
450
|
+
| **CLOSED** | the superior answered CLOSED | `mark` + `world:announce` |
|
|
451
|
+
| **NOTIFY** | the loop hit a decision it cannot make | `human:notify` — with the evidence AND the options |
|
|
452
|
+
|
|
453
|
+
Anything else is a round, and rounds are **bounded at 3**. Still open after three
|
|
454
|
+
→ escalate one rung and say so. A loop with no bound is not autonomy, it is a
|
|
455
|
+
process nobody can predict the cost of.
|
|
456
|
+
|
|
457
|
+
**A notification without options is an interruption.** When the loop escalates it
|
|
458
|
+
carries what it found, what it ruled out, and the shortlist it could not choose
|
|
459
|
+
between — the way `close-owner.sh` refuses: it prints `owner=REFUSED` *and*
|
|
460
|
+
`candidates=`, because a decision handed up without its options makes the human
|
|
461
|
+
redo the search the loop already did.
|
|
462
|
+
|
|
463
|
+
### Self-healing: every failure repairs itself, or names why it can't
|
|
464
|
+
|
|
465
|
+
The loop must survive its own failures without a person. The substrate already
|
|
466
|
+
does this in four places — copy the shape, don't invent one:
|
|
467
|
+
|
|
468
|
+
| Failure | Heals by |
|
|
469
|
+
|---|---|
|
|
470
|
+
| a worker died holding a slot | the claim **evaporates** — dead pid (`kill -0`) or an expired lease, checked on the READ path, so the next contender reclaims dead ground by itself |
|
|
471
|
+
| a proof leg cannot reach its substrate | **exit 3**, and the promise stays open to be re-run — never recorded BROKEN for a cluster blip |
|
|
472
|
+
| a gate already ran on this exact tree | the receipt is **content-addressed**, so a neighbour's pass counts and nothing recomputes |
|
|
473
|
+
| a check's premise went stale | the **red proof** — gut the mechanism and assert the check fails. A check that stays green against a gutted mechanism was never checking |
|
|
474
|
+
|
|
475
|
+
Three rules that keep a self-healing loop honest, because each has a failure mode
|
|
476
|
+
that looks exactly like success:
|
|
477
|
+
|
|
478
|
+
1. **Never heal by weakening the check.** When a test goes red after a change,
|
|
479
|
+
decide first whether it is a *regression* or a *corrected belief*, and say
|
|
480
|
+
which in the commit. Editing an assertion to match new behaviour is legitimate
|
|
481
|
+
ONLY when the old assertion was wrong — and then the comment must say why it
|
|
482
|
+
was wrong. Silently relaxing it is how a suite becomes decorative.
|
|
483
|
+
2. **Empty work ⇒ do the full thing, never ⇒ pass.** A loop that goes green by
|
|
484
|
+
selecting nothing is worse than a slow one.
|
|
485
|
+
3. **A repair is a rung, not a detour.** If healing means touching code the close
|
|
486
|
+
never owned, file it — a close that grows scope while running stops being
|
|
487
|
+
something anyone can review.
|
|
488
|
+
|
|
489
|
+
### The loop's own three questions, each round
|
|
490
|
+
|
|
491
|
+
- What changed since the last round, and did the gate actually *run*? (an exit
|
|
492
|
+
code is a claim about a command that may never have executed)
|
|
493
|
+
- Did this round's repair break something that was green? If so — regression or
|
|
494
|
+
corrected belief?
|
|
495
|
+
- Is the remaining gap something I can decide, or something only the human can?
|
|
496
|
+
If the latter, stop and notify **now**, with the shortlist. Looping past a
|
|
497
|
+
decision you cannot make burns budget and changes nothing.
|
|
498
|
+
|
|
499
|
+
## Act 8 — the loop closes where it opened
|
|
500
|
+
|
|
501
|
+
Post the verdict into the same thread, `mark`/`warn` per the Four Outcomes, then
|
|
502
|
+
`world:announce` so whoever staked on those tags learns it happened. Read the
|
|
503
|
+
final row back from D1.
|
|
504
|
+
|
|
505
|
+
**This is not Act 4b again.** That act is the account a PERSON reads — prose, in
|
|
506
|
+
the director's name, in the room. This one is the machine close: the outcome the
|
|
507
|
+
substrate records and the fan-out to stakers. Skipping either leaves half a close
|
|
508
|
+
— a company that knows and a substrate that does not, or the reverse. The thread is now the durable record — a terminal
|
|
509
|
+
transcript is read once by one person; a room is read by everyone after.
|
|
510
|
+
|
|
511
|
+
---
|
|
39
512
|
|
|
40
513
|
## Steps
|
|
41
514
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
The
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
515
|
+
Six legs, four states each (`pass` · `fail` · `unrun` · `n/a` — do-w4-gates.sh's
|
|
516
|
+
law, unchanged: **an unrun leg is not a closed leg**, plus `skipped` for a leg
|
|
517
|
+
`--only` left out — which licenses nothing, because an operator narrowing the run
|
|
518
|
+
is not evidence about the tree), run in three waves because
|
|
519
|
+
there are exactly three dependency edges and no more.
|
|
520
|
+
|
|
521
|
+
| Leg | Closes | Wave |
|
|
522
|
+
|---|---|---|
|
|
523
|
+
| `gate` | is there a FULL-suite receipt for **this exact tree**? | 1 |
|
|
524
|
+
| `derives` | every `derives: … true` artifact is on disk | 1 |
|
|
525
|
+
| `dims` | the five rubric axes → `/api/mark-dims` | 1 |
|
|
526
|
+
| `task` | the board write — `tasks:status` (**needs `gate`**) | 2 |
|
|
527
|
+
| `promise` | `do-promise-settle.sh` (**needs `derives`**) | 2 |
|
|
528
|
+
| `feedback` | the return-path pheromone → `/api/signal/loop:feedback` (**needs everything**) | 3 |
|
|
529
|
+
|
|
530
|
+
Measured on a warm tree: **~1s** for the two network legs, **~5s** for the whole
|
|
531
|
+
battery. Every curl carries `--max-time`; the two legs that shell out to scripts
|
|
532
|
+
this one does not control go through `run_bounded`.
|
|
533
|
+
|
|
534
|
+
## The three rules it will not trade for speed
|
|
535
|
+
|
|
536
|
+
1. **It never runs a suite.** `/close` is a FULL-lane trigger and *a fast pass is
|
|
537
|
+
never reported as a full pass*. So it asks `test-full.sh` (`TEST_CACHE_KEY_ONLY=1`,
|
|
538
|
+
~1s, cannot start vitest) whether this tree already carries a receipt. The key
|
|
539
|
+
is content-addressed, so **a neighbour's suite counts as yours** — a memo HIT is
|
|
540
|
+
free and it counts. No receipt is `unrun`, filed, and named.
|
|
541
|
+
2. **No receipt ⇒ the board is not told `done`.** That edge is the whole reason
|
|
542
|
+
this repo once had cycles closed green at 0.92 with "tsc not measured".
|
|
543
|
+
3. **Exit 3 from the promise settle is UNVERIFIABLE, never `warn`.** A `proof:`
|
|
544
|
+
is an `&&`-join; a leg that cannot reach its substrate has not broken a
|
|
545
|
+
promise. The promise stays open and gets a row.
|
|
546
|
+
|
|
547
|
+
## The numbers it mines
|
|
548
|
+
|
|
549
|
+
Six leg states say the doors were knocked on. They do not say what changed,
|
|
550
|
+
whether this tree is proven, where the branch sits, or *why* something could not
|
|
551
|
+
be measured. So every close ends with a measured block —
|
|
552
|
+
`.claude/scripts/close-metrics.sh`, additive, **never a seventh leg**: it decides
|
|
553
|
+
nothing, it can turn nothing red, and its exit code is discarded. A metric that
|
|
554
|
+
could fail a close would make this a closer people route around, and the numbers
|
|
555
|
+
would leave with it.
|
|
556
|
+
|
|
557
|
+
| Section | Rows | Owner it composes |
|
|
558
|
+
|---|---|---|
|
|
559
|
+
| `work` | files changed · lines ± · new/deleted/renamed · untracked · test/doc/code split · tests-per-code-file · largest file | `git diff HEAD` |
|
|
560
|
+
| `tree` | branch · worktree kind · dirty paths · ahead/behind trunk · **landed** · worktree count | git; landed is `merge-base --is-ancestor`, an ancestry FACT, never a merge result |
|
|
561
|
+
| `gates` | full-suite receipt HIT/MISS per lane · W4 pack pass/fail/unrun · `delta_tsc` | `test-full.sh` (`TEST_CACHE_KEY_ONLY=1`) · `.w4-gates.json` if a cycle left one |
|
|
562
|
+
| `rubric` | composite vs gate · per-axis · **velocity** vs the last recorded composite | `rubric-weights.json` · `text/learnings.md` |
|
|
563
|
+
| `close` | ledger open rows · unlanded deploy rows · **`packages/claude` mirror drift** · derives | `do-close.sh --pending` · `deploy-record.sh --pending` · `sync-claude-mirror.sh --check` · `do-derives-check.sh` |
|
|
564
|
+
| `machine` | load/cores · free MB · gate slots held · locks | the governor's own dir |
|
|
565
|
+
| `substrate` | which door answered · promise has a `proof:` | `/api/health` · `text/<slug>.md` |
|
|
566
|
+
|
|
567
|
+
Three rules it inherits and one it adds.
|
|
568
|
+
|
|
569
|
+
1. **It composes, it never re-derives.** Every row names a script that already
|
|
570
|
+
owns that number. A second opinion is drift with a delay on it — which is how
|
|
571
|
+
the receipt row was born broken: it called `test-cached.sh` (which needs a
|
|
572
|
+
`<folder>` and prints usage without one) instead of `test-full.sh`, and read
|
|
573
|
+
`unrun` on trees that were proven.
|
|
574
|
+
2. **It never runs a gate.** `TEST_CACHE_KEY_ONLY=1` structurally cannot start
|
|
575
|
+
vitest, and `.w4-gates.json` is READ, never regenerated — running
|
|
576
|
+
`do-w4-gates.sh` bare would run `do-reconcile.sh types`, a tsc that queues
|
|
577
|
+
minutes behind the governor. No metrics row is worth a gate slot.
|
|
578
|
+
3. **Four states, and `unrun` never prints a zero.** Measured in this repo:
|
|
579
|
+
`grep -c "error TS" <file>` returned `0` against an output file a queued gate
|
|
580
|
+
had not written yet. "Zero errors" and "never ran" were the same three
|
|
581
|
+
characters. `--self-test` drives the unrun branch on purpose — point it at an
|
|
582
|
+
absent base ref, an empty cache, no `--composite` — and fails if any of them
|
|
583
|
+
answers with a number instead.
|
|
584
|
+
4. **The `machine` section is evidence, not vanity.** An `unrun` with no pressure
|
|
585
|
+
reading beside it is a mystery; with one it is a reason. "tsc not measured,
|
|
586
|
+
box saturated" once closed three cycles green at 0.92.
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
bash .claude/scripts/close-metrics.sh --json # for a caller
|
|
590
|
+
bash .claude/scripts/close-metrics.sh --section gates # one section
|
|
591
|
+
bash .claude/scripts/do-close.sh <slug> --no-metrics # legs + verdict only
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
## Blockers it decides, instead of asking
|
|
595
|
+
|
|
596
|
+
A blocker that resolves the same way every time is not a decision — it is a chore
|
|
597
|
+
with a human bolted to it. And a blocker that *is* a decision used to be filed as
|
|
598
|
+
bare prose ("gate unrun") with no options, no cost and no recommendation, so the
|
|
599
|
+
person re-derived the same fork every time.
|
|
600
|
+
|
|
601
|
+
`.claude/scripts/do-decide.sh` sits between "a leg is open" and "a human is
|
|
602
|
+
asked". For every open leg it names the fork, ranks the options, prices them,
|
|
603
|
+
recommends one — and **takes** it when, and only when, the substrate has already
|
|
604
|
+
watched that choice come good five times.
|
|
605
|
+
|
|
606
|
+
**The confidence is measured, and it is not this script's opinion.** The oracle
|
|
607
|
+
is the substrate's own routing verdict — `world:route decide:true`
|
|
608
|
+
(`subscriptions.ts:672`), the TS twin of `0043_route_verdict.tql`, a pure read
|
|
609
|
+
with no side effects and no tokens:
|
|
610
|
+
|
|
611
|
+
| verdict | means | /close does |
|
|
612
|
+
|---|---|---|
|
|
613
|
+
| `highway` | `best ≥ 5` **and** `best ≥ 2 × second` | **acts**, and notifies |
|
|
614
|
+
| `classified` | a candidate, not a proven one | recommends |
|
|
615
|
+
| `ceo` | no candidate at all | recommends |
|
|
616
|
+
|
|
617
|
+
`best` is `SUM(strength) − SUM(resistance)` over `claw_paths` from
|
|
618
|
+
`tag:one:decide:close:<id>` to each option. Measured against production
|
|
619
|
+
2026-09-09, the whole loop in three calls:
|
|
620
|
+
|
|
621
|
+
```
|
|
622
|
+
0 marks → {"reason":"ceo","receiver":null,"rung":3}
|
|
623
|
+
5 marks of strength 1 → {"reason":"highway","receiver":"optionA","rung":1}
|
|
624
|
+
+ 1 warn → {"reason":"ceo","receiver":null,"rung":3}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
So the first five times a fork appears **a person answers it**; after that /close
|
|
628
|
+
answers it; and **one bad outcome takes the authority back on the very next
|
|
629
|
+
run.** That is the root `CLAUDE.md`'s "human gates that auto-skip when trust
|
|
630
|
+
earns it", applied to close blockers, on the mechanism G4 already uses for the
|
|
631
|
+
auto-ship gate.
|
|
632
|
+
|
|
633
|
+
**The evidence is written from outcomes, never from exit codes.** A taken
|
|
634
|
+
decision leaves `"decide":"taken:<option>"` on its ledger row. The *next* close
|
|
635
|
+
reads that row before overwriting it and settles it against what actually
|
|
636
|
+
happened to the leg — `pass` → `mark`, still open → `warn`. "The command exited
|
|
637
|
+
0" measures the command, not the choice.
|
|
638
|
+
|
|
639
|
+
**`strength` is always 1, and that is load-bearing.** `rankCandidates` sums
|
|
640
|
+
strength and cannot tell five marks of 1 from one mark of 5, so
|
|
641
|
+
`HARDEN_STRENGTH = 5` is a de-facto **n ≥ 5 sample floor** only while every
|
|
642
|
+
writer on `tag:one:decide:close:*` writes 1. `do-decide.sh` is the sole writer
|
|
643
|
+
and `--self-test` pins the literal.
|
|
644
|
+
|
|
645
|
+
### The table, and the two things no confidence can buy
|
|
646
|
+
|
|
647
|
+
```bash
|
|
648
|
+
bash .claude/scripts/do-decide.sh --table
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
| id | fires on | options | |
|
|
652
|
+
|---|---|---|---|
|
|
653
|
+
| `gate-mint` | `gate` unrun, no receipt | `mint` (~87s + a gate slot) · `file` | |
|
|
654
|
+
| `derives-retry` | `derives` unrun (out of clock) | `retry` · `file` | |
|
|
655
|
+
| `promise-retry` | `promise` unrun (exit 3 / clock) | `retry` · `file` | |
|
|
656
|
+
| `writeback` | `dims`/`feedback`/`task` door timeout | `file` | **VETO** |
|
|
657
|
+
| `task-gate` | `task` unrun, `gate` not pass | `file` | **VETO** |
|
|
658
|
+
|
|
659
|
+
A fork **not in this table is not a decision** — exit 5, and /close files the row
|
|
660
|
+
exactly as before. Adding a row is how this grows; generalising it is how it
|
|
661
|
+
starts guessing.
|
|
662
|
+
|
|
663
|
+
The two vetoes are checked **before the oracle is called**, so a vetoed fork is
|
|
664
|
+
structurally unreachable rather than reachable-and-outranked:
|
|
665
|
+
|
|
666
|
+
- **`writeback` — the self-poisoning veto.** `dims`, `feedback` and `task` write
|
|
667
|
+
*through* doors. Their `unrun` is a curl timeout: the answer is UNKNOWN and the
|
|
668
|
+
write may well have landed. A retry that double-writes deposits strength twice
|
|
669
|
+
on exactly the `claw_paths` rows this layer reads back as confidence — it would
|
|
670
|
+
inflate its own evidence, and look *more* certain the more it fired.
|
|
671
|
+
- **`task-gate`** — the board write must never be decided around its gate. Cycles
|
|
672
|
+
once closed green at 0.92 with the suite unmeasured; that edge is why they
|
|
673
|
+
can't again.
|
|
674
|
+
|
|
675
|
+
On top of the table there is a blanket class applied to the **command about to
|
|
676
|
+
run**, not to the table row — so a row edited to smuggle one in still cannot
|
|
677
|
+
fire: anything that ships, deploys, pushes, force-writes, deletes, rotates a
|
|
678
|
+
secret, or spends. **Blast radius is a second axis, and confidence does not buy
|
|
679
|
+
it.** That is the one place this layer deliberately does not do what "don't ask
|
|
680
|
+
if you're sure" would imply, and it is a boundary, not an omission.
|
|
681
|
+
|
|
682
|
+
**Every failure direction points at the human.** No oracle answer, a malformed
|
|
683
|
+
answer, an unreachable D1 mirror, a missing key, no `--apply`, no gate headroom —
|
|
684
|
+
all of them read `ceo` and recommend. A layer that acts when it cannot measure is
|
|
685
|
+
worse than the ask it replaced.
|
|
686
|
+
|
|
687
|
+
**A taken decision always pages, and always carries its undo.** The trade this
|
|
688
|
+
layer makes is moving the human's veto from *before* the action to *after* it,
|
|
689
|
+
and that is only honest while the reversal is actually in their hand:
|
|
690
|
+
|
|
691
|
+
```
|
|
692
|
+
gate → TAKEN mint (confidence: highway)
|
|
693
|
+
undo: bash .claude/scripts/do-decide.sh --settle --leg gate --status mint --outcome open
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
A recommendation never pages — it is already on the board and in the report, and
|
|
697
|
+
a notifier that fires for things nobody must act on is a notifier the human
|
|
698
|
+
mutes.
|
|
699
|
+
|
|
700
|
+
`--no-decide` restores the old behaviour: every blocker waits for a person.
|
|
701
|
+
`--dry-run` prints what it *would* take and takes nothing (structural — the
|
|
702
|
+
script cannot act without `--apply`, and `--self-test` proves it).
|
|
703
|
+
|
|
704
|
+
## What the signal carries
|
|
705
|
+
|
|
706
|
+
`feedback` used to fire in wave 1 with four fields — slug, tid, composite,
|
|
707
|
+
outcome. That is a pheromone saying a close happened and nothing about what it
|
|
708
|
+
found, on the one channel the next agent's routing actually reads.
|
|
709
|
+
|
|
710
|
+
It is now **wave 3**, the third dependency edge (`feedback ← everything`), and it
|
|
711
|
+
carries the whole close:
|
|
712
|
+
|
|
713
|
+
| | |
|
|
714
|
+
|---|---|
|
|
715
|
+
| `legs[]` | all six, each with `status`, **`secs`**, and its detail |
|
|
716
|
+
| `closed` · `pass`/`fail`/`unrun`/`na` · `elapsed_s` · `door` · `branch` | the verdict and where it was taken |
|
|
717
|
+
| `decisions[]` | every fork: `taken`/`recommend`/`veto`, the choice, the confidence |
|
|
718
|
+
| `metrics` | the entire measured block, verbatim |
|
|
719
|
+
|
|
720
|
+
**Measured once, read twice.** The measured block is ~17s of real work. Running
|
|
721
|
+
it for the human and again for the machine would double it *and* measure two
|
|
722
|
+
different instants of a tree seven sessions are editing — so
|
|
723
|
+
`close-metrics.sh --json-out <file>` writes the machine copy while stdout stays
|
|
724
|
+
the table, and the report prints the run the signal carried.
|
|
725
|
+
|
|
726
|
+
### Tags are derived, and the close closes its own loop on them
|
|
727
|
+
|
|
728
|
+
Tags are how the world routes. A close tagged only `close,<slug>` is a close
|
|
729
|
+
nobody can route on, so the tags are **derived from what was measured** — bare
|
|
730
|
+
words, because a namespaced subscribe tag matches zero subscribers
|
|
731
|
+
(`subscribe-tags-parity.test.ts`):
|
|
732
|
+
|
|
733
|
+
```
|
|
734
|
+
close · <slug> · closed|unclosed · failing · unrun · decided · recommended
|
|
735
|
+
· <each failing leg> · fast|slow · degraded
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
Then **Rule 1, on the tags themselves**: each one takes one verb on the edge
|
|
739
|
+
`tag:<workspace>:<tag> → close:kept|open`, carrying the whole tag set.
|
|
740
|
+
|
|
741
|
+
```
|
|
742
|
+
verbs: mark=0 warn=8 on tag:one:<tag>→close:open
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
`closed` marks, open warns, and **`--status timeout` writes neither** — a held
|
|
746
|
+
lease is neutral by the same rule that makes `task` and `dims` `n/a` on a
|
|
747
|
+
timeout. A halt is not an outcome. These marks use a `tag:` source, deliberately
|
|
748
|
+
disjoint from the `decide:close:*` sources the decide layer reads: the close must
|
|
749
|
+
never vote on its own authority.
|
|
750
|
+
|
|
751
|
+
### Accretion, the trail, and the way back
|
|
752
|
+
|
|
753
|
+
*The operator, 2026-09-09: "every time a signal hits a node we should add
|
|
754
|
+
exponentially more metadata and data and images etc. to it… and we can combine
|
|
755
|
+
them back again."*
|
|
756
|
+
|
|
757
|
+
A signal that carries the same four fields at hop 5 as at hop 1 has learned
|
|
758
|
+
nothing from the journey, and the journey is the only thing that knows what
|
|
759
|
+
mattered. So the payload budget **doubles per hop** — `ACCRETE_BASE << hop`,
|
|
760
|
+
`CLOSE_SIGNAL_HOP` read from the caller and re-emitted incremented, so a signal
|
|
761
|
+
crossing four nodes arrives with 16× the room it started with.
|
|
762
|
+
|
|
763
|
+
Two bounds, because "exponentially more" without them is a signal nobody can
|
|
764
|
+
store and every door refuses:
|
|
765
|
+
|
|
766
|
+
1. **A hard ceiling** (`ACCRETE_MAX`, 256KB). The doubling is how fast the room
|
|
767
|
+
grows, not a promise it grows forever.
|
|
768
|
+
2. **Bulk travels by reference.** Images, JSON dumps, diffs and gate packs go as
|
|
769
|
+
`{path, bytes, sha256}`, each `test -s`-verified at emit time — never as
|
|
770
|
+
bytes. That is what makes "and images etc." affordable: a screenshot costs
|
|
771
|
+
~120 bytes of envelope, and the node that wants the pixels reads the path. A
|
|
772
|
+
reference to a file that is not there is "file exists is theater" with an
|
|
773
|
+
extra hop of delay on it.
|
|
774
|
+
|
|
775
|
+
Numbers pack first, then details, then references — a budget that runs out drops
|
|
776
|
+
the biggest thing, never the verdict.
|
|
777
|
+
|
|
778
|
+
**Accretion alone is not enough, and this is the actual insight.** It makes every
|
|
779
|
+
node richer and leaves the one who *asked* knowing nothing. So the envelope
|
|
780
|
+
carries four more fields:
|
|
781
|
+
|
|
782
|
+
| | |
|
|
783
|
+
|---|---|
|
|
784
|
+
| `origin` | who started the work — survives every hop unchanged |
|
|
785
|
+
| `sender` | the previous node, one hop back |
|
|
786
|
+
| `reply_to` | where a return leg goes (`sender`, else `origin`) |
|
|
787
|
+
| `trail[]` | one entry per node crossed: hop, node, bytes added, artifacts, tags, verdict |
|
|
788
|
+
|
|
789
|
+
and `--reply` folds the enriched envelope **back one hop**, tagged `reply` and
|
|
790
|
+
`for:<node>` so the router delivers it to whoever staked on that node instead of
|
|
791
|
+
broadcasting. It returns the outbound payload **verbatim, not a summary** — the
|
|
792
|
+
sender asked for work, and a summary here would be this node deciding what the
|
|
793
|
+
previous node is allowed to know.
|
|
794
|
+
|
|
795
|
+
**This is the ant, and we already had half of it.** Outbound the trail gains
|
|
796
|
+
data; the return trail carries what was found back down the same path, and *the
|
|
797
|
+
return is what moves the weights*. `loop:feedback` has been called "the
|
|
798
|
+
return-path pheromone" for months while it returned four fields — the right name
|
|
799
|
+
for a leg that had nothing to return.
|
|
800
|
+
|
|
801
|
+
Three consequences worth stating, because they generalise past `/close`:
|
|
802
|
+
|
|
803
|
+
- **Routing quality is bounded by return quality.** Paths re-rank on `mark`/`warn`,
|
|
804
|
+
so a mark deposited by a signal that carried no evidence is a vote cast blind.
|
|
805
|
+
Enrich the return leg and the weights start meaning something.
|
|
806
|
+
- **The trail is append-only.** A node may add to the history it was handed; it
|
|
807
|
+
may never rewrite it. A node that edits its own trail is a node whose trail
|
|
808
|
+
cannot be trusted.
|
|
809
|
+
- **The close must never vote on its own authority.** The tag marks use a `tag:`
|
|
810
|
+
source, deliberately disjoint from the `decide:close:*` sources the decide
|
|
811
|
+
layer reads as confidence.
|
|
812
|
+
|
|
813
|
+
```
|
|
814
|
+
trail: hop=3 origin=pannels sender=do-auto reply=do-auto budget=32768B artifacts=6
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
The return leg is **opt-in** (`--reply`): a close with no caller has nobody to
|
|
818
|
+
reply to, and a reply nobody asked for is one more signal in a world that already
|
|
819
|
+
routes.
|
|
820
|
+
|
|
821
|
+
### Speed is a row, not a feeling
|
|
822
|
+
|
|
823
|
+
Every leg is timed, and the measured block is timed separately — because "the
|
|
824
|
+
close was slow" and "the promise proof was slow" are different facts with
|
|
825
|
+
different fixes, and a fixed ~17s cost blamed on a door is a number nobody acts
|
|
826
|
+
on. `slow` is judged on the legs alone.
|
|
827
|
+
|
|
828
|
+
```
|
|
829
|
+
speed: gate=2s derives=0s task=0s dims=0s feedback=1s promise=0s | metrics=17s total=45s
|
|
830
|
+
tags: close __probe__ unclosed unrun recommended gate fast degraded
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
The budget does not starve the closing signal: every other leg is skipped once
|
|
834
|
+
the wall clock is spent, `feedback` is exempt — it runs after the measured block,
|
|
835
|
+
so the guard would otherwise fire on the one leg that carries the whole close,
|
|
836
|
+
breaking Rule 1 by its own bound. It stays bounded by its own `--leg-budget`.
|
|
837
|
+
|
|
838
|
+
## What it cannot close, it files
|
|
839
|
+
|
|
840
|
+
Every non-pass leg lands in three places, in this order — each one a fallback for
|
|
841
|
+
the last failing:
|
|
842
|
+
|
|
843
|
+
1. **the local ledger** (`.claude/.close-pending.jsonl`) — this write cannot fail
|
|
844
|
+
and it is what `--pending` reads back. Keyed by `<slug>:<leg>`, so a re-run
|
|
845
|
+
replaces its own row: the ledger measures open work, never close attempts.
|
|
846
|
+
2. **a board task**, tagged `close,unclosed,leg:<name>`, whose `notes` **is the
|
|
847
|
+
paste-able command** that finishes it — not prose. That is the difference
|
|
848
|
+
between a task and a nag.
|
|
849
|
+
3. **the human** (`notify.sh`) — only when the board was *unreachable*, so the
|
|
850
|
+
row reached nothing anyone watches. `--notify` forces it. `--no-file` (ledger
|
|
851
|
+
only) is the operator's choice and never pages anyone.
|
|
852
|
+
|
|
853
|
+
```bash
|
|
854
|
+
bash .claude/scripts/do-close.sh --pending # the open rows + each fix. exit 6 if any
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
## Run it at the end of every workflow
|
|
858
|
+
|
|
859
|
+
Last line, wrapped, always:
|
|
860
|
+
|
|
861
|
+
```bash
|
|
862
|
+
bash .claude/scripts/do-close.sh <slug> || true
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
It exits **0 even with open legs** — by design. A closer that fails its caller is
|
|
866
|
+
a closer callers stop calling, and a skipped close is Rule 1 broken: the signal
|
|
867
|
+
never closes and the paths never learn. `--strict` opts into exit 1 for a caller
|
|
868
|
+
that genuinely wants to stop. Re-running is cheap and safe: the ledger is keyed
|
|
869
|
+
by `<slug>:<leg>` so a row replaces itself, and a leg that passed clears its row.
|
|
870
|
+
|
|
871
|
+
Wired, verified 2026-09-09:
|
|
872
|
+
|
|
873
|
+
| Caller | Where | Status passed |
|
|
874
|
+
|---|---|---|
|
|
875
|
+
| `do-auto.sh` | plan complete (it replaced the bare `--task-link`) | `done` |
|
|
876
|
+
| `do-auto.sh` | `--max-cycles` halt | `timeout` — the lease stays held |
|
|
877
|
+
| `land.sh` | both exits | `--only dims,feedback` |
|
|
878
|
+
| `deploy.sh` | both exits | `--only dims,feedback` |
|
|
879
|
+
| `factory-executor.js` | Close stage, step 6 | `--only dims,feedback,promise,derives` |
|
|
880
|
+
|
|
881
|
+
**Three of the five pass `--only`, and the reason is the same each time: the
|
|
882
|
+
board write must have exactly one writer.**
|
|
883
|
+
|
|
884
|
+
- The factory executor's Close stage already owns it, and its status logic is
|
|
885
|
+
richer than done/failed (a UI task stays `picked` until a person looks). Two
|
|
886
|
+
writers on one row is how the first factory run reported `done` over a row the
|
|
887
|
+
board read `picked`.
|
|
888
|
+
- `land.sh` and `deploy.sh` pass a **branch name** and the literal `deploy`. That
|
|
889
|
+
is also why `_tag_add` sanitises: a branch name carries a `/`, the mark edge is
|
|
890
|
+
a URL path segment, and `tag:one:feat/x→close:open` split into a route that does
|
|
891
|
+
not exist — measured 2026-09-09 as 6 tags derived, 5 verbs landed, and the sixth
|
|
892
|
+
gone with no error. Bare word chars only, pinned by `--self-test` check 13.
|
|
893
|
+
Those names
|
|
894
|
+
are not plan slugs — without `--tid` the task leg resolves by the `slug:` tag
|
|
895
|
+
and would close whatever plan happens to share the name. And `land.sh` runs the
|
|
896
|
+
*fast* lane, so the receipt gate is `unrun` on every land: filing that would
|
|
897
|
+
fire a row on every land forever with nothing anyone can do — the EXPIRED class
|
|
898
|
+
`deploy-record.sh --pending` exists to not be.
|
|
899
|
+
|
|
900
|
+
Only `do-auto.sh` passes a real plan slug, so only it runs the full battery.
|
|
901
|
+
|
|
902
|
+
`do-folder.sh` is deliberately NOT wired: it is a pure emitter whose stdout is
|
|
903
|
+
parsed JSON, and a close line printed there corrupts its only output.
|
|
904
|
+
|
|
905
|
+
## Outcome flags
|
|
906
|
+
|
|
907
|
+
`--status` picks the outcome word; the legs follow it.
|
|
908
|
+
|
|
909
|
+
```
|
|
910
|
+
result --status done mark() strength++ chain strengthens
|
|
911
|
+
timeout --status timeout neutral no change lease HELD, status untouched
|
|
912
|
+
dissolved --status dissolved warn(0.5) resist+=0.5 mild — path missing
|
|
913
|
+
failure --status failed warn(1) resist+=1 full — agent failed
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
A non-`done` close asserts no green, so `gate` reports `n/a` and never blocks it.
|
|
917
|
+
|
|
918
|
+
## Three specifics that live in the script, not here
|
|
919
|
+
|
|
920
|
+
Moved into `do-close.sh` as code plus comment so they cannot rot apart from the
|
|
921
|
+
behaviour they govern — read them there:
|
|
922
|
+
|
|
923
|
+
- **the three doors take three different credentials** (`mark-dims` and `ask` want
|
|
924
|
+
`GATEWAY_API_KEY`; `/api/signal/*` wants `SERVER_SECRET`, `signal-dispatch.ts:78`).
|
|
925
|
+
Measured 2026-09-09: the feedback pheromone had been 401ing.
|
|
926
|
+
- **`mark-dims` Zod-parses a bare `{edge, dims}`** — a `{data:{…}}` wrapper is a 400.
|
|
927
|
+
- **`w4-rubric.ts`'s `task-composite` is not the cycle gate.** It scores five axes
|
|
928
|
+
with no goal-fit, because a bare diff carries no plan context. The cycle gate is
|
|
929
|
+
`composite ≥ 0.65` AND `goal-fit ≥ 0.50` AND no adversarial > 0.5 AND `delta_tsc ≤ 0`,
|
|
930
|
+
weights in `.claude/scripts/rubric-weights.json` (`cycle` block). Pass the composite
|
|
931
|
+
in with `--composite`; the script does not score judgment and never will.
|
|
176
932
|
|
|
177
933
|
---
|
|
178
934
|
|
|
179
|
-
*`/close` is `mark()` made
|
|
935
|
+
*`/close` is `mark()` made bounded. Every outcome closes its loop — or leaves the command that does.*
|