@staix/agent-hub 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +27 -5
- package/docs/agent-notes/adapters.md +1 -0
- package/docs/agent-notes/bus.md +1 -0
- package/docs/agent-notes/daemon.md +15 -2
- package/docs/agent-notes/tasks.md +9 -2
- package/docs/agent-notes/tests.md +2 -1
- package/docs/dogfood.md +12 -2
- package/docs/memory.md +1 -1
- package/docs/operations.md +137 -4
- package/docs/profiles.md +2 -0
- package/docs/quickstart.md +10 -3
- package/docs/security.md +155 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +25 -1
- package/docs/specs/2026-09-19-multi-project-design.md +12 -1
- package/docs/specs/2026-11-01-research-campaigns-design.md +351 -0
- package/docs/verification/2026-11-01-0.25.0.md +91 -0
- package/docs/verified.json +31 -15
- package/package.json +4 -3
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +5118 -4708
- package/src/adapters/claude-channel.ts +29 -6
- package/src/adapters/definitions/native-cli-doctor.ts +30 -0
- package/src/adapters/jcode.ts +3 -0
- package/src/adapters/registry.ts +1 -1
- package/src/cli/acon-register.ts +29 -0
- package/src/cli/handoff-args.ts +27 -0
- package/src/cli/handoff-output.ts +25 -0
- package/src/cli/help.ts +6 -3
- package/src/cli/identity.ts +7 -1
- package/src/cli/main.ts +76 -7
- package/src/cli/output.ts +8 -5
- package/src/cli/schedule-output.ts +8 -4
- package/src/cli/study-output.ts +71 -0
- package/src/cli/study.ts +58 -0
- package/src/cli/task-output.ts +1 -0
- package/src/hub/board.ts +871 -2
- package/src/hub/channel-project.ts +61 -0
- package/src/hub/control-client.ts +10 -1
- package/src/hub/daemon.ts +247 -11
- package/src/hub/envelope.ts +2 -0
- package/src/hub/events.ts +4 -1
- package/src/hub/execution-budget.ts +12 -0
- package/src/hub/handoff.ts +126 -0
- package/src/hub/hub-tools.ts +12 -2
- package/src/hub/manager.ts +64 -57
- package/src/hub/no-writes.ts +1 -1
- package/src/hub/path-policy.ts +12 -8
- package/src/hub/project.ts +14 -5
- package/src/hub/provenance.ts +30 -0
- package/src/hub/research-campaign.ts +265 -0
- package/src/hub/research-experiments.ts +445 -0
- package/src/hub/restart.ts +26 -0
- package/src/hub/schedules.ts +210 -9
- package/src/hub/session-identity.ts +49 -0
- package/src/hub/tasks.ts +894 -22
- package/src/hub/ui.ts +15 -6
- package/src/memory/capture.ts +2 -2
- package/src/ui/index.html +5 -3
- package/src/ui/office-space.ts +118 -0
- package/src/ui/plugin.ts +293 -0
- package/templates/AGENTS.block.md +1 -0
- package/templates/profiles/general.json +1 -1
- package/templates/profiles/research.json +4 -4
- package/templates/profiles/writing.json +1 -1
- package/templates/roles/research/analyst.md +1 -0
- package/templates/roles/research/fact-checker.md +1 -0
- package/templates/roles/research/research-lead.md +1 -0
- package/templates/roles/research/researcher.md +1 -0
- package/templates/roles/research/statistician.md +1 -0
package/docs/operations.md
CHANGED
|
@@ -557,7 +557,7 @@ and so is every help entry the gate has no class for yet;
|
|
|
557
557
|
|
|
558
558
|
| Command | Class | Why |
|
|
559
559
|
| --- | --- | --- |
|
|
560
|
-
| `ahub doctor [--json] [--full] [--color=...]` | Allowed | a read-only check of tools, daemon, plugin, gateway, models and memory |
|
|
560
|
+
| `ahub doctor [--json] [--full] [--color=...]` | Allowed | a read-only check of tools, daemon, plugin, gateway, models and memory; the `memory capture: codex` and `memory capture: kimi` rows come from agent-mem's setup state (`amem setup <tool> --json` on PATH, a preview that writes nothing), as set up, `amem setup <tool> --apply` needed, client missing, or unknown when `amem` is missing, refuses or prints no setup plan (#1008) |
|
|
561
561
|
| `ahub doctor --stale-anchors [--json]` | Allowed | a bounded read-only report of memory symbol anchors at current HEAD |
|
|
562
562
|
| `ahub queue list [--peer <id>] [--json] [--full] [--color=...]` | Allowed | public queue metadata only; held bodies and resolution remain conductor operations |
|
|
563
563
|
| `ahub roster [--json] [--color=...]` | Allowed | public roster status; add/remove retain conductor admission |
|
|
@@ -573,6 +573,7 @@ and so is every help entry the gate has no class for yet;
|
|
|
573
573
|
| `ahub task cancel\|label\|role <id> ...` | Allowed | the daemon decides per task (#933): `label` and `role` only on a task the caller owns, `role` not while it is in review, and `cancel` by the `hub_task_cancel` rule (the planner's own unaccepted backlog or proposed work) |
|
|
574
574
|
| `ahub deliverables [--task <id>] [--approved] [--check] [--json]` | Allowed | only the rows `hub_task_show` would show the caller, through the same public screening (#933) |
|
|
575
575
|
| `ahub room list [--json]`, `ahub room status [<name>] [--json]` | Allowed | room metadata only; every other `room` form, the inbox included, stays Console |
|
|
576
|
+
| `ahub study show [<id> \| --after <id>] [--json] [--color=...]` | Allowed | the same read as `hub_study_show`: a private campaign, task or source shows ids, counts and states only; `study draft\|revise\|approve\|approve-protocol\|close` stay Console (#930) |
|
|
576
577
|
|
|
577
578
|
`backlog list`, `release list` and `sdlc costs|metrics` retain their existing
|
|
578
579
|
planner/person policy. Their CLI forms stay Console for ordinary agents; a
|
|
@@ -926,6 +927,37 @@ Incompatible or unverified processes are shown explicitly and are never killed b
|
|
|
926
927
|
PID-name matching. See the operations guide and the
|
|
927
928
|
[multi-project specification](specs/2026-09-19-multi-project-design.md).
|
|
928
929
|
|
|
930
|
+
## acon console plugin (#935, #1012)
|
|
931
|
+
|
|
932
|
+
`ahub setup` registers agent-hub as a plugin of the family console
|
|
933
|
+
([agent-console](https://github.com/STAIxBWLB/agent-console), `acon ui`). It writes
|
|
934
|
+
`$AGENTHUB_HOME/console/plugins/ahub.json` (default `~/.agenthub/console/plugins/ahub.json`;
|
|
935
|
+
the directory 0700, the file 0600, replaced atomically) naming the installed module
|
|
936
|
+
`@staix/agent-hub/ui-plugin` and its sha256, and prints `acon plugin registered`, `acon plugin
|
|
937
|
+
up to date` or why it was not registered. acon reads the file at its next start and marks the
|
|
938
|
+
plugin stale when the module changed after setup, so run `ahub setup` again after each upgrade.
|
|
939
|
+
|
|
940
|
+
- **Panels.** The plugin's panels are the dashboard's seven views, and the panel is the `ahub ui`
|
|
941
|
+
page itself, following acon's theme. It reaches project hubs only through the manager's own
|
|
942
|
+
listing and forwarding (`ahub ui --all`): registered project and instance ids, the manager's
|
|
943
|
+
closed action list and the seven view names. There is no new protocol or action.
|
|
944
|
+
- **Inbox.** acon's inbox lists each running project's pending approvals (Allow once / Deny,
|
|
945
|
+
answered with the hub's own `permit` and an option the request offers) and in-review tasks whose
|
|
946
|
+
reviewer is you (Open board). A local-worker or Pi approval stays deny-only there, as on the
|
|
947
|
+
dashboard: answer it with `ahub permit` after `ahub tail`.
|
|
948
|
+
- **Confirmation.** `stop`, `permit`, `send`, the settings writes (`setting`, `setting_undo`,
|
|
949
|
+
`setting_reload`, `config_edit_apply`), `start_peer`, `backlog_split` and the inbox's Allow once
|
|
950
|
+
and Deny answer `needsConfirm` with a preview first; acon runs them after you confirm.
|
|
951
|
+
Previews and the other dashboard actions run directly, as on the page.
|
|
952
|
+
- **Space.** The plugin feeds acon's Space view from the dashboard's office model
|
|
953
|
+
(`src/ui/office-space.ts`): peers are actors (state from condition, approvals and review, with
|
|
954
|
+
approval, queue, paused and mode marks), the office rooms are queue, work, review and done zones,
|
|
955
|
+
tasks are cards, and overlap, conflict and cohort links and recent message arcs are links. Ids
|
|
956
|
+
are project-prefixed, every running project's scene is merged within acon's caps (32 actors, 200
|
|
957
|
+
cards, 64 links, newest links kept), and a project that has not answered within about 3 s is left
|
|
958
|
+
out of that poll. Only labels and states reach the scene, never message bodies, task details,
|
|
959
|
+
approval titles or reasons; a PII task's card shows the `[pii]` stub.
|
|
960
|
+
|
|
929
961
|
## Install and start
|
|
930
962
|
|
|
931
963
|
Use Bun 1.4.2 or newer. Source checks and CI use the version pinned in
|
|
@@ -943,7 +975,8 @@ ahub up
|
|
|
943
975
|
`ahub init` writes the project configuration and managed instruction blocks.
|
|
944
976
|
Run it after an upgrade when those blocks need refreshing. `ahub setup`
|
|
945
977
|
installs or updates the Claude plugin and a project-neutral PreToolUse hook in each
|
|
946
|
-
Claude configuration home in use (see "Machine autonomy and Claude homes")
|
|
978
|
+
Claude configuration home in use (see "Machine autonomy and Claude homes"), and
|
|
979
|
+
registers the acon console plugin (see "acon console plugin"). Keep `ahub console` open when a local worker
|
|
947
980
|
may request an approval.
|
|
948
981
|
|
|
949
982
|
`.agenthub/config.json` can be committed and shared. The fields that choose
|
|
@@ -1135,6 +1168,15 @@ and start): a committed file must not choose commands the hub runs. Keys that
|
|
|
1135
1168
|
are not task classes are ignored with a log line. A hub stopped mid-check leaves
|
|
1136
1169
|
the task in progress (`check interrupted`); the owner marks it done again.
|
|
1137
1170
|
|
|
1171
|
+
The owner may call `hub_task_done` again while the task is in review, with a
|
|
1172
|
+
different full lowercase commit SHA in `refs.commit` (#1000, #1021). It replaces the
|
|
1173
|
+
delivered artifact, not a verdict: the reviewer gets a fresh request for the new
|
|
1174
|
+
head, the old CI watch is replaced, stale observed, reviewed and merge evidence is
|
|
1175
|
+
cleared, and open Important findings, spec acceptance and the rejection count stay.
|
|
1176
|
+
A configured completion check first returns the task to in progress until it passes.
|
|
1177
|
+
The same head, a malformed SHA, another owner or a running check refuse without
|
|
1178
|
+
effect, and a verdict started on the old request no longer applies.
|
|
1179
|
+
|
|
1138
1180
|
While the project root checkout is on a branch other than its default
|
|
1139
1181
|
(`origin/HEAD`, else `main`), finishing a task adds `the main checkout is on
|
|
1140
1182
|
<branch>; move the work to a worktree under .worktrees/` to the owner's result
|
|
@@ -1427,6 +1469,60 @@ ahub research backfill # build this project's records once from even
|
|
|
1427
1469
|
ahub task label 12 reverted # a later verdict (ok, regressed, reverted, incomplete, wrong, abandoned)
|
|
1428
1470
|
```
|
|
1429
1471
|
|
|
1472
|
+
## Research campaigns: `ahub study` (#930)
|
|
1473
|
+
|
|
1474
|
+
A research campaign splits one question on an approved parent task into bounded units, each a task with
|
|
1475
|
+
its own sources and required output. Nothing happens until a campaign is drafted, and the `ahub research`
|
|
1476
|
+
telemetry above is unrelated. The contract, its bounds and the slices are in the
|
|
1477
|
+
[research campaigns spec](specs/2026-11-01-research-campaigns-design.md); the worked three-source example is in
|
|
1478
|
+
[profiles](profiles.md).
|
|
1479
|
+
|
|
1480
|
+
```bash
|
|
1481
|
+
ahub study draft 12 contract.json # store revision 1 as a draft; creates no task
|
|
1482
|
+
ahub study revise rc-0123456789ab 1 next.json # store the next draft; a stale revision refuses
|
|
1483
|
+
ahub study approve rc-0123456789ab 2 # person only: create the unit tasks as Backlog cards
|
|
1484
|
+
ahub study show [rc-0123456789ab] # list campaigns (50 per page, --after continues) or one projection
|
|
1485
|
+
ahub study close rc-0123456789ab 2 --reason "answered"
|
|
1486
|
+
ahub study approve-protocol <64-hex hash> # person only: approve one exact experiment protocol
|
|
1487
|
+
```
|
|
1488
|
+
|
|
1489
|
+
- **Contract.** The JSON names the question, scope, exclusions, registered source ids, 1 to 32 units
|
|
1490
|
+
(round, question, perspective, required output, sources, dependencies and a purpose: discovery,
|
|
1491
|
+
independent check, analysis or synthesis), deliverable sections, a stop policy (`maxUnits`,
|
|
1492
|
+
`maxFollowUpRounds` up to 5, a deadline at most 90 days after approval) and an optional execution budget.
|
|
1493
|
+
A contract over its bounds refuses whole, naming the field.
|
|
1494
|
+
- **Who does what.** Draft and revise are the person's (`ahub study`) or a configured planner's
|
|
1495
|
+
(`hub_study_draft`, `hub_study_revise`); they store drafts only. Approve, approve-protocol and close are
|
|
1496
|
+
console commands. Approval is the only step that creates unit tasks: they go to Backlog and the planner
|
|
1497
|
+
offers them through its ordinary pull; the campaign never assigns owners or approves a task. Re-running
|
|
1498
|
+
approve on an approved revision creates nothing.
|
|
1499
|
+
- **Dispatch gate.** Before a unit task is offered, assigned, pulled or handed to another owner the hub
|
|
1500
|
+
re-reads the campaign and holds the task with a reason when its revision is no longer the latest approved
|
|
1501
|
+
one, the deadline has passed or the stop policy is exceeded. A newer draft holds older bound work until it
|
|
1502
|
+
is approved, and an owner offer held this way is re-offered after approval, across a restart too. A hold
|
|
1503
|
+
never stops a native turn already running.
|
|
1504
|
+
- **Handoffs and coverage.** The current in-progress owner of a unit records `hub_study_handoff`: evidence per
|
|
1505
|
+
source, unresolved questions, contradictions and limitations, or `insufficientEvidence`. `ahub study show` and
|
|
1506
|
+
`hub_study_show` report per unit its round, task state, handoff completeness, covered sources and duplicate
|
|
1507
|
+
discovery assignments, the budget support label per resource (`enforceable`, `advisory`, `unknown` or
|
|
1508
|
+
`unavailable`) and why dispatch is held. A follow-up round needs every discovery unit of the previous round
|
|
1509
|
+
settled by a complete handoff or `insufficientEvidence`.
|
|
1510
|
+
- **Budgets.** A contract's `budget: { id, hardCap: [...] }` names an existing execution budget. With a non-empty
|
|
1511
|
+
`hardCap`, approval refuses unless the budget is enabled, every capped resource has a limit and is not
|
|
1512
|
+
`tokens`, and at least one of its peers is instrumented (today `pi` and `local`); every later dispatch and
|
|
1513
|
+
owner change rechecks that same budget. The ledger is shared with the peers' other work.
|
|
1514
|
+
- **Experiments.** Protocols, comparison plans and attempts are stored with their provenance (`declared`,
|
|
1515
|
+
`host-observed` from a check receipt or a hub-hashed deliverable), and `ahub study approve-protocol` binds an
|
|
1516
|
+
approval to one exact protocol hash and its evaluator; a changed protocol needs its own approval. The hub
|
|
1517
|
+
never ranks results. This release has no hub tool or CLI that records experiments; they are host storage
|
|
1518
|
+
for the slices that follow.
|
|
1519
|
+
- **Privacy.** A campaign whose parent task is PII, or that names a private source, is private: every view keeps
|
|
1520
|
+
ids, counts, states and provenance and stubs its text as `[pii]`. Study tool arguments and results are kept out
|
|
1521
|
+
of hub-managed memory capture.
|
|
1522
|
+
- **Upgrades.** The daemon advertises `research_records`, `research_dispatch`, `research_dropped_offers`,
|
|
1523
|
+
`research_experiments` and `research_comparisons`. While the matching rows exist, a restart, upgrade or
|
|
1524
|
+
recovery onto a release without the feature is refused.
|
|
1525
|
+
|
|
1430
1526
|
## Hub memory
|
|
1431
1527
|
|
|
1432
1528
|
The hub opens the selected amem memory profile under `memoryHome()` (default `~/.agenthub/memory/`), with no claude-mem worker dependency ([how it works](memory.md)): notes saved
|
|
@@ -1442,7 +1538,7 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
|
|
|
1442
1538
|
|
|
1443
1539
|
`"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
|
|
1444
1540
|
|
|
1445
|
-
The exact-pinned `@staix/agent-mem` 0.
|
|
1541
|
+
The exact-pinned `@staix/agent-mem` 0.18.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
|
|
1446
1542
|
|
|
1447
1543
|
The daemon checks its selector at hub operation entry and before candidate/counter writes. It does not hot-switch handles or provide an atomic fence against selection changes during an in-flight core call. Quiesce consumers and close their handles before migration or selection changes; reopen through the supported session-preserving lifecycle afterward. Candidate/counter storage remains `M/candidates.db`, independent of `G`; since agent-mem 0.7.0 `amem review` also lists and accepts candidates on a selected generation. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Existing JSON memory/goal results remain unchanged; the additive binding receipt belongs to daemon status, profile-check and the decay object. This source integration does not authorize installing a new runtime, migrating real memory, restarting sessions or claiming real-machine sync/capture receipts. Migration/deactivation and their limits belong to [agent-mem](https://github.com/STAIxBWLB/agent-mem); see [the hub profile contract](memory.md#selected-memory-profile).
|
|
1448
1544
|
|
|
@@ -4323,6 +4419,22 @@ A bundled adapter module is authorized by the person with `adapter_modules` (at
|
|
|
4323
4419
|
- Refresh instructions with init and restart the daemon/MCP clients to apply their resolved contracts. Existing flow-role and executable-check authority remains in `roles`/trusted `checks`. Native model/account evidence remains unverified.
|
|
4324
4420
|
|
|
4325
4421
|
|
|
4422
|
+
## Calendar schedules: `cron` and `tz` (#1017)
|
|
4423
|
+
|
|
4424
|
+
A `schedules` entry in `.agenthub/config.local.json` takes either `every` (an interval, `<n>m|h|d`, 15 minutes at least) or `cron`, never both. `cron` ties the run to the clock:
|
|
4425
|
+
|
|
4426
|
+
```json
|
|
4427
|
+
{ "schedules": [{ "id": "digest", "cron": "30 7 * * 1-5", "tz": "Asia/Seoul", "class": "plan", "title": "Morning digest" }] }
|
|
4428
|
+
```
|
|
4429
|
+
|
|
4430
|
+
- **The expression** is the standard five fields, minute, hour, day of month, month, day of week, with numbers, lists (`1,15`), ranges (`1-5`) and steps (`*/15`, `10-50/10`). Day of week 0 and 7 are Sunday. There are no seconds, no year, no `@daily` macros and no names (`MON`, `jan`). When both day fields are restricted a day matches if either does (the classic rule); a field that begins with `*` leaves the other to decide.
|
|
4431
|
+
- **`tz`** is an IANA zone name (`Asia/Seoul`, `America/New_York`) and applies to `cron` only. Without it the machine's own zone is used. An unknown zone is refused with the entry named.
|
|
4432
|
+
- **Daylight saving, one rule.** A local time the clock skips runs at the next valid minute of that day; a local time the clock repeats runs once, at its first occurrence.
|
|
4433
|
+
- **Too frequent.** An expression that can fire more often than every 15 minutes (`* * * * *`, `*/5 * * * *`, `0,10 7 * * *`) is refused when the config loads, naming the schedule, as is one that never fires (`0 0 31 2 *`).
|
|
4434
|
+
- **Everything else is as for `every`.** A schedule never runs at first sight; a hub that was down or held through several fire times proposes one task when it can, not one per missed fire; a budget pause or hold skips and counts the tick; no second task while the first is open. `ahub schedule list`, `ahub status` and the dashboard show the expression and the next run in the schedule's zone, for example `2026-11-02 07:30 Asia/Seoul`.
|
|
4435
|
+
- **Upgrades.** The daemon advertises `calendar_schedules`. While a schedule uses `cron` or `tz`, a restart, upgrade or recovery onto a release without it is refused, naming the reason.
|
|
4436
|
+
- Out of scope: holiday and exchange calendars. A task whose first step finds a non-working day can end itself.
|
|
4437
|
+
|
|
4326
4438
|
## Staging work in Backlog (#263)
|
|
4327
4439
|
|
|
4328
4440
|
Use `ahub task propose implement "Later work" --backlog --owner codex` to stage
|
|
@@ -4941,7 +5053,7 @@ Native launcher connections retry transport timeouts/unclean closes with bounded
|
|
|
4941
5053
|
|
|
4942
5054
|
## amem runtime (agent-mem#13 P4)
|
|
4943
5055
|
|
|
4944
|
-
The hub exact-pins @staix/agent-mem 0.
|
|
5056
|
+
The hub exact-pins @staix/agent-mem 0.18.0 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
|
|
4945
5057
|
|
|
4946
5058
|
## Backlog issue identity and notes
|
|
4947
5059
|
|
|
@@ -5334,3 +5446,24 @@ Lists are complete up to 1,100 rows. If an all-room list exceeds that limit, the
|
|
|
5334
5446
|
CLI explicitly asks you to choose a room name; it never silently truncates.
|
|
5335
5447
|
ponytail: pagination is later work; a filtered room's 100 unread plus 1,000
|
|
5336
5448
|
settled stubs fit the current response ceiling.
|
|
5449
|
+
|
|
5450
|
+
## Handing work to another project (#604, agent-hub#1030)
|
|
5451
|
+
|
|
5452
|
+
Work that belongs to another repository that runs agent-hub on this machine goes onto that repository's board, not into this session. Its own roles plan, implement and review it, and each state change it reaches is reported back here. The first case: after a library you maintain publishes a release, raising its exact pin in a consumer belongs on the consumer's board.
|
|
5453
|
+
|
|
5454
|
+
```
|
|
5455
|
+
ahub task handoff --project <root|id> [<class>] <title...> [--path <p>]... [--issue <n>] [--task <n>] [--detail <text>]
|
|
5456
|
+
```
|
|
5457
|
+
|
|
5458
|
+
- `--project` is the other project's canonical root or its id from `ahub projects`. It must be registered and its hub must be running (`ahub up` in that project). Nothing is queued: a hub that is not running is refused with that action.
|
|
5459
|
+
- A feature or behavior change first gets a spec issue in the other repository: pass its number as `--issue`. A mechanical chore (an exact version pin, a regenerated file) goes straight to a task.
|
|
5460
|
+
- `--path` names files in the other project, repository-relative (no absolute path, `~` or `..`). `--task` names your own task this comes from, so a `[pii]` one is refused.
|
|
5461
|
+
- `ahub task handoff` is the person's command (an agent's shell is refused it as a console command). Agents use `hub_task_handoff` (the same fields) when they hold the planner or conductor role; `hub_task_handoffs` and `ahub board --handoffs` list what this project handed over and the last state each target reported.
|
|
5462
|
+
|
|
5463
|
+
What the other project sees: an ownerless proposed task, made by its hub, whose detail ends with where it came from; `ahub board` and `ahub task show` there name the origin project and peer. Its own routing offers it as it would a local proposal. The requesting peer has no identity or authority there: it cannot accept, review, approve, assign or cancel.
|
|
5464
|
+
|
|
5465
|
+
What comes back: each change is recorded in `ahub board --handoffs`, and an agent that handed the task over also gets one `kind: task` message per change, `agent-hub task 123 accepted`, `in review`, `approved: PR #7 at <sha>`, `declined` or `cancelled` (a handoff you made from the terminal sends you no message: read the list). It carries an event name, a PR number and a full head, never the other project's text. If this project's hub was stopped meanwhile, it asks each running target hub where its open handoffs stand when it starts again; a target that is down then is asked at the next start.
|
|
5466
|
+
|
|
5467
|
+
Refusals, each named with the action: an unregistered target, this very project, a hub that is not running, a request over 8 KiB or with an unknown field, a caller that is not a planner, the conductor or the person, a private (PII) turn, a `[pii]` task named with `--task`, text that matches the target's PII patterns or that the target's screen cannot clear, a target hub in recovery or shutting down (hand it off again after that ends), and an older target hub (it lacks the `task_handoff` feature: run `ahub upgrade` in that project).
|
|
5468
|
+
|
|
5469
|
+
Recovery: `task_handoff` is its own recovery label. A project that holds a handoff row, in either direction, refuses a recovery target that lacks it.
|
package/docs/profiles.md
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
- Configure an explicit machine-local `checks.fact_check` command if desired. Alias checks take precedence over a base `checks.test`; no check runs merely because a profile was selected.
|
|
22
22
|
- Example deterministic check: `bun /path/to/agent-hub/scripts/check-citations.ts report.md references.bib`. It checks bracketed Markdown `[@key; @other]` keys against BibTeX entry keys, with 256 KiB/4 MiB input caps. It performs no fetch, validates no source quality and is not a full Markdown/BibTeX parser. A missing key exits 1. A resolved bibliography is one check, not proof that claims are supported.
|
|
23
23
|
- Keep a person's sign-off as the final review. The live three-source/two-agent example remains unverified and was not run during solo implementation.
|
|
24
|
+
- A multi-unit study (#930, #601) goes through `ahub study`: the person (or a configured planner with `hub_study_draft` / `hub_study_revise`) stores a contract as a draft, only the person approves it with `ahub study approve <id> <revision>`, which creates one task per unit, and anyone reads coverage with `ahub study show [<id>]` or `hub_study_show`. A unit owner records `hub_study_handoff` with evidence for every source the unit names, or `insufficientEvidence`; a done task is not a handoff, and an incomplete handoff does not close a discovery round. A private campaign, task or source shows ids, counts and states only.
|
|
25
|
+
- The deterministic three-source, two-peer example (`test/study-daemon.test.ts`, "three-source, two-peer example"): one contract names sources S0, S1 and S2 and two discovery units, `alpha` (S0 and S1, owned by codex) and `beta` (S2, an independent check, owned by kimi). After `ahub study approve` both units read `none` and `0/2 covered`, `0/1 covered`. A handoff for S0 only moves `alpha` to `incomplete` with `1/2 covered`. A handoff for both S0 and S1, and one for S2, make both `complete` and the discovery round complete. Another peer's handoff for `alpha` is refused. This runs with fake peers: it shows the hub's rules, not that a native model follows the guidance.
|
|
24
26
|
|
|
25
27
|
# General project example
|
|
26
28
|
|
package/docs/quickstart.md
CHANGED
|
@@ -17,7 +17,7 @@ ahub setup # installs the channel plugin and a pr
|
|
|
17
17
|
Or use the matching GitHub release:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
bun add -g github:STAIxBWLB/agent-hub#v0.
|
|
20
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.25.0
|
|
21
21
|
ahub setup
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -30,6 +30,10 @@ and upgrade or crash recovery, see the [operations guide](operations.md). To
|
|
|
30
30
|
move running projects to a newer release later, run `ahub upgrade` in a
|
|
31
31
|
terminal: it reviews the plan with you, applies it and follows it.
|
|
32
32
|
|
|
33
|
+
## Upgrading to 0.25.0
|
|
34
|
+
|
|
35
|
+
Nothing changes until you use something new. `ahub setup` now also registers agent-hub as a plugin of the family console ([agent-console](https://github.com/STAIxBWLB/agent-console), `acon ui`): it writes `~/.agenthub/console/plugins/ahub.json` with the sha256 of the installed `@staix/agent-hub/ui-plugin` module, so acon shows the dashboard's views as panels for every running project, lists pending approvals (Allow once / Deny) and in-review tasks whose reviewer is you in its inbox, and asks you to confirm every outward or destructive action first (#935, #1032). The same plugin feeds acon's Space view from the dashboard's office model: peers are actors, the board columns are zones and task links and recent messages are links, with labels and states only, never bodies, details or approval titles (#1012, #1033). acon refuses a module that changed after setup, so run `ahub setup` again after this and every later upgrade. The hub exact-pins `@staix/agent-mem` 0.18.0 (was 0.15.0); since agent-mem 0.16.0 every amem writer on one memory home, hub pins included, must run 0.16.0 or later, because an older drain deletes hook-spool records a newer one staged without reading them, so upgrade the hub together with a global `amem` of 0.16.0 or later (#1035, #1042). `ahub doctor` now reads its `memory capture: codex` and `memory capture: kimi` rows from that `amem` (`amem setup <tool> --json`, a preview that writes nothing) instead of `dot ai memory status`, and names `amem setup <tool> --apply` when setup is pending (#1025). Research campaigns are new and start only when you draft one: `ahub study draft` stores a contract (question, scope, sources, units and a stop policy) as a draft, a configured planner may draft and revise it with `hub_study_draft` and `hub_study_revise`, only your `ahub study approve` creates the unit tasks as Backlog cards for the planner to offer, unit owners record their evidence with `hub_study_handoff`, and `ahub study show` reports coverage per unit, budget support and why dispatch is held; `ahub study approve-protocol` approves one exact experiment protocol hash (#1024, #1027, #1028, #1029, #1031, #1041; see [research campaigns](operations.md#research-campaigns-ahub-study-930)). While campaign or experiment records exist, a restart, upgrade or recovery onto an older release is refused. A schedule in `config.local.json` can now take a five-field `cron` expression with an optional IANA `tz` instead of `every`; while one does, a restart or recovery onto an older release is refused (#1022; see [calendar schedules](operations.md#calendar-schedules-cron-and-tz-1017)). An owner can now re-file `hub_task_done` during review with a different full commit SHA, and the reviewer gets a fresh request for the new head (#1021). Work that belongs to another project that runs agent-hub on this machine can now go onto that project's board: `ahub task handoff --project <root|id> <title...>` (you) or `hub_task_handoff` (a planner or the conductor) files it there as an ownerless proposal when that hub is running and has the `task_handoff` feature, and `ahub board --handoffs` and the requesting agent learn each state change it reaches (#1030, #1043; see [handing work to another project](operations.md#handing-work-to-another-project-604-agent-hub1030)); while a handoff record exists, a restart, upgrade or recovery onto an older release is refused. A connection that names a peer but is not that peer's current Claude session, such as a plugin that inherited another project's hub, now joins as a distinct `<peer>~<id>` sender that can send and read but holds none of that peer's roles, and every message and attach in `events.jsonl` records the sending connection; nothing that connected before is refused, this tells sessions apart without authenticating them, and while an unsettled delivery holds such a session's message, a restart, upgrade or recovery onto an older release is refused (#1036, #1037). Upgrade running hubs with `ahub upgrade` and restart Claude sessions so they load the 0.25.0 plugin.
|
|
36
|
+
|
|
33
37
|
## Upgrading to 0.24.0
|
|
34
38
|
|
|
35
39
|
Nothing changes until you opt in. `ahub autonomy on` (person only, from a terminal) previews the shipped autonomy preset for this project and applies it with one confirmation: task auto-pull and sweep switches, permission and start defaults, and `action_policy.floor: true`. `--all-projects` applies it to every registered project. `ahub autonomy status [--json]` reports each peer's effective mode, where the floor is enforced and the Claude homes. `ahub autonomy off` removes the preset keys, including hand-set values of those keys, and the retained consent (#1009, #1010; see [project autonomy](operations.md#project-autonomy-one-preset-and-one-confirmation-999-t1)). `on` also writes the machine default `~/.agenthub/defaults.json` (owner-only, 0600), which a newly initialized project inherits; `off` disables it (see [machine autonomy](operations.md#machine-autonomy-and-claude-homes-999-t2)).
|
|
@@ -144,7 +148,7 @@ The restarted Claude planner gets the release-train tools (`hub_release_new`, `h
|
|
|
144
148
|
|
|
145
149
|
Jcode must be 0.98.0 or later, below 1.0.0; 0.94.0 is no longer accepted, so `ahub doctor` fails its Jcode row for an older install and the peer does not start (#529). Every owned jcode process runs with `JCODE_NO_TELEMETRY=1`.
|
|
146
150
|
|
|
147
|
-
Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.
|
|
151
|
+
Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.18.0. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
|
|
148
152
|
|
|
149
153
|
From 0.12.30 or earlier, existing Jcode project MCP entries need one refresh before conductor or dashboard starts. Once per project, the person runs:
|
|
150
154
|
|
|
@@ -212,7 +216,7 @@ More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-de
|
|
|
212
216
|
|
|
213
217
|
## Shared memory
|
|
214
218
|
|
|
215
|
-
The hub exact-pins the first-party `@staix/agent-mem` 0.
|
|
219
|
+
The hub exact-pins the first-party `@staix/agent-mem` 0.18.0 core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
|
|
216
220
|
|
|
217
221
|
Before any real generation migration, upgrade every hub sharing that memory home to 0.12.30 or later and check its authenticated binding with `ahub memory profile-check` in each custom registry. Ready bindings prove compatibility, not an atomic live cutover: quiesce consumers and close their handles before migrating, then reopen through the supported session-preserving lifecycle. Invalid or changed profiles disable native memory without falling back to legacy writes; the bus and board continue. See [memory operations](operations.md#hub-memory) for the remaining rollout gates.
|
|
218
222
|
|
|
@@ -245,6 +249,9 @@ load historical logs. An unopened dashboard has no listener. A paused budget
|
|
|
245
249
|
window cannot be overridden on the page; the CLI's `ahub budget resume <peer>`
|
|
246
250
|
remains the explicit override.
|
|
247
251
|
|
|
252
|
+
If you use the family console (`acon ui`), `ahub setup` registers the same
|
|
253
|
+
dashboard there as a plugin; see [the acon console plugin](operations.md#acon-console-plugin-935-1012).
|
|
254
|
+
|
|
248
255
|
|
|
249
256
|
## Concurrent project development (0.4.0)
|
|
250
257
|
|
package/docs/security.md
CHANGED
|
@@ -7,7 +7,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
|
|
|
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 started with `ahub claude`; otherwise a fixed header line (`frame()`, `[agent-hub message from ...]`) with an instruction that says it is untrusted, the plugin's server instructions for a tools-only Claude reading `hub_inbox` and 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`. The bearer header a Codex TUI sends to the proxy carries only the terminal launch id (#586): it binds terminal ownership and is not a credential. External clients cannot claim the console user's id or a hub-managed peer's id.
|
|
9
9
|
- **Permission prompts stay on unless an operator selects a permission mode.** `ask` leaves native decisions unchanged. `ask-when-needed` grants scoped Claude file tools or Pi/local writes and edits, and a headless Claude implementer's proven shell commands through the host wrapper (#845, below); Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation, a person's console `y` for a config default at hub start, or retained autonomy consent bound to that peer's exact never-ask value in the untracked `config.local.json` (#999, below). Without one of these the peer runs in `ask`. 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 invoke hub-owned operations rather than arbitrary file or shell tools, and every call passes the hub's own checks. A call the always-ask policy classes (below) is never approved this way, whatever its title. Identity comes from the exact permission title, or from the earlier tool-call title bound to the same call id and resolved against the configured MCP servers when the permission title contains argument JSON (Qwen). Payload text and unrelated display titles never establish identity. Configured ACP kinds (#451) never get this exception: a request whose title spells a hub tool exactly still goes to the person approval path, and only a mapped native mode changes what the agent itself asks. For ACP agents, a payload longer than the console shows is marked as cut and never offers a session-wide grant. Pi's write, edit, shell and git write requests are relayed the same way and can be answered `Always allow <tool> until Pi restarts`: later calls of that tool by the same Pi start skip the prompt but stay inside the path guard and sandbox. The grant covers every later call of that tool whatever its payload, is kept in memory only, cannot be given from the dashboard, and each call it allows is logged by tool name. A Pi or `local` request whose call was cancelled or whose turn ended is withdrawn at once: a later answer is refused and grants nothing, an `Always allow` included. One shell command runs without a hub approval: the `!command` a person types in the managed Pi TUI. It stays inside the same sandbox and denylist, and the hub takes it only from the verified owner process of a Pi in TUI mode, while Pi is idle, on a reservation that is used once; a model's own bash call never takes that path (issue #253). `--unattended` turns prompts off, says so loudly, and is never the default; a classed call from a hub-owned ACP peer, the local worker or Pi still waits for a person under it.
|
|
10
|
-
- **A committed config cannot choose launch commands, credential files, data endpoints, permission modes or a wider sandbox.** The machine-local fields (`kimi_cmd`, `permission_modes`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url` (still accepted, ignored since 0.16.0), `local.read_allow`, `local.bash_network`, `local.network_allow`, `local.doc_read`, `local.web_fetch`, `terminal.open`, `tasks.auto_pull`, `tasks.queue_backpressure`, `context.max_assignment_used`, `schedules`, `folder_triggers`, `corpus.paths`, `invocation`, `instances`, `action_policy`, `rooms`) apply only from a config file git confirms nobody committed (`instances`, `action_policy` and `rooms` are read only from an untracked `.agenthub/config.local.json`; in `config.json` they are ignored even when untracked): `.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, a permission mode, the command that opens a terminal, a folder the hub reads, work proposed on a timer, a host a model may fetch from, which of your agents' skills other peers may call, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. 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. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot Git calls of one burst share a 250 ms budget, each call at most 10 s; a boundary that runs out is recorded as unknown, never as a stale baseline) (#576). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
|
|
10
|
+
- **A committed config cannot choose launch commands, credential files, data endpoints, permission modes or a wider sandbox.** The machine-local fields (`kimi_cmd`, `permission_modes`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url` (still accepted, ignored since 0.16.0), `local.read_allow`, `local.bash_network`, `local.network_allow`, `local.doc_read`, `local.web_fetch`, `terminal.open`, `tasks.auto_pull`, `tasks.queue_backpressure`, `context.max_assignment_used`, `schedules` (the `every`, `cron` and `tz` forms alike), `folder_triggers`, `corpus.paths`, `invocation`, `instances`, `action_policy`, `rooms`) apply only from a config file git confirms nobody committed (`instances`, `action_policy` and `rooms` are read only from an untracked `.agenthub/config.local.json`; in `config.json` they are ignored even when untracked): `.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, a permission mode, the command that opens a terminal, a folder the hub reads, work proposed on a timer, a host a model may fetch from, which of your agents' skills other peers may call, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. 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. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot Git calls of one burst share a 250 ms budget, each call at most 10 s; a boundary that runs out is recorded as unknown, never as a stale baseline) (#576). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
|
|
11
11
|
- **Telemetry holds no bodies.** `.agenthub/state/events.jsonl` (issue #40) records envelope ids, routing and sizes, task ids and states, overlapping paths and token counts, and the task id (with a PII flag) that each usage and token record is attributed to. It never records a message body, a task title or detail, and marks private (PII) envelopes and tasks as such. Budget pauses, resumes and quota resets carry the peer, window, times and the hub's own reason text, and a person's release authorization its commit, version and docs digest. The usage roll-up (#683) folds the usage, token and limit records into `hub.db` and joins issue, PR and release from the board when read; `ahub usage` and the console's Usage panel answer only the console and a configured planner, and a planner not during a private turn. Kimi's token records (#285) are counted from its own session wire log under its home: only the four token integers, the model label and the row time are copied, and the record id is an opaque hash, never a path, session id or text. The log stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved task the ids, states, counts, tokens, times and PII flag the events hold, a person's label from a closed list, and a hash in place of the project id; benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
|
|
12
12
|
- **Snapshots stay in your repository.** Per-turn snapshots (issue #33) are git objects in the project's own object store, written through a temporary index; nothing is referenced, pushed or copied elsewhere, and `git gc` prunes them. They hold what the work tree held, including untracked files that are not ignored, so keep secrets in ignored files. They carry the repository's own permissions, and nothing caps their disk use but `git gc`. A turn of a peer holding an open PII task is not snapshotted; a PII file left in the project is snapshotted by later turns like any other file. `ahub undo` restores only files whose current content is exactly what the turn left.
|
|
13
13
|
- **The edit hook reads, never decides.** `ahub check-path --hook` (issue #32) reads hub.db and returns context for Claude and a line for you; it sets no permission decision, so your permission rules stay in charge. It names other owners' task ids, titles and states, which then reach Claude's model; PII tasks are left out.
|
|
@@ -24,13 +24,13 @@ agent-hub connects agents that can each run commands. This page says what the hu
|
|
|
24
24
|
|
|
25
25
|
## Native memory authority and profile selection
|
|
26
26
|
|
|
27
|
-
The portable core is the exact-pinned first-party `@staix/agent-mem` 0.
|
|
27
|
+
The portable core is the exact-pinned first-party `@staix/agent-mem` 0.18.0 package; it does not replace the hub's caller identity, PII policy or task-write authority. Deny, hardened git, real paths and process identity are single-sourced in its Node-safe platform entry. Current project privacy is applied by the hub before promotion and again before model-facing reads. Standalone person reads are unscreened, and the standalone `--policy-free` acknowledgement grants no hub or peer authority.
|
|
28
28
|
|
|
29
29
|
`hub_memory_search`, `hub_memory_show` and `hub_memory_graph` take the project from the daemon, require `memory_read`, refuse private turns and validate UTF-8 input before logs or storage work. Complete records and graph metadata are screened before counts and truncation. Search is at most 20 lines/4 KiB (default 8); show accepts at most 8 ids and returns at most 8 KiB; graph is at most 120 nodes/240 edges/6 KiB (default 60 nodes), with no edge lacking an endpoint. Unknown, ambiguous and withheld ids share one neutral refusal. Arguments and results are excluded from hub-managed capture; this does not govern a vendor's independent transcript or memory hooks.
|
|
30
30
|
|
|
31
31
|
Code neighbours (#684 phase 4). For a symbol start, `hub_memory_graph` and the dashboard Memory view's Code neighbours toggle add one hop of callers and callees, read at the current commit through the optional code-facts boundary and shown with that commit; these edges are never stored. They share the graph limits above, an ambiguous or name-only seed is withheld, attached notes are screened again when published, current HEAD, profile, policy and caller privacy are rechecked across awaits, and a PII task or private turn starts no code query. `ahub doctor --stale-anchors` (#684 phase 3) only reads: it never edits, deletes or repairs a note, and an anchor whose task record is now private, foreign or missing stays unknown.
|
|
32
32
|
|
|
33
|
-
`hub_memory_correct` requires `memory_correct` and proposes a machine-local candidate after the same project/privacy checks, including revalidation after asynchronous screening. It cannot accept or alter a note. Public review failures with unmet items may also propose candidates through Tasks, and `ahub ask --remember` answers and context checkpoints are saved only as candidates, never as curated notes (agent-mem#13 P4). Candidates and usage counters stay at `M/candidates.db`, are not synced/imported, and never enter recall, briefs, ask or graph evidence. A person accepts shared-v1 candidates through the package CLI's TTY/policy checks; the accepted result is an ordinary v1 note, while the sidecar remains local. Generation `amem review`
|
|
33
|
+
`hub_memory_correct` requires `memory_correct` and proposes a machine-local candidate after the same project/privacy checks, including revalidation after asynchronous screening. It cannot accept or alter a note. Public review failures with unmet items may also propose candidates through Tasks, and `ahub ask --remember` answers and context checkpoints are saved only as candidates, never as curated notes (agent-mem#13 P4). Candidates and usage counters stay at `M/candidates.db`, are not synced/imported, and never enter recall, briefs, ask or graph evidence. A person accepts shared-v1 candidates through the package CLI's TTY/policy checks; the accepted result is an ordinary v1 note, while the sidecar remains local. Generation `amem review` is supported since agent-mem 0.7.0, subject to the package CLI's applicable TTY and policy checks. Citation counters are observation proxies, not evidence that an agent understood or used a note.
|
|
34
34
|
|
|
35
35
|
The hub binds the selected root/profile at open. Invalid/missing generation metadata refuses native memory without a shared-v1 fallback; the bus and board continue. Explicit checks at hub operation entry and before sidecar writes stop later stale calls, but are not atomic against a selector change during an in-flight core operation. Migration therefore requires quiescing consumers and closing handles before changing selection, then reopening through the supported lifecycle. A public core pre-write hook is the upgrade path for atomic in-flight cutover.
|
|
36
36
|
|
|
@@ -417,6 +417,33 @@ This is an honest default against accidental impersonation and injected commands
|
|
|
417
417
|
It does not make the token inaccessible to an agent with unrestricted project
|
|
418
418
|
shell access. The existing OS sandbox and loopback authentication boundaries apply.
|
|
419
419
|
|
|
420
|
+
Distinguishable sessions and sender provenance (#1036, #1037) make a mix-up
|
|
421
|
+
between two sessions of one person visible; they are not authentication. A tools
|
|
422
|
+
hello that names a Claude channel peer while presenting a launch id that is not
|
|
423
|
+
that peer's current launch record, or that asks to be a session (the channel
|
|
424
|
+
plugin does when its process has no project of its own and inherited another
|
|
425
|
+
project's state directory), is bound to an id the daemon mints, `<peer>~<origin>`.
|
|
426
|
+
Such a session may send, read status and run the read task ops in
|
|
427
|
+
`SESSION_READ_OPS`; every other message is refused with `this session is <id>,
|
|
428
|
+
not this project's <peer>`. It is never a bus peer: no delivery, no push, none of
|
|
429
|
+
the peer's roles, and it replaces nobody. Its envelopes carry `session: true` and
|
|
430
|
+
recipients see the minted id in the header; an origin label is kept only when it
|
|
431
|
+
already is a plain lowercase token, so no path or sentence reaches a sender name.
|
|
432
|
+
Every envelope event and one `attach` event per admitted peer or tools hello in
|
|
433
|
+
`events.jsonl` carry `origin`: the connection's role, peer and launch or instance
|
|
434
|
+
id, never a path, cwd, token or body. The channel plugin applies the CLI's
|
|
435
|
+
ownership proof to an inherited `AGENTHUB_STATE_DIR` instead of trusting it, and
|
|
436
|
+
does not join a hub whose `status.json` lacks `session_identity` as a session,
|
|
437
|
+
since that hub would bind the real peer id. What this does not guarantee: a hello
|
|
438
|
+
with no launch id still becomes the peer (the native facts hook, helpers and a
|
|
439
|
+
person's shell connect that way), a launch id is an environment value every child
|
|
440
|
+
of the session inherits, not a secret, and a same-user process that reads the
|
|
441
|
+
control token can present the right id, present none, or connect as the console.
|
|
442
|
+
Nothing that connected before is refused. A recipient weighs a `<peer>~<origin>`
|
|
443
|
+
sender as another session's message; a plain peer id proves no more than it did
|
|
444
|
+
before. A restart, upgrade or recovery onto a daemon without `session_identity`
|
|
445
|
+
is refused while an unsettled delivery holds a session's message.
|
|
446
|
+
|
|
420
447
|
Steering tools require an explicit `conductor` role, independent of default-allow
|
|
421
448
|
capabilities. Only one peer may hold that role. Two task-scoped exceptions need
|
|
422
449
|
no role: a task's current owner and reviewer may read its public view with
|
|
@@ -565,6 +592,42 @@ not inherit state, unattended or gateway URL/key overrides from the session that
|
|
|
565
592
|
opened the manager. Native memory is deliberately not a project-isolation boundary:
|
|
566
593
|
its existing basename/worktree aliases and provider accounts can remain shared.
|
|
567
594
|
|
|
595
|
+
## acon console plugin (#935, #1012)
|
|
596
|
+
|
|
597
|
+
`@staix/agent-hub/ui-plugin` runs inside the agent-console (`acon`) process as the
|
|
598
|
+
same user. It holds no credential of its own: it reaches project hubs only through
|
|
599
|
+
the multi-project manager's listing and forwarding above, which connect as the
|
|
600
|
+
console with each project's control token, so it has exactly what `ahub ui --all`
|
|
601
|
+
has. Every action must be on the manager's closed list and every view one of the
|
|
602
|
+
seven view names, and there is no generic control proxy, task-detail read, shell,
|
|
603
|
+
file access or caller-chosen command.
|
|
604
|
+
|
|
605
|
+
- **Registration.** `ahub setup` writes `console/plugins/ahub.json` under the hub
|
|
606
|
+
home (directory 0700, file 0600, atomic replace) with the module path and its
|
|
607
|
+
sha256; acon refuses to load a module whose hash no longer matches.
|
|
608
|
+
- **Confirmation.** Outward or destructive actions (`stop`, `permit`, `send`,
|
|
609
|
+
`setting`, `setting_undo`, `setting_reload`, `config_edit_apply`, `start_peer`,
|
|
610
|
+
`backlog_split` and the inbox's Allow once and Deny) first answer `needsConfirm`
|
|
611
|
+
with a preview and a fingerprint, a sha256 of the request's own fields; the call
|
|
612
|
+
runs only when it comes back with `confirmed` and that fingerprint. The
|
|
613
|
+
fingerprint binds a confirmation to one exact request, so a changed request needs
|
|
614
|
+
a new confirmation; it is not a secret, and the confirm step is acon's host UI
|
|
615
|
+
contract, not authentication. A process that can load the module already has the
|
|
616
|
+
console's reach, and the hostile-agent boundary above applies unchanged.
|
|
617
|
+
- **Inbox.** Items are the pending approvals and the in-review tasks whose reviewer
|
|
618
|
+
is the person, read from the same redacted dashboard snapshot: a local-worker or
|
|
619
|
+
Pi approval shows only the fixed private stub and offers Deny, and Allow once for
|
|
620
|
+
it is refused with "answer this approval with ahub permit". Allow once and Deny
|
|
621
|
+
resolve to the hub's own `permit` with an option the pending request offers,
|
|
622
|
+
looked up again at the time of the call; nothing else is reachable from an item.
|
|
623
|
+
Task titles are the public view's, so a PII task shows the `[pii]` stub.
|
|
624
|
+
- **Space.** The Space scene is built only from the office model the dashboard
|
|
625
|
+
already allowlists (#264): peer ids, conditions and counts, task ids, classes,
|
|
626
|
+
states, owners and public titles, approval counts, and overlap, conflict, cohort
|
|
627
|
+
and message link ids. No envelope body, task detail, approval title, reason, path
|
|
628
|
+
or symbol reaches it, a PII task never joins a link, and the scene is trimmed to
|
|
629
|
+
acon's caps. `test/acon-plugin.test.ts` pins this with a marker fixture.
|
|
630
|
+
|
|
568
631
|
## Peer roster authority
|
|
569
632
|
|
|
570
633
|
The running peer roster changes only through console-only settings requests.
|
|
@@ -760,6 +823,95 @@ Person release authorization (#481 S6). `ahub sdlc authorize-release <version>`
|
|
|
760
823
|
|
|
761
824
|
SDLC metrics and control bands (#481 S7). `ahub sdlc metrics` and `hub_sdlc_metrics` are planner or person only, read-only, and refused during a private or PII turn. A private task is counted as withheld and read for nothing else; the report calls no model and makes no GitHub read. Band breaches are written only by the daemon's evaluator through Tasks, never while it is stopping, recovering or has a storage error. A `diagnose` or `propose` card is a backlog task (`triage` or `plan`) whose text is fixed words, numbers and task ids, never task text; it passes the PII signal check and the candidate-text screen before it is written, and no GitHub issue is created. A `log` breach is one hub notice, remembered in the board's `band_notices` table as a marker (`<metric>:#<task>:log`, checked against a fixed pattern) and a time, nothing else.
|
|
762
825
|
|
|
826
|
+
## Research campaigns and study tools (#930, #932)
|
|
827
|
+
|
|
828
|
+
Campaign authority is the person's. `ahub study draft|revise|approve|approve-protocol|close`
|
|
829
|
+
are Console in `classifyPeerCommand`, and the daemon answers the `study_*` operations
|
|
830
|
+
to the console role only; `ahub study show` is the only form an agent shell may run.
|
|
831
|
+
Agents use hub tools instead: `hub_study_draft` and `hub_study_revise` take the
|
|
832
|
+
configured planner's planning authority and store drafts only, `hub_study_show` is
|
|
833
|
+
read-only for every connection, and `hub_study_handoff` takes only the current
|
|
834
|
+
in-progress owner of the bound unit task. No tool approves a campaign or a protocol,
|
|
835
|
+
closes a campaign or attests anything, and a unit's `purpose` or a research role
|
|
836
|
+
grants no authority: dispatch, ownership and review use the existing routing and
|
|
837
|
+
role admission.
|
|
838
|
+
|
|
839
|
+
- **Approval creates work, nothing else does.** Only the person's `ahub study approve`
|
|
840
|
+
creates unit tasks, as Backlog cards the planner offers through its ordinary pull.
|
|
841
|
+
The approval revalidates and screens the revision before a single transaction
|
|
842
|
+
that rechecks it is still the latest draft; a changed campaign, task or
|
|
843
|
+
dependency refuses whole. A campaign never assigns an owner or approves a task.
|
|
844
|
+
- **Dispatch is rechecked at the write.** Before a unit task is offered, assigned,
|
|
845
|
+
pulled or handed to another owner, after any asynchronous screen, the hub rereads
|
|
846
|
+
the stored campaign and holds the task when the revision is not the latest
|
|
847
|
+
approved one, the deadline has passed or the stored stop policy is exceeded. A
|
|
848
|
+
contract's `budget.hardCap` is checked against that exact budget id at approval
|
|
849
|
+
and again at every dispatch and owner change; another budget covering the task
|
|
850
|
+
proves nothing. A hold never stops a running native turn.
|
|
851
|
+
- **Provenance is never upgraded.** Experiment fields are `declared` unless the hub
|
|
852
|
+
measured them itself from a finished check receipt for the same task and commit,
|
|
853
|
+
or by hashing a deliverable through the #509 path policy; an agent-supplied hash
|
|
854
|
+
is never host evidence. `ahub study approve-protocol` binds approval to one exact
|
|
855
|
+
protocol hash and evaluator identity hash, and nothing copies it to a changed
|
|
856
|
+
protocol. Environment descriptors are refused when they contain assignment
|
|
857
|
+
forms, Bearer credentials or known secret shapes; that screen detects those forms
|
|
858
|
+
only and does not prove a secret is absent. This release exposes no hub tool for
|
|
859
|
+
recording experiments, and runner attestation (`research.runners`) is not
|
|
860
|
+
implemented yet.
|
|
861
|
+
- **Privacy.** A campaign whose parent task is PII, or that names a private source,
|
|
862
|
+
is private, and so is an attempt with a private task, input or output path.
|
|
863
|
+
Public views keep ids, counts, states and provenance and stub every question,
|
|
864
|
+
scope, text, path, excerpt and metric name as `[pii]`; source text is never stored
|
|
865
|
+
in a campaign or handoff, only source ids. Study tool arguments and results are
|
|
866
|
+
excluded from hub-managed memory capture, and raw contract, handoff and attempt
|
|
867
|
+
text never enters task history, notices or memory.
|
|
868
|
+
- **Recovery.** A restart, upgrade or recovery onto a daemon lacking the matching
|
|
869
|
+
`research_*` feature is refused while campaign, dispatch, re-offer, experiment or
|
|
870
|
+
comparison rows exist, or while `research.runners` is configured.
|
|
871
|
+
|
|
872
|
+
## Cross-project task handoff (#604, #1030)
|
|
873
|
+
|
|
874
|
+
A handoff crosses a project boundary, but not a security boundary: both projects
|
|
875
|
+
belong to the same machine user. The origin daemon reads the target's control
|
|
876
|
+
token file and connects to the target's daemon as the `console` role through
|
|
877
|
+
`ControlClient`, like every control message; the hello checks the target's project
|
|
878
|
+
root and instance. It sends that connection only the three handoff messages, and
|
|
879
|
+
it has no reach a same-user process lacks (see "What is not defended").
|
|
880
|
+
|
|
881
|
+
- **Who may hand off.** `ahub task handoff` is Console in `classifyPeerCommand`;
|
|
882
|
+
agents use `hub_task_handoff`, admitted for the person, a configured planner or
|
|
883
|
+
the conductor only, and never for a remote room peer. The target is chosen only
|
|
884
|
+
from the machine registry by canonical root or id, this project is refused, and
|
|
885
|
+
the target's own `status.json` must advertise `task_handoff` and name no other project. A target
|
|
886
|
+
that is not running is refused with the action; nothing is queued.
|
|
887
|
+
- **No authority in the target.** The target answers `task_handoff`,
|
|
888
|
+
`task_handoff_event` and `task_handoff_states` to a console connection only, and
|
|
889
|
+
refuses a new handoff while it recovers or shuts down. The task is an ownerless proposal
|
|
890
|
+
its own hub makes and routes. The origin project and peer are a record in
|
|
891
|
+
`task_handoff_origin` and a line at the end of the detail, never an actor, a
|
|
892
|
+
roster peer or a role: the origin cannot accept, review, approve, assign or
|
|
893
|
+
cancel there.
|
|
894
|
+
- **What crosses.** To the target: a title (300 characters), a class, a detail
|
|
895
|
+
(4,000), up to 16 repository-relative paths, a spec issue number, and the origin
|
|
896
|
+
project id, peer id and task id, at most 8 KiB in all. The request is parsed
|
|
897
|
+
closed on both sides: an unknown field, an accessor or a control character is
|
|
898
|
+
refused, never dropped, and the target sanitizes before it stores. No memory,
|
|
899
|
+
evidence, turn content or task history crosses. Back to the origin: an event
|
|
900
|
+
name, a PR number and a full 40-hex head, never the target's title, notes or
|
|
901
|
+
other text; the restart catch-up answers only for tasks whose recorded origin
|
|
902
|
+
is the asking project, with the same fields. The origin peer gets that as a
|
|
903
|
+
hub-written `kind: task` message.
|
|
904
|
+
- **Privacy.** The origin refuses during a private turn and for a `[pii]` task
|
|
905
|
+
named as the source, before anything leaves. The text itself is checked by the
|
|
906
|
+
target, against the target's PII patterns and, when the target's model screen is
|
|
907
|
+
on, that screen under the target's routing; a match or an uncleared verdict
|
|
908
|
+
refuses the whole handoff and nothing is stored on either board. The origin's
|
|
909
|
+
own patterns are not applied to the text, so keep private text out of a handoff.
|
|
910
|
+
`hub_task_handoff` and `hub_task_handoffs` arguments and results are excluded
|
|
911
|
+
from hub-managed memory capture.
|
|
912
|
+
- **Recovery.** A restart, upgrade or recovery onto a daemon lacking
|
|
913
|
+
`task_handoff` is refused while either handoff table holds a row.
|
|
914
|
+
|
|
763
915
|
## Bounded task specs and isolated checks (#418)
|
|
764
916
|
|
|
765
917
|
`hub_task_spec` reads only the GitHub issues named by a current owner's or reviewer's task in this project's origin repository, including the validated canonical GitHub intake source when its title/body has no issue token. Bare `#N` references count only in the title; detail references require `owner/repo#N` or `https://github.com/owner/repo/issues/N`. Bare detail numbers denote hub tasks, and arbitrary file paths are not parsed as issue references. A qualified reference cannot begin after a slash or a name character, so `src/hub/tasks.ts#12` is a file path. Routing uses this same parser. Without a GitHub project identity, bare title numbers do not require spec access, but explicit qualified/URL references, `refs.repo` and imported GitHub identity still fail closed. `refs.repo`, which `hub_task_propose` and `hub_task_accept` take since #476 to scope overlap per repository, is metadata only: it grants no file access or execution root, and a spec read for a task naming another repository refuses. The daemon resolves authenticated and container executables outside the project, pins their absolute paths and uses fixed arguments; repository-writable PATH shims are refused. Runner Git resolves its host executable through a restricted PATH. For specs, the model receives no credential or arbitrary authenticated URL capability. Foreign/transferred issues, more than three references, bodies over 64 KiB and aggregate results over 80 KiB fail explicitly. Spec fetches share a 25-second budget inside the control client's 30-second cap; each gh call remains bounded to 15 seconds. Current PII, secret and private-path policy withholds the complete spec rather than returning a partial contract. PII turns and PII tasks refuse these tools. The independent `spec_read` and `check_run` capabilities apply when a peer has a narrowed capability list. Arguments and results are excluded from capture. Proven deterministic pre-effect guards return PreEffectToolRefusal so Pi settles the exact refusal receipt; generic transport/storage failures and failures after durable receipt/output changes remain uncertain, never reclassified from error text.
|
|
@@ -4700,6 +4700,12 @@ ahub builds the review brief that the workspace gate used to take from `ocr dele
|
|
|
4700
4700
|
- **Coverage gate.** `hub_review` takes `coverage: { reviewed: [paths], skipped: [{ path, reason }] }`. An approval must account for every reviewable path. The gate follows the lifecycle mode (deviation from the issue, which refuses always): `enforce` refuses before any write, naming the paths, and also refuses when the brief is unavailable (an unknown inventory is never an empty one); `advise` records `Coverage gap (N): ...` or `Coverage unknown: ...` in the verdict note; `off` and a person's own verdict skip it. The commit under review is the task's commit, else the head the verdict names, else the head its lifecycle receipt records. Code work (a task with a commit, branch or PR) for which none of these is known, or whose brief cannot be built, has an unavailable brief. Work with no commit, branch or PR is exempt, with nothing to account for. So is a `Tasks` built without a brief provider (library use and tests; the daemon always provides one). One head throughout: the head the verdict names, or the `reviewedHead` it would inherit from an earlier review, must equal the brief's head (with a PR, the lifecycle approval gate already binds the reviewed head to the PR head). The task's commit and its lifecycle receipt (which holds the inherited `reviewedHead`) are pinned when coverage is checked and rechecked before every write after an await, so a commit or receipt that moves during the verdict is refused.
|
|
4701
4701
|
|
|
4702
4702
|
|
|
4703
|
+
### Owner re-file during review (#1000)
|
|
4704
|
+
|
|
4705
|
+
The owner may submit `hub_task_done` again while the task is `in_review` with a different full lowercase commit SHA. This replaces the delivered artifact, not a reviewer's approval. Same-head, malformed-head, foreign-owner and active-check submissions refuse without effects. Normal privacy, lifecycle, readiness and completion-check gates still apply; a failed admission leaves the old review intact. Concurrent submissions are fenced by their initial history revision, so a delayed submission cannot overwrite a newer delivery.
|
|
4706
|
+
|
|
4707
|
+
The new delivery clears stale observed/reviewed/merge evidence and retains local check evidence only when it already names the new head. Spec acceptance, open Important findings and rejection counts remain. A configured completion check atomically returns the task to `in_progress` with the new head; failure leaves it with the owner, and success publishes the new review. Other state transitions are unchanged. Every verdict, even without an explicit head in off mode, is fenced by the request revision it began reviewing. A refreshed delivery publishes a new request and replaces the old CI watcher; it never records a rejection or escalation.
|
|
4708
|
+
|
|
4703
4709
|
### Optional changed symbols (#682, #684 phase 1a)
|
|
4704
4710
|
|
|
4705
4711
|
`src/hub/code-facts.ts` is the sole runtime caller of optional ripwire. The initial verified CLI contract is 0.6.7: strict `ripwire.pr-context/v1` and `ripwire.at/v1` XML (these verbs do not support JSON), and validated callers/callees JSON keys. Unknown versions, schemas, malformed/capped output, tool failures and timeouts give `unknown`, never a passing gate. The rendered advice has a 16000-byte ceiling (oversize is unknown). Parse metadata values only; discard raw text, owners, author email and source fragments. Calls have a 5 s execution deadline and an owned-process cleanup bound; cache successful values by canonical repo, SHA and query (including the base for a diff). A busy reader gives new distinct queries unknown instead of making review wait in a queue. Name-only graph edges are marked ambiguous and hidden; graph count floors are retained.
|
|
@@ -5188,7 +5194,7 @@ Where the code differs from or fixes a detail the section above leaves open:
|
|
|
5188
5194
|
### Schedules and folder triggers (#512)
|
|
5189
5195
|
|
|
5190
5196
|
- `schedules` is a machine-local key: add it to `MACHINE_LOCAL` in `config-trust.ts`, so only `config.local.json` can set it. A committed config that creates tasks is refused, like the other machine-local keys.
|
|
5191
|
-
- Entry: `{id, every, class, title, detail?, sources?, owner?, queue?: "backlog" | "ready"}`. `every` is `<n>m|h|d` with a 15-minute minimum. `cron`
|
|
5197
|
+
- Entry: `{id, every, class, title, detail?, sources?, owner?, queue?: "backlog" | "ready"}`. `every` is `<n>m|h|d` with a 15-minute minimum. `cron` was refused here until #1017; see "Calendar-anchored schedules (#1017)" below for the calendar form.
|
|
5192
5198
|
- Due checks run on their own interval timer, started whenever `schedules` or folder triggers are configured, independent of `task_sweep.enabled` (the task sweep's timer exists only when that option is on, and it defaults to off). The timer runs no task sweep, and it is re-armed on config reload. `queue` defaults to `backlog`. Missed runs coalesce: a schedule proposes at most one task per tick, and none while its previous task is still open. A global pause or hold skips the tick and coalesces it. Last and next run times live in `hub.db`, and status and the dashboard show the next run.
|
|
5193
5199
|
- Folder trigger: `{id, folder, glob?, class, title, ...}`. `folder` is resolved with `realPath`, must lie inside the project or a declared data path, and is polled on the schedules timer, not watched. Symlinks that leave the folder are skipped. A new file (by `realPath`, size, mtime and SHA-256 recorded in `hub.db`) is registered as a source (#508) and proposes one task that names it. The trigger therefore lands after the source registry.
|
|
5194
5200
|
- `ahub schedule list|run <id>|pause <id>|resume <id>` is person-only for `list`; `run`, `pause` and `resume` are also the configured conductor's since #933 (implementation notes in #512b below).
|
|
@@ -5220,6 +5226,20 @@ Where the code states or fixes a detail the section above leaves open:
|
|
|
5220
5226
|
- **Dashboard.** The UI snapshot carries `schedules` (the same views, ids, enums and times only; absent when none is configured, so an older page and an unconfigured project see no change). The Agents view has a read-only Schedules table, built from the existing `usage-table` classes so no style changes, with a next-run column worded by `scheduleNextLabel`: the time for `scheduled`, `Paused (would be <time>)`, `Due now`, `Due, waiting for task #N to close` or `Due, held`, and `unknown` for anything else, never an invented time. The dashboard has no run, pause or resume control: those stay the person's CLI commands.
|
|
5221
5227
|
- **Not in this slice.** Folder triggers (#512c) and cron remain refused or absent.
|
|
5222
5228
|
|
|
5229
|
+
#### Calendar-anchored schedules (#1017)
|
|
5230
|
+
|
|
5231
|
+
Issue #1017 (the person's), AC1 to AC5. It replaces the "cron not supported yet" refusal and changes nothing about `every`.
|
|
5232
|
+
|
|
5233
|
+
- **Entry.** `{id, every | cron, tz?, class, title, ...}`. Exactly one of `every` and `cron`; both is refused ("give every or cron, not both"), neither is refused, and `tz` without `cron` is refused. The refusals name the entry like every other config error. `cron` is stored with its fields single-spaced; `tz` is stored as the runtime spells the zone (`asia/seoul` is `Asia/Seoul`).
|
|
5234
|
+
- **Expression.** The standard five fields with numbers, lists, ranges and steps (`*/n`, `a-b/n`; a bare `a/n` is refused); day of week 0 and 7 are Sunday. No seconds, no year, no `@` macros, no names, at most 100 printable characters. Both day fields restricted: a day matches if either does; a field that begins with `*`: both must match (the classic rule); the month always has to match.
|
|
5235
|
+
- **Zones.** `tz` is an IANA name validated with `Intl.DateTimeFormat`; without it the machine's own zone. No third-party dependency (AGENTS.md): the zone arithmetic is `Intl` and `Date` only, in `src/hub/schedules.ts` (`parseCron`, `cronNext`, `cronMinGapMinutes`, `scheduleNext`, `scheduleZone`), pure, each taking the time it works from.
|
|
5236
|
+
- **Daylight saving, the one rule.** Fires are found on the zone's wall clock and then placed on the timeline: a wall time the clock skips runs at the first valid minute of that day (`30 2 * * *` on the spring-forward day runs at 03:00), and a wall time the clock repeats runs once, at its first occurrence (`30 1 * * *` on the fall-back day runs at the first 01:30). A wall time never maps to two fires.
|
|
5237
|
+
- **Too frequent (AC3).** `schedulesConfig` refuses an expression whose smallest gap between fires, read on the calendar over one 400-year Gregorian cycle, is under 15 minutes, with the gap in the reason, and an expression that never fires. Daylight saving can move two fires closer on its one change day; a schedule proposes at most one task per pass and none while its previous task is open, so that costs no flood. ponytail: the gap is read on the wall clock, not per zone; upgrade is to walk `cronNext` over the change days of a zone.
|
|
5238
|
+
- **Next run and coalescing (AC4).** The period still starts at the anchor (first sight, then the last run) and the schedule is due once `scheduleNext(entry, anchor)` has passed, where `every` is anchor plus the period and `cron` is the next fire strictly after the anchor. A run restarts the period at `now`, so any number of missed fire times is one run: nothing a restart, a hold or a long outage missed is replayed. Holds, budget pauses, the open-task rule, the pause and resume commands, and the fences are `runSchedule`'s, unchanged. A cron with no matching day in a full 400-year Gregorian cycle (146097 days, after which every date and weekday pattern repeats; a Feb 29 that is a Sunday recurs 28 to 40 years apart, so a shorter scan would call it never) has a next run of `Number.MAX_SAFE_INTEGER` and never runs.
|
|
5239
|
+
- **Where it shows.** A view carries `cron` and `zone` instead of `every`; the task's detail says `Scheduled on the calendar "<cron>" (<zone>)`. `ahub schedule list` has a SCHEDULE column (`cron 30 7 * * 1-5 (Asia/Seoul)` or `every 1h`) and the next run in the schedule's zone with the relative time; `ahub status` and the dashboard read the same way (the dashboard shows the time in the schedule's zone, and says so when the browser does not know it). An `every` schedule reads as before.
|
|
5240
|
+
- **Older targets.** An older daemon refuses a `cron` or `tz` key at load ("cron not supported yet"), so a controlled recovery (`prepare`, `commit` and a target-checking `inspect`) refuses a target without the daemon feature `calendar_schedules` while a raw `schedules` entry in `config.json` or `config.local.json` has either key, before any effect. The raw files decide, so an entry the effective config drops still counts; an unreadable file is unknown, never absent. A project with only `every` entries is unchanged.
|
|
5241
|
+
- **Not here.** Holiday and exchange calendars.
|
|
5242
|
+
|
|
5223
5243
|
#### Folder triggers (#512c) implementation notes (hub task #336: `folder_triggers`, `src/hub/folder-trigger.ts`)
|
|
5224
5244
|
|
|
5225
5245
|
- **Config.** `folder_triggers` is a separate machine-local key (not inside `schedules`, whose entries are time-based), in `MACHINE_LOCAL` and `NEVER_EDITABLE`, validated by the pure `foldersConfig` in the loader and in `config-apply.ts`. Entry: `{id, folder, glob?, class, title, detail?, owner?, queue?}`, at most 16. `glob` is a file-name pattern (letters, digits, `. _ -`, space, `*`, `?`; never a path) that matches whole names; a dot file never matches.
|
|
@@ -6426,3 +6446,7 @@ C2 runs `read` invocations end to end. `write` and `external` stay refused (the
|
|
|
6426
6446
|
The writing domain pack is named `writing-pack` (base `writing`), preserving the existing rule that a pack cannot shadow a profile. `analysis` uses `research` and the existing analyst role; `ops` uses `general`. The catalog adds four original writing roles (editor, translator, style-checker, terminology-checker) and two original ops roles (project-manager, meeting-actions-owner). Their guidance grants no authority, and pack loading does not assign peers automatically.
|
|
6427
6447
|
|
|
6428
6448
|
Each pack supplies criterion ids in its rubric and exactly one advisory check entry. Standalone examples in `templates/checks/` validate writing JSON metadata/literal terminology/mechanical style, rerun an explicitly selected analysis program twice against declared input/expected bytes, or validate meeting-action owner/due/status records. They are shipped with synthetic fixtures, not executed by init or by parsing pack data. The existing person-owned `checks` policy is still the only way to select a task command. Inputs are bounded to 1 MiB, analysis runs to 5 seconds and 1 MiB stdout each, and errors never echo artifact contents. Checks emit criterion ids and a deterministic final result; semantic quality, meeting coverage and undeclared analysis dependencies remain reviewer judgments. `docs/profiles.md` records exact invocations, formats and limits. The role inventory lint compares every template group with the bundled registry rather than assuming the first eleven definitions. No live adoption or native model ingestion is claimed.
|
|
6449
|
+
|
|
6450
|
+
## Amendment: research campaigns, experiment receipts and runner attestation (#930, #932, #1018)
|
|
6451
|
+
|
|
6452
|
+
The implementation contract for #929 R1 and R3 and for #1018 is `docs/specs/2026-11-01-research-campaigns-design.md`: campaign, protocol, attempt and receipt schemas and bounds; the provenance levels `declared`, `host-observed`, `runner-attested` and `person-attested`; the Board tables; the person-only activation, attestation and runner registration; the `ahub study` and `hub_study_*` / `hub_experiment_*` wire names; the `research_records` recovery feature; and the delivery slices (#596 to #602). Slices that must differ amend that document in their own PR.
|