@staix/agent-hub 0.7.7 → 0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,7 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the previous repository, archived on 2026-09-30 when this repository's history was rewritten; the one exception is the open smoke-check issue, formerly #12, which moved here as #1. Numbers in newer entries refer to this repository.
4
+
3
5
  ## Unreleased
4
6
 
7
+ ## 0.7.9
8
+
9
+ - A committed `.agenthub/config.json` can no longer choose what the hub runs, which files it sends as credentials, where task text goes, or how far the local worker's sandbox reaches: those machine-local fields (launch commands, checks, the legacy MLX binary and model directory, gateway URLs and key files, the memory endpoint, sandbox widening) apply only from a file git confirms nobody committed, such as the new `.agenthub/config.local.json`, and are otherwise ignored with a log line and a doctor row. A tracked file is recognised by identity, which also closes a way past the 0.7.8 completion-check gate: a committed file under a spelling macOS opens as `config.json` but git does not match (such as `ſ` for `s`) counted as uncommitted (#17).
10
+
11
+ ## 0.7.8
12
+
13
+ - README, the operations guide and the upgrade-recovery spec no longer describe protocol 10 and the queue commands as unreleased (#9).
14
+ - Codex answers a digest to every sender in it, and a sender steered into its turn, like the other adapters; an answer to a condensed digest no longer goes to the reserved `digest` sender and reaches no peer, also when a steer joined the turn (the bus now maps `digest` for any adapter). A TUI that detaches mid-turn and comes back no longer answers the old turn's senders (#3).
15
+ - A waiting approval raises a macOS notification naming the peer and, for Kimi, the tool (never the payload); the approval timeout is configurable as `approvals.timeout_s`, and an unanswered request leaves a console and log line (#5).
16
+ - Claims: a self-claim without a class is filed as `implement` when no model can name one; the earlier owner of an overlapping task hears of it with its next message; an owner offline past `tasks.release_after_min` (default 30) loses its open tasks to a peer that can take them (#6).
17
+ - A class can carry a completion check (`checks.<class>` in a `.agenthub/config.json` git confirms nobody committed; outside a repository none runs): marking such a task done runs it, sends the task to review with the result on success, and keeps it with its owner with the failure otherwise. A hub stop interrupts a check without a verdict (#7).
18
+ - `scripts/overlaps.ts` counts the claim overlap warnings in hub logs per week and names the task pairs, for the two-week measurement that decides whether per-task worktrees are built. The procedure and the numbers go in `docs/smoke.md` (#8).
19
+ - Releases publish to npm through trusted publishing (OIDC) instead of a stored token, and the workflows use the Node 24 releases of `actions/checkout` and `actions/setup-node` (#4).
20
+
5
21
  ## 0.7.7
6
22
 
7
23
  - `ahub setup` without a terminal and without `--yes` prints the step it would take and exits non-zero instead of waiting forever at its prompt, as `restart` and `upgrade` already did. A piped answer (`yes | ahub setup`) is no longer read: use `--yes` (#73).
@@ -91,7 +107,7 @@
91
107
  ## 0.4.1 (included in 0.5.0)
92
108
 
93
109
  - Synchronize the daemon digest integration fixture without changing batching semantics or weakening digest/FYI assertions (#15).
94
- - Reconcile completed live smoke and npm publication evidence; infrastructure-dependent checks remain tracked in #12.
110
+ - Reconcile completed live smoke and npm publication evidence; infrastructure-dependent checks remain tracked in #1 (formerly #12).
95
111
 
96
112
  ## 0.4.0
97
113
 
package/README.md CHANGED
@@ -4,7 +4,7 @@ Native multi-agent hub for one developer's machine: Claude Code, Codex, Kimi Cod
4
4
  hub-owned local-LLM worker collaborate as peers in independent project directories, with
5
5
  task-aware model routing (Switchyard) in front of a self-hosted gateway (OmniRoute).
6
6
 
7
- Status: 0.7.7, control protocol 10. Durable delivery records distinguish queued
7
+ Status: 0.7.9, control protocol 10. Durable delivery records distinguish queued
8
8
  work from uncertain execution. The [smoke checklist](docs/smoke.md) records
9
9
  verified paths and remaining prerequisites.
10
10
 
@@ -28,7 +28,7 @@ cd <your project> && ahub init && ahub up && ahub tail
28
28
  Or install the same version from GitHub:
29
29
 
30
30
  ```bash
31
- bun add -g github:STAIxBWLB/agent-hub#v0.7.7 && ahub setup
31
+ bun add -g github:STAIxBWLB/agent-hub#v0.7.9 && ahub setup
32
32
  ```
33
33
 
34
34
  The installed commands remain `ahub` and `agent-hub`.
@@ -113,8 +113,9 @@ A project has one managed Pi session owner. Mode changes require the agent to
113
113
  settle and preserve the session file. Before the first generation, a live source
114
114
  verified to be empty can instead retain its exact session ID without a file.
115
115
  Missing history from a nonempty session blocks handover. Protocol 9 adds Pi
116
- recovery metadata. Protocol 10 is the 0.7.0 target for durable delivery
117
- receipts and conservative queue recovery; it is pending live verification.
116
+ recovery metadata. Protocol 10, released in 0.7.0, adds durable delivery
117
+ receipts and conservative queue recovery; it is verified live (see the
118
+ [smoke checklist](docs/smoke.md)).
118
119
  Older protocol hubs still require their matching CLI and an attended bootstrap.
119
120
 
120
121
  ## Runtime
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.7.7 and control protocol 10. Live verification
3
+ This guide describes ahub 0.7.9 and control protocol 10. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Install and start
@@ -22,6 +22,19 @@ Run it after an upgrade when those blocks need refreshing. `ahub setup`
22
22
  updates the shared Claude plugin. Keep `ahub tail` open when a local worker
23
23
  may request an approval.
24
24
 
25
+ `.agenthub/config.json` can be committed and shared. The fields that choose
26
+ what the hub runs, which files it sends as credentials, where task text goes,
27
+ or how far the local worker's sandbox reaches (`kimi_cmd`, `codex_bin`,
28
+ `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`,
29
+ `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url`,
30
+ `local.read_allow`, `local.bash_network`) are machine-local: they apply only
31
+ from a file git confirms nobody committed. Put them in
32
+ `.agenthub/config.local.json` (`ahub init` adds it to `.gitignore`), which is
33
+ read after `config.json`; outside a git repository they keep their defaults, and
34
+ an empty value always means the default.
35
+ A committed value is ignored with a line in `hub.log`, a note from `ahub
36
+ codex` and `ahub models`, and a row in `ahub doctor`.
37
+
25
38
  Managed launchers attach native peers to the project daemon:
26
39
 
27
40
  ```bash
@@ -58,11 +71,31 @@ ahub review <task-id> approved "verified against the requested behavior"
58
71
  ahub review <task-id> changes_requested "describe the required correction"
59
72
  ```
60
73
 
74
+ A class can carry a completion check in `.agenthub/config.json`, for example
75
+ `"checks": { "implement": "scripts/check.sh", "timeout_s": 600 }`. When the owner
76
+ marks such a task done, the tool answers at once, the hub runs the command in the
77
+ project root (one at a time; what it left in its process group is killed when it
78
+ exits, at the timeout or on shutdown), and then either
79
+ sends the task to review with the command, exit code and output tail, or keeps it
80
+ with its owner and tells it what failed. `checks` is machine-local (see Install
81
+ and start): a committed file must not choose commands the hub runs. Keys that
82
+ are not task classes are ignored with a log line. A hub stopped mid-check leaves
83
+ the task in progress (`check interrupted`); the owner marks it done again.
84
+
61
85
  A task reaching `approved` is a board transition. It is not proof that an
62
86
  external side effect happened unless the task's refs and a live readback show
63
87
  that effect. The hub can reassign after repeated review changes according to
64
88
  the routing configuration.
65
89
 
90
+ An agent can claim work nobody assigned it by proposing a task with itself as
91
+ owner; without a class, and with no model to name one, the claim is filed as
92
+ `implement`. When a task's paths overlap another owner's open task, the newcomer
93
+ is told to settle it and the earlier owner gets one line with its next message,
94
+ at no turn of its own. An owner offline longer than `tasks.release_after_min`
95
+ (default 30, `0` turns it off) in `.agenthub/config.json` loses its open tasks
96
+ to a peer routing can give them to; with nobody to take them they stay, and a
97
+ paused peer or a hub in a recovery operation is left alone.
98
+
66
99
  Use targeted messages for coordination:
67
100
 
68
101
  ```bash
@@ -86,7 +119,15 @@ ahub permit <request-id> allow
86
119
  ```
87
120
 
88
121
  Use the exact option shown by `ahub tail`; do not approve an unresolved or
89
- unexpected request. `ahub pause <peer>` holds delivery and `ahub resume
122
+ unexpected request. On macOS a waiting request also raises a desktop
123
+ notification that names the peer and, for Kimi, the tool, never what it would
124
+ run. An unanswered request is cancelled after `approvals.timeout_s` (default
125
+ 120, 30 to 3600) in `.agenthub/config.json`, and the console and log say so.
126
+ Set `approvals.notify` to `false` to turn notifications off; a hub started
127
+ without a project config file raises none. While Kimi waits for an answer its
128
+ turn is kept alive, so a timeout longer than the inactivity watchdog does not
129
+ cancel it.
130
+ `ahub pause <peer>` holds delivery and `ahub resume
90
131
  <peer>` releases a manual pause. Budget pauses are distinct:
91
132
 
92
133
  ```bash
@@ -114,11 +155,10 @@ ahub queue show <delivery-id>
114
155
  ahub queue resolve <delivery-id> --action completed|retry|discard --reason "<text>"
115
156
  ```
116
157
 
117
- These queue commands are part of 0.7.0 and are not available in the released
118
- 0.6.4 CLI. Until protocol 10 is installed, use `ahub status`, `ahub tail`,
119
- and the task board. A known offline recipient may be queued explicitly once
120
- the 0.7.0 journal is active; broadcast and automatic assignment still require
121
- an attached peer.
158
+ These queue commands ship with protocol 10 (0.7.0 and later), where a known
159
+ offline recipient may also be queued explicitly; broadcast and automatic
160
+ assignment still require an attached peer. A hub still on protocol 9 (0.6.x)
161
+ has only `ahub status`, `ahub tail` and the task board.
122
162
 
123
163
  Resolution requires the current observed revision. A retry closes the old
124
164
  record and creates one linked attempt. Stale or conflicting resolutions fail.
@@ -186,8 +226,8 @@ source and carries every recovery fix released up to it. Protocol 8 and older
186
226
  project directory, without replacing the global CLI first:
187
227
 
188
228
  ```bash
189
- bunx --package @staix/agent-hub@0.7.7 ahub upgrade --to 0.7.7 --dry-run
190
- bunx --package @staix/agent-hub@0.7.7 ahub upgrade --to 0.7.7 --yes
229
+ bunx --package @staix/agent-hub@0.7.9 ahub upgrade --to 0.7.9 --dry-run
230
+ bunx --package @staix/agent-hub@0.7.9 ahub upgrade --to 0.7.9 --yes
191
231
  ```
192
232
 
193
233
  | Running now | Coordinator to use |
@@ -209,19 +249,19 @@ The coordinator verifies and retains the exact target package, preserves its
209
249
  own source, and promotes the global CLI only after restored projects pass
210
250
  readback.
211
251
 
212
- Once the installed CLI is 0.7.7, review the current project or all registered
252
+ Once the installed CLI is 0.7.9, review the current project or all registered
213
253
  projects first:
214
254
 
215
255
  ```bash
216
256
  ahub restart --dry-run
217
- ahub upgrade --to 0.7.7 --dry-run
257
+ ahub upgrade --to 0.7.9 --dry-run
218
258
  ```
219
259
 
220
260
  Apply only after reviewing the plan:
221
261
 
222
262
  ```bash
223
263
  ahub restart --yes
224
- ahub upgrade --to 0.7.7 --yes
264
+ ahub upgrade --to 0.7.9 --yes
225
265
  ahub recovery status <operation-id>
226
266
  ahub recovery resume <operation-id>
227
267
  ahub recovery abort <operation-id>
@@ -264,7 +304,7 @@ peers and therefore did not show that backlog. No messages were deleted. The
264
304
  0.7.1 attended cutover and production readback verified queue visibility and
265
305
  completion receipts; see the final section of the smoke checklist.
266
306
 
267
- Issue [#12](https://github.com/STAIxBWLB/agent-hub/issues/12) remains open for
307
+ Issue [#1](https://github.com/STAIxBWLB/agent-hub/issues/1) remains open for
268
308
  off-campus Access credentials and a natural near-limit budget pause. Tests,
269
309
  synthetic approvals, and an authenticated internal network do not close those
270
310
  prerequisites. Adapter acceptance is not task completion, and package
@@ -17,7 +17,7 @@ ahub setup # installs the Claude Code channel plu
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.7.7
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.7.9
21
21
  ahub setup
22
22
  ```
23
23
 
package/docs/security.md CHANGED
@@ -6,7 +6,8 @@ agent-hub connects agents that can each run commands. This page says what the hu
6
6
 
7
7
  - **Other agents' text is untrusted.** Every message that crosses from one peer to another is framed as untrusted input (a channel tag with `meta.source` for Claude, a fixed header line plus a standing instruction for the others). A message body cannot forge the hub's own headers: such lines are quoted (`sanitize`). Replies inherit a hop count capped at 3, so agents cannot ping-pong forever; neither a digest nor a steer can reset it.
8
8
  - **The control link is loopback plus a secret.** The daemon and the Codex proxy bind 127.0.0.1 only. The control WebSocket requires a per-run token (`.agenthub/state/control-token`, mode 600), and both servers refuse any request that carries an `Origin` header: any web page can open a WebSocket to localhost, and browsers always send `Origin`. External clients cannot claim the console user's id or a hub-managed peer's id.
9
- - **Permission prompts stay on.** `ahub claude` and `ahub codex` add nothing that weakens the agents' own prompts. Kimi's and `local`'s permission requests are relayed to the console and cancelled after 120 s of silence. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They touch no file and run no process, and every call passes the hub's own checks. The match relies on the agent putting the tool name in the request's `title`, as Kimi does; an ACP agent that titles calls with model-written text must not be configured as `kimi_cmd`. A payload longer than the console shows is marked as cut and never offers a session-wide grant. `--unattended` turns prompts off, says so loudly, and is never the default.
9
+ - **Permission prompts stay on.** `ahub claude` and `ahub codex` add nothing that weakens the agents' own prompts. Kimi's and `local`'s permission requests are relayed to the console and cancelled after `approvals.timeout_s` (default 120 s) of silence; the macOS notification for a waiting request carries the peer and the tool name only. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They touch no file and run no process, and every call passes the hub's own checks. The match relies on the agent putting the tool name in the request's `title`, as Kimi does; an ACP agent that titles calls with model-written text must not be configured as `kimi_cmd`. A payload longer than the console shows is marked as cut and never offers a session-wide grant. `--unattended` turns prompts off, says so loudly, and is never the default.
10
+ - **A committed config cannot choose launch commands, credential files, data endpoints or a wider sandbox.** The machine-local fields (`kimi_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url`, `local.read_allow`, `local.bash_network`) apply only from a config file git confirms nobody committed: `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust.
10
11
  - **The local worker is boxed in.** Paths are resolved through symlinks and must stay inside the project; a secrets denylist (`.env*`, keys, credential files, the hub's own state) applies to its file tools, its git arguments and its memory capture alike; `.git` and `.agenthub` are not writable. Writes, edits, shell commands and mutating git wait for approval, and the approver sees what will be written or run, with control characters escaped. Everything it executes runs under the macOS sandbox, attended or not: no writes outside the project, its git dir and temp; the home directory is unreadable except toolchains; no `.git/hooks` or `.git/config` writes; no network, loopback included. Without the sandbox there is no `bash` tool.
11
12
  - **PII has an enforced path.** A task matching `signals.pii_patterns` goes to `local` or to nobody; its text is absent from other peers' envelopes, the console stream, the log and the board listing; the console user reviews it; `local` answers such a turn to the console only, keeps it out of its history, refuses it when the only gateway is off campus, and may not save notes or spin off tasks during it. Nothing about it is sent to claude-mem, whose observer is a cloud model.
12
13
  - **Secrets stay where they are read.** The gateway key and Cloudflare Access values are read inside the gateway client and go only into request headers: never into logs, errors, envelopes, tool output, memory, or the generated Switchyard config (the key travels by environment variable name). Subscription logins of Claude, Codex and Kimi are never proxied or pooled.
package/docs/smoke.md CHANGED
@@ -170,7 +170,7 @@ Verified on 2026-09-20, without upgrading the developer's running hubs:
170
170
  Still pending: a real Orca smoke with native Codex/Claude sessions after an
171
171
  attended protocol-8 bootstrap; actual registry package/plugin upgrade; production
172
172
  rollout. Fake terminal receipts do not establish native conversation restoration.
173
- Issue #12's Access and natural-quota prerequisites remain unchanged.
173
+ Issue #1's Access and natural-quota prerequisites remain unchanged.
174
174
 
175
175
  ## Install
176
176
 
@@ -333,9 +333,9 @@ No production task or source file was used as a test target.
333
333
  | Claude task proposal and review tools | pass | The actual plugin `hub_task_propose` created scratch task #1, `ISSUE7-REVIEW-FLOW`, at 12:44:20 UTC, attributed to `claude`. The first assignment remained empty because the Codex peer had been detached; proposal success is separate from handoff success. Claude subsequently issued two actual `hub_review` calls with `changes_requested` at 12:47:50 and 12:47:59 UTC. |
334
334
  | Real Codex TUI queue and MCP tools | pass | With Codex 0.155.1 TUI attached, a typed user turn ran `sleep 30`; the status message showed `busy queued 1`, appeared in the transcript only after the user turn completed, and invoked the actual `hub_task_list` before replying `QUEUE-DELIVERED`. |
335
335
  | Real Codex TUI steer | pass | In a second typed user turn, the TUI prompt explicitly authorized a console marker update while `sleep 20` ran. An important message arrived at 12:48:15 UTC; queue remained 0; the same turn ended at 12:48:39 UTC with `STEER-VERIFIED` at hop 1. The first probe delivered its steer but the model retained the original user instruction, so that attempt was not counted as behavioral success. |
336
- | Unassisted Claude proposal -> Codex completion -> Claude review and escalation | fail, [#11](https://github.com/STAIxBWLB/agent-hub/issues/11) | Task #1 was delivered to Codex at 12:47:05 UTC; it replied `Task #1 noted; no action taken on the hub reference message.` and left the task proposed. Generated instructions classify every hub item as reference-only memory. After a corrective user prompt in the TUI, board history recorded Codex accepted/done, Claude changes_requested, Codex done, Claude changes_requested, and hub escalation to Kimi. Kimi then accepted/done through its real tools, and Claude approved the task. That assisted success does not erase the default-flow failure. |
337
- | Off-campus Cloudflare Access headers | blocked, [#12](https://github.com/STAIxBWLB/agent-hub/issues/12) | Internal gateway reachable; both Access credential-file settings empty; unauthenticated public models probe HTTP 403. Needs authorized Access credentials and an off-campus network. VPN was not disconnected. |
338
- | Natural Codex budget pause with TUI attached | blocked, [#12](https://github.com/STAIxBWLB/agent-hub/issues/12) | Actual TUI-connected weekly reading was approximately 12%, below the gate. No manual budget reading was injected. Needs a naturally near-limit account; parser and manual-injection tests are not this live leg. |
336
+ | Unassisted Claude proposal -> Codex completion -> Claude review and escalation | fail, #11 (previous repository) | Task #1 was delivered to Codex at 12:47:05 UTC; it replied `Task #1 noted; no action taken on the hub reference message.` and left the task proposed. Generated instructions classify every hub item as reference-only memory. After a corrective user prompt in the TUI, board history recorded Codex accepted/done, Claude changes_requested, Codex done, Claude changes_requested, and hub escalation to Kimi. Kimi then accepted/done through its real tools, and Claude approved the task. That assisted success does not erase the default-flow failure. |
337
+ | Off-campus Cloudflare Access headers | blocked, [#1](https://github.com/STAIxBWLB/agent-hub/issues/1) | Internal gateway reachable; both Access credential-file settings empty; unauthenticated public models probe HTTP 403. Needs authorized Access credentials and an off-campus network. VPN was not disconnected. |
338
+ | Natural Codex budget pause with TUI attached | blocked, [#1](https://github.com/STAIxBWLB/agent-hub/issues/1) | Actual TUI-connected weekly reading was approximately 12%, below the gate. No manual budget reading was injected. Needs a naturally near-limit account; parser and manual-injection tests are not this live leg. |
339
339
 
340
340
  ### Environment findings
341
341
 
@@ -368,7 +368,7 @@ check: OK
368
368
  ```
369
369
 
370
370
  This gate covers type checking, bundle freshness, package contents and tests;
371
- it does not replace the live outcomes or unblock #11 and #12.
371
+ it does not replace the live outcomes or unblock #11 (previous repository) and #1.
372
372
 
373
373
  ## Issue #11 default workflow retest (0.3.1)
374
374
 
@@ -400,7 +400,7 @@ instruction was entered into the Codex TUI. Real board history for
400
400
 
401
401
  This closes the default acceptance/review/escalation failure found in the
402
402
  0.3.0 issue #7 run. The earlier failure is retained above as historical evidence.
403
- The off-campus Access and natural budget-pause prerequisites in #12 remain open.
403
+ The off-campus Access and natural budget-pause prerequisites in #1 remain open.
404
404
 
405
405
  Regression gate: `scripts/check.sh` completed with `159 pass`, `0 fail`,
406
406
  `1045 expect() calls`, `check: OK`. Added coverage exercises rendered workflow
@@ -492,7 +492,7 @@ The 0.7.0 protocol-10 durable-delivery implementation, source-9-to-target-10
492
492
  transition, queue CLI, receipt reconciliation, and attended Claude
493
493
  confirmation remain pending live verification. Do not claim a successful
494
494
  0.7.0 production cutover from package installation, tests, or synthetic
495
- approval alone. Issue [#12](https://github.com/STAIxBWLB/agent-hub/issues/12)
495
+ approval alone. Issue [#1](https://github.com/STAIxBWLB/agent-hub/issues/1)
496
496
  remains open for off-campus Access credentials and a natural near-limit budget
497
497
  pause.
498
498
 
@@ -522,7 +522,7 @@ pause.
522
522
  - The synthetic Claude control peer acknowledged bridge receipt and replied
523
523
  with a correlated FYI. This does not establish a real Claude inference turn.
524
524
  - Production application, attended Claude restoration and post-deploy readback
525
- remain pending until the tagged artifact is installed. Issue #12 remains open.
525
+ remain pending until the tagged artifact is installed. Issue #1 remains open.
526
526
 
527
527
 
528
528
  ## 0.7.0 attended production cutover and follow-up
@@ -569,7 +569,7 @@ The 0.7.1 attended repeat is recorded separately after application.
569
569
  - `ahub doctor` reported all configured checks healthy, including the delivery
570
570
  journal and Claude recovery identity. The hwp-cli hub remained stopped.
571
571
  - These are supervised live checks. Native confirmation screens, uncertain
572
- delivery review, and the infrastructure prerequisites in #12 remain explicit
572
+ delivery review, and the infrastructure prerequisites in #1 remain explicit
573
573
  operator responsibilities; this does not certify unattended operation.
574
574
 
575
575
 
@@ -595,7 +595,7 @@ verified application; no daemon, plugin, terminal, or journal mutation.
595
595
  for the 0.7.1 application; no source change is present on HEAD.
596
596
  - Kimi reports 2.0.2 in doctor; earlier live legs on this page used 2.0.1.
597
597
 
598
- Issue [#12](https://github.com/STAIxBWLB/agent-hub/issues/12) remains open
598
+ Issue [#1](https://github.com/STAIxBWLB/agent-hub/issues/1) remains open
599
599
  for off-campus Access credentials and a natural near-limit budget pause.
600
600
 
601
601
 
@@ -690,8 +690,10 @@ cutover; it also exposed three recovery defects that became 0.7.3 and 0.7.4.
690
690
  sourceRoot digest guard correctly refused a hand-patched receipt.
691
691
  - `ahub setup` is interactive (one confirmation gates all steps); piping an
692
692
  answer works, running it detached does not.
693
+ (Changed in 0.7.7: without a terminal it no longer reads a piped answer; use
694
+ `--yes`.)
693
695
 
694
- Issue [#12](https://github.com/STAIxBWLB/agent-hub/issues/12) remains open
696
+ Issue [#1](https://github.com/STAIxBWLB/agent-hub/issues/1) remains open
695
697
  for off-campus Access credentials and a natural near-limit budget pause.
696
698
 
697
699
  ## Shared notes and claims live run (issue #68, 0.7.6, 2026-09-30)
@@ -806,3 +808,42 @@ ran a released hub through `bunx --package @staix/agent-hub@<version> ahub up`:
806
808
  with the 0.7.0 coordinator, see above), and session or terminal planning: no peer
807
809
  was attached, so `peers` and `terminals` were empty.
808
810
  - Both hubs were stopped and their registrations removed after recording.
811
+
812
+ ## Approval notice and timeout (issue #5, 2026-09-30)
813
+
814
+ A disposable project initialised by the PR branch (`approvals: { timeout_s: 120 }`
815
+ from the template, `notify` left to its macOS default) with real Kimi 2.1.1. Times
816
+ are hub.log timestamps (UTC).
817
+
818
+ - Asked to run `echo notify-live-5`, Kimi's request was logged at 11:33:43.793Z as
819
+ `permission 8743d41c requested by kimi (38 chars, shown on the console; cancelled
820
+ after 120s)`. Notifications were on by the macOS default (the template sets no
821
+ `notify`), and no notifier failure line appeared (it logs a spawn error, a
822
+ non-zero exit or its 5 s kill). A clean exit does not prove a banner showed: that
823
+ is visual and not captured here, so the owner confirms it on the next use.
824
+ - The request was rejected from the console; Kimi said so at 11:34:03.468Z and ran
825
+ nothing.
826
+ - The disposable hub was stopped and its registration removed after recording.
827
+
828
+ ## Overlap measurement (issue #8, from 2026-09-30)
829
+
830
+ Whether per-task worktrees get built depends on this: two weeks of normal use with
831
+ the claim overlap warnings (0.7.6 and later), then the owner decides in issue #8.
832
+
833
+ - Count, from a checkout of this repository: `ahub projects --json | jq -r
834
+ '.[].stateDir + "/hub.log"' | tr '\n' '\0' | xargs -0 bun scripts/overlaps.ts
835
+ --since <start date>`. It reads only the hub's overlap notice lines and prints,
836
+ per week (Monday, UTC), the number of warnings and each pair of tasks once per
837
+ project, with its owners, shared paths, first warning and count. A registered
838
+ project without a hub.log is skipped with a note.
839
+ - Per pair, decide whether it led to conflicting edits: from that project's root,
840
+ with its hub running, `ahub task show <id>` for both tasks, and `git log --since
841
+ <first warning> -- <shared paths>`. A conflicting edit is a commit that
842
+ overwrote or reverted the other task's change, a merge conflict between the two,
843
+ or a build or test run broken by the other task's half-done change.
844
+ - Record only the numbers and the verdicts below. The script's output names tasks
845
+ and paths of private projects: it stays local.
846
+
847
+ | Week of | Warnings | Task pairs | Pairs with a conflicting edit | Notes |
848
+ |---|---|---|---|---|
849
+ | 2026-09-28 | 0 | 0 | 0 | baseline counted on 2026-09-30 over every registered project; the issue #68 live run's project was already removed |
@@ -1,6 +1,6 @@
1
1
  # agent-hub design spec
2
2
 
3
- Date: 2026-09-19. Status: M1 to M5 merged; M6 implemented on `feat/m6-packaging-inference` (phase spec: issue #2 of the public repository). Version 0.1.0.
3
+ Date: 2026-09-19. Status: M1 to M5 merged; M6 implemented on `feat/m6-packaging-inference` (phase spec: issue #2 of the first public repository, archived). Version 0.1.0.
4
4
  Owner: Young Joon Lee. Repo: STAIxBWLB/agent-hub.
5
5
 
6
6
  Facts below are tagged **verified** (measured on 2026-09-19 on the owner's Mac) or
@@ -565,7 +565,7 @@ M2 coordination
565
565
 
566
566
  M3 local worker and routing L2/L3
567
567
  - [x] Local worker agent loop with cwd-scoped tools, secrets denylist, approvals and seatbelt sandbox
568
- - [x] OmniRoute client with Cloudflare Access headers and the owner-issued inference key; the off-campus live path remains blocked in `docs/smoke.md` (#12)
568
+ - [x] OmniRoute client with Cloudflare Access headers and the owner-issued inference key; the off-campus live path remains blocked in `docs/smoke.md` (#1)
569
569
  - [x] Switchyard sidecar: config generation, lifecycle, health, fallback to fixed model
570
570
  - [x] `ahub local`, smoke through OmniRoute with provider header check, and through the real sidecar
571
571
  - [x] Local worker capture into claude-mem (`sessions/init`, `observations`, `summarize`, `session-end`, skip list)
@@ -584,7 +584,7 @@ M5 budget relay
584
584
  - [x] Reassignment to local, one resume envelope, idempotency and restart recovery
585
585
  - [x] Handoff context (checkpoint summary or memory block) plus task briefs in the task envelope
586
586
  - [x] Live: the Claude status line tee in an interactive session (`docs/smoke.md`, 2026-09-19)
587
- - [ ] Live: a real pause driven by Codex's own numbers with a TUI attached; the natural near-limit prerequisite remains blocked by issue #12
587
+ - [ ] Live: a real pause driven by Codex's own numbers with a TUI attached; the natural near-limit prerequisite remains blocked by issue #1
588
588
 
589
589
  M6 internal inference, packaging
590
590
  - [x] Status digests and triage through `sy/fast` (amended: optional and fail-open with a backoff. Only plain status
@@ -615,6 +615,9 @@ M6 internal inference, packaging
615
615
  publicly with provenance and `NPM_TOKEN`. The token belongs to an authorized
616
616
  organization member. Organization conversion and credential provisioning are
617
617
  owner operations; this implementation does not perform them.
618
+ - Amended 2026-09-30 (issue #4 of this repository): publishing uses npm trusted
619
+ publishing (OIDC) with no stored token; npm 11.5.1 or later publishes provenance by
620
+ itself. Registering the trusted publisher on npmjs.com is an owner operation.
618
621
  - The v0.4.0 release workflow completed registry publication with provenance.
619
622
  Registry metadata/provenance readback and real Claude setup from a clean registry
620
623
  installation remain to be verified.
@@ -751,3 +754,88 @@ session modes (`default`, `plan`, `auto`, `yolo`) but nothing per server or tool
751
754
  anything outside `[a-zA-Z0-9_-]`, and a server passed in `session/new` shadows a
752
755
  configured server of the same name, so only the hub's `agent-hub` server can
753
756
  produce the approved names.
757
+
758
+ ## Amendment: approval notifications and timeout (issue #5)
759
+
760
+ - A waiting permission request raises one macOS notification (`osascript`) with
761
+ the peer and, when the adapter reports it apart from the title (ACP), the tool
762
+ name; never the title or payload, which can quote a PII turn. Failures are logged
763
+ once and never affect the request.
764
+ - `approvals.timeout_s` (default 120, accepted 30 to 3600, otherwise 120 with a log
765
+ line) replaces the fixed 120 s; an unanswered request leaves a console and log
766
+ line.
767
+ - `approvals.notify` defaults to on for macOS when a project config file exists and
768
+ to off otherwise (tests, a hub without a config), so test runs raise nothing; a
769
+ non-boolean value is ignored.
770
+ - The ACP adapter keeps its inactivity watchdog alive while a permission request
771
+ waits (as the local worker already did), so a timeout above `watchdog_ms` does
772
+ not cancel the turn. The tool name travels to the notifier only; the console
773
+ `permission` push keeps its shape, so the control protocol is unchanged.
774
+
775
+ ## Amendment: claim robustness (issue #6)
776
+
777
+ - A self-claim (`owner` equal to the caller) without a class, when triage names
778
+ none, is filed as `implement` and its history says so; a proposal for another
779
+ owner still needs a class.
780
+ - When a new owner's paths overlap an open task of another owner, that earlier
781
+ owner gets one line through the preface path (`note from hub [finding]: task #N
782
+ (owner X) now overlaps your #M on <paths>; X is told to settle it`), capped with
783
+ shared notes, never a delivery of its own. PII tasks stay out on both sides.
784
+ - An owner offline longer than `tasks.release_after_min` (default 30; 0 disables)
785
+ loses its open tasks, released and reassigned by class routing (not the budget
786
+ handoff's local-first list), but only when routing finds another owner;
787
+ otherwise the task stays. Each task is re-read before it moves, so one changed
788
+ meanwhile (reassigned, finished, owner back) is left alone; runs never overlap,
789
+ and a recovery commit waits for a run in progress. Paused peers and hubs in a
790
+ recovery operation are skipped. A peer never seen attached counts from hub
791
+ start. The gone owner gets a line on its next delivery naming where each task
792
+ went. Reviewers are not moved by this.
793
+
794
+ ## Amendment: completion checks (issue #7)
795
+
796
+ - `checks.<class>` in `.agenthub/config.json` names a command; `checks.timeout_s`
797
+ (default 600, 1 to 3600) bounds it. The hub runs `checks` only when git
798
+ confirms nobody committed that file (any letter case, nor a symlink or submodule
799
+ at `.agenthub`); without a repository or on a git error it runs none. Keys that
800
+ are not task classes are ignored with a log line.
801
+ - `hub_task_done` on such a class records `done (checking)` and answers at once
802
+ (a check can outlast an agent's tool timeout); the task stays `in_progress`. The
803
+ hub runs checks one at a time in the project root, in their own process group,
804
+ killed at the timeout or on shutdown; a check still queued at shutdown never
805
+ starts. A check ends when its command exits: what it left in its process group
806
+ is killed then, and a descendant that left the group gets 0.5 s more to flush
807
+ output and is not waited for. Exit 0: the task
808
+ completes as before, the review envelope carrying `Check: <command> -> exit 0`
809
+ and the output tail; shared memory gets the summary and the outcome line, never
810
+ the output. Any other outcome: the task stays with its owner, who gets a task
811
+ envelope with the command, outcome and tail; no reviewer turn is spent. A second
812
+ `done` while the check runs is refused; a new owner who took the task meanwhile is
813
+ told to try again later, since the result reaches only the owner the check was
814
+ started for. A task with any board event since the
815
+ `done` other than an answer or a reviewer change only records the result
816
+ (`check finished late`); if it is in progress with the same owner again, that
817
+ owner is asked to mark it done again. A check interrupted by a hub stop records
818
+ `check interrupted`, is not resumed, and the owner marks the task done again.
819
+
820
+ ## Amendment: machine-local config fields (issue #17)
821
+
822
+ - Machine-local fields: `kimi_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`,
823
+ `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`,
824
+ `omniroute.api_key_file`, `omniroute.cf_client_id_file`,
825
+ `omniroute.cf_client_secret_file`, `memory.worker_url`, `local.read_allow`,
826
+ `local.bash_network` (`MACHINE_LOCAL` in `src/hub/config-trust.ts`).
827
+ - `loadConfig` reads `.agenthub/config.json`, then `.agenthub/config.local.json`
828
+ over it block by block. In each file, an empty machine-local value is dropped
829
+ (it means the default); one set to anything but its default applies only when
830
+ git confirms that file is untracked, matching tracked files under `.agenthub`
831
+ by identity (dev and inode) rather than by name, and `.agenthub` not a committed
832
+ symlink or submodule; otherwise it is dropped and the config's `ignored` list
833
+ says which and why. No repository or a git error refuses. A file that sets none
834
+ of them asks git nothing, so a committed copy of the template is quiet.
835
+ The containing repository answers "untracked": a checkout copied into an
836
+ unrelated repository is trusted (documented limit).
837
+ - The Claude channel reads `roles` from both files in the same order.
838
+ - This replaces the checks-only gate of issue #7: the daemon logs `ignored` at
839
+ start, `ahub codex` and `ahub models` print it, `ahub doctor` shows a row.
840
+ `ahub init` adds `.agenthub/config.local.json` to `.gitignore`.
841
+ - `routing.toml` and the shared fields still come from the checkout.
@@ -8,7 +8,7 @@ Target: 0.7.0, protocol 10
8
8
 
9
9
  Make ahub suitable for supervised daily development: expose disconnected recipients' pending work, survive daemon termination without silently losing accepted queued messages, and make uncertain execution an explicit operator decision. Publish an English operations guide and verify the release before applying it to the current installation.
10
10
 
11
- The user chose conservative recovery and deployment through production application. Never automatically replay uncertain or partially executed work. Exactly-once external effects, automatic rollback, and unattended native approval handling remain outside this release. Infrastructure prerequisites in issue #12 remain pending unless independently available; never manufacture quota exhaustion or disconnect the user's network.
11
+ The user chose conservative recovery and deployment through production application. Never automatically replay uncertain or partially executed work. Exactly-once external effects, automatic rollback, and unattended native approval handling remain outside this release. Infrastructure prerequisites in issue #1 remain pending unless independently available; never manufacture quota exhaustion or disconnect the user's network.
12
12
 
13
13
  ## Delivery model
14
14
 
@@ -113,6 +113,12 @@ stopped with its matching CLI before a protocol-8 manager is launched.
113
113
 
114
114
  ## Current 0.7.0 correction and recovery contract
115
115
 
116
+ Amended 2026-09-30: 0.7.0 shipped protocol 10 and the queue commands; protocol 10
117
+ went into production with 0.7.0 and was verified there with 0.7.1, where
118
+ `ahub queue list` read back live receipts ([smoke checklist](../smoke.md)). `ahub queue show` and `ahub queue resolve` are
119
+ covered by tests, not by a live run. The paragraphs below keep the pre-release
120
+ wording.
121
+
116
122
  The earlier sections record the protocol-8 implementation history. The current
117
123
  release is 0.6.4 with protocol 9; the next target is 0.7.0 with protocol 10.
118
124
  This correction supersedes the earlier protocol-8 source/target statement for
@@ -146,6 +152,6 @@ The documented verification sequence is: run a dry-run, verify package and
146
152
  session identity, manually confirm Claude recovery, read back version, session,
147
153
  queue, task, and budget state, then release the held queues. Do not claim a
148
154
  successful 0.7.0 cutover from tests, synthetic approvals, or package
149
- installation alone. Issue [#12](https://github.com/STAIxBWLB/agent-hub/issues/12)
155
+ installation alone. Issue [#1](https://github.com/STAIxBWLB/agent-hub/issues/1)
150
156
  remains open for off-campus Access credentials and a natural near-limit budget
151
157
  pause.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-hub",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
5
5
  "author": {
6
6
  "name": "Young Joon Lee",
@@ -15727,7 +15727,7 @@ class ControlClient {
15727
15727
  // package.json
15728
15728
  var package_default = {
15729
15729
  name: "@staix/agent-hub",
15730
- version: "0.7.7",
15730
+ version: "0.7.9",
15731
15731
  description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
15732
15732
  license: "MIT",
15733
15733
  type: "module",
@@ -15806,7 +15806,7 @@ var TASK_TOOLS = [
15806
15806
  tool("hub_task_propose", "Put a piece of work on the shared task board. The hub assigns an owner by class (routing.toml) unless you name one. Classes: plan, implement, bulk_edit, test, review, summarize, triage. Name the class when you know it; without one the hub tries to pick it. Name yourself as owner to claim work nobody assigned you: it starts in progress. Put the paths the work touches in refs; the hub says when they overlap another open task.", { title: str, class: { type: "string", enum: ["plan", "implement", "bulk_edit", "test", "review", "summarize", "triage"] }, detail: str, refs, owner: { type: "string", description: "peer id; yours to claim the work, omit to let the hub route it" } }, ["title"]),
15807
15807
  tool("hub_task_accept", "Take a task that was assigned to you.", { id }, ["id"]),
15808
15808
  tool("hub_task_decline", "Pass on a task assigned to you; the hub offers it to the next peer.", { id, reason: str }, ["id"]),
15809
- tool("hub_task_done", "Mark your task finished. It goes to its reviewer with your summary and refs.", { id, summary: { type: "string", description: "what changed, why, and the check you ran with its result" }, refs }, ["id", "summary"]),
15809
+ tool("hub_task_done", "Mark your task finished. It goes to its reviewer with your summary and refs. When the project configures a check for its class, the hub runs it first and the result comes as a task message: a failed check keeps the task with you.", { id, summary: { type: "string", description: "what changed, why, and the check you ran with its result" }, refs }, ["id", "summary"]),
15810
15810
  tool("hub_task_list", "The task board. PII tasks show as [pii].", { state: { type: "string", enum: ["proposed", "in_progress", "in_review", "approved", "changes_requested"] } }),
15811
15811
  tool("hub_review", "Give your verdict on a task you were asked to review. Two changes_requested in a row move the task to another peer.", { id, verdict: { type: "string", enum: ["approved", "changes_requested"] }, note: str }, ["id", "verdict"]),
15812
15812
  tool("hub_checkpoint", "Answer a checkpoint request from the hub (your quota window is nearly used up): what you were doing, what is half done, what whoever continues must know. Write the same to .agenthub/checkpoint.md first if you can.", { summary: str }, ["summary"]),
@@ -15834,11 +15834,14 @@ var projectRoot2 = process.env.AGENTHUB_PROJECT_DIR ?? process.cwd();
15834
15834
  var peerId = process.env.AGENTHUB_PEER_ID ?? "claude";
15835
15835
  var toolsOnly = process.env.AGENTHUB_MODE === "tools";
15836
15836
  function roles() {
15837
- try {
15838
- return { ...DEFAULT_ROLES, ...JSON.parse(readFileSync3(join3(projectRoot2, ".agenthub", "config.json"), "utf8")).roles };
15839
- } catch {
15840
- return DEFAULT_ROLES;
15841
- }
15837
+ const read = (name) => {
15838
+ try {
15839
+ return JSON.parse(readFileSync3(join3(projectRoot2, ".agenthub", name), "utf8")).roles ?? {};
15840
+ } catch {
15841
+ return {};
15842
+ }
15843
+ };
15844
+ return { ...DEFAULT_ROLES, ...read("config.json"), ...read("config.local.json") };
15842
15845
  }
15843
15846
  var MAX_RECONNECT_DELAY_MS = 30000;
15844
15847
  var TERMINAL_CLOSES = {
@@ -13,6 +13,8 @@ export interface PermissionRequest {
13
13
  peer: PeerId;
14
14
  title: string;
15
15
  options: PermissionOption[];
16
+ /** The tool's name alone, when the adapter knows it apart from the title (which can quote the payload). */
17
+ tool?: string;
16
18
  }
17
19
 
18
20
  export interface AcpOptions {
@@ -243,7 +245,17 @@ export class AcpPeer extends BasePeer {
243
245
  // An unknown payload is never dressed up as a description, and it must not buy a blanket grant.
244
246
  const title: string = raw === undefined ? `${call.title ?? "tool call"} (payload not reported by the agent)` : `${call.title ?? "tool call"}${input}`;
245
247
  const options: PermissionOption[] = (msg.params?.options ?? []).filter((o: PermissionOption) => (raw !== undefined && !cut) || o.kind !== "allow_always");
246
- const picked = await this.opts.onPermission?.({ peer: this.id, title, options }).catch(() => undefined);
248
+ // Only a bare tool name travels on its own (a desktop notice shows it); anything prose-like stays in the title.
249
+ const tool = typeof call.title === "string" && /^[\w.:@-]{1,80}$/.test(call.title) ? call.title : undefined;
250
+ // Waiting for a person is not the agent going silent: keep the watchdog from cancelling the turn meanwhile.
251
+ const turn = this.turn;
252
+ const alive = setInterval(() => this.state === "busy" && turn === this.turn && this.touch(), Math.max(10, Math.min(30_000, Math.floor(this.watchdogMs / 3))));
253
+ let picked: string | undefined;
254
+ try {
255
+ picked = await this.opts.onPermission?.({ peer: this.id, title, options, ...(tool ? { tool } : {}) }).catch(() => undefined);
256
+ } finally {
257
+ clearInterval(alive); // also when there is no handler at all
258
+ }
247
259
  const valid = options.some((o) => o.optionId === picked);
248
260
  this.send({
249
261
  jsonrpc: "2.0",
@@ -21,11 +21,15 @@ const peerId = process.env.AGENTHUB_PEER_ID ?? "claude";
21
21
  const toolsOnly = process.env.AGENTHUB_MODE === "tools";
22
22
 
23
23
  function roles(): Record<string, string[]> {
24
- try {
25
- return { ...DEFAULT_ROLES, ...JSON.parse(readFileSync(join(projectRoot, ".agenthub", "config.json"), "utf8")).roles };
26
- } catch {
27
- return DEFAULT_ROLES;
28
- }
24
+ // The machine's own file overrides the shared one, as in loadConfig (issue #17).
25
+ const read = (name: string) => {
26
+ try {
27
+ return JSON.parse(readFileSync(join(projectRoot, ".agenthub", name), "utf8")).roles ?? {};
28
+ } catch {
29
+ return {};
30
+ }
31
+ };
32
+ return { ...DEFAULT_ROLES, ...read("config.json"), ...read("config.local.json") };
29
33
  }
30
34
  const MAX_RECONNECT_DELAY_MS = 30_000;
31
35
  /** Close codes a retry cannot fix: bad token, wrong project, reserved or taken id, wire version. */