@staix/agent-hub 0.12.28 → 0.12.30
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 +24 -0
- package/README.md +79 -219
- package/docs/agent-notes/README.md +12 -0
- package/docs/agent-notes/adapters.md +1 -1
- package/docs/agent-notes/bus.md +3 -1
- package/docs/agent-notes/daemon.md +13 -0
- package/docs/agent-notes/local-worker.md +2 -2
- package/docs/agent-notes/tasks.md +9 -0
- package/docs/agent-notes/tests.md +1 -1
- package/docs/memory.md +70 -152
- package/docs/operations.md +237 -14
- package/docs/plans/2026-10-16-amem-failure-registry.md +101 -0
- package/docs/plans/2026-10-16-amem-p0-baseline.md +110 -0
- package/docs/plans/2026-10-16-amem-p2-read-tools.md +28 -0
- package/docs/plans/amem-cases.json +482 -0
- package/docs/quickstart.md +9 -2
- package/docs/security.md +39 -2
- package/docs/smoke.md +65 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +113 -5
- package/docs/verification/2026-10-16-0.12.29.md +25 -0
- package/docs/verification/2026-10-16-273-live-verification.md +150 -0
- package/docs/verification/2026-10-17-0.12.30.md +70 -0
- package/docs/verified.json +13 -13
- package/package.json +5 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +64 -44
- package/src/cli/console-state.ts +92 -20
- package/src/cli/console.ts +25 -6
- package/src/cli/help.ts +4 -2
- package/src/cli/identity-audit.ts +2 -2
- package/src/cli/identity.ts +15 -1
- package/src/cli/main.js +2 -2
- package/src/cli/main.ts +78 -39
- package/src/cli/memory.ts +87 -187
- package/src/cli/output.ts +6 -0
- package/src/cli/recovery-package.ts +59 -10
- package/src/cli/upgrade-runtime.ts +60 -18
- package/src/cli/upgrade.ts +8 -0
- package/src/hub/board.ts +2 -1
- package/src/hub/bus.ts +33 -12
- package/src/hub/daemon.ts +141 -31
- package/src/hub/envelope.ts +30 -0
- package/src/hub/events.ts +1 -1
- package/src/hub/git.ts +2 -38
- package/src/hub/hub-tools.ts +5 -1
- package/src/hub/lifecycle.ts +2 -2
- package/src/hub/manager.ts +15 -11
- package/src/hub/memory-candidates.ts +103 -0
- package/src/hub/memory-fail.ts +25 -0
- package/src/hub/memory-observations.ts +47 -0
- package/src/hub/memory-plan-guard.ts +33 -0
- package/src/hub/memory-policy.ts +42 -0
- package/src/hub/memory-profile-check.ts +170 -0
- package/src/hub/memory-profile.ts +192 -0
- package/src/hub/memory-staleness.ts +55 -0
- package/src/hub/path-policy.ts +8 -0
- package/src/hub/project.ts +4 -37
- package/src/hub/read-tools.ts +127 -0
- package/src/hub/tasks.ts +201 -41
- package/src/hub/ui.ts +10 -7
- package/src/local/deny.ts +4 -17
- package/src/memory/brief.ts +8 -5
- package/src/memory/capture.ts +2 -1
- package/src/memory/recall.ts +50 -110
- package/src/pi/process-signature.ts +2 -42
- package/src/memory/import.ts +0 -90
- package/src/memory/store.ts +0 -805
- package/src/memory/sync.ts +0 -145
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# agent-mem P2: hub memory read tools contract
|
|
2
|
+
|
|
3
|
+
Copied verbatim from issue #412 (plan v3, 2026-10-16) so sandboxed peers can read it. The issue is the source; if they differ, the issue wins.
|
|
4
|
+
|
|
5
|
+
## Collaboration, between agents (from #412)
|
|
6
|
+
|
|
7
|
+
- **Between agents.**
|
|
8
|
+
- Stage A: hub read tools `hub_memory_search`, `hub_memory_show`, `hub_memory_graph`, so sandboxed peers read through the daemon. Their contract:
|
|
9
|
+
- The project is scoped server-side from the caller (`memoryProjectKey`), never passed as a parameter.
|
|
10
|
+
- The query and every returned title, node, edge, error and count are screened.
|
|
11
|
+
- Refused in a PII turn, behind their own capability.
|
|
12
|
+
- Registered in the MCP, Pi and local catalogs; their arguments and results are excluded from capture.
|
|
13
|
+
- A schema frozen before P2, with every limit finite and counted in UTF-8 bytes. Proposed numbers (policy, adjustable; the requirement is a finite tested contract):
|
|
14
|
+
- Inputs: query ≤ 200 bytes; show ≤ 8 ids, each 6 to 64 bytes; graph ≤ 8 start nodes, each ≤ 200 bytes, hops 1 or 2. Over-limit input is refused before any work or log line.
|
|
15
|
+
- Output budget for the whole result, metadata and errors included: search default 8 / max 20 lines, ≤ 4 KB; show ≤ 8 KB; graph default 60 / max 120 nodes and max 240 edges, ≤ 6 KB, fails first.
|
|
16
|
+
- Truncation is deterministic. A graph never returns an edge without both endpoints. A cut text excerpt is marked incomplete; id, provenance and kind headers are never cut.
|
|
17
|
+
- `truncated:N` counts only results the caller may see, after screening. Hidden or unscreened rows are never counted or named.
|
|
18
|
+
- Ids resolve as today: exact id or a unique prefix among project-visible ids, original ids redirected. An ambiguous, unknown or withheld id gets one refusal that does not reveal candidates.
|
|
19
|
+
- Tests: UTF-8 input and output, oversized input, dense graph, shared and private rows, ambiguous prefix, every eligible result over budget.
|
|
20
|
+
- Writes stay `hub_remember` with the existing gates.
|
|
21
|
+
|
|
22
|
+
## P2 row (from #412 Phases)
|
|
23
|
+
|
|
24
|
+
| Phase | Where | Content | Gate |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| P2 Read tools | agent-hub | `hub_memory_search`/`show`/`graph` per the collaboration contract and its frozen schema. | The schema tests listed there. C3 and capture-exclusion tests for these tools. A live receipt of pi reading through them. No file-read grant. |
|
|
27
|
+
|
|
28
|
+
Phase rules from #412: each gate names `scripts/check.sh` ending `check: OK`, `bun run build` when hub tools or the plugin change, and reading `docs/agent-notes/` before editing `daemon.ts`, `bus.ts`, `hub-tools.ts` or `main.ts`. Tests that set `AGENTHUB_HOME` restore it.
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "T01",
|
|
4
|
+
"mode": "Truth/lifecycle",
|
|
5
|
+
"symptom": "Readers with different configured TTLs disagree about expiry.",
|
|
6
|
+
"resolution": "Persist deadlines in entries/settle/demote ops, instead of deriving with reader configuration.",
|
|
7
|
+
"status": "regression-covered",
|
|
8
|
+
"source": "PR #352; test/memory-store.test.ts:140"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "T02",
|
|
12
|
+
"mode": "Truth/lifecycle",
|
|
13
|
+
"symptom": "A late note on an already approved task has no correct lifetime.",
|
|
14
|
+
"resolution": "Settle old approvals and give late short notes their own clock; approved promotable notes can start long.",
|
|
15
|
+
"status": "regression-covered",
|
|
16
|
+
"source": "PR #352; test/memory-native.test.ts:158; test/memory-store.test.ts:320"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "T03",
|
|
20
|
+
"mode": "Truth/lifecycle",
|
|
21
|
+
"symptom": "An explicit short-horizon note is promoted on approval or loses its own deadline.",
|
|
22
|
+
"resolution": "Keep explicit short intent and deadline through settlement.",
|
|
23
|
+
"status": "regression-covered",
|
|
24
|
+
"source": "PR #352; test/memory-native.test.ts:190; test/memory-store.test.ts:433"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "T04",
|
|
28
|
+
"mode": "Truth/lifecycle",
|
|
29
|
+
"symptom": "A crash between board approval, settle append and promotion loses settlement effects.",
|
|
30
|
+
"resolution": "Reconcile approvals at startup and finish logged settlement on later reads.",
|
|
31
|
+
"status": "regression-covered",
|
|
32
|
+
"source": "PR #352; test/memory-native.test.ts:169; test/memory-store.test.ts:140"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "T05",
|
|
36
|
+
"mode": "Truth/lifecycle",
|
|
37
|
+
"symptom": "Demoting a promoted copy exposes a hidden long-lived original; repeated promotion chains drift.",
|
|
38
|
+
"resolution": "Derive the original/copy chain together for demote/forget operations.",
|
|
39
|
+
"status": "regression-covered",
|
|
40
|
+
"source": "PR #352; test/memory-store.test.ts:178; test/memory-store.test.ts:197"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "T06",
|
|
44
|
+
"mode": "Truth/lifecycle",
|
|
45
|
+
"symptom": "A superseded settled source expires or fails to return/promote when its replacement disappears.",
|
|
46
|
+
"resolution": "Retain promotable source, derive replacement liveness and promote held sources on forget/expiry.",
|
|
47
|
+
"status": "regression-covered",
|
|
48
|
+
"source": "PR #352; test/memory-store.test.ts:360; test/memory-store.test.ts:379; test/memory-store.test.ts:391; CM #186471"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "T07",
|
|
52
|
+
"mode": "Truth/lifecycle",
|
|
53
|
+
"symptom": "Promotion changes chronology or drops a replacement relation.",
|
|
54
|
+
"resolution": "Promotion keeps source date/provenance and supersession.",
|
|
55
|
+
"status": "regression-covered",
|
|
56
|
+
"source": "PR #352; test/memory-store.test.ts:415; test/memory-store.test.ts:542"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "T08",
|
|
60
|
+
"mode": "Truth/lifecycle",
|
|
61
|
+
"symptom": "Local or separately written supersessions form a cycle.",
|
|
62
|
+
"resolution": "Refuse local cycle creation; derive a deterministic broken cycle for split writers.",
|
|
63
|
+
"status": "regression-covered",
|
|
64
|
+
"source": "PR #352; test/memory-store.test.ts:458; test/memory-store.test.ts:472; CM #186635"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "T09",
|
|
68
|
+
"mode": "Truth/lifecycle",
|
|
69
|
+
"symptom": "An older embedded supersedes relation clobbers a later explicit operation.",
|
|
70
|
+
"resolution": "Fold both sources chronologically with deterministic time/id precedence.",
|
|
71
|
+
"status": "regression-covered",
|
|
72
|
+
"source": "PR #352; test/memory-store.test.ts:485; CM #186788"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "T10",
|
|
76
|
+
"mode": "Truth/lifecycle",
|
|
77
|
+
"symptom": "Notes on tasks never approved remain forever in the active index.",
|
|
78
|
+
"resolution": "Persist fixed 180-day backstop; keep append-only history.",
|
|
79
|
+
"status": "regression-covered",
|
|
80
|
+
"source": "PR #352; test/memory-store.test.ts:584; docs/memory.md:17"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "T11",
|
|
84
|
+
"mode": "Truth/lifecycle",
|
|
85
|
+
"symptom": "Model-written answers or unconfirmed inferred relations become circular evidence.",
|
|
86
|
+
"resolution": "Exclude ahub ask answers from import/evidence; keep suggestions non-evidence until a person confirms them.",
|
|
87
|
+
"status": "guard/design",
|
|
88
|
+
"source": "PR #356; src/memory/import.ts:68; test/memory-phase3.test.ts:23; test/memory-phase3.test.ts:57"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "D01",
|
|
92
|
+
"mode": "Duplicate/identity",
|
|
93
|
+
"symptom": "One claude-mem observation hashes differently on different checkouts or deny settings.",
|
|
94
|
+
"resolution": "Text-only import associations stabilize content identity across machines.",
|
|
95
|
+
"status": "fixed-source",
|
|
96
|
+
"source": "issue #380; PR #389; test/memory-sync.test.ts:188; CHANGELOG.md:26"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "D02",
|
|
100
|
+
"mode": "Duplicate/identity",
|
|
101
|
+
"symptom": "Legacy duplicate import groups survive sync; naive dedupe can overwrite person replacements.",
|
|
102
|
+
"resolution": "Read-only dedupe plan plus explicit append-only apply, preserve existing replacements and group forget.",
|
|
103
|
+
"status": "fixed-source",
|
|
104
|
+
"source": "issue #380; PR #389; test/memory-dedupe.test.ts:33; test/memory-dedupe.test.ts:61; test/memory-sync.test.ts:218"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "D03",
|
|
108
|
+
"mode": "Duplicate/identity",
|
|
109
|
+
"symptom": "Two machines promoting one demoted entry can create two copies.",
|
|
110
|
+
"resolution": "Deterministic promotion identity independent of promoting process.",
|
|
111
|
+
"status": "regression-covered",
|
|
112
|
+
"source": "PR #352; test/memory-store.test.ts:497"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "D04",
|
|
116
|
+
"mode": "Duplicate/identity",
|
|
117
|
+
"symptom": "A previously given short-term id no longer resolves after promotion or on a remote with only the synced copy.",
|
|
118
|
+
"resolution": "Resolve original ids through promotedFrom, locally and remotely.",
|
|
119
|
+
"status": "regression-covered",
|
|
120
|
+
"source": "PR #352; test/memory-store.test.ts:445; test/memory-store.test.ts:522"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "R01",
|
|
124
|
+
"mode": "Retrieval/capture",
|
|
125
|
+
"symptom": "Native own-platform recall repeats context already injected by hooks.",
|
|
126
|
+
"resolution": "Claude/Codex get other-platform blocks; other peers get combined recall.",
|
|
127
|
+
"status": "guard/design",
|
|
128
|
+
"source": "src/memory/recall.ts:15; test/recall.test.ts:11"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "R02",
|
|
132
|
+
"mode": "Retrieval/capture",
|
|
133
|
+
"symptom": "The first platform consumes a cross-platform recall budget.",
|
|
134
|
+
"resolution": "Split the budget across returned platform blocks and remove repeated legends.",
|
|
135
|
+
"status": "regression-covered",
|
|
136
|
+
"source": "src/memory/recall.ts:78; test/recall.test.ts:70"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"id": "R03",
|
|
140
|
+
"mode": "Retrieval/capture",
|
|
141
|
+
"symptom": "A stopped/changed/hung worker could block or fail a turn.",
|
|
142
|
+
"resolution": "2-second fail-open client returns undefined and logs the dropped call.",
|
|
143
|
+
"status": "guard/design",
|
|
144
|
+
"source": "src/memory/client.ts:20; test/memory.test.ts:14"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "R04",
|
|
148
|
+
"mode": "Retrieval/capture",
|
|
149
|
+
"symptom": "Local-worker summarize was skipped as subagent context.",
|
|
150
|
+
"resolution": "Omit agentId on summarize; live 13.25.1 observation recorded.",
|
|
151
|
+
"status": "historical-fixed",
|
|
152
|
+
"source": "src/memory/capture.ts:74; docs/smoke.md:658"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"id": "R05",
|
|
156
|
+
"mode": "Retrieval/capture",
|
|
157
|
+
"symptom": "Unawaited session-end disappears when the process exits.",
|
|
158
|
+
"resolution": "Await bounded Capture.end at shutdown.",
|
|
159
|
+
"status": "historical-fixed",
|
|
160
|
+
"source": "src/memory/capture.ts:83; docs/smoke.md:658"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"id": "R06",
|
|
164
|
+
"mode": "Retrieval/capture",
|
|
165
|
+
"symptom": "A new fail note reaches only the next session, after a peer retries the rejected approach.",
|
|
166
|
+
"resolution": "Ride notes on the other peers next delivery, newest ten, without a separate turn.",
|
|
167
|
+
"status": "historical-fixed",
|
|
168
|
+
"source": "CHANGELOG.md:427 (previous repository #68); docs/smoke.md:1092"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "R07",
|
|
172
|
+
"mode": "Retrieval/capture",
|
|
173
|
+
"symptom": "A failed delivery returns an old preface over a newer preface.",
|
|
174
|
+
"resolution": "Merge/restore the preface without overwriting newer memory.",
|
|
175
|
+
"status": "historical-fixed",
|
|
176
|
+
"source": "CHANGELOG.md:427 (previous repository #68)"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"id": "R08",
|
|
180
|
+
"mode": "Retrieval/capture",
|
|
181
|
+
"symptom": "Generated guidance treats workflow task/review events as reference-only memory.",
|
|
182
|
+
"resolution": "Distinguish message kinds; only presence recall is reference.",
|
|
183
|
+
"status": "historical-fixed",
|
|
184
|
+
"source": "CHANGELOG.md:526; docs/smoke.md:714 (previous repository #11)"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"id": "R09",
|
|
188
|
+
"mode": "Retrieval/capture",
|
|
189
|
+
"symptom": "Observer returns prose twice instead of observation XML and drops queued batches.",
|
|
190
|
+
"resolution": "Later observation reports lastSuccess and zero consecutiveFailures; exact dropped data recovery was not established.",
|
|
191
|
+
"status": "historical-partial",
|
|
192
|
+
"source": "CM #119860; CM #120247"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"id": "R10",
|
|
196
|
+
"mode": "Retrieval/capture",
|
|
197
|
+
"symptom": "Observer allowance/provider failure stops capture.",
|
|
198
|
+
"resolution": "Historical OpenRouter switch restored processing; ongoing cost and maintenance conflict remained.",
|
|
199
|
+
"status": "historical-partial",
|
|
200
|
+
"source": "CM S5886"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"id": "R11",
|
|
204
|
+
"mode": "Retrieval/capture",
|
|
205
|
+
"symptom": "Plugin registration exists but cache runtime, correct home or recall adapter is absent.",
|
|
206
|
+
"resolution": "Historical cache dependency installation and later dot memory adapter repair; not a current live-readiness claim.",
|
|
207
|
+
"status": "historical-partial",
|
|
208
|
+
"source": "CM S6037; CM #197226; MEMORY.md:177; rollout 01a13bd8, lines 35-37"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"id": "R12",
|
|
212
|
+
"mode": "Retrieval/capture",
|
|
213
|
+
"symptom": "SessionStart context hooks run before the claude-mem launcher compilation completes.",
|
|
214
|
+
"resolution": "Self-heal completes; identical hook rerun exits zero. Missing-PATH/version-cache hypotheses were disproved.",
|
|
215
|
+
"status": "historical-fixed",
|
|
216
|
+
"source": "CM S11000; CM #191224; CM #191227"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"id": "R13",
|
|
220
|
+
"mode": "Retrieval/capture",
|
|
221
|
+
"symptom": "Global LIMIT before project filtering starves target imports; repeated runs replay the same window or skip a recreated lower-id source.",
|
|
222
|
+
"resolution": "Project-aware SQL and persisted cursor advance handled rows; restart reconsiders skips; reset compares selected-project candidate ids.",
|
|
223
|
+
"status": "fixed-source",
|
|
224
|
+
"source": "issue #380; PR #389; test/memory-phase3.test.ts:120; test/memory-phase3.test.ts:147; test/memory-phase3.test.ts:171; commit d5738472"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"id": "X01",
|
|
228
|
+
"mode": "Project isolation",
|
|
229
|
+
"symptom": "Unrelated repositories with the same basename share claude-mem recall/import scope.",
|
|
230
|
+
"resolution": "Native memory origin key fixes its own namespace; claude-mem basename ambiguity remains. Import comment gives no local cure.",
|
|
231
|
+
"status": "open-ceiling",
|
|
232
|
+
"source": "src/memory/import.ts:57; docs/smoke.md:1101; src/memory/recall.ts:98"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"id": "X02",
|
|
236
|
+
"mode": "Project isolation",
|
|
237
|
+
"symptom": "Importing the whole superproject chain imports another projects notes under this projects policy.",
|
|
238
|
+
"resolution": "Import selects only chain.at(-1), the current source project.",
|
|
239
|
+
"status": "regression-covered",
|
|
240
|
+
"source": "src/memory/import.ts:45; src/memory/import.ts:54; test/memory-phase3.test.ts:23"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"id": "X03",
|
|
244
|
+
"mode": "Project isolation",
|
|
245
|
+
"symptom": "Native basename/path or unscoped-id lookups collide across projects/machines.",
|
|
246
|
+
"resolution": "Canonical origin key, local path hash fallback, project-scoped id access and native worktree aliases.",
|
|
247
|
+
"status": "regression-covered",
|
|
248
|
+
"source": "test/memory-store.test.ts:140; test/memory-store.test.ts:296; test/recall.test.ts:51"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"id": "X04",
|
|
252
|
+
"mode": "Project isolation",
|
|
253
|
+
"symptom": "A replacement narrows visibility and suppresses an entry for readers that cannot see the replacement.",
|
|
254
|
+
"resolution": "Reject narrower replacement visibility.",
|
|
255
|
+
"status": "regression-covered",
|
|
256
|
+
"source": "PR #352; test/memory-store.test.ts:284"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"id": "P01",
|
|
260
|
+
"mode": "Privacy",
|
|
261
|
+
"symptom": "A done/review/unmet/context summary on an ordinary task carries PII to cloud memory.",
|
|
262
|
+
"resolution": "Pattern/screen withheld text remains on board; memory and public peer messages receive no private body.",
|
|
263
|
+
"status": "fixed-source",
|
|
264
|
+
"source": "issue #69; PR #73; issue #185 / PR #192; CHANGELOG.md:363; CHANGELOG.md:232"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"id": "P02",
|
|
268
|
+
"mode": "Privacy",
|
|
269
|
+
"symptom": "Raw hub tool arguments/results bypass Tasks privacy refusal via local capture.",
|
|
270
|
+
"resolution": "Exclude task/conductor/hub_send capture, keeping ordinary tool capture.",
|
|
271
|
+
"status": "fixed-source",
|
|
272
|
+
"source": "issue #230; PR #237; CHANGELOG.md:188"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"id": "P03",
|
|
276
|
+
"mode": "Privacy",
|
|
277
|
+
"symptom": "An ordinary local turn reads full PII board rows and then captures/repeats them.",
|
|
278
|
+
"resolution": "Return stubs except during an in-process PII turn; PII turns never capture.",
|
|
279
|
+
"status": "fixed-source",
|
|
280
|
+
"source": "issue #230; PR #237"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"id": "P04",
|
|
284
|
+
"mode": "Privacy",
|
|
285
|
+
"symptom": "Private path references in text, refs, symbols or Unicode/space paths reach either memory.",
|
|
286
|
+
"resolution": "Check private paths before native/cloud writes and in model-facing records.",
|
|
287
|
+
"status": "regression-covered",
|
|
288
|
+
"source": "test/memory-native.test.ts:204; test/memory-store.test.ts:556; src/hub/tasks.ts:2293"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"id": "P05",
|
|
292
|
+
"mode": "Privacy",
|
|
293
|
+
"symptom": "A previously accepted/shared/synced note violates the current receiving policy or has sensitive metadata.",
|
|
294
|
+
"resolution": "Rescreen whole records on model-facing reads, including nodes/project/secret patterns.",
|
|
295
|
+
"status": "regression-covered",
|
|
296
|
+
"source": "src/hub/daemon.ts:838; test/memory-store.test.ts:556; src/memory/recall.ts:153"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"id": "P06",
|
|
300
|
+
"mode": "Privacy",
|
|
301
|
+
"symptom": "Invalid unrelated config field silently empties pii.paths for memory CLI reads.",
|
|
302
|
+
"resolution": "Strict policy load fails closed; malformed present config is distinct from absence.",
|
|
303
|
+
"status": "regression-covered",
|
|
304
|
+
"source": "CM S10834; test/memory-store.test.ts:584; src/cli/memory.ts:32"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"id": "P07",
|
|
308
|
+
"mode": "Privacy",
|
|
309
|
+
"symptom": "Promotion sends local short notes to synced long-term storage without a current policy verdict.",
|
|
310
|
+
"resolution": "PromotionScreen must promote/keep/skip under source-project policy; rescreen task current paths and hold unknown project.",
|
|
311
|
+
"status": "regression-covered",
|
|
312
|
+
"source": "PR #352; test/memory-store.test.ts:672; test/memory-store.test.ts:691; src/memory/store.ts:327"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"id": "P08",
|
|
316
|
+
"mode": "Privacy",
|
|
317
|
+
"symptom": "Sync publishes note text to a public remote, or visibility cannot be verified.",
|
|
318
|
+
"resolution": "Known-public refused; unknown visibility only warns (residual policy limit); forget does not purge Git history.",
|
|
319
|
+
"status": "guard/design",
|
|
320
|
+
"source": "PR #354; src/memory/sync.ts:94; test/memory-sync.test.ts:57; docs/memory.md:145"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"id": "P09",
|
|
324
|
+
"mode": "Privacy",
|
|
325
|
+
"symptom": "Tracked project configuration redirects the cloud memory endpoint to receive task text.",
|
|
326
|
+
"resolution": "Memory endpoint applies only from machine-local untracked configuration, checked by file identity.",
|
|
327
|
+
"status": "fixed-source",
|
|
328
|
+
"source": "issue #17; PR #19; CHANGELOG.md:406"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"id": "I01",
|
|
332
|
+
"mode": "Index integrity/freshness",
|
|
333
|
+
"symptom": "Malformed, bad-id, non-string or invalid-time synced lines poison derived state.",
|
|
334
|
+
"resolution": "Validate schema/content hash/types/time; skip invalid rows and wait for partial tails.",
|
|
335
|
+
"status": "regression-covered",
|
|
336
|
+
"source": "PR #352; test/memory-store.test.ts:73; test/memory-store.test.ts:178; test/memory-store.test.ts:542; test/memory-sync.test.ts:159"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"id": "I02",
|
|
340
|
+
"mode": "Index integrity/freshness",
|
|
341
|
+
"symptom": "Truncation or same-size/deep prefix rewrite evades byte-offset indexing.",
|
|
342
|
+
"resolution": "Hash indexed prefixes and rebuild on shrink/change.",
|
|
343
|
+
"status": "regression-covered",
|
|
344
|
+
"source": "PR #352; test/memory-store.test.ts:217; test/memory-store.test.ts:237; test/memory-store.test.ts:255"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"id": "I03",
|
|
348
|
+
"mode": "Index integrity/freshness",
|
|
349
|
+
"symptom": "A durable log append is reported failed when derived SQLite refresh is busy, encouraging duplicate retries.",
|
|
350
|
+
"resolution": "Return saved id after durable append; mark index stale and log refresh failure.",
|
|
351
|
+
"status": "regression-covered",
|
|
352
|
+
"source": "CM S10834; src/memory/store.ts:223; test/memory-store.test.ts:607"
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"id": "I04",
|
|
356
|
+
"mode": "Index integrity/freshness",
|
|
357
|
+
"symptom": "Catching the failed refresh without retry makes saved notes permanently invisible to this process.",
|
|
358
|
+
"resolution": "Read paths ensure freshness and retry a dirty index.",
|
|
359
|
+
"status": "regression-covered",
|
|
360
|
+
"source": "CM #187423; CM #187425; test/memory-store.test.ts:607"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"id": "I05",
|
|
364
|
+
"mode": "Index integrity/freshness",
|
|
365
|
+
"symptom": "A long-lived reader misses another processes durable write whose indexing failed.",
|
|
366
|
+
"resolution": "Compare segment shape on reads as well as local stale state.",
|
|
367
|
+
"status": "regression-covered",
|
|
368
|
+
"source": "PR #352; test/memory-store.test.ts:618; src/memory/store.ts:585"
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"id": "I06",
|
|
372
|
+
"mode": "Index integrity/freshness",
|
|
373
|
+
"symptom": "Goal/link/lease and suggestion/verdict reads bypass note freshness recovery.",
|
|
374
|
+
"resolution": "Share ensureFresh across those read families; no new log/index format.",
|
|
375
|
+
"status": "fixed-source",
|
|
376
|
+
"source": "issue #378; PR #379; test/memory-store.test.ts:633; docs/verification/2026-10-14-0.12.24-audit.md:24"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"id": "I07",
|
|
380
|
+
"mode": "Index integrity/freshness",
|
|
381
|
+
"symptom": "Idle expiry is hidden by view filtering while index rows still differ from rebuild.",
|
|
382
|
+
"resolution": "Prune on read so derived rows and rebuild agree.",
|
|
383
|
+
"status": "regression-covered",
|
|
384
|
+
"source": "PR #352; test/memory-store.test.ts:511"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"id": "S01",
|
|
388
|
+
"mode": "Sync/concurrency",
|
|
389
|
+
"symptom": "Concurrent first-open or corrupt winning machine.json files yield inconsistent/undefined machine identity.",
|
|
390
|
+
"resolution": "Exclusive creation then common validated reread; malformed id fails store construction.",
|
|
391
|
+
"status": "regression-covered",
|
|
392
|
+
"source": "PR #352; test/memory-store.test.ts:272; test/memory-store.test.ts:360; CM #186471; CM #186474"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"id": "S02",
|
|
396
|
+
"mode": "Sync/concurrency",
|
|
397
|
+
"symptom": "Killed Git leaves index.lock or interrupted merge, hiding subsequent local entries from sync.",
|
|
398
|
+
"resolution": "Signed machine sync lock serializes Git; clear stale owned Git lock and abort interrupted merge.",
|
|
399
|
+
"status": "regression-covered",
|
|
400
|
+
"source": "PR #354; src/memory/sync.ts:31; test/memory-sync.test.ts:159"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"id": "S03",
|
|
404
|
+
"mode": "Sync/concurrency",
|
|
405
|
+
"symptom": "Concurrent pushes or absent/unreachable remote make memory transport fail.",
|
|
406
|
+
"resolution": "Own-machine segments, pull/merge, one retry after push race; errors return fail-open result.",
|
|
407
|
+
"status": "guard/design",
|
|
408
|
+
"source": "PR #354; src/memory/sync.ts:61; test/memory-sync.test.ts:33; test/memory-sync.test.ts:57"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"id": "S04",
|
|
412
|
+
"mode": "Sync/concurrency",
|
|
413
|
+
"symptom": "Two machines claim one goal before synchronization.",
|
|
414
|
+
"resolution": "Advisory earlier-claim winner with conflict displayed; force resolution by person; broker needed for consensus.",
|
|
415
|
+
"status": "open-ceiling",
|
|
416
|
+
"source": "src/memory/store.ts:718; test/memory-sync.test.ts:69"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"id": "C01",
|
|
420
|
+
"mode": "Scale/cost/measurement",
|
|
421
|
+
"symptom": "Promote check and append race with another processes forget/replacement.",
|
|
422
|
+
"resolution": "Open ceiling: per-machine mutation lock would make check+append atomic.",
|
|
423
|
+
"status": "open-ceiling",
|
|
424
|
+
"source": "src/memory/store.ts:335"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"id": "C02",
|
|
428
|
+
"mode": "Scale/cost/measurement",
|
|
429
|
+
"symptom": "Every refresh hashes the whole indexed prefix, O(segment bytes).",
|
|
430
|
+
"resolution": "Open ceiling: block hash chain if measured scale warrants it.",
|
|
431
|
+
"status": "open-ceiling",
|
|
432
|
+
"source": "src/memory/store.ts:392"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"id": "C03",
|
|
436
|
+
"mode": "Scale/cost/measurement",
|
|
437
|
+
"symptom": "Repeated concurrent writers exceed bounded refresh retries.",
|
|
438
|
+
"resolution": "Open ceiling: five retry levels then deferred work; shared read lock is proposed upgrade.",
|
|
439
|
+
"status": "open-ceiling",
|
|
440
|
+
"source": "src/memory/store.ts:430"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"id": "C04",
|
|
444
|
+
"mode": "Scale/cost/measurement",
|
|
445
|
+
"symptom": "Views materialize/filter all rows in process memory.",
|
|
446
|
+
"resolution": "Open ceiling: SQL filtering when store size warrants it.",
|
|
447
|
+
"status": "open-ceiling",
|
|
448
|
+
"source": "src/memory/store.ts:490"
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"id": "C05",
|
|
452
|
+
"mode": "Scale/cost/measurement",
|
|
453
|
+
"symptom": "Clock rollback resurrects an expired row after rebuild.",
|
|
454
|
+
"resolution": "Open ceiling: durable prune entry, instead of wall-clock-only expiry.",
|
|
455
|
+
"status": "open-ceiling",
|
|
456
|
+
"source": "src/memory/store.ts:569"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"id": "C06",
|
|
460
|
+
"mode": "Scale/cost/measurement",
|
|
461
|
+
"symptom": "Three-characters-per-token approximation is not an exact token ceiling.",
|
|
462
|
+
"resolution": "Open ceiling: tokenizer if exact bounds are required.",
|
|
463
|
+
"status": "open-ceiling",
|
|
464
|
+
"source": "src/memory/recall.ts:54"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"id": "C07",
|
|
468
|
+
"mode": "Scale/cost/measurement",
|
|
469
|
+
"symptom": "Dashboard Memory recomputes the whole index per poll.",
|
|
470
|
+
"resolution": "Open ceiling: cache by index revision when measured poll cost warrants it.",
|
|
471
|
+
"status": "open-ceiling",
|
|
472
|
+
"source": "src/hub/daemon.ts:3197"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
"id": "C08",
|
|
476
|
+
"mode": "Scale/cost/measurement",
|
|
477
|
+
"symptom": "Adoption decisions lack causal effectiveness/cost measurements; brief counts cannot prove a failed approach was retried.",
|
|
478
|
+
"resolution": "#247/#248/#258 ship counts/arms/report plumbing; controlled arms, field window and manual retry labels remain owed.",
|
|
479
|
+
"status": "open-measurement",
|
|
480
|
+
"source": "issue #248 AC2-4; issue #258 AC11; PR #351; PR #356; docs/smoke.md:68; docs/research.md:88"
|
|
481
|
+
}
|
|
482
|
+
]
|
package/docs/quickstart.md
CHANGED
|
@@ -4,7 +4,7 @@ agent-hub lets the coding agents on one machine work as peers in one project dir
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
Needs [Bun](https://bun.sh) 1.
|
|
7
|
+
Needs [Bun](https://bun.sh) 1.4.2 or newer and macOS (Linux works without the `local` worker's `bash` and `git` tools, which need the macOS sandbox).
|
|
8
8
|
|
|
9
9
|
Node alone is not supported. Keep `bun` on PATH even if you install with npm.
|
|
10
10
|
The registry package uses the `staix` organization scope. Install from npm:
|
|
@@ -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.12.
|
|
20
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.12.30
|
|
21
21
|
ahub setup
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -69,12 +69,19 @@ or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local]
|
|
|
69
69
|
| `ahub task propose [--class <c>] <title>` | put work on the board; the hub picks an owner and a reviewer, and the class if you name none |
|
|
70
70
|
| `ahub board` | who has what; `ahub task show <id>` for one task |
|
|
71
71
|
| `ahub status` | peers, queues, pauses; `ahub budget` for quota windows |
|
|
72
|
+
| `ahub memory profile-check` | a person's read-only check of running memory bindings in the current registry |
|
|
72
73
|
| `ahub ask <question>` | what the board, shared memory and the log say, with the ids the answer rests on |
|
|
73
74
|
| `ahub stop <peer>` | stop one headless peer (Kimi, headless Pi, `local`); the hub and the other peers keep running |
|
|
74
75
|
| `ahub kill` | stop the daemon and everything it started |
|
|
75
76
|
|
|
76
77
|
More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-design.md`](specs/2026-09-19-agent-hub-design.md). What it protects and what it does not: [`docs/security.md`](security.md).
|
|
77
78
|
|
|
79
|
+
## Shared memory
|
|
80
|
+
|
|
81
|
+
The hub exact-pins the first-party `@staix/agent-mem` 0.3.0 core. 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. Generation candidate acceptance is still refused.
|
|
82
|
+
|
|
83
|
+
Before any real generation migration, upgrade every hub sharing that memory home to 0.12.30 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.
|
|
84
|
+
|
|
78
85
|
## Browser dashboard
|
|
79
86
|
|
|
80
87
|
After `ahub up`, run `ahub ui` to open the local dashboard. It refreshes the
|
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`. 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; Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation or a person's console `y` for a config default at hub start. Until that config confirmation 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. 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. 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.
|
|
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`, `local.read_allow`, `local.bash_network`, `local.network_allow`, `terminal.open`) 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, a permission mode, the command that opens a terminal, 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
|
|
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`, `local.read_allow`, `local.bash_network`, `local.network_allow`, `terminal.open`) 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, a permission mode, the command that opens a terminal, 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 has a 10 s cap). 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. It 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.
|
|
@@ -21,6 +21,18 @@ agent-hub connects agents that can each run commands. This page says what the hu
|
|
|
21
21
|
- **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.
|
|
22
22
|
- **The hub's own model calls are fenced.** Digest condensation, task triage and the PII screen read agent-written text as data; their output is capped text framed as untrusted, or a value checked against a closed list. It is never used as a route, a peer id, a tool call or an instruction.
|
|
23
23
|
|
|
24
|
+
## Native memory authority and profile selection
|
|
25
|
+
|
|
26
|
+
The portable core is the exact-pinned first-party `@staix/agent-mem` 0.3.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.
|
|
27
|
+
|
|
28
|
+
`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.
|
|
29
|
+
|
|
30
|
+
`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. 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` remains unsupported and refuses. Citation counters are observation proxies, not evidence that an agent understood or used a note.
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
Before migrating any real home, every hub sharing it must first be on 0.12.30 with positive authenticated binding or unbound/quiesced proof from `ahub memory profile-check` in each relevant registry. An unknown or unreadable runtime is not proof of absence, and a matching ready binding still holds a handle that must be closed. Profile-check reports only its named registry inventory. Root paths, selector receipts and malformed-legacy counts are person-local diagnostics, excluded from ordinary peer status and allowed agent CLI reads. Source/fixture checks do not establish native capture, real migration or account/device-sync success.
|
|
35
|
+
|
|
24
36
|
## What is not defended
|
|
25
37
|
|
|
26
38
|
- **A malicious local process running as you.** It can read the control token, `hub.db`, the project and your home directory without the hub's help. The token keeps web pages and other users out, not your own processes.
|
|
@@ -71,10 +83,35 @@ or the native Claude/Codex shell marker when the hub marker is absent. It connec
|
|
|
71
83
|
as that peer in tools mode. Messages and task changes retain that actor;
|
|
72
84
|
`say` defaults to status priority and important messages still require the peer's
|
|
73
85
|
capability. Conflicting or malformed markers fail closed. There is no `--as-user`.
|
|
74
|
-
|
|
86
|
+
By default, console operations are refused before connecting, including permission answers
|
|
75
87
|
and permission modes, queue resolution, budget overrides, benchmark runs, research
|
|
76
88
|
backfill and task labels, stopping a peer (`ahub stop`), lifecycle and recovery
|
|
77
89
|
operations, and `ask`.
|
|
90
|
+
For live verification explicitly authorized by the user, an agent may invoke
|
|
91
|
+
`ahub --live-test [--project <path|id>] <command> [args...]`. The flag delegates
|
|
92
|
+
the existing console authority for console-class operations and conductor-class peer starts/holds, including approvals,
|
|
93
|
+
settings, roster and lifecycle operations. It does not restrict effects to a
|
|
94
|
+
scratch project: the agent must stay within the authorized commands and targets.
|
|
95
|
+
Malformed or conflicting caller markers still refuse. The original peer and
|
|
96
|
+
closed command label are recorded with outcome `delegated` before execution;
|
|
97
|
+
if that audit cannot be written, the command refuses. The current daemon emits
|
|
98
|
+
`agent_cli` with `delegated: true` and `refused: false`. Allowed-class commands retain the peer identity and capability checks, including
|
|
99
|
+
messages, proposals and reviews. Peer starts, pause and resume may delegate
|
|
100
|
+
operational console authority; task assign/escalate under the flag refuse. `--as-user`, person task labels,
|
|
101
|
+
person-authored planning/release/memory/goal writes, SDLC record, benchmark run
|
|
102
|
+
and the pet composer are refused. No argument text is copied into this audit.
|
|
103
|
+
Delegated dashboard restrictions are bound to its server-issued ticket/session:
|
|
104
|
+
operational settings/start/stop controls remain, but message, proposal and
|
|
105
|
+
workflow-history actions refuse even if the body tries to clear the restriction.
|
|
106
|
+
The flag is not stored in settings or exported to native agent children. Ahub's
|
|
107
|
+
own daemon launch, console subprocesses, recovery runner, interactive follow-up commands and upgrade coordinator
|
|
108
|
+
handover carry it explicitly in argv. Each new top-level invocation needs its
|
|
109
|
+
own flag. An older CLI does not support it; use a candidate with this feature
|
|
110
|
+
for the continuation too. Staging rejects a target without the pinned
|
|
111
|
+
`LIVE_TEST_SUPPORTED` declaration before preparation or commit, including a
|
|
112
|
+
previously cached stage. Delegated headless-native restoration uses the ordinary target CLI with its
|
|
113
|
+
operationId through that operational console authority. Native and TUI
|
|
114
|
+
restoration keep their existing identity and ownership checks. External computer-use restrictions remain unchanged.
|
|
78
115
|
The operator uses a plain terminal or `ahub console`. A Claude `!` command that
|
|
79
116
|
inherits the agent markers follows the same rule. The console strips control
|
|
80
117
|
sequences from agent and daemon text before it quotes forged headers, and its
|