@staix/agent-hub 0.30.0 → 0.31.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +13 -7
  3. package/docs/agent-notes/README.md +1 -0
  4. package/docs/agent-notes/adapters.md +5 -2
  5. package/docs/agent-notes/budget.md +1 -0
  6. package/docs/agent-notes/bus.md +9 -4
  7. package/docs/agent-notes/daemon.md +15 -8
  8. package/docs/agent-notes/local-worker.md +1 -0
  9. package/docs/agent-notes/tasks.md +7 -3
  10. package/docs/agent-notes/tests.md +6 -4
  11. package/docs/design/adapters.md +52 -0
  12. package/docs/design/agent-hub-architecture.md +46 -0
  13. package/docs/design/bus.md +139 -1
  14. package/docs/design/task-board.md +82 -0
  15. package/docs/dogfood.md +22 -4
  16. package/docs/events.md +3 -0
  17. package/docs/memory.md +17 -1
  18. package/docs/operations.md +307 -78
  19. package/docs/profiles.md +35 -0
  20. package/docs/quickstart.md +18 -5
  21. package/docs/security.md +154 -60
  22. package/docs/specs/2026-09-19-agent-hub-design.md +84 -14
  23. package/docs/specs/2026-09-19-multi-project-design.md +7 -2
  24. package/docs/verification/2026-11-04-1171-channel-client-evidence.md +31 -0
  25. package/docs/verification/2026-11-05-0.31.0.md +182 -0
  26. package/docs/verification/2026-11-05-claude-implementer-2.1.318.md +70 -0
  27. package/docs/verification/2026-11-05-hardcoded-values-classification-rules.md +257 -0
  28. package/docs/verification/2026-11-05-hardcoded-values-ratchet.md +55 -0
  29. package/docs/verification/2026-11-06-rooms-presence-cost.md +152 -0
  30. package/docs/verification/claude-shell-wrapper-native.py +1 -1
  31. package/docs/verified.json +14 -14
  32. package/package.json +3 -4
  33. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  34. package/plugins/agent-hub/server.js +4393 -3609
  35. package/src/adapters/acp.ts +5 -4
  36. package/src/adapters/channel-peer.ts +72 -3
  37. package/src/adapters/claude-channel-evidence.ts +52 -0
  38. package/src/adapters/claude-channel.ts +61 -18
  39. package/src/adapters/claude-implementer.ts +29 -7
  40. package/src/adapters/claude-print-child.ts +12 -2
  41. package/src/adapters/codex-appserver.ts +13 -12
  42. package/src/adapters/compat.ts +3 -3
  43. package/src/adapters/definitions/builtin-metadata.ts +13 -13
  44. package/src/adapters/definitions/qwen.ts +1 -0
  45. package/src/adapters/local-worker.ts +61 -18
  46. package/src/adapters/pi.ts +48 -6
  47. package/src/adapters/registry-types.ts +12 -0
  48. package/src/adapters/registry.ts +2 -0
  49. package/src/adapters/reviewer-print.ts +50 -11
  50. package/src/broker/server.ts +44 -13
  51. package/src/broker/store.ts +38 -8
  52. package/src/cli/answer-args.ts +50 -0
  53. package/src/cli/attention.ts +17 -2
  54. package/src/cli/backlog-return-args.ts +37 -0
  55. package/src/cli/console-question.ts +110 -0
  56. package/src/cli/console-state.ts +77 -16
  57. package/src/cli/console.ts +54 -1
  58. package/src/cli/handoff-args.ts +15 -5
  59. package/src/cli/handoff-output.ts +15 -2
  60. package/src/cli/help.ts +8 -5
  61. package/src/cli/identity.ts +13 -3
  62. package/src/cli/main.ts +128 -16
  63. package/src/cli/memory.ts +107 -3
  64. package/src/cli/output.ts +29 -6
  65. package/src/cli/pet.ts +4 -4
  66. package/src/cli/results-args.ts +30 -0
  67. package/src/cli/runtime-version-warning.ts +50 -0
  68. package/src/cli/status-lines.ts +41 -1
  69. package/src/cli/tail-render.ts +1 -0
  70. package/src/cli/upgrade-interactive.ts +14 -2
  71. package/src/hub/action-class.ts +9 -1
  72. package/src/hub/board.ts +369 -18
  73. package/src/hub/bus.ts +493 -53
  74. package/src/hub/completion-detail.ts +50 -0
  75. package/src/hub/completions-store.ts +168 -0
  76. package/src/hub/completions.ts +120 -0
  77. package/src/hub/config-apply.ts +7 -5
  78. package/src/hub/config-reload.ts +10 -1
  79. package/src/hub/control-client.ts +40 -4
  80. package/src/hub/daemon.ts +1569 -134
  81. package/src/hub/delivery-failure.ts +14 -0
  82. package/src/hub/delivery-hints.ts +14 -4
  83. package/src/hub/delivery-journal.ts +751 -42
  84. package/src/hub/delivery-superseded.ts +4 -1
  85. package/src/hub/envelope.ts +3 -3
  86. package/src/hub/events.ts +6 -0
  87. package/src/hub/handoff.ts +145 -6
  88. package/src/hub/hub-tools.ts +56 -7
  89. package/src/hub/manager.ts +59 -3
  90. package/src/hub/memory-policy.ts +44 -1
  91. package/src/hub/memory-sync-policy.ts +109 -0
  92. package/src/hub/memory-sync-state.ts +145 -0
  93. package/src/hub/no-writes.ts +1 -1
  94. package/src/hub/operational-settings.ts +11 -2
  95. package/src/hub/operational-view.ts +12 -1
  96. package/src/hub/peers.ts +25 -2
  97. package/src/hub/planning.ts +6 -1
  98. package/src/hub/question-desk.ts +201 -0
  99. package/src/hub/question-detach.ts +6 -0
  100. package/src/hub/questions.ts +260 -0
  101. package/src/hub/research-evidence.ts +106 -0
  102. package/src/hub/restart.ts +14 -0
  103. package/src/hub/review-anchor.ts +181 -0
  104. package/src/hub/review-brief.ts +13 -0
  105. package/src/hub/review-comments.ts +115 -0
  106. package/src/hub/review-findings.ts +7 -2
  107. package/src/hub/review-material.ts +76 -0
  108. package/src/hub/routing.ts +3 -1
  109. package/src/hub/task-sweep.ts +2 -0
  110. package/src/hub/tasks.ts +577 -63
  111. package/src/memory/capture.ts +5 -2
  112. package/src/pet/config.ts +25 -13
  113. package/src/rooms/link.ts +209 -21
  114. package/src/rooms/remote-peer.ts +186 -19
  115. package/src/rooms/service.ts +338 -22
  116. package/src/rooms/store.ts +437 -65
  117. package/src/rooms/wire.ts +24 -6
  118. package/src/ui/index.html +138 -7
  119. package/src/ui/needs.ts +10 -4
  120. package/src/ui/plugin-questions.ts +56 -0
  121. package/src/ui/plugin-results.ts +81 -0
  122. package/src/ui/plugin.ts +207 -27
  123. package/src/pet/window.html +0 -114
  124. package/src/pet/window.jxa.js +0 -122
  125. package/src/pet/window.ts +0 -154
