agents-handoff 0.0.0-stage → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/LICENSE +21 -0
  3. package/README.md +150 -2
  4. package/SKILL.md +147 -0
  5. package/capability-registry.json +27 -0
  6. package/docs/ARCHITECTURE.md +187 -0
  7. package/docs/CHANGELOG.md +196 -0
  8. package/docs/CLI.md +299 -0
  9. package/docs/COMPATIBILITY.md +124 -0
  10. package/docs/CONTRIBUTING.md +134 -0
  11. package/docs/FORMAT.md +185 -0
  12. package/docs/INSTALL.md +394 -0
  13. package/docs/INTEGRATION.md +188 -0
  14. package/docs/LEVEL4.md +202 -0
  15. package/docs/LEVEL5.md +96 -0
  16. package/docs/PERMISSIONS.md +145 -0
  17. package/docs/PROVENANCE.md +110 -0
  18. package/docs/SECURITY.md +93 -0
  19. package/docs/SESSIONS.md +97 -0
  20. package/docs/TROUBLESHOOTING.md +158 -0
  21. package/docs/UNINSTALL.md +148 -0
  22. package/docs/UPGRADE.md +177 -0
  23. package/docs/_config.yml +18 -0
  24. package/docs/_data/nav.yml +36 -0
  25. package/docs/_layouts/default.html +31 -0
  26. package/docs/assets/style.css +88 -0
  27. package/docs/index.md +92 -0
  28. package/docs/sessions.json +34 -0
  29. package/handoff.config.example.json +35 -0
  30. package/handoff.config.schema.json +117 -0
  31. package/install/CHANGELOG.md +48 -0
  32. package/install/README.md +76 -0
  33. package/install/install.mjs +1455 -0
  34. package/install/package.json +39 -0
  35. package/package.json +66 -4
  36. package/permission-policy.json +33 -0
  37. package/refs/ADAPTERS.md +33 -0
  38. package/refs/bootstrap.md +59 -0
  39. package/refs/brief-checklist.md +79 -0
  40. package/refs/handbook.md +58 -0
  41. package/refs/protocol.md +117 -0
  42. package/refs/roles.md +75 -0
  43. package/refs/validator.md +73 -0
  44. package/schemas/handoff.schema.json +275 -0
  45. package/skill.json +147 -0
  46. package/templates/HANDOFF.llm.schema.json +144 -0
  47. package/templates/HANDOFF.template.md +40 -0
  48. package/tests/acceptance/acceptance.yaml +209 -0
  49. package/tests/fixtures/minimal-transcript.jsonl +2 -0
  50. package/tools/agent-handoff.mjs +22 -0
  51. package/tools/agents-handoff.mjs +410 -0
  52. package/tools/capability-registry.mjs +120 -0
  53. package/tools/handoff.mjs +398 -0
  54. package/tools/handoff.test.mjs +668 -0
  55. package/tools/lib/handoff-root.mjs +161 -0
  56. package/tools/runtime-engine.mjs +330 -0
