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.
- package/CHANGELOG.md +150 -0
- package/LICENSE +21 -0
- package/README.md +110 -2
- package/SKILL.md +147 -0
- package/capability-registry.json +27 -0
- package/docs/ARCHITECTURE.md +164 -0
- package/docs/CHANGELOG.md +151 -0
- package/docs/CLI.md +196 -0
- package/docs/COMPATIBILITY.md +124 -0
- package/docs/CONTRIBUTING.md +134 -0
- package/docs/FORMAT.md +157 -0
- package/docs/INSTALL.md +179 -0
- package/docs/INTEGRATION.md +188 -0
- package/docs/LEVEL4.md +202 -0
- package/docs/LEVEL5.md +96 -0
- package/docs/PERMISSIONS.md +145 -0
- package/docs/PROVENANCE.md +83 -0
- package/docs/SECURITY.md +93 -0
- package/docs/SESSIONS.md +66 -0
- package/docs/TROUBLESHOOTING.md +158 -0
- package/docs/UNINSTALL.md +122 -0
- package/docs/UPGRADE.md +139 -0
- package/docs/_config.yml +16 -0
- package/docs/_data/nav.yml +36 -0
- package/docs/_layouts/default.html +31 -0
- package/docs/assets/style.css +88 -0
- package/docs/index.md +83 -0
- package/handoff.config.example.json +35 -0
- package/handoff.config.schema.json +117 -0
- package/install/CHANGELOG.md +48 -0
- package/install/README.md +76 -0
- package/install/install.mjs +856 -0
- package/install/package.json +39 -0
- package/package.json +66 -4
- package/permission-policy.json +33 -0
- package/refs/ADAPTERS.md +33 -0
- package/refs/bootstrap.md +59 -0
- package/refs/brief-checklist.md +79 -0
- package/refs/handbook.md +58 -0
- package/refs/protocol.md +117 -0
- package/refs/roles.md +75 -0
- package/refs/validator.md +73 -0
- package/schemas/handoff.schema.json +275 -0
- package/skill.json +147 -0
- package/templates/HANDOFF.llm.schema.json +144 -0
- package/templates/HANDOFF.template.md +40 -0
- package/tests/acceptance/acceptance.yaml +209 -0
- package/tests/fixtures/minimal-transcript.jsonl +2 -0
- package/tools/agent-handoff.mjs +410 -0
- package/tools/capability-registry.mjs +120 -0
- package/tools/handoff.mjs +398 -0
- package/tools/handoff.test.mjs +465 -0
- package/tools/lib/handoff-root.mjs +161 -0
- 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.
|
package/docs/INSTALL.md
ADDED
|
@@ -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).
|