@@ -0,0 +1,257 @@
1
+ # Hard-coded values inventory: how a key is classified (#1180, task #738)
2
+
3
+ Date: 2026-11-05. Refs #1180. `scripts/inventory-hardcoded-values.classifications.json` is plain JSON that the generator parses as a map from key to class, so it has no place for a header: this file is that header. It was written **before** any key of the baseline was labelled, from two sources only: the K01 to K18 reasons of [the closure](2026-10-30-operational-settings-closure.md) and the finding of [the ratchet record](2026-11-05-hardcoded-values-ratchet.md) that a reader who knew only the closure agreed with the committed file on 58% of a sample. It is not fitted to the sample that checks it.
4
+
5
+ A key names one literal in one function (`file:function:kind:name=value#n`). Ask the questions in order and stop at the first that answers. The class says **why the value is a constant**, not how important it is.
6
+
7
+ 1. **Test seam.** The literal is the fallback of an injection point that exists for tests, and the code or its name says so: a `/** Test seam ... */` option, a `*ForTest` helper, a port a test replaces, or an environment variable that only a test, a gate or a benchmark sets (`AHUB_CHECK_HANG_S`, `AHUB_*_MS` knobs read with `${VAR:-N}` or `process.env.X ?? N`). A default parameter (`ms = 90000`, `limit = 20`) or a bare `opts.x ?? N` is **not** a test seam: see rule 5.
8
+ 2. **Contract.** The value is part of a representation, a wire or storage shape, or an accepted API: a protocol or schema number, a format or identifier width, a unit conversion (`1000`, `86400000`, `60`), an index or position in a stored or parsed structure, a bound that is part of a control message's or a tool's documented shape, `?? 0` or `Math.max(x, 1)` that gives the meaning of an absent or zero stored value, and a default that is **exported or published**: the default of a configuration key (`DEFAULT_CONFIG`, a `*_SETTING.defaultValue`), a tool schema's `minimum` or `maximum`, a declared model context size. A default that only lives in the function that reads it is not published: it is operational (rule 5). Changing a contract changes what stored or exchanged data means.
9
+ 3. **Safety floor.** The value bounds what comes from outside the hub's own control, or what could exhaust it: a cap on a path, name, id, command, request, model or foreign-hub text, a truncation of such text before it is shown or stored, a limit on a parser, a proof or a diagnostic read, a native-session ownership record limit. Changing it changes what untrusted input the hub accepts or shows.
10
+ 4. **Operational.** Everything else that tunes how the hub behaves without changing what its data means or what it accepts: timeouts, deadlines, polling and batching intervals, retry and backoff numbers, retention and window lengths, TTLs, hub-internal caps on its own queues, caches and lists, display and page sizes, benchmark and smoke-script deadlines and sizes, the numbers inside a backoff expression.
11
+ 5. **The three confusable cases, which the first reader got wrong** (the committed file treats each as operational unless rule 1 to 3 applies on its own):
12
+ - a **hub-internal cap** on the hub's own lists, queues, caches or batches (`slice(0, 50)` of the hub's own rows, a queue cap) is operational, not a safety floor. A cap on text that came from a peer, a model or another project is a safety floor.
13
+ - an **injected default** (`opts.x ?? N`, `config.x ?? N`, `input.x ?? N`) and a **default parameter** (`function f(ms = 90000)`, `list(limit = 20)`) are operational: the injection point or the parameter makes the value tunable, it does not make it a seam. They are a test seam only under rule 1 (an environment knob or a documented test seam), and a contract only under rule 2 (exported or published).
14
+ - a **script or smoke deadline** (`scripts/`, `AbortSignal.timeout(N)`, a benchmark's window or size) is operational.
15
+
16
+ A key that none of these decides is **not labelled**: it stays in the baseline and is reported for a ruling. The rules are the reason a label is written, so a label that needs a new rule adds that rule here first.
17
+
18
+ ## How a batch is checked
19
+
20
+ Before a batch is written, a fresh blind sample of 80 already-decided keys is drawn at random with a recorded seed, each is labelled from the code with only these rules (the committed label is not shown), and the labels are compared with the file. The batch is written only at 85% agreement or more, and the figure and the disagreements go in the batch's PR. A sample that fails the bar sends the disagreement back to these rules before any key is labelled.
21
+
22
+ ## What a batch labels, and what it leaves in the baseline
23
+
24
+ A batch labels only shapes where the committed file is consistent with these rules: module constants, timers, deadlines and `Math.min`/`Math.max` clamps, injected defaults, script and benchmark values, and bounds and truncations in `src/adapters`, `src/models`, `scripts/` and `src/local`. The baseline keeps every length bound and every truncation (`.slice`, `.substring`) in `src/hub`, `src/cli`, `src/ui`, `src/rooms` and `src/broker`: the file labels those inconsistently (in `src/hub`, 91 safety floor, 61 contract and 63 operational for length bounds), so no reading of the code can reach the bar there, and their convention is a separate decision by the conductor (the working rule: a bound on text, ids or input from a peer, model or another project is a safety floor; a wire-shape limit on our own protocol is a contract; the hub's own list caps are operational). The sample that checks a batch is drawn only from the same population.
25
+
26
+ ## Calibration record
27
+
28
+ | Round | Seed | Population | Reader | Agreement | Result |
29
+ | --- | --- | --- | --- | ---: | --- |
30
+ | 1 | 738001 | every decided key | blind reader, these rules | 62 of 80 (77.5%) | below the 85% bar: 18 misses, 12 of them in length bounds, truncations and defaults; the rules were corrected for default parameters and environment knobs and the population narrowed |
31
+ | 2 | 738002 | decided keys outside the `src/hub`, `src/cli`, `src/ui`, `src/rooms`, `src/broker` length bounds and truncations | blind reader, corrected rules | 56 of 80 (70.0%) | below the bar: 24 misses (below) |
32
+ | 3 | 738005 | the 303 decided keys the mechanical rules below cover | the mechanical rules | 78 of 80 (97.5%) | passes, **in-sample**: the rules were written after reading the file's labels |
33
+
34
+ Round 2 misses, by cause (the starting evidence for #739): 10 literals read as a contract that the file calls operational (a `= 0` reduce start, a `?? 2048` max-tokens default, config-shaped `*_s = 90` defaults, a `timeout_s > 86_400` bound, `hops = 2`, `id = 0`, `start = 0`); 7 read as operational that the file calls a safety floor (200-character error and output truncations, `Math.min(8000, ...)` timeout clamps); 4 read as operational that the file calls a test seam (default parameters such as `timeoutMs = 2000`, `idleTimeoutMs = 600_000`, `opts.pollMs ?? 2000`); 2 read as a safety floor that the file calls operational (`MAX_FILES = 4096`, `totalBytes = 512 MiB`); 1 read as operational that the file calls a contract. The file itself is internally consistent only 78 to 88% on these shapes (default-parameter timeouts 78%, `Math.min(<ms>, ...)` 87%, constants named `MAX_*` 50%), and for `src/hub` length bounds and truncations it is 42% and 48%, so no reading of the code reaches 85% on them.
35
+
36
+ ## The mechanical rules of the first batch (#738)
37
+
38
+ A key is labelled by a rule only when the rule is a pure function of its name, kind, value and file, and the committed file agrees with that rule on at least 94% of the keys it already decides. The rules, with how the file agrees with each over every decided key (the whole file, not the sample):
39
+
40
+ | Rule | Label | Decided keys it covers | File agrees |
41
+ | --- | --- | ---: | ---: |
42
+ | R1 a `constant` named `*_MS`, except unit conversions (`DAY_MS`, `MIN_MS`, ...), `*DATE_MAX_MS`, `*WINDOW_MS` and the security exclusion R1x below | operational | 51 | 94% |
43
+ | R1x a `*_MS` constant that bounds replay, revocation or rate: the window in which a replayable request is still accepted, how long a revoked or rotated credential stays valid, or the floor on how often an agent turn may be triggered | safety floor | 0 | none |
44
+ | R2 an inline literal that is the argument of `setTimeout`, `setInterval`, `Bun.sleep`, `sleep`, `AbortSignal.timeout` or `this.backOff` | operational | 134 | 98% |
45
+ | R3 an inline default named `*_s`, `*_min` or `*_ms` (a config-shaped value) | operational | 37 | 100% |
46
+ | R5 a `0` or `1` that starts a reduce, loop or counter (`start`, `id`, `count`, `sum`, `i`, `*.reduce`) | operational | 8 | 100% |
47
+ | R6 a `.slice` or `.substring` of error, output or message text in `scripts/benchmarks/` | safety floor | 28 | 96% |
48
+
49
+ R1x is the conductor's ruling on the review of PR #1212, not a calibrated rule: the committed file had no decided key of that kind (its decided `*GRACE_MS` keys are process-kill grace periods and `RATE_LIMIT_MS` throttles a log line, all operational), so its agreement is not measured. It labels four baseline keys: `tool-request.ts` `MAX_FUTURE_MS` (the replay window of request ids), `broker/store.ts` and `rooms/store.ts` `GRACE_MS` (how long a rotated epoch's frames stay accepted, which bounds how long a revoked member's key still works) and `schedules.ts` `MIN_EVERY_MS` (the floor that stops a schedule triggering an agent turn more often than every 15 minutes, a spend guard).
50
+
51
+ R4 (a `maxTokens` or `contextWindow` default, 97%) and R7 (a length bound in `src/adapters`, `src/models`, `src/local`, 91%) agree with the file, but no baseline key was labelled by R4, and R7's five baseline keys have no exact agreeing precedent, so they stay in the baseline. Taken together the rules agree with 303 of the 311 decided keys they cover (97.4%) and with 97% and 98% on the two halves of a by-file hold-out (files split by a hash of their path), which is the same data seen differently, not an independent reader.
52
+
53
+ Not labelled, on purpose: every length bound and truncation in `src/hub`, `src/cli`, `src/ui`, `src/rooms` and `src/broker`; `Math.min`/`Math.max` clamps of a timeout; default-parameter timeouts (`timeoutMs = 2000`); `MAX_*` and `*_BYTES` constants; unit conversions. #739 settles their convention first and then relabels the inconsistent existing entries by name.
54
+
55
+ ## The `src/hub` convention for bounds and truncations (#739)
56
+
57
+ Ruled by the conductor on 2026-11-05. It decides bounds (`x.length > N`, `Object.keys(x).length > N`, a byte limit) and truncations (`.slice(0, N)`, `.substring`) in `src/hub`. Ask in order:
58
+
59
+ 1. A validation bound on an argument of a **documented tool or control message** is a **contract**, whether or not `hub-tools.ts` repeats the number: it is part of that message's shape.
60
+ 2. A bound or truncation on **free text, ids or raw input that no message shape defines** (a peer's body, a model's output, another project's or GitHub's text, a person's input) is a **safety floor**.
61
+ 3. A cap on **the hub's own lists, batches or reports**, which the hub trims, pages or reports as truncated, is **operational**.
62
+ 4. A bound on the **shape of a documented file format the person writes** (config, profile, role, extension, text projection) is a **contract**, like a documented message.
63
+
64
+ Truncations (`.slice`, `.substring`) are decided by what is cut:
65
+
66
+ 5. A **shortened id, hash or commit** (`commit.slice(0, 12)`, `sha256.slice(0, 16)`, a log timestamp used as an evidence id) is a **contract**: a displayed identity format that other tools and people match on.
67
+ 6. The **hub's own list** cut to N (`pending.slice(0, 64)`, `history.slice(-5)`) is **operational**, and so is a **display cap on the hub's own report** (`pool.walk.length > 16`, `paths.length > 20`, a held-rows cap).
68
+ 7. **Text cut for a log line, notice or reply** is a **safety floor** only when the cut text **originates outside the hub process**: a model, a peer, a person, another project, a native hook process, or a subprocess, network, git or file-parse error that can carry external content. The hub's own composed message, or its own internal error text logged locally, is **operational**. Ask where the text comes from, not where it goes.
69
+
70
+ A number is never evidence by itself. Many unrelated schema entries share `32`, `64`, `256` or `4096`, so "the same number appears in `hub-tools.ts`" decides nothing: the first measurement of #739 counted 280 of 378 `src/hub` bounds as mirrors of a schema by number, and only a handful really repeat a tool schema's limit once the call sites are read.
71
+
72
+ ### What the committed file did before this change (measured on `8e44babc`)
73
+
74
+ | Shape | Decided keys | operational | safety floor | contract |
75
+ | --- | ---: | ---: | ---: | ---: |
76
+ | `src/hub` length bounds | 213 | 63 | 91 | 59 |
77
+ | `src/hub` truncations (`.slice`) | 151 | 73 | 47 | 31 |
78
+
79
+ Among the 213, those that refuse or validate input (171) were 77 safety floor, 53 contract and 41 operational, and the file labelled the same kind of check differently by file: `tasks.ts` 19 of 20 contract, `sdlc.ts` 14 of 22 safety floor, `test-integrity.ts` 6 of 6 safety floor. Across the whole decided file 214 expressions appear in more than one place and 28 of them carried conflicting labels (133 keys), mostly default-parameter timeouts and `Math.min`/`Math.max` clamps, which this change does not touch.
80
+
81
+ ### Existing labels changed by this convention (44, by key, old to new)
82
+
83
+ All 173 decided `src/hub` validation bounds (outside `hub-tools.ts`) were read at their call sites. 44 contradicted the convention and are changed; every other decided bound already agrees with it and was left alone, including `deferred-hub.ts` `env.body.length > 1_000_000` and `delivery-hints.ts` `row.id.length > 256` (a peer's raw body and a hub-built hint, no message shape: safety floor). Group membership is a reading of each call site, reviewed by the conductor.
84
+
85
+ #### A. Argument of a documented tool or control message: to contract (19)
86
+
87
+ Reason: Each is an argument of a documented tool or control message (`taskOpBody`, `uiAction`, `recoveryOp`, `onMessage`, the check runner's `files`, the planning `deps` list). The bound is part of that message's shape, whether or not `hub-tools.ts` repeats the number.
88
+
89
+ | Key | Old | New |
90
+ | --- | --- | --- |
91
+ | `check-runner.ts` `request.files.length > 32=32` (in `start`) | operational | contract |
92
+ | `daemon.ts` `a.files.length > 32=32` (in `taskOpBody`) | safety floor | contract |
93
+ | `daemon.ts` `a.delivery_id.length > 256=256` (in `taskOpBody`) | safety floor | contract |
94
+ | `daemon.ts` `a.delivery_id.length > 256=256` (in `taskOpBody`) | safety floor | contract |
95
+ | `daemon.ts` `a.reason.length > 2000=2000` (in `taskOpBody`) | safety floor | contract |
96
+ | `daemon.ts` `a.superseded_by.length > 2000=2000` (in `taskOpBody`) | safety floor | contract |
97
+ | `daemon.ts` `a.release.length > 100=100` (in `taskOpBody`) | safety floor | contract |
98
+ | `daemon.ts` `liveIssues.length > 32=32` (in `uiAction`) | operational | contract |
99
+ | `daemon.ts` `a.to.length > 32=32` (in `uiAction`) | operational | contract |
100
+ | `daemon.ts` `msg.operationId.length > 128=128` (in `recoveryOp`) | operational | contract |
101
+ | `daemon.ts` `msg.launchId.length <= 128=128` (in `onMessage`) | operational | contract |
102
+ | `daemon.ts` `msg.launchId.length <= 128=128` (in `onMessage`) | operational | contract |
103
+ | `daemon.ts` `msg.launchId.length <= 128=128` (in `onMessage`) | operational | contract |
104
+ | `daemon.ts` `msg.reason.length > 2000=2000` (in `onMessage`) | operational | contract |
105
+ | `daemon.ts` `msg.id.length > 256=256` (in `onMessage`) | operational | contract |
106
+ | `daemon.ts` `msg.reason.length > 2000=2000` (in `onMessage`) | operational | contract |
107
+ | `daemon.ts` `(msg.liveIssues ?? []).length > 32=32` (in `onMessage`) | operational | contract |
108
+ | `daemon.ts` `(msg.liveIssues ?? []).length > 32=32` (in `onMessage`) | operational | contract |
109
+ | `planning.ts` `value.length > 64=64` (in `planningPatch`) | operational | contract |
110
+
111
+ #### B. Text or replies from GitHub, another project or a person: to safety floor (6)
112
+
113
+ Reason: Each bounds text or a reply that comes from GitHub, another project or a person, which no tool or control message defines, so it limits untrusted input.
114
+
115
+ | Key | Old | New |
116
+ | --- | --- | --- |
117
+ | `dogfood-read.ts` `r.pull_requests.length <= 100=100` (in `validRun`) | operational | safety floor |
118
+ | `github-import.ts` `row.title.length>300=300` (in `<anonymous>`) | operational | safety floor |
119
+ | `github-import.ts` `row.body.length>8000=8000` (in `<anonymous>`) | operational | safety floor |
120
+ | `sdlc-queue.ts` `p.baseRefName.length > 200=200` (in `<anonymous>`) | operational | safety floor |
121
+ | `sdlc-queue.ts` `threads.nodes.length > 100=100` (in `finalSnapshot`) | operational | safety floor |
122
+ | `sdlc-queue.ts` `checks.nodes.length > 100=100` (in `finalSnapshot`) | operational | safety floor |
123
+
124
+ #### C. The hub's own derived list: to operational (1)
125
+
126
+ Reason: A cap on the hub's own derived list, which the hub builds itself and reports as truncated, so it tunes a list size and is not an input bound.
127
+
128
+ | Key | Old | New |
129
+ | --- | --- | --- |
130
+ | `task-code-symbols.ts` `brief.reviewable.length > 500=500` (in `buildTaskCodeSymbols`) | safety floor | operational |
131
+
132
+ #### D. Shape of a documented file the person writes: to contract (18)
133
+
134
+ Reason: Each is the bound on the shape of a documented file format the person writes (config, profile, role, extension or text projection), which is a contract like a documented message (the conductor's wording of the convention).
135
+
136
+ | Key | Old | New |
137
+ | --- | --- | --- |
138
+ | `github-import.ts` `value.length<=100=100` (in `selectionText`) | operational | contract |
139
+ | `path-policy.ts` `raw.length > 64=64` (in `policyPaths`) | safety floor | contract |
140
+ | `config-tier.ts` `part.length > 64=64` (in `<anonymous>`) | safety floor | contract |
141
+ | `text-projection.ts` `Object.keys(raw).length > 16=16` (in `projectionConfig`) | safety floor | contract |
142
+ | `text-projection.ts` `argv.length > 16=16` (in `projectionConfig`) | safety floor | contract |
143
+ | `text-projection.ts` `arg.length > 2000=2000` (in `<anonymous>`) | safety floor | contract |
144
+ | `profiles.ts` `Object.keys(data.classes).length > 32=32` (in `resolveProfile`) | safety floor | contract |
145
+ | `profiles.ts` `Object.keys(data.role_text).length > 8=8` (in `resolveProfile`) | operational | contract |
146
+ | `profiles.ts` `data.example_checks.length > 16=16` (in `resolveProfile`) | operational | contract |
147
+ | `profiles.ts` `data.review_checklist.length > 16=16` (in `resolveProfile`) | safety floor | contract |
148
+ | `profiles.ts` `value.length > 500=500` (in `<anonymous>`) | operational | contract |
149
+ | `planning.ts` `Object.keys(input).length > 64=64` (in `planningWipLimits`) | operational | contract |
150
+ | `roles.ts` `sanitize(body).length > 2000=2000` (in `parseRole`) | safety floor | contract |
151
+ | `roles.ts` `files.length > 64=64` (in `loadRoleCatalog`) | safety floor | contract |
152
+ | `roles.ts` `Object.keys(raw).length > 64=64` (in `catalogAssignments`) | safety floor | contract |
153
+ | `roles.ts` `ids.length > 2=2` (in `catalogAssignments`) | safety floor | contract |
154
+ | `roles.ts` `roles.length > 2=2` (in `catalogSection`) | safety floor | contract |
155
+ | `roles.ts` `text.length > 2000=2000` (in `taskRoleText`) | safety floor | contract |
156
+
157
+
158
+ ### Additional relabels (47, by key, old to new)
159
+
160
+ A second round, after the first sample showed the convention did not decide truncations. Each change was asserted against its old label when applied; nothing else changed. The 17 truncations that fit no line (for example `action-class.ts` `text.slice(i, i + 256)`, `permission-payload.ts` `full.slice` 600, `envelope.ts` `noteLine`) keep their labels.
161
+
162
+ #### Shortened id, hash or commit: to contract (6)
163
+
164
+ | Key | Old | New |
165
+ | --- | --- | --- |
166
+ | `ask.ts` `l.slice=10` (in `id`) | operational | contract |
167
+ | `daemon.ts` `publicStatus.generation.slice=8` (in `generation`) | operational | contract |
168
+ | `deliverables.ts` `createHash(sha256).update(${format}\0${path}).digest(hex).slice=12` (in `deliverableId`) | operational | contract |
169
+ | `registry.ts` `createHash(sha256).update(root).digest(hex).slice=24` (in `idFor`) | operational | contract |
170
+ | `research.ts` `createHash(sha256).update(projectId).digest(hex).slice=16` (in `projectKey`) | operational | contract |
171
+ | `task-code-group.ts` `r.head.slice=8` (in `head`) | operational | contract |
172
+
173
+ #### The hub's own list cut to N: to operational (10)
174
+
175
+ | Key | Old | New |
176
+ | --- | --- | --- |
177
+ | `activity.ts` `u.locations.slice=12` (in `updateActivity`) | safety floor | operational |
178
+ | `tasks.ts` `(Array.isArray(v) ? v : typeof v === string ? [v] : []).map(text).filter((p): p is string => !!p).slice=50` (in `textList`) | safety floor | operational |
179
+ | `tasks.ts` `holders.slice=8` (in `detail`) | safety floor | operational |
180
+ | `tasks.ts` `history.slice=-5` (in `publicHistory`) | safety floor | operational |
181
+ | `tasks.ts` `[...this.incomingBacklog].sort((a, b) => a - b).slice=64` (in `incoming`) | contract | operational |
182
+ | `tasks.ts` `[...pending].sort((a, b) => a - b).slice=64` (in `ids`) | contract | operational |
183
+ | `tasks.ts` `pool.walk.slice=16` (in `walk`) | safety floor | operational |
184
+ | `tasks.ts` `paths.slice=20` (in `list`) | safety floor | operational |
185
+ | `tasks.ts` `[...new Set([taskNode, ...symbols.nodes, ...about])].slice=64` (in `saveNative`) | safety floor | operational |
186
+ | `text-projection.ts` `(process.env.PATH ?? /usr/bin:/bin).split(:).slice=32` (in `choices`) | safety floor | operational |
187
+
188
+ #### Text cut for a log line, notice or reply: to safety floor (origin test noted per entry) (21)
189
+
190
+ | Key | Old | New | Origin of the cut text |
191
+ | --- | --- | --- | --- |
192
+ | `ask.ts` `raw.slice=2000` (in `answer`) | operational | safety floor | model output |
193
+ | `conflicts.ts` `String(r.title).slice=100` (in `<anonymous>`) | operational | safety floor | another agent's task title |
194
+ | `daemon.ts` `sanitize(row.key).slice=160` (in `retainedConfigAfterReload`) | operational | safety floor | a rejected config key the person wrote |
195
+ | `daemon.ts` `sanitize(row.reason).slice=1200` (in `retainedConfigAfterReload`) | operational | safety floor | a rejected config reason built from the person's config value |
196
+ | `daemon.ts` `${v.title ? ${v.title}: : }${v.text}.replace(/\s+/g, ).slice=160` (in `memoryLine`) | operational | safety floor | memory text written by peers and the model |
197
+ | `daemon.ts` `${v.title ?? }\n${v.text}.slice=1500` (in `note`) | operational | safety floor | memory note text written by peers and the model |
198
+ | `daemon.ts` `${c.title ?? }\n${c.text}.slice=600` (in `note`) | operational | safety floor | memory note text written by peers and the model |
199
+ | `daemon.ts` `JSON.stringify(v).slice=60` (in `peerArg`) | operational | safety floor | the caller's tool argument echoed in a refusal |
200
+ | `daemon.ts` `sanitize(e.reason ?? delivery retries exhausted).replace(/\s+/g, ).slice=300` (in `reason`) | operational | safety floor | a delivery retry reason, which can carry a native adapter's error text |
201
+ | `daemon.ts` `e.env.body.slice=200` (in `<anonymous>`) | operational | safety floor | a peer's message body logged |
202
+ | `daemon.ts` `v.text.slice=600` (in `text`) | operational | safety floor | memory text written by peers and the model |
203
+ | `daemon.ts` `msg.tool.slice=64` (in `tool`) | operational | safety floor | a native hook process's tool name |
204
+ | `daemon.ts` `msg.toolUseId.slice=128` (in `toolUseId`) | operational | safety floor | a native hook process's tool use id |
205
+ | `daemon.ts` `msg.sessionId.slice=128` (in `sessionId`) | operational | safety floor | a native hook process's session id |
206
+ | `daemon.ts` `String(msg.question).slice=80` (in `title`) | operational | safety floor | a person's question to ahub ask |
207
+ | `daemon.ts` `String(msg.question).slice=300` (in `text`) | operational | safety floor | a person's question to ahub ask |
208
+ | `deliverables.ts` `extra[0]!.slice=32` (in `own`) | operational | safety floor | an unknown field name from the caller's deliverable |
209
+ | `packs.ts` `(error as Error).message.slice=200` (in `reason`) | operational | safety floor | a project role-pack file's parse error, which echoes the file's content |
210
+ | `progress.ts` `tool.slice=80` (in `name`) | operational | safety floor | a native hook process's tool name |
211
+ | `progress.ts` `item.name.slice=80` (in `name`) | operational | safety floor | a progress item sent by a peer |
212
+ | `progress.ts` `item.source.slice=40` (in `source`) | operational | safety floor | a progress item sent by a peer |
213
+
214
+ #### Display caps on the hub's own report: to operational (5)
215
+
216
+ | Key | Old | New |
217
+ | --- | --- | --- |
218
+ | `tasks.ts` `pool.walk.length > 16=16` (in `walk`) | safety floor | operational |
219
+ | `tasks.ts` `paths.length > 20=20` (in `list`) | safety floor | operational |
220
+ | `tasks.ts` `result.length < 32=32` (in `outsideIds`) | safety floor | operational |
221
+ | `daemon.ts` `guarded.length > 200=200` (in `prepareConfiguration`) | safety floor | operational |
222
+ | `delivery-hints.ts` `result.rows.length >= 32=32` (in `heldDeliveryHints`) | safety floor | operational |
223
+
224
+ #### Bounds on the shape of the person's config (`config-reload.ts`): to contract (5)
225
+
226
+ | Key | Old | New |
227
+ | --- | --- | --- |
228
+ | `config-reload.ts` `Object.keys(v).length > 256=256` (in `table`) | operational | contract |
229
+ | `config-reload.ts` `v.length > 4096=4096` (in `text`) | operational | contract |
230
+ | `config-reload.ts` `v.length > 128=128` (in `peers`) | safety floor | contract |
231
+ | `config-reload.ts` `v.length > 16_384=16_384` (in `openValue`) | operational | contract |
232
+ | `config-reload.ts` `v.length > 1024=1024` (in `openValue`) | safety floor | contract |
233
+
234
+ #### Three omissions found by the second sample: to contract (3)
235
+
236
+ The first list of argument bounds missed three that the convention (line 1) already covers; found when the blind reader labelled them contract.
237
+
238
+ | Key | Old | New | Why |
239
+ | --- | --- | --- | --- |
240
+ | `workflow.ts` `p.criteria.length > 32=32` (in `criteriaReceipt`) | operational | contract | the `criteria` argument of `hub_sdlc_record` (schema `maxItems: 32`) |
241
+ | `workflow.ts` `c.refs.length > 8=8` (in `<anonymous>`) | operational | contract | the `refs` of one criterion of `hub_sdlc_record` (schema `maxItems: 8`) |
242
+ | `claude-launch-proof.ts` `row.launchId.length > 128=128` (in `readClaudeLaunchProof`) | operational | contract | the `launchId` control field, the same bound that `daemon.ts` `onMessage` already labels contract |
243
+
244
+ ### Calibration of the `src/hub` convention (#739)
245
+
246
+ The population is the 365 decided `src/hub` bounds and truncations (`hub-tools.ts` excluded). Each round is a blind 80-sample read with only the convention, the committed labels hidden.
247
+
248
+ | Round | Seed | File state | Agreement | Bounds | Truncations |
249
+ | --- | --- | --- | ---: | ---: | ---: |
250
+ | 1 | 739101 | after the first 44 relabels, lines 1 to 4 only | 56 of 80 (70.0%) | 36 of 44 (82%) | 20 of 36 (56%) |
251
+ | 2 | 739202 | after 47 more relabels, lines 1 to 7 | 71 of 80 (88.8%) | 42 of 47 (89%) | 29 of 33 (88%) |
252
+
253
+ Round 2 clears the 85% bar. It is **in-sample in one respect**: lines 5 to 7 and the 47 relabels were written after round 1's misses, so the sample measures whether the convention now decides what it was extended to decide, not an independent reader. A fresh seed drawn from the relabelled file is the check that matters, and it was drawn only after the relabels were applied.
254
+
255
+ Round 2's nine misses: three are keys the first list left out (`workflow.ts` criteria 32, criterion refs 8 and `claude-launch-proof.ts` launchId 128, relabelled contract above); `check-runner.ts` `lines.slice(-40)` (a cut of a subprocess's output, which line 7 calls safety floor, labelled by me operational) and `version.stdout.trim().slice(0, 80)` (the converse); `daemon.ts` `schedules pass failed: ...message.slice(0, 160)` and `tasks.ts` `task.title.slice(0, 120)` (a memory title from the task's own title, where whether the origin is outside the hub is a judgement); `execution-budget.ts` `input.id.length > 200` and `delivery-hints.ts` `held.rows.length > 32`. The remaining disagreements are about where text originates, and line 7 is the place to sharpen if a later sample falls short.
256
+
257
+ No baseline key is labelled in this change. That is #741.
@@ -0,0 +1,55 @@
1
+ # Hard-coded values inventory: why it drifted, and the ratchet that stops it (#1180, task #719)
2
+
3
+ Date: 2026-11-05. Refs #1180. The issue stays open: its AC1 (zero unknown keys) is not met by this change. This record exists so the follow-up that classifies the rest starts from measurements, not from a guess.
4
+
5
+ ## Cause
6
+
7
+ Not a generator bug. `scripts/inventory-hardcoded-values.ts` and `scripts/inventory-hardcoded-values.classifications.json` were committed together by #764 on 2026-10-24, and nothing ever ran the generator again: no step of `scripts/check.sh`, no workflow and no test. The file was edited by hand twice (#1099 changed one value, #1167 removed 11 keys), and every other change to `src/` and `scripts/` added constants nobody classified.
8
+
9
+ Measured by running the generator at past commits:
10
+
11
+ | Commit | Date | Unknown keys |
12
+ | --- | --- | ---: |
13
+ | `7c1d2065` (#764, the commit that added the inventory) | 2026-10-24 | 22 |
14
+ | `b454e527` | 2026-10-25 | 63 |
15
+ | `438e9b23` | 2026-10-28 | 527 |
16
+ | `9680a4db` | 2026-11-02 | 1065 |
17
+ | `8511d01a` (#1099) | 2026-11-02 | 1087 |
18
+ | main at `4b34fab1` | 2026-11-05 | 1438 |
19
+
20
+ The 22 at #764's own head are the memory-code files that landed in the same week. The 1282 in the issue and the 1438 here are the same drift counted at two commits. At this commit 3243 inventory entries exist, 1805 were decided, 1438 were unknown and 36 committed keys matched nothing (the code they named had moved or changed).
21
+
22
+ ## What this change does
23
+
24
+ 1. Removes the 36 stale keys.
25
+ 2. Classifies 216 unknown keys by precedent in the file itself, and nothing else. A key is adopted only when every already-decided key sharing its parts agrees, taking the tiers in this order and stopping at the first that decides:
26
+ - same file, kind, name and value (82 keys): leave-one-out accuracy over the committed decisions 99.4%;
27
+ - same file, kind and name (44 keys): 97.6%;
28
+ - same kind, name and value, with at least two agreeing precedents (72 keys): 95.7%;
29
+ - same kind and name, with at least two agreeing precedents (18 keys): 94.1%.
30
+ The same two weaker tiers with a single precedent measured 92.6% and 89.2%, which is why they need two. The 216 are 109 contract, 81 operational, 23 safety floor and 3 test seam.
31
+ 3. Puts the other 1222 unknown keys in `scripts/inventory-hardcoded-values.baseline.json`, the list of known-unclassified keys. They are not classified. `bun scripts/inventory-hardcoded-values.ts --check` fails on any unknown key that is not in that list, on a baseline key that has since been classified, and on a baseline key that matches no entry. The baseline only shrinks. `scripts/check.sh` runs the check with the other once-only steps.
32
+
33
+ ## What this change does not do, and why
34
+
35
+ It classifies none of the 1222 remaining keys. The attempt was measured first. 190 of them were labelled by reading each literal's source line against the K01-K18 reasons of `2026-10-30-operational-settings-closure.md`, then 80 already-decided entries (seeded random draw, labels hidden) were labelled the same way and compared with the committed file: **47 of 80 agreed (58%)**. The disagreements were systematic, not noise:
36
+
37
+ | Committed class | I labelled | Count | Typical case |
38
+ | --- | --- | ---: | --- |
39
+ | operational | safety floor | 12 | a hub-internal cap constant (`MAX_STATES = 256`, `tasks.length > 500`, a 1024-entry map) |
40
+ | operational | contract | 7 | a saturating `Math.max(0, ...)`, a `limit < 1` check |
41
+ | operational | test seam | 6 | a smoke script's `deadline`, an injected `opts.x ?? N` default |
42
+ | contract | safety floor | 5 | a short id or text slice bound |
43
+ | safety floor | operational | 2 | a `termMs`/`killMs` in a review-isolation stop |
44
+
45
+ So the file's operative rule for `operational` is wider than the prose of the K groups: a hub-internal cap, an injected default and a script deadline all count as operational, and the prose does not say so. A rule pass built from the K-group text was 65% accurate on the 1805 decided entries and a text-feature classifier 71%, so neither decides anything. Writing 1222 labels at 58% would be the blind classification the issue forbids.
46
+
47
+ ## How the remaining 1222 get classified
48
+
49
+ As a separate task, in batches a person can review, under these rules:
50
+
51
+ - Before the first batch, write the rule for each confusable case into the header of the classifications file or this record: hub-internal caps, injected `opts.x ?? N` defaults, script deadlines, short id and hash slices, saturating zero bounds.
52
+ - Each batch is labelled by those written rules, never by feel.
53
+ - Each batch needs a fresh blind sample of 80 already-decided entries labelled by the same rules, and an agreement of 85% or more with the committed file, before the batch is written.
54
+ - Classifying a key means adding it to the classifications file and removing it from the baseline in the same commit; `--check` refuses the half-done state.
55
+ - Do not regenerate `docs/verification/2026-10-24-hardcoded-values.md`: `test/operational-settings.test.ts` reads it by row.
@@ -0,0 +1,152 @@
1
+ # Rooms presence producer cost
2
+
3
+ Refs #1204, CI task 742. Historical macOS comparisons and Linux candidate
4
+ measurements and targeted hosted macOS candidate acceptance are recorded below.
5
+
6
+ ## Failed jobs and revision attribution
7
+
8
+ - [PR 1217 macOS job](https://github.com/STAIxBWLB/agent-hub/actions/runs/42098376188/job/127501666095)
9
+ checked out `92486ab5c48b4c0281643750590fa6693aaaa7e6` and timed out the
10
+ unchanged 1,000-flip test at 20,025.68 ms.
11
+ - [PR 1215 macOS job](https://github.com/STAIxBWLB/agent-hub/actions/runs/42074839676/job/127420743582)
12
+ checked out `dc9e482c659a29fa3f6b4597bc04219f5b5f3fbd` and timed out at
13
+ 20,034.80 ms.
14
+ - The workflow checks out the PR head. Neither failed head contains T2 commit
15
+ `3185dd4c57e36213a8ca398dd304f3284daa03ed`. Their rooms files, Bus,
16
+ delivery journal and delivery test are byte-identical to
17
+ `b7aec7d5621e25e0ea1df1e5b8b7366b92512bfe` in those paths.
18
+ - The requested b7..3185 range has only two intervening commits: handoff T2
19
+ `b5c2525495b42a986207308af815e530f78c0c61`, then rooms T2. The old
20
+ producer path reproduces at b7 itself, so the observed failures do not establish
21
+ T2 as a first bad commit.
22
+
23
+ ## Measured producer paths
24
+
25
+ The repo-only preload `scripts/diagnose-presence-cost-742.ts` counts fixed
26
+ method names and inclusive wall time, never argument text or ids. The original
27
+ test and its 20-second guard are unchanged.
28
+
29
+ The natural run is separate from a controlled scheduling probe. In the probe,
30
+ the first hot publication waits for the existing serial seal before calling the
31
+ original producer. No state flag is overwritten. This exercises an idle producer
32
+ instead of relying on a boot-time publication still being in flight.
33
+
34
+ Pinned Bun 1.4.2, Linux x64, frozen installs on dgx02:
35
+
36
+ | Runtime and mode | Flip phase | Sequence reservations in phase | Store transactions in phase | Bus persists, whole case |
37
+ | --- | ---: | ---: | ---: | ---: |
38
+ | Pre-fix natural, `0a32d86a` | 2 ms | 0 | 0 | 4 |
39
+ | b7, controlled idle entry | 10,645 ms | 1,000 | 3,173 | 4 |
40
+ | b5c, controlled idle entry | 11,324 ms | 1,000 | 3,161 | 4 |
41
+ | Pre-fix `ab751794`, controlled idle entry | 11,868 ms | 1,000 | 4,145 | 4 |
42
+ | Fixed FIFO candidate `2452b905`, controlled idle entry | 109 ms | 7 | 33 | 4 |
43
+
44
+ The pre-fix ab751 phase spent 2,401 ms inside sequence reservation and 5,205 ms
45
+ inside Store transactions. These measurements overlap and must not be added.
46
+ Transaction timing includes SQL and durable I/O; this is not a separate fsync
47
+ syscall trace. The Store uses DELETE journaling and synchronous EXTRA. The data
48
+ does not attribute all remaining time to either crypto or the filesystem.
49
+
50
+ b7 and b5c were separate historical checkouts with the same diagnostic preload.
51
+ 0a32/ab751 include only close guards and the diagnostic on top of 3185; their room
52
+ producer was still unchanged. The b7/b5c comparison and named-red checks ran from
53
+ the diagnostic harness at `11e3121f51eff2a9cd60eb95400e568051163072`.
54
+
55
+ The code defect is cancellation of scheduled coalesced work: ordinary calls
56
+ clear the pending republish before starting another seal. If the producer is
57
+ already busy, calls collapse; if it starts idle, an awaited loop can repeatedly
58
+ reserve durable counters. Task 740's persist fix remains effective: all measured
59
+ cases retain four Bus persists.
60
+
61
+ ## Fix and regressions
62
+
63
+ - Ordinary calls reuse pending republish; response/forced-request priority stays.
64
+ - Chat observations precede the chat, and the chat slot is reserved synchronously
65
+ before awaiting the earlier presence. This avoids overtaking by a concurrent
66
+ chat. Tests check ciphertext and real Store insertion order.
67
+ - Receipt durability, ACK ordering, caps and actual-send exposure/current fences
68
+ remain covered by unchanged backpressure tests and new focused regressions.
69
+ - Closed public and deferred publication return before touching the journal or
70
+ observers. Live storage failures still propagate.
71
+ - A failed presence barrier remains an error of its reserved chat, rather than
72
+ a successful empty send or an unhandled rejection.
73
+
74
+ ## Hosted macOS reference comparison
75
+
76
+ The [reference job](https://github.com/STAIxBWLB/agent-hub/actions/runs/42106515415/job/127522644864)
77
+ ran on Darwin arm64 with Bun 1.4.2. Each result below is the unchanged test with
78
+ the same diagnostic preload; controlled scheduling is labelled separately.
79
+
80
+ | Runtime and mode | Whole test | Flip phase | Reservations | Store transaction time in phase |
81
+ | --- | ---: | ---: | ---: | ---: |
82
+ | b7, natural | 17,806.52 ms | 17,110 ms | 1,000 | 1,194 ms |
83
+ | b7, controlled idle entry | 18,798.21 ms | 16,656 ms | 1,000 | 1,438 ms |
84
+ | b5c, natural | 698.92 ms | 4 ms | 0 | 0 ms |
85
+ | b5c, controlled idle entry | 17,084.92 ms | 16,440 ms | 1,000 | 1,065 ms |
86
+ | 3185, natural | 710.99 ms | 2 ms | 0 | 0 ms |
87
+ | 3185, controlled idle entry | 17,604.82 ms | 16,902 ms | 1,000 | 1,139 ms |
88
+
89
+ The slow producer therefore costs about 16.4-17.1 ms per flip on this runner.
90
+ For 3185, sequence reservation accounts for 511 ms inside the overlapping
91
+ 1,139 ms Store transaction time; durable I/O is not the dominant measured time.
92
+ The remaining seal/async producer work was not split into separate CPU/syscall
93
+ traces. All cases retain four Bus persists, confirming the task-740 fix remains.
94
+
95
+ The range's lower bound is not unconditionally fast: b7 naturally selects the
96
+ slow path in this job. A monotonic good/bad bisect cannot assign the defect to a
97
+ newer commit; exhaustive comparison of the two intervening commits shows the
98
+ pre-existing scheduling path. This is not a claim that the failed jobs merely
99
+ needed their timeout increased.
100
+
101
+ The job completed all six cases above, then failed because abbreviated reference
102
+ 924 was absent from that checkout. That final workflow error does not invalidate
103
+ the completed cases, but supplies no 924 measurement. The follow-up explicitly
104
+ fetches its full SHA and checks the candidate separately.
105
+
106
+ Controlled red evidence uses unchanged historical source with only the new
107
+ regression file copied into an isolated checkout:
108
+
109
+ - At 3185, the count test fails its named assertion: expected one reserve,
110
+ received 1,001.
111
+ - At pre-FIFO-fix 7b99, the concurrent-chat test fails its named assertion with
112
+ B before A.
113
+ - The existing seed classifier accepted both as assertion failures, not timeouts
114
+ or runtime diagnostics.
115
+
116
+ ## Hosted macOS candidate acceptance
117
+
118
+ The [candidate job](https://github.com/STAIxBWLB/agent-hub/actions/runs/42110940560/job/127537997562)
119
+ passed on Darwin arm64, Bun 1.4.2 at
120
+ `e269d5c3be4a81fbd95220d760fb1f104fae3e3f`:
121
+
122
+ - The unchanged, unprofiled AC1 test passed in 787.72 ms under its original
123
+ 20-second guard.
124
+ - All 19 focused close, coalescing and unchanged backpressure tests passed,
125
+ with 210 expectations and no failures.
126
+ - Natural candidate profile: whole test 692.06 ms, flip phase 2 ms, no sequence
127
+ reservations or Store transactions in that phase.
128
+ - Controlled idle-entry candidate profile: whole test 783.51 ms, flip phase
129
+ 114 ms, six reservations and 26 Store transactions in that phase.
130
+ - The explicitly fetched historical 924 head passed naturally in 669.40 ms,
131
+ but controlled idle entry took 18,277.53 ms (17,679 ms in the flip phase,
132
+ 1,000 reservations, 2,883 Store transactions). Its transaction time was
133
+ 2,082 ms, overlapping the 1,078 ms sequence-reservation time.
134
+
135
+ This confirms the scheduling-dependent cause and candidate improvement on
136
+ macOS without changing an assertion or timeout. The job is targeted evidence,
137
+ not a full PR gate. Production source is unchanged since 11e312; subsequent
138
+ changes add evidence and remove the temporary diagnostic workflows.
139
+
140
+ ## Evidence limits
141
+
142
+ - Latest focused source check at 11e312 passed 12 tests, 0 failed, 94 expectations.
143
+ Earlier 2452 passed 18 focused/backpressure tests and the controlled AC1 case.
144
+ - The first reference run measured b7, b5c and 3185; the successful candidate
145
+ job additionally measured the full 924 SHA. Neither temporary workflow is
146
+ retained in the final change.
147
+ - The original failed macOS jobs did not record per-flip counters. Attribution
148
+ of those exact executions to this reproduced path remains an inference;
149
+ hosted reference measurements establish the slow path; the separate candidate
150
+ job supplies targeted macOS acceptance. Linux timing alone is not macOS acceptance.
151
+ - No original test assertion or timeout was changed. No Mac-local gate, live
152
+ room enrollment, native-account acceptance or release is claimed.
@@ -68,7 +68,7 @@ results=[]
68
68
  for raw_binary in args.claude:
69
69
  binary=pathlib.Path(raw_binary).resolve(); identity=ROOT/('identity-'+binary.name); identity.mkdir()
70
70
  version=subprocess.run([str(binary),'--version'],env={'HOME':str(identity),'CLAUDE_CONFIG_DIR':str(identity/'config'),'PATH':'/usr/bin:/bin'},capture_output=True,text=True,timeout=10)
71
- assert version.returncode==0 and version.stdout.strip().split()[0] in ('2.1.309','2.1.310','2.1.314','2.1.316'),(version.returncode,version.stdout)
71
+ assert version.returncode==0 and version.stdout.strip().split()[0] in ('2.1.309','2.1.310','2.1.314','2.1.316','2.1.318'),(version.returncode,version.stdout)
72
72
  label=version.stdout.strip().split()[0]; digest=sha(binary)
73
73
  for mode in ('context','normalized'):
74
74
  d=ROOT/(label+'-'+mode); d.mkdir(); home=d/'home';home.mkdir(); work=d/"work 'quoted'";work.mkdir();(work/'sub').mkdir()
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "documents": {
4
4
  "README.md": {
5
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
5
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
6
6
  "paths": [
7
7
  "package.json",
8
8
  "src/",
@@ -11,14 +11,14 @@
11
11
  ]
12
12
  },
13
13
  "docs/security.md": {
14
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
14
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
15
15
  "paths": [
16
16
  "src/",
17
17
  "templates/"
18
18
  ]
19
19
  },
20
20
  "docs/operations.md": {
21
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
21
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
22
22
  "paths": [
23
23
  "package.json",
24
24
  "src/",
@@ -35,7 +35,7 @@
35
35
  ]
36
36
  },
37
37
  "docs/quickstart.md": {
38
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
38
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
39
39
  "paths": [
40
40
  "package.json",
41
41
  "src/",
@@ -43,7 +43,7 @@
43
43
  ]
44
44
  },
45
45
  "docs/agent-notes/adapters.md": {
46
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
46
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
47
47
  "paths": [
48
48
  "src/adapters/",
49
49
  "src/pi/",
@@ -81,7 +81,7 @@
81
81
  ]
82
82
  },
83
83
  "docs/agent-notes/benchmarks.md": {
84
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
84
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
85
85
  "paths": [
86
86
  "scripts/benchmarks/",
87
87
  "test/benchmarks/",
@@ -93,7 +93,7 @@
93
93
  ]
94
94
  },
95
95
  "docs/agent-notes/budget.md": {
96
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
96
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
97
97
  "paths": [
98
98
  "src/hub/budget.ts",
99
99
  "src/cli/statusline-tee.ts",
@@ -117,7 +117,7 @@
117
117
  ]
118
118
  },
119
119
  "docs/agent-notes/bus.md": {
120
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
120
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
121
121
  "paths": [
122
122
  "src/hub/bus.ts",
123
123
  "src/hub/envelope.ts",
@@ -132,7 +132,7 @@
132
132
  ]
133
133
  },
134
134
  "docs/agent-notes/daemon.md": {
135
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
135
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
136
136
  "paths": [
137
137
  "src/hub/daemon.ts",
138
138
  "src/hub/control-client.ts",
@@ -223,7 +223,7 @@
223
223
  ]
224
224
  },
225
225
  "docs/agent-notes/local-worker.md": {
226
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
226
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
227
227
  "paths": [
228
228
  "src/adapters/local-worker.ts",
229
229
  "src/local/",
@@ -243,7 +243,7 @@
243
243
  ]
244
244
  },
245
245
  "docs/agent-notes/tasks.md": {
246
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
246
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
247
247
  "paths": [
248
248
  "src/hub/tasks.ts",
249
249
  "src/hub/board.ts",
@@ -291,7 +291,7 @@
291
291
  ]
292
292
  },
293
293
  "docs/agent-notes/tests.md": {
294
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
294
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
295
295
  "paths": [
296
296
  "test/",
297
297
  "scripts/check.sh",
@@ -320,7 +320,7 @@
320
320
  ]
321
321
  },
322
322
  "docs/agent-notes/README.md": {
323
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
323
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
324
324
  "paths": [
325
325
  "src/cli/console-state.ts",
326
326
  "src/cli/attention.ts",
@@ -329,7 +329,7 @@
329
329
  ]
330
330
  },
331
331
  "src/cli/help.ts": {
332
- "verifiedAgainst": "8612cfd740abd013689532d993ebc981548510bb",
332
+ "verifiedAgainst": "af37f8fd8b44a2c566aa47629ec5b02ee9f91d69",
333
333
  "paths": [
334
334
  "src/cli/main.ts",
335
335
  "src/cli/launch.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,18 +17,17 @@
17
17
  "bun": ">=1.4.2"
18
18
  },
19
19
  "dependencies": {
20
- "@staix/agent-mem": "0.20.1"
20
+ "@staix/agent-mem": "0.21.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@modelcontextprotocol/sdk": "^1.27.1",
24
+ "@staix/agent-console": "0.1.3",
24
25
  "@types/bun": "^1.3.0",
25
26
  "typescript": "^5.8.0"
26
27
  },
27
28
  "files": [
28
29
  ".claude-plugin",
29
30
  "src",
30
- "src/pet/window.jxa.js",
31
- "src/pet/window.html",
32
31
  "plugins",
33
32
  "templates",
34
33
  "scripts/check-docs.mjs",