package/docs/FORMAT.md ADDED
@@ -0,0 +1,185 @@
1
+ ---
2
+ title: Handoff format
3
+ ---
4
+
5
+ # Handoff format
6
+
7
+ One handoff is a directory of files generated from a session transcript. The source
8
+ transcript is read, never written: every handoff file is a render of it, and the
9
+ renders carry the digests that let you prove which source bytes produced them.
10
+
11
+ Everything below describes the behaviour of `tools/handoff.mjs`.
12
+
13
+ ## Where handoffs live
14
+
15
+ `tools/lib/handoff-root.mjs` is the single implementation of this order. First match wins:
16
+
17
+ | Order | Source | Notes |
18
+ |---|---|---|
19
+ | 1 | `HANDOFFS_ROOT` | Environment override. Always wins. The test suite uses it to stay hermetic. |
20
+ | 2 | `handoff.config.json` | Found by walking up from the current directory, at most 10 levels. `storage.path` beats `handoff_dir`; a relative `handoff_dir` resolves against the directory holding the config. |
21
+ | 3 | `<dir>/handoffs/` | Zero-config convention, checked at each level of the same upward walk. |
22
+ | 4 | the skill directory | Default store when no environment variable, config or `handoffs/` directory is found. |
23
+
24
+ A `handoff.config.json` that is present but invalid fails the run with exit 2 instead of
25
+ falling back, because falling back would write the session to a store other than the one
26
+ that was configured. The schema for that file is `handoff.config.schema.json`.
27
+
28
+ ```
29
+ node tools/handoff.mjs config
30
+ ```
31
+
32
+ prints the resolved root, which rule chose it (`env`, `config`, `discover`, `default`),
33
+ the config file used, the schema path, the configured project name and whether
34
+ cross-project linking is enabled.
35
+
36
+ ## Directory layout
37
+
38
+ ```
39
+ <root>/
40
+ INDEX.json index of every session, rewritten on each build
41
+ links/<other-project>.md cross-project relation notes
42
+ projects/<project>/
43
+ PROJECT.md project file: one line per session
44
+ <session>/ one handoff
45
+ ```
46
+
47
+ A legacy layout — session directories directly under `<root>` — is migrated on the next
48
+ build: any directory holding a `manifest.json` is moved to `projects/<project>/`, using the
49
+ project recorded in its manifest.
50
+
51
+ ## Files in a session directory
52
+
53
+ | File | Written by | Contents |
54
+ |---|---|---|
55
+ | `HANDOFF.md` | `build`, `retitle`, `rename` | Human render: objective, current state, open loops, the last 40 turns as a table, tool-call digest, where the other files are, provenance. |
56
+ | `HANDOFF.summary.json` | `build`, `retitle`, `rename` | Compact machine payload, `schema_version` `2.0.0-summary`. Objective, `state_now` head (600 chars), open loops, counts, artifact names, provenance. |
57
+ | `HANDOFF.llm.json` | `build`, `retitle`, `rename` | Full machine payload, `schema_version` `2.0.0`. The summary fields plus the complete `timeline[]` and `tool_calls[]` arrays, with the turn class stored as `class`. |
58
+ | `timeline.jsonl` | `build` (append-only) | One JSON object per turn: `{seq, ts, class, text}`. New turns are appended; existing lines are never rewritten. |
59
+ | `TOOLS.md` | `build` | Every tool turn in full, untruncated, as `## [seq] <ts>` sections. The table in `HANDOFF.md` is a 120-character digest of the same turns. |
60
+ | `manifest.json` | `build`, `retitle`, `rename` | The record the other files are verified against. See below. |
61
+
62
+ Renders (`HANDOFF.md`, the two JSON payloads) are rewritten on every build; `timeline.jsonl`
63
+ is not.
64
+
65
+ ## Installed-copy artifacts (not part of a session)
66
+
67
+ Two files belong to an INSTALLATION rather than to a session, and neither is written by the
68
+ capture engine. They sit in the directory the installer copied into, beside `SKILL.md`, and
69
+ nothing above in this document describes them.
70
+
71
+ | File | Written by | Contents |
72
+ |---|---|---|
73
+ | `package.json` | `install`, `update` | Only when the directory has none. A minimal manifest marked `private`, so the copy can report its own version and cannot be published to npm by accident. |
74
+ | `.agents-handoff-install.json` | `install`, `update` | The install record: what version landed, from which source, and a sha256 over the installed file set. |
75
+
76
+ The install record:
77
+
78
+ | Field | Meaning |
79
+ |---|---|
80
+ | `schema_version` | `1.0-install-provenance`. |
81
+ | `product`, `version` | The skill this is a copy of, and the version that landed, read back out of the installed `SKILL.md`. |
82
+ | `installer_version`, `installed_at` | The installer that wrote the record, and when. |
83
+ | `harness`, `target` | The harness the copy went to (`claude`, `codex`, `agents`) when one was named, else `null`, and the absolute target path. |
84
+ | `source` | `{kind: 'tree', path}` for a copy made from a tree beside the installer, or `{kind: 'archive', ref, label, archive_url, archive_sha256}` when the copy was fetched instead. |
85
+ | `file_count`, `files_sha256` | How many manifest files the copy holds, and one sha256 over all of them. |
86
+ | `files` | Per-path sha256 values, so a mismatch names the file that changed. |
87
+
88
+ `verify` re-hashes the manifest files, recomputes `files_sha256` and fails when one changed or
89
+ went missing. An installation made before this record existed has none: `verify` says so and
90
+ checks everything else, and treats the absence as neither a pass nor a failure.
91
+ [PROVENANCE.md](PROVENANCE.md) states what the record proves and what it cannot.
92
+
93
+ ## manifest.json
94
+
95
+ | Field | Meaning |
96
+ |---|---|
97
+ | `session` | Session id as resolved for this handoff. |
98
+ | `project` | Project slug. |
99
+ | `harness`, `model` | From `--harness` / `--model`, from the source's own fields, or `unknown` / `""`. |
100
+ | `created_at`, `updated_at` | ISO 8601. `created_at` is set once on creation. |
101
+ | `source_paths` | Every source path this session was ever built from. |
102
+ | `watermark` | Highest `seq` already emitted. Turn `seq <= watermark` is already in `timeline.jsonl`. |
103
+ | `raw_sha256` | SHA-256 of the source bytes at the last build. |
104
+ | `revisions` | Number of writes. Increments on every build, `retitle` and `rename`. |
105
+ | `turn_count` | Number of turns in `timeline.jsonl` after the build. |
106
+ | `counts` | Turn counts by class: `USER`, `AGENT`, `THOUGHT`, `TOOL`. |
107
+ | `manifest_sha256` | Self-hash: SHA-256 of the manifest JSON with this field removed. |
108
+ | `prev_project` | Set when `rename` moves the session to another project. |
109
+ | `titled_from` | Previous directory names, set by `retitle`. |
110
+
111
+ ## Turn classes
112
+
113
+ `classify()` assigns each turn one class, in this order:
114
+
115
+ | Class | Rule |
116
+ |---|---|
117
+ | `TOOL` | `kind` contains `tool`, or `role` is `tool`. |
118
+ | `THOUGHT` | `kind` contains `reason` or `think`. |
119
+ | `USER` | `role` is `user`, or `kind` is `human`. |
120
+ | `AGENT` | `role` is `assistant`, or `kind` is `ai`. |
121
+ | `OTHER` | Everything else. Kept in `timeline.jsonl`, omitted from the tables in `HANDOFF.md`. |
122
+
123
+ Turn text is taken from `text`, then `content`, then `parts[].text`.
124
+
125
+ ## Accepted input
126
+
127
+ A source file ending in `.jsonl` is read line by line. Each line is parsed
128
+ independently; a line that does not parse, or whose text is blank, is skipped. `seq` is used
129
+ when it is a finite number, and the line index otherwise.
130
+
131
+ Any other extension is parsed as text: a line matching `user:`, `human:`, `assistant:`,
132
+ `ai:`, `system:` or `tool:` (optionally prefixed with `#`) starts a turn, and following
133
+ lines are appended to it. The role marker decides the class. Adapters that produce either
134
+ shape are listed in [ADAPTERS.md](https://github.com/Alot1z/agent-handoff/blob/main/refs/ADAPTERS.md).
135
+
136
+ If no turn parses, the build fails with exit 4.
137
+
138
+ ## Session id and directory name
139
+
140
+ 1. `--session <id>`, if given.
141
+ 2. else the `session` field of the first JSONL line, if present.
142
+ 3. else the source file name without its `.jsonl`, `.txt` or `.md` extension.
143
+
144
+ The directory name replaces every character outside `[\w.-]` with `_`. `INDEX.json` is
145
+ checked first: if a session with that id or with the same session UUID already exists, its
146
+ project and directory name are reused, so a rebuild lands in the same place.
147
+
148
+ `retitle <id-prefix> <new-name>` renames the directory to the slugified name, records the
149
+ old name in `titled_from`, increments `revisions`, re-seals the manifest and refreshes every
150
+ render. `rename <id-prefix> <new-project>` moves the session under another project and sets
151
+ `prev_project`.
152
+
153
+ ## Revisions, watermark and idempotence
154
+
155
+ Each build appends only the turns with `seq > watermark`, then sets `watermark` to the
156
+ highest `seq` seen and increments `revisions`. A rebuild with no new turns and an unchanged
157
+ `raw_sha256` prints `handoff: up-to-date` and writes nothing.
158
+
159
+ ## Provenance and verification
160
+
161
+ | Value | Definition |
162
+ |---|---|
163
+ | `raw_sha256` | SHA-256 of the exact source bytes read. |
164
+ | `manifest_sha256` | SHA-256 of the manifest with `manifest_sha256` removed. |
165
+ | `provenance` block in both JSON payloads | `sources`, `raw_sha256`, `manifest_sha256`, `revision`, `watermark`, `total_turns`. |
166
+
167
+ ```
168
+ node tools/handoff.mjs verify <id-prefix>
169
+ ```
170
+
171
+ recomputes the manifest hash, requires `timeline.jsonl` to exist, compares its line count
172
+ with `turn_count`, and parses `HANDOFF.llm.json`. It prints `PASS <id> [...]` and exits 0, or
173
+ `FAIL <id>: ...` and exits 1. Exit 2 is a usage error, 3 an ambiguous prefix, 4 no match.
174
+
175
+ ## Payload schemas
176
+
177
+ `schemas/handoff.schema.json` is the portable handoff payload contract, version `1.0`: a
178
+ single JSON object with required keys `schema_version`, `handoff_id`, `mission_id`,
179
+ `task_id`, `created_at`, `updated_at`, `source`, `state` and `next_action`, and optional
180
+ blocks for capabilities, permissions, checkpoint, artifacts, evidence, decisions, errors and
181
+ the next action.
182
+
183
+ The engine does not emit that payload. Its own outputs are the session files listed above,
184
+ and the JSON it writes is validated by consumption, not by that schema. The schema is the
185
+ interchange contract for a consumer that wants a single structured document.
@@ -0,0 +1,394 @@
1
+ ---
2
+ title: Installation
3
+ ---
4
+
5
+ # Installation
6
+
7
+ agents-handoff turns a working session into a portable handoff folder, and verifies that folder
8
+ later. It is a Node.js command-line skill with no runtime dependencies.
9
+
10
+ ## Requirements
11
+
12
+ | Requirement | Notes |
13
+ |---|---|
14
+ | Node.js >= 18.0.0 | The engine uses ES modules and `node:fs`. |
15
+ | `unzip` | Only needed to extract a release archive by hand. |
16
+ | `tar` | Only needed when the installer fetches an archive instead of copying the tree beside it. |
17
+ | No network at run time | The engine never makes a network call. `npx` and `--verify-package` do. |
18
+
19
+ ## Quick start
20
+
21
+ ```bash
22
+ npx agents-handoff --all # every harness found on this machine
23
+ ```
24
+
25
+ One run covers one harness, several, or a directory of your own; the table in
26
+ [Install into the harness you use](#install-into-the-harness-you-use) has the whole set. The
27
+ installer copies the skill files into the resolved global root (or the harness directories
28
+ named), then reports how many files it copied. The published package carries the whole tree, so
29
+ this needs no download; only a bare copy of `install/` falls back to fetching the archive for
30
+ the requested version.
31
+
32
+ Confirm the installation:
33
+
34
+ ```bash
35
+ node "<install-path>/tools/handoff.mjs" config
36
+ ```
37
+
38
+ That prints the handoff root the engine will use:
39
+
40
+ ```
41
+ handoff: config root=<dir>
42
+ handoff: config source=default
43
+ handoff: config file=none
44
+ handoff: config schema=<dir>/handoff.config.schema.json
45
+ ```
46
+
47
+ ## Installing from GitHub
48
+
49
+ Two ways, and both install the same tree:
50
+
51
+ ```bash
52
+ # npm runs the repository's own package straight from GitHub — no registry copy involved
53
+ npx github:Alot1z/agent-handoff --all
54
+
55
+ # or clone it and run the installer from the checkout
56
+ git clone https://github.com/Alot1z/agent-handoff.git
57
+ cd agent-handoff
58
+ node install/install.mjs --all
59
+ ```
60
+
61
+ The clone is also the way to install a version that is not on npm at all: a branch, a commit or
62
+ a tag. The installer accepts the same flags either way, and a checkout installs the tree in front
63
+ of it.
64
+
65
+ ## Install into the harness you use
66
+
67
+ With no harness flag the installer uses the resolved global root (next section). A harness flag
68
+ puts the skill where that tool reads its skills, and several flags cover several tools in one
69
+ run:
70
+
71
+ ```bash
72
+ npx agents-handoff --claude # Claude Code
73
+ npx agents-handoff --codex # Codex CLI
74
+ npx agents-handoff --agents # the harness-neutral store
75
+ npx agents-handoff --all # every harness found on this machine
76
+ npx agents-handoff --claude --codex # exactly these two, one run
77
+ npx agents-handoff --harness claude,codex
78
+ npx agents-handoff --skills-dir ~/.config/mytool/skills
79
+ npx agents-handoff --project --claude # this repository only (./.claude/skills)
80
+ ```
81
+
82
+ | Flag | Harness | User-level directory | Per-repository directory, with `--project` |
83
+ |---|---|---|---|
84
+ | `--claude` | Claude Code | `~/.claude/skills` | `./.claude/skills` |
85
+ | `--codex` | Codex CLI | `~/.codex/skills` | `./.codex/skills` |
86
+ | `--agents` | the harness-neutral store | `~/.agents/skills` | `./.agents/skills` |
87
+ | `--harness <a,b>` | named harness(es), comma separated, repeatable | as above | as above |
88
+ | `--skills-dir <dir>` | any other stack, exactly | `<dir>` | `<dir>` |
89
+ | `--all` | every harness whose configuration directory exists here | as above | — |
90
+
91
+ Each target gets the skill folder `agents-handoff/` below that directory, so Claude Code finds
92
+ `~/.claude/skills/agents-handoff/`. Targets are installed, reported and recorded one by one,
93
+ and the run ends with a summary:
94
+
95
+ ```
96
+ ── target 1 of 2: claude ──
97
+ ✓ Installed agents-handoff v<version> to <dir>/agents-handoff
98
+ Provenance: the tree beside the installer (.) · file-set sha256 3dab5c0d754a27fd… (.agents-handoff-install.json)
99
+
100
+ ── target 2 of 2: custom dir <dir> ──
101
+ …
102
+
103
+ Summary
104
+ ✓ installed <dir> — v<version>
105
+ ✓ installed <dir> — v<version>
106
+ ```
107
+
108
+ `--all` installs into the harnesses whose configuration directory exists in your home directory
109
+ and names the ones it skipped; `--claude` and its siblings install there whether or not the
110
+ directory exists yet. Detection is a suggestion, never a decision.
111
+
112
+ Ask what this machine has, and whether what is installed is still intact:
113
+
114
+ ```bash
115
+ npx agents-handoff doctor
116
+ ```
117
+
118
+ ```
119
+ agents-handoff doctor
120
+ product v<version> · installer v<version> · node v<node version>
121
+
122
+ Harnesses
123
+ ✓ claude present <home>/.claude/skills
124
+ v<version> · 39/39 files · provenance OK
125
+ · codex not found <home>/.codex/skills
126
+ ✓ agents present <home>/.agents/skills
127
+
128
+ Global resolution
129
+ <dir> — <reason>
130
+
131
+ Store (where handoffs are written)
132
+ handoff: config root=<dir>
133
+
134
+ Verdict
135
+ 1 harness installation(s) found.
136
+ ```
137
+
138
+ `doctor` reads and reports. It never installs, updates or removes anything.
139
+
140
+ ## Where a global install goes
141
+
142
+ `--location global` does not point at a fixed directory. The installer searches for a store and
143
+ reports the reason it chose one. The order is:
144
+
145
+ | # | Condition | Result |
146
+ |---|---|---|
147
+ | 1 | `AGENT_HANDOFF_GLOBAL_DIR` is set | that directory |
148
+ | 2 | A skill store already holds an `agents-handoff` install | the newest such location |
149
+ | 3 | `~/.agents/skills` exists | `~/.agents/skills` |
150
+ | 4 | A skill store exists | its first account-skill root |
151
+ | 5 | Nothing found | the default store, created on install |
152
+
153
+ An account-skill store keeps skills two identifier levels below the store itself:
154
+
155
+ ```
156
+ <store>/<account-id>/<profile-id>/agents-handoff/
157
+ ```
158
+
159
+ `<store>` is `%APPDATA%\<client>\account-skills` on Windows, and `~/.<client>/account-skills` or
160
+ `~/.config/<client>/account-skills` on macOS and Linux, for whichever desktop client keeps
161
+ skills there. The installer searches every store it can find and never assumes one of them.
162
+
163
+ Inspect the decision before installing anything:
164
+
165
+ ```bash
166
+ npx agents-handoff where
167
+ ```
168
+
169
+ ```
170
+ Global install root: <dir>
171
+ chosen because: <reason>
172
+ override with: AGENT_HANDOFF_GLOBAL_DIR=<dir> or --path <dir>
173
+ ```
174
+
175
+ `--list` shows the resolved root, the local location, every candidate root, each harness
176
+ directory (whether or not it exists on this machine), and — inside a git repository — the
177
+ project location. Each installation found is printed with its version, the number of manifest
178
+ files present, and whether it still matches its install record.
179
+
180
+ ## Locations
181
+
182
+ | Location | Target directory |
183
+ |---|---|
184
+ | `global` (default) | `<resolved global root>/agents-handoff` |
185
+ | `local` | `./local/skills/agents-handoff` |
186
+ | `project` | `./skills/agents-handoff` |
187
+ | `--path <dir>` | exactly `<dir>` |
188
+
189
+ ## Options
190
+
191
+ | Option | Default | Effect |
192
+ |---|---|---|
193
+ | `--location <global\|local\|project>` | `global` | Which location to install, update, remove or verify. |
194
+ | `--path <dir>` | none | Use this directory instead of a resolved location. |
195
+ | `--version <v>` | `latest` | Request a version. Confirm what landed with `--verify`, which prints the installed version. |
196
+ | `--force`, `-f` | off | Skip confirmations and overwrite an existing installation. |
197
+ | `--claude`, `--codex`, `--agents` | none | Install into that harness's skills directory (table above). Repeatable, and combinable. |
198
+ | `--harness <a,b>` | none | Named harness(es), comma separated. Repeatable. |
199
+ | `--all` | none | Every harness whose configuration directory exists on this machine. |
200
+ | `--skills-dir <dir>` | none | Any other stack, exactly. Repeatable. |
201
+ | `--project` | off | With a harness flag: use the per-repository directory instead of the user-level one. |
202
+ | `--provenance` | off | With `verify`: print the install record the verification was checked against. |
203
+ | `--record` | off | With `verify-package`: store the tarball hashes in the install record. |
204
+ | `--help`, `-h` | — | Print the installer usage text. |
205
+
206
+ The installer accepts both bare verbs and flag forms: `install`/`--install`, `update`/`--update`,
207
+ `remove`/`--remove`, `verify`/`--verify`, `verify-package`/`--verify-package`, `list`/`--list`,
208
+ `doctor`/`--doctor`.
209
+
210
+ ## Commands
211
+
212
+ | Command | Description |
213
+ |---|---|
214
+ | `install`, `i` (default) | Copy the skill files to the target. |
215
+ | `update`, `u` | Update every installation found, or the harnesses named. See [UPGRADE.md](UPGRADE.md). |
216
+ | `remove`, `rm` | Remove an installation, keeping everything the manifest does not own. See [UNINSTALL.md](UNINSTALL.md). |
217
+ | `verify`, `v` | Check every installation found: each manifest file, the skill metadata, that the engine runs, and the install record. |
218
+ | `verify-package`, `vp` | Check an installation against the published npm tarball for its version. |
219
+ | `list`, `ls` | List installed locations with version, manifest file count and provenance state. |
220
+ | `where` | Print the global root and why it was chosen. |
221
+ | `doctor` | Which harnesses are present here, what is installed where, and whether each installation still matches its record. |
222
+
223
+ ## Verify an installation
224
+
225
+ ```bash
226
+ npx agents-handoff --verify
227
+ ```
228
+
229
+ Verification runs four kinds of check:
230
+
231
+ 1. every file named in the installer manifest exists in the target;
232
+ 2. `SKILL.md` declares both `name` and `version`;
233
+ 3. `node tools/handoff.mjs config` exits 0 and prints the `handoff: config root=` marker — this
234
+ exercises the engine's module graph, so a missing module fails here;
235
+ 4. the installation still matches the provenance record written when it was installed.
236
+
237
+ Each check is printed with a pass or fail mark. On success the installer also prints the
238
+ installed location and version; on failure it exits non-zero and names the files that changed.
239
+
240
+ With no harness flag, `--verify` verifies **every installation found on this machine**, not just
241
+ the one the global resolver would pick: a machine that holds the skill in `~/.claude/skills` and
242
+ in `~/.agents/skills` has two copies, and both are checked. A harness flag or `--skills-dir`
243
+ scopes the run to those targets. The run fails if any installation fails.
244
+
245
+ There is no `--verbose` flag.
246
+
247
+ ## Prove an installation matches the published package
248
+
249
+ `--verify` compares an installation with its own record. `--verify-package` compares it with the
250
+ artifact npm is actually serving for its version — the strongest check available, and it works
251
+ whichever way the install happened: from a tree, from a GitHub archive, or from `npx`.
252
+
253
+ ```bash
254
+ npx agents-handoff --verify-package
255
+ npx agents-handoff --verify-package --record
256
+ ```
257
+
258
+ It runs three checks, in this order, because each one makes the next meaningful:
259
+
260
+ 1. the downloaded tarball matches the hashes the **registry declares** for that version
261
+ (`dist.integrity` and `dist.shasum`) — otherwise "the published package" would be whatever
262
+ the network handed over;
263
+ 2. every file named in the installer manifest is byte-identical to the file of that name in the
264
+ published tarball — each difference is named, with `not installed`, `not in the published
265
+ package` or `content differs`;
266
+ 3. the tarball's sha256 matches the one recorded for this installation, when one was recorded.
267
+
268
+ It prints the package name and version, the tarball URL and the tarball's sha256. `--record`
269
+ stores that sha256 (plus sha512, the integrity string and the shasum) in the `package` block of
270
+ the install record, so every later run compares against a stored value instead of re-deriving
271
+ one.
272
+
273
+ This check needs the network. When the registry cannot be reached, or the installed version is
274
+ not published, it fails and says so — it never passes by default.
275
+
276
+ ## The install record (provenance)
277
+
278
+ Every install writes `.agents-handoff-install.json` inside the installed copy. It records what
279
+ landed and what it was made from:
280
+
281
+ | Field | Meaning |
282
+ |---|---|
283
+ | `product`, `version`, `installer_version` | What was installed, and which installer did it. |
284
+ | `installed_at` | When. |
285
+ | `harness`, `target` | Which harness flag selected the target, and its absolute path. |
286
+ | `source` | `the tree beside the installer`, or the tag archive with its URL and sha256. |
287
+ | `package` | The npm identity this installation should match: `name`, `version`, `registry`, `tarball`, and — once `--verify-package --record` has run — `sha256`, `sha512`, `integrity`, `shasum` and `verified_at`. |
288
+ | `file_count`, `files_sha256` | The installed file set, folded into one hash. |
289
+ | `files` | Every manifest path with its own sha256. |
290
+
291
+ `verify` re-hashes the same file set and compares it with the record, so a changed, missing or
292
+ renamed file fails the check and is named. That is the difference between an installation being
293
+ present and being checkable: the file that changed is reported, not just `verification failed`.
294
+
295
+ ```bash
296
+ npx agents-handoff --verify --provenance
297
+ ```
298
+
299
+ `--provenance` prints the record it checked against, so the verification can be read without
300
+ opening the file:
301
+
302
+ ```
303
+ provenance record
304
+ installed_at: <iso timestamp>
305
+ source: the tree beside the installer (.)
306
+ harness: claude
307
+ files: 39 · file-set sha256 3dab5c0d754a27fd…
308
+ ```
309
+
310
+ An installation made before the record existed reports
311
+ `· no provenance record — installed before 2.0.3; reinstall to record one` and is otherwise
312
+ verified as before. Reinstall to write one.
313
+
314
+ The `package` block is the hook for the published-tarball check: installing from a tree cannot
315
+ know the hash of a tarball it did not download, so the hash is recorded the first time
316
+ `npx agents-handoff --verify-package --record` runs, and compared on every run after that.
317
+
318
+ ## What `remove` keeps
319
+
320
+ `remove` deletes exactly what the install manifest owns — the skill files, and the two files the
321
+ installer writes for itself — and nothing else. Everything the manifest does not own is kept and
322
+ listed:
323
+
324
+ ```
325
+ Kept — not the installer's to delete:
326
+ .agent-handoff/
327
+ handoff-session.md
328
+ handoff.config.json
329
+ handoffs/
330
+ projects/
331
+ ```
332
+
333
+ The rule is stated as a removal set rather than a keep list on purpose: a keep list deletes
334
+ whatever nobody remembered to name, so a store called anything other than the expected
335
+ directories would have gone with the skill. A `package.json` you wrote yourself is kept too; only
336
+ the installer's own private stub is removed.
337
+
338
+ ## Installing again
339
+
340
+ Installing over an existing installation does nothing by default when the requested version is
341
+ `latest`: the installer reports the version already present, then stops. It tells you to use
342
+ `--update` to upgrade, or `--force` to reinstall the same files.
343
+
344
+ ## Manual installation
345
+
346
+ Use the release archive when you cannot run `npx`.
347
+
348
+ 1. Download the archive the release attaches: `agents-handoff-v<version>.zip` from the
349
+ [releases page](https://github.com/Alot1z/agent-handoff/releases) (there is no
350
+ `latest` asset — the newest release carries its own version in the file name).
351
+ 2. Extract it into the target directory with `unzip`.
352
+ 3. Confirm the engine runs: `node "<target>/tools/handoff.mjs" config`.
353
+
354
+ A complete installation contains `SKILL.md`, `skill.json`, the manifest JSON files,
355
+ `tools/` (the engine and its runtime), `tools/lib/`, `schemas/`, `refs/`, `templates/`, `docs/`,
356
+ and `tests/`. That list is not a description: it is the installer's manifest, and a run that
357
+ cannot resolve any entry fails rather than reporting an incomplete installation as a success.
358
+
359
+ ## Handoff storage is separate
360
+
361
+ Two environment variables decide two different things, and they are not interchangeable:
362
+
363
+ | Variable | Decides |
364
+ |---|---|
365
+ | `AGENT_HANDOFF_GLOBAL_DIR` | Where the installer puts the skill. |
366
+ | `HANDOFFS_ROOT` | Where the engine stores handoff data. |
367
+
368
+ By default the engine stores handoff data under its own root, inside the installation. Set
369
+ `HANDOFFS_ROOT` when you want handoffs somewhere else, for example in a versioned directory.
370
+
371
+ ## Troubleshooting
372
+
373
+ | Symptom | Cause and fix |
374
+ |---|---|
375
+ | `Cannot write to <dir>` | The target is not writable. Pick another location with `--path`, or fix permissions. |
376
+ | `Not installed at <dir>` | `--path` or a harness flag named a directory that holds no installation. Drop the flag to act on every installation found, or run `install` first. |
377
+ | `Nothing to update:` / `Nothing to verify:` | No installation was found anywhere the installer looks. Install one, or name a target with `--path`. |
378
+ | `verify-package` fails with `is not on the npm registry, or the registry is unreachable` | The installed version has no published tarball to compare against (a checkout install, or no network). The installation itself is untouched by that result. |
379
+ | `verify-package` fails with `does NOT match ... as published` | The check names each differing file. Reinstall with `--force`, then run it again; a file you edited on purpose will keep failing until it is reverted. |
380
+ | `Incomplete install: <n> of <m> file(s) missing — …` | The tree the installer reads from is not a complete one. Install from a fresh clone, or from a freshly downloaded archive. |
381
+ | `no published release found — fetching the main branch` | Not an error: `--version latest` found no release object, so the archive of `main` is used instead. Pass `--version <x>` to install a released tag. |
382
+ | `Cannot extract the archive (tar exited …)` | `tar` is missing, or the download did not arrive intact. The message prints the `curl` and `tar` commands that do the same job by hand. |
383
+ | The wrong root was chosen | Run `where` to see the reason, then set `AGENT_HANDOFF_GLOBAL_DIR` or pass `--path`. |
384
+ | `remove` did nothing | Removal asks for confirmation, and refuses in a non-interactive shell. Pass `--force`. |
385
+ | Verification fails | The output names the failing check. Fix it, or reinstall with `--force`. |
386
+ | `provenance: file-set sha256 matches the install record` fails | A file in the installation changed or went missing after it was installed. The changed paths are listed under the check; reinstall with `--force` to restore them, or keep the edit knowingly. |
387
+ | `no provenance record` | The installation predates the record. That is not a failure; reinstall to write one. |
388
+ | A harness install went somewhere unexpected | Harness directories are listed in the table above and by `doctor`/`list`. Use `--path` or `--skills-dir` to name the directory yourself. |
389
+
390
+ ## See also
391
+
392
+ - [UPGRADE.md](UPGRADE.md)
393
+ - [UNINSTALL.md](UNINSTALL.md)
394
+ - [../README.md](https://github.com/Alot1z/agent-handoff/blob/main/README.md)