agents-handoff 0.0.0-stage → 2.0.2

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 (54) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/LICENSE +21 -0
  3. package/README.md +110 -2
  4. package/SKILL.md +147 -0
  5. package/capability-registry.json +27 -0
  6. package/docs/ARCHITECTURE.md +164 -0
  7. package/docs/CHANGELOG.md +151 -0
  8. package/docs/CLI.md +196 -0
  9. package/docs/COMPATIBILITY.md +124 -0
  10. package/docs/CONTRIBUTING.md +134 -0
  11. package/docs/FORMAT.md +157 -0
  12. package/docs/INSTALL.md +179 -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 +83 -0
  18. package/docs/SECURITY.md +93 -0
  19. package/docs/SESSIONS.md +66 -0
  20. package/docs/TROUBLESHOOTING.md +158 -0
  21. package/docs/UNINSTALL.md +122 -0
  22. package/docs/UPGRADE.md +139 -0
  23. package/docs/_config.yml +16 -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 +83 -0
  28. package/handoff.config.example.json +35 -0
  29. package/handoff.config.schema.json +117 -0
  30. package/install/CHANGELOG.md +48 -0
  31. package/install/README.md +76 -0
  32. package/install/install.mjs +856 -0
  33. package/install/package.json +39 -0
  34. package/package.json +66 -4
  35. package/permission-policy.json +33 -0
  36. package/refs/ADAPTERS.md +33 -0
  37. package/refs/bootstrap.md +59 -0
  38. package/refs/brief-checklist.md +79 -0
  39. package/refs/handbook.md +58 -0
  40. package/refs/protocol.md +117 -0
  41. package/refs/roles.md +75 -0
  42. package/refs/validator.md +73 -0
  43. package/schemas/handoff.schema.json +275 -0
  44. package/skill.json +147 -0
  45. package/templates/HANDOFF.llm.schema.json +144 -0
  46. package/templates/HANDOFF.template.md +40 -0
  47. package/tests/acceptance/acceptance.yaml +209 -0
  48. package/tests/fixtures/minimal-transcript.jsonl +2 -0
  49. package/tools/agent-handoff.mjs +410 -0
  50. package/tools/capability-registry.mjs +120 -0
  51. package/tools/handoff.mjs +398 -0
  52. package/tools/handoff.test.mjs +465 -0
  53. package/tools/lib/handoff-root.mjs +161 -0
  54. package/tools/runtime-engine.mjs +330 -0
package/docs/FORMAT.md ADDED
@@ -0,0 +1,157 @@
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
+ ## manifest.json
66
+
67
+ | Field | Meaning |
68
+ |---|---|
69
+ | `session` | Session id as resolved for this handoff. |
70
+ | `project` | Project slug. |
71
+ | `harness`, `model` | From `--harness` / `--model`, from the source's own fields, or `unknown` / `""`. |
72
+ | `created_at`, `updated_at` | ISO 8601. `created_at` is set once on creation. |
73
+ | `source_paths` | Every source path this session was ever built from. |
74
+ | `watermark` | Highest `seq` already emitted. Turn `seq <= watermark` is already in `timeline.jsonl`. |
75
+ | `raw_sha256` | SHA-256 of the source bytes at the last build. |
76
+ | `revisions` | Number of writes. Increments on every build, `retitle` and `rename`. |
77
+ | `turn_count` | Number of turns in `timeline.jsonl` after the build. |
78
+ | `counts` | Turn counts by class: `USER`, `AGENT`, `THOUGHT`, `TOOL`. |
79
+ | `manifest_sha256` | Self-hash: SHA-256 of the manifest JSON with this field removed. |
80
+ | `prev_project` | Set when `rename` moves the session to another project. |
81
+ | `titled_from` | Previous directory names, set by `retitle`. |
82
+
83
+ ## Turn classes
84
+
85
+ `classify()` assigns each turn one class, in this order:
86
+
87
+ | Class | Rule |
88
+ |---|---|
89
+ | `TOOL` | `kind` contains `tool`, or `role` is `tool`. |
90
+ | `THOUGHT` | `kind` contains `reason` or `think`. |
91
+ | `USER` | `role` is `user`, or `kind` is `human`. |
92
+ | `AGENT` | `role` is `assistant`, or `kind` is `ai`. |
93
+ | `OTHER` | Everything else. Kept in `timeline.jsonl`, omitted from the tables in `HANDOFF.md`. |
94
+
95
+ Turn text is taken from `text`, then `content`, then `parts[].text`.
96
+
97
+ ## Accepted input
98
+
99
+ A source file ending in `.jsonl` is read line by line. Each line is parsed
100
+ independently; a line that does not parse, or whose text is blank, is skipped. `seq` is used
101
+ when it is a finite number, and the line index otherwise.
102
+
103
+ Any other extension is parsed as text: a line matching `user:`, `human:`, `assistant:`,
104
+ `ai:`, `system:` or `tool:` (optionally prefixed with `#`) starts a turn, and following
105
+ lines are appended to it. The role marker decides the class. Adapters that produce either
106
+ shape are listed in [ADAPTERS.md](https://github.com/Alot1z/agent-handoff/blob/main/refs/ADAPTERS.md).
107
+
108
+ If no turn parses, the build fails with exit 4.
109
+
110
+ ## Session id and directory name
111
+
112
+ 1. `--session <id>`, if given.
113
+ 2. else the `session` field of the first JSONL line, if present.
114
+ 3. else the source file name without its `.jsonl`, `.txt` or `.md` extension.
115
+
116
+ The directory name replaces every character outside `[\w.-]` with `_`. `INDEX.json` is
117
+ checked first: if a session with that id or with the same session UUID already exists, its
118
+ project and directory name are reused, so a rebuild lands in the same place.
119
+
120
+ `retitle <id-prefix> <new-name>` renames the directory to the slugified name, records the
121
+ old name in `titled_from`, increments `revisions`, re-seals the manifest and refreshes every
122
+ render. `rename <id-prefix> <new-project>` moves the session under another project and sets
123
+ `prev_project`.
124
+
125
+ ## Revisions, watermark and idempotence
126
+
127
+ Each build appends only the turns with `seq > watermark`, then sets `watermark` to the
128
+ highest `seq` seen and increments `revisions`. A rebuild with no new turns and an unchanged
129
+ `raw_sha256` prints `handoff: up-to-date` and writes nothing.
130
+
131
+ ## Provenance and verification
132
+
133
+ | Value | Definition |
134
+ |---|---|
135
+ | `raw_sha256` | SHA-256 of the exact source bytes read. |
136
+ | `manifest_sha256` | SHA-256 of the manifest with `manifest_sha256` removed. |
137
+ | `provenance` block in both JSON payloads | `sources`, `raw_sha256`, `manifest_sha256`, `revision`, `watermark`, `total_turns`. |
138
+
139
+ ```
140
+ node tools/handoff.mjs verify <id-prefix>
141
+ ```
142
+
143
+ recomputes the manifest hash, requires `timeline.jsonl` to exist, compares its line count
144
+ with `turn_count`, and parses `HANDOFF.llm.json`. It prints `PASS <id> [...]` and exits 0, or
145
+ `FAIL <id>: ...` and exits 1. Exit 2 is a usage error, 3 an ambiguous prefix, 4 no match.
146
+
147
+ ## Payload schemas
148
+
149
+ `schemas/handoff.schema.json` is the portable handoff payload contract, version `1.0`: a
150
+ single JSON object with required keys `schema_version`, `handoff_id`, `mission_id`,
151
+ `task_id`, `created_at`, `updated_at`, `source`, `state` and `next_action`, and optional
152
+ blocks for capabilities, permissions, checkpoint, artifacts, evidence, decisions, errors and
153
+ the next action.
154
+
155
+ The engine does not emit that payload. Its own outputs are the session files listed above,
156
+ and the JSON it writes is validated by consumption, not by that schema. The schema is the
157
+ interchange contract for a consumer that wants a single structured document.
@@ -0,0 +1,179 @@
1
+ ---
2
+ title: Installation
3
+ ---
4
+
5
+ # Installation
6
+
7
+ agent-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
+ | No network at run time | The engine never makes a network call. |
17
+
18
+ ## Quick start
19
+
20
+ ```bash
21
+ npx agents-handoff
22
+ ```
23
+
24
+ The installer copies the skill files into the resolved global root, then reports how many files
25
+ it copied. Confirm the installation:
26
+
27
+ ```bash
28
+ node "<install-path>/tools/handoff.mjs" config
29
+ ```
30
+
31
+ That prints the handoff root the engine will use:
32
+
33
+ ```
34
+ handoff: config root=<dir>
35
+ handoff: config source=default
36
+ handoff: config file=none
37
+ handoff: config schema=<dir>/handoff.config.schema.json
38
+ ```
39
+
40
+ ## Where a global install goes
41
+
42
+ `--location global` does not point at a fixed directory. The installer searches for a store and
43
+ reports the reason it chose one. The order is:
44
+
45
+ | # | Condition | Result |
46
+ |---|---|---|
47
+ | 1 | `AGENT_HANDOFF_GLOBAL_DIR` is set | that directory |
48
+ | 2 | A skill store already holds an `agent-handoff` install | the newest such location |
49
+ | 3 | `~/.agents/skills` exists | `~/.agents/skills` |
50
+ | 4 | A skill store exists | its first account-skill root |
51
+ | 5 | Nothing found | the default store, created on install |
52
+
53
+ An account-skill store keeps skills two identifier levels below the store itself:
54
+
55
+ ```
56
+ <store>/<account-id>/<profile-id>/agent-handoff/
57
+ ```
58
+
59
+ `<store>` is `%APPDATA%\<client>\account-skills` on Windows, and `~/.<client>/account-skills` or
60
+ `~/.config/<client>/account-skills` on macOS and Linux, for whichever desktop client keeps
61
+ skills there. The installer searches every store it can find and never assumes one of them.
62
+
63
+ Inspect the decision before installing anything:
64
+
65
+ ```bash
66
+ npx agents-handoff where
67
+ ```
68
+
69
+ ```
70
+ Global install root: <dir>
71
+ chosen because: <reason>
72
+ override with: AGENT_HANDOFF_GLOBAL_DIR=<dir> or --path <dir>
73
+ ```
74
+
75
+ `--list` shows the resolved root, the local location, every candidate root, the harness skills
76
+ home, and — inside a git repository — the project location. Each installed location is printed
77
+ with its version and the number of manifest files present.
78
+
79
+ ## Locations
80
+
81
+ | Location | Target directory |
82
+ |---|---|
83
+ | `global` (default) | `<resolved global root>/agent-handoff` |
84
+ | `local` | `./local/skills/agent-handoff` |
85
+ | `project` | `./skills/agent-handoff` |
86
+ | `--path <dir>` | exactly `<dir>` |
87
+
88
+ ## Options
89
+
90
+ | Option | Default | Effect |
91
+ |---|---|---|
92
+ | `--location <global\|local\|project>` | `global` | Which location to install, update, remove or verify. |
93
+ | `--path <dir>` | none | Use this directory instead of a resolved location. |
94
+ | `--version <v>` | `latest` | Request a version. Confirm what landed with `--verify`, which prints the installed version. |
95
+ | `--force`, `-f` | off | Skip confirmations and overwrite an existing installation. |
96
+ | `--help`, `-h` | — | Print the installer usage text. |
97
+
98
+ The installer accepts both bare verbs and flag forms: `install`/`--install`, `update`/`--update`,
99
+ `remove`/`--remove`, `verify`/`--verify`, `list`/`--list`.
100
+
101
+ ## Commands
102
+
103
+ | Command | Description |
104
+ |---|---|
105
+ | `install`, `i` (default) | Copy the skill files to the target. |
106
+ | `update`, `u` | Reinstall over an existing installation. See [UPGRADE.md](UPGRADE.md). |
107
+ | `remove`, `rm` | Remove the installation. See [UNINSTALL.md](UNINSTALL.md). |
108
+ | `verify`, `v` | Check every manifest file, the skill metadata, and that the engine runs. |
109
+ | `list`, `ls` | List installed locations with version and manifest file count. |
110
+ | `where` | Print the global root and why it was chosen. |
111
+
112
+ ## Verify an installation
113
+
114
+ ```bash
115
+ npx agents-handoff --verify
116
+ ```
117
+
118
+ Verification runs three kinds of check:
119
+
120
+ 1. every file named in the installer manifest exists in the target;
121
+ 2. `SKILL.md` declares both `name` and `version`;
122
+ 3. `node tools/handoff.mjs config` exits 0 and prints the `handoff: config root=` marker — this
123
+ exercises the engine's module graph, so a missing module fails here.
124
+
125
+ Each check is printed with a pass or fail mark. On success the installer also prints the
126
+ installed location and version; on failure it exits non-zero and tells you to reinstall.
127
+
128
+ There is no `--verbose` flag.
129
+
130
+ ## Installing again
131
+
132
+ Installing over an existing installation does nothing by default when the requested version is
133
+ `latest`: the installer reports the version already present, then stops. It tells you to use
134
+ `--update` to upgrade, or `--force` to reinstall the same files.
135
+
136
+ ## Manual installation
137
+
138
+ Use the release archive when you cannot run `npx`.
139
+
140
+ 1. Download the archive from the repository releases page: `agent-handoff-latest.zip`, or
141
+ `agent-handoff-v<version>.zip` for a pinned version.
142
+ 2. Extract it into the target directory with `unzip`.
143
+ 3. Confirm the engine runs: `node "<target>/tools/handoff.mjs" config`.
144
+
145
+ A complete installation contains `SKILL.md`, `skill.json`, the manifest JSON files,
146
+ `tools/` (the engine and its runtime), `tools/lib/`, `schemas/`, `refs/`, `templates/`, `docs/`,
147
+ and `tests/`. That list is not a description: it is the installer's manifest, and a run that
148
+ cannot resolve any entry fails rather than reporting an incomplete installation as a success.
149
+
150
+ ## Handoff storage is separate
151
+
152
+ Two environment variables decide two different things, and they are not interchangeable:
153
+
154
+ | Variable | Decides |
155
+ |---|---|
156
+ | `AGENT_HANDOFF_GLOBAL_DIR` | Where the installer puts the skill. |
157
+ | `HANDOFFS_ROOT` | Where the engine stores handoff data. |
158
+
159
+ By default the engine stores handoff data under its own root, inside the installation. Set
160
+ `HANDOFFS_ROOT` when you want handoffs somewhere else, for example in a versioned directory.
161
+
162
+ ## Troubleshooting
163
+
164
+ | Symptom | Cause and fix |
165
+ |---|---|
166
+ | `Cannot write to <dir>` | The target is not writable. Pick another location with `--path`, or fix permissions. |
167
+ | `Not installed at <dir>` | `update` and `verify` require an existing installation. Run `install` first. |
168
+ | `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. |
169
+ | `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. |
170
+ | `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. |
171
+ | The wrong root was chosen | Run `where` to see the reason, then set `AGENT_HANDOFF_GLOBAL_DIR` or pass `--path`. |
172
+ | `remove` did nothing | Removal asks for confirmation, and refuses in a non-interactive shell. Pass `--force`. |
173
+ | Verification fails | The output names the failing check. Fix it, or reinstall with `--force`. |
174
+
175
+ ## See also
176
+
177
+ - [UPGRADE.md](UPGRADE.md)
178
+ - [UNINSTALL.md](UNINSTALL.md)
179
+ - [../README.md](https://github.com/Alot1z/agent-handoff/blob/main/README.md)
@@ -0,0 +1,188 @@
1
+ ---
2
+ title: Integrating agent-handoff
3
+ ---
4
+
5
+ # Integrating agent-handoff
6
+
7
+ The engine is a command-line program with a file contract. There is no daemon, no network
8
+ call and no database. Integration means three things: getting a transcript into the canonical
9
+ input shape, running the engine, and reading the artifacts it writes.
10
+
11
+ - Put a transcript in the canonical shape: [Input contract](#input-contract).
12
+ - Run it: [Build a handoff](#build-a-handoff) and [Exit codes](#exit-codes).
13
+ - Read the result: [Output artifacts](#output-artifacts) and [Manifest fields](#manifest-fields).
14
+
15
+ ## Input contract
16
+
17
+ Every input line is one JSON object (JSONL). Any program that can write JSONL can feed the
18
+ engine; no client is required.
19
+
20
+ ```json
21
+ {"seq":0,"ts":"1787669764492","harness":"dsh","source":"<origin path>",
22
+ "session":"<id>","thread":"<project/thread>","role":"user|assistant|system|tool",
23
+ "kind":"<free-form: reasoning|tool_use|text>","text":"<message body>"}
24
+ ```
25
+
26
+ | Field | Used for | Notes |
27
+ |---|---|---|
28
+ | `seq` | ordering, watermark | Falls back to the line index when absent or non-numeric. |
29
+ | `ts` | timeline ordering | `timestamp` is accepted as an alias. |
30
+ | `role` | turn class | `user`, `assistant`, `system`, `tool`. |
31
+ | `kind` | turn class refinement | `tool_use` → TOOL, `reasoning`/`thinking` → THOUGHT. |
32
+ | `text` | the turn body | `content` and `parts[].text` are accepted as aliases. |
33
+ | `session` | handoff id | Used when `--session` is not passed. |
34
+ | `thread` | project inference | A `--tag--` inside the thread name is read as the project. |
35
+ | `harness` | provenance | Not read from the line. Set it with `--harness`; otherwise it is `unknown`. |
36
+ | `source` | provenance | Not read from the line. `source_paths` records the file given to `--source`. |
37
+
38
+ `harness` and `source` are carried for whatever reads the file later. The engine itself reads
39
+ `seq`, `ts`, `role`, `kind` and `text`, plus `session` and `thread` from the first line.
40
+
41
+ Turn classification order (`classify()` in [handoff.mjs](https://github.com/Alot1z/agent-handoff/blob/main/tools/handoff.mjs)):
42
+
43
+ 1. `kind` contains `tool`, or `role` is `tool` → **TOOL**
44
+ 2. `kind` contains `reason` or `think` → **THOUGHT**
45
+ 3. `role` is `user`, or `kind` is `human` → **USER**
46
+ 4. `role` is `assistant`, or `kind` is `ai` → **AGENT**
47
+ 5. anything else → **OTHER**
48
+
49
+ A line whose body is empty after that mapping is skipped. A file whose name ends in `.jsonl`
50
+ is parsed as JSONL; every other file is parsed as text, using role markers:
51
+
52
+ ```
53
+ user: what needs to happen next
54
+ assistant: the plan is in refs/plan.md
55
+ ```
56
+
57
+ The marker is `user|human|assistant|ai|system|tool`, optionally prefixed by `#` and followed
58
+ by `:` or `>`. Lines after a marker are appended to that turn until the next marker. A
59
+ `system:` line becomes OTHER, so it stays in the record without appearing as dialogue.
60
+
61
+ If no usable turn is parsed, the build fails with exit 4 rather than writing an empty handoff.
62
+
63
+ ## Build a handoff
64
+
65
+ ```bash
66
+ node tools/handoff.mjs build --source transcript.jsonl \
67
+ --session my-session --harness claude-code --model <name> \
68
+ --project my-project --objective "what this session was for"
69
+ ```
70
+
71
+ | Flag | Effect |
72
+ |---|---|
73
+ | `--source <file>` | The transcript to ingest. Required. |
74
+ | `--session <id>` | Handoff id. Default: the `session` field, else the file name. |
75
+ | `--harness <name>` | Provenance label. Default `unknown`; the engine reads no harness field. |
76
+ | `--model <name>` | Provenance label. Stored, never invented. |
77
+ | `--project <name>` | Project group. Default: config, then the `--tag--` in the thread, then the harness. |
78
+ | `--objective <text>` | Overrides the objective taken from the transcript. |
79
+ | `--force-harness` | Rewrite an existing harness value. Without it, a set value is kept. |
80
+ | `--force-model` | Rewrite an existing model value. Without it, a set value is kept. |
81
+
82
+ `node tools/handoff.mjs --handoff --source transcript.jsonl` is an alias of `build`.
83
+
84
+ A rebuild of the same session is incremental: only turns above the manifest watermark are
85
+ appended, and a source whose bytes and watermark are unchanged prints `up-to-date` and writes
86
+ nothing.
87
+
88
+ ## Where handoffs are stored
89
+
90
+ The root is resolved by one module, [tools/lib/handoff-root.mjs](https://github.com/Alot1z/agent-handoff/blob/main/tools/lib/handoff-root.mjs),
91
+ in this order:
92
+
93
+ | # | Source | Detail |
94
+ |---|---|---|
95
+ | 1 | `HANDOFFS_ROOT` environment variable | Always wins. This is what makes a CI run hermetic. |
96
+ | 2 | `handoff.config.json` | Found by walking up from the current directory. |
97
+ | 3 | a `handoffs/` directory | Found by walking up from the current directory. |
98
+ | 4 | the skill directory | Final fallback when nothing else exists. |
99
+
100
+ Inside a config, `storage.path` beats `handoff_dir`, and a relative `handoff_dir` resolves
101
+ against the config's own directory. The walk is bounded to ten levels. A config file that is
102
+ present but invalid fails the run with exit 2 — it is never ignored, because falling back
103
+ would write to a different store than the one configured.
104
+
105
+ `node tools/handoff.mjs config` prints the resolved root, the source that decided it, the
106
+ config path, the schema path, the project name and whether cross-linking is on.
107
+
108
+ ## Output artifacts
109
+
110
+ ```
111
+ <root>/
112
+ ├── INDEX.json # rebuilt from every manifest
113
+ ├── projects/<project>/
114
+ │ ├── PROJECT.md # project name, session list
115
+ │ └── <session>/
116
+ │ ├── HANDOFF.md # the brief a human or agent reads
117
+ │ ├── HANDOFF.summary.json # compact payload (schema 2.0.0-summary)
118
+ │ ├── HANDOFF.llm.json # full payload (schema 2.0.0)
119
+ │ ├── timeline.jsonl # append-only, one turn per line
120
+ │ ├── TOOLS.md # every tool call, verbatim
121
+ │ └── manifest.json # provenance and counters
122
+ └── links/<other-project>.md # cross-project relation notes
123
+ ```
124
+
125
+ `TOOLS.md` is the fidelity tier: it carries each tool call in full. The table inside
126
+ `HANDOFF.md` is a digest of the same turns.
127
+
128
+ ## Manifest fields
129
+
130
+ | Field | Meaning |
131
+ |---|---|
132
+ | `session`, `project`, `harness`, `model` | Identity and provenance. |
133
+ | `created_at`, `updated_at` | First write, last write. |
134
+ | `source_paths` | Every source file ingested for this session. |
135
+ | `watermark` | Highest ingested `seq`. Everything below it is already in the timeline. |
136
+ | `raw_sha256` | Hash of the source bytes at the last build. |
137
+ | `revisions` | Build count for this session. |
138
+ | `turn_count` | Lines in `timeline.jsonl`. |
139
+ | `counts` | Turn count per class: USER, AGENT, THOUGHT, TOOL. |
140
+ | `manifest_sha256` | Hash of the manifest without this field. |
141
+
142
+ ## Verify a handoff
143
+
144
+ ```bash
145
+ node tools/handoff.mjs list # all sessions
146
+ node tools/handoff.mjs list <project> # one project
147
+ node tools/handoff.mjs show <id-prefix> # print the brief
148
+ node tools/handoff.mjs verify <id-prefix> # manifest sha, turn count, payload parses
149
+ ```
150
+
151
+ `verify` recomputes the manifest hash, compares the timeline line count against
152
+ `turn_count`, and parses `HANDOFF.llm.json`. It prints `PASS <id> [...]` or fails with exit 1
153
+ and the first broken check. See [PROVENANCE.md](PROVENANCE.md) for what the hash chain does
154
+ and does not detect.
155
+
156
+ ## Exit codes
157
+
158
+ | Code | Meaning |
159
+ |---|---|
160
+ | 0 | Success. |
161
+ | 1 | Integrity failure: tampered manifest, unparsable manifest, unexpected error. |
162
+ | 2 | Usage or configuration error: missing `--source`, missing argument, invalid config. |
163
+ | 3 | Ambiguous id prefix — more than one session matched. |
164
+ | 4 | No usable turns, no handoffs, or no session matching the prefix. |
165
+
166
+ ## CI recipe
167
+
168
+ ```yaml
169
+ - name: Build and verify the handoff
170
+ env:
171
+ HANDOFFS_ROOT: ${{ github.workspace }}/.handoffs
172
+ run: |
173
+ node skills/agent-handoff/tools/handoff.mjs build \
174
+ --source exported-transcript.jsonl --project ci --harness generic
175
+ node skills/agent-handoff/tools/handoff.mjs verify "$(ls .handoffs/projects/ci | head -1)"
176
+ ```
177
+
178
+ `HANDOFFS_ROOT` keeps the run off any configured store, and `verify` returns the exit code a
179
+ pipeline can gate on.
180
+
181
+ ## Limits
182
+
183
+ - Filesystem only: nothing is uploaded, and no network call is made.
184
+ - No watcher: a build happens when it is invoked. See [LEVEL4.md](LEVEL4.md) for the layer
185
+ that probes for staleness and runs the build for you.
186
+ - The engine reads whatever the transcript contains, including secrets. Redaction is the
187
+ caller's job. See [SECURITY.md](SECURITY.md).
188
+ - Adapter routes for common session stores: [../refs/ADAPTERS.md](https://github.com/Alot1z/agent-handoff/blob/main/refs/ADAPTERS.md).