delegation-layer 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -20,6 +20,8 @@ already use instead of replacing them.
20
20
  and time budget.
21
21
  - **Recoverable results** — a later `collect` call can read a finished task
22
22
  without launching the provider again.
23
+ - **Resumable turns** — a durable timeout can continue the exact provider
24
+ session through a linked successor task.
23
25
  - **Runtime compatibility** — the selected provider's executable and advertised
24
26
  capabilities are checked when the task is prepared.
25
27
  - **Native authentication** — provider login stays with the provider CLI; task
@@ -65,7 +67,7 @@ npx --yes delegation-layer install \
65
67
  ```
66
68
 
67
69
  The skill installer only installs guidance for your agent. It does not install
68
- the CLI, change provider logins, or configure a supervisor.
70
+ the CLI, change provider logins, or run a task.
69
71
 
70
72
  ### Install the CLI
71
73
 
@@ -85,8 +87,9 @@ pnpm dlx delegation-layer install-cli
85
87
  bunx --bun delegation-layer install-cli
86
88
  ```
87
89
 
88
- The release installer verifies checksums and installs `delegate` and
89
- `delegate-run` into `$HOME/.local/bin` by default. Set
90
+ The release installer verifies checksums and installs `delegate`,
91
+ `delegate-run`, `pueue`, and `pueued` into `$HOME/.local/bin` by default. The
92
+ CLI uses the bundled supervisor automatically. Set
90
93
  `DELEGATION_LAYER_INSTALL_DIR` to use another directory.
91
94
 
92
95
  Check the installation:
@@ -102,14 +105,17 @@ Normal users do not need Go or Node after choosing an installation method.
102
105
  Dispatching a task requires:
103
106
 
104
107
  - a released Delegation Layer CLI;
105
- - Pueue 4.0.4 with a private, running `pueued` daemon;
106
- - a signed-in supported provider CLI such as `agy`, `codex`, `claude`, `pi`,
107
- or `opencode`;
108
+ - a compatible provider CLI with an available native login;
109
+ - a supported provider CLI such as `agy`, `codex`, `claude`, `pi`, or `opencode`;
108
110
  - Darwin or Linux on amd64 or arm64 for the supplied release builds.
109
111
 
110
- Go is only needed for source builds and contributor work. Node.js 18 or newer
111
- is needed for the npm-based installers; it is not a runtime requirement for the
112
- released Go CLI. Provider authentication remains native to each provider.
112
+ The release contains the supervisor client and daemon and starts a private
113
+ instance under the state root, recovering it when a control command finds it
114
+ stopped. An explicit `--pueue-config` or
115
+ `DELEGATE_PUEUE_CONFIG` can still select an existing compatible supervisor for
116
+ advanced integrations. Node.js 18 or newer is needed only for the npm-based
117
+ installers; it is not a runtime requirement for the released Go CLI. Provider
118
+ authentication remains native to each provider.
113
119
 
114
120
  ## Try one bounded task
115
121
 
@@ -119,8 +125,8 @@ Create a brief and a workspace outside the private state root:
119
125
  mkdir -p "$HOME/delegation-workspace"
120
126
  printf '%s\n' 'Inspect the repository and summarize the current build status.' > brief.txt
121
127
 
122
- delegate --pueue-config /absolute/path/to/pueue.yml dispatch \
123
- --provider codex:exec \
128
+ delegate --root "$HOME/delegation-state" dispatch \
129
+ --auto \
124
130
  --brief "$PWD/brief.txt" \
125
131
  --cwd "$HOME/delegation-workspace" \
126
132
  --permission read-only \
@@ -138,8 +144,21 @@ delegate collect TASK_ID --watch 5s --json
138
144
 
139
145
  Use `delegate providers --json` for the compiled provider catalog and
140
146
  `delegate capabilities --provider PROFILE --json` for a provider-neutral
141
- capability response. Dispatch still performs the host-specific runtime probe;
142
- catalog discovery alone does not prove authentication or supervisor readiness.
147
+ capability response. `delegate preflight --provider PROFILE --cwd ABS --json`
148
+ checks static admission without creating a task. Dispatch still performs the
149
+ host-specific runtime probe; catalog discovery alone does not prove
150
+ authentication or supervisor readiness.
151
+
152
+ If a task reports `status: "timed_out"` and
153
+ `continuation.resumable: true`, continue the exact session:
154
+
155
+ ```sh
156
+ delegate --root "$HOME/delegation-state" continue \
157
+ --task TASK_ID --brief "$HOME/follow-up.txt" --budget 30m --json
158
+ ```
159
+
160
+ The successor receives a new task ID and is observed with the same `status` and
161
+ `collect` commands.
143
162
 
144
163
  ## Documentation
145
164
 
package/install.sh CHANGED
@@ -65,12 +65,12 @@ fi
65
65
 
66
66
  tar -xzf "$ARCHIVE" -C "$TEMP_DIR" || fail "could not extract $ARCHIVE"
67
67
  mkdir -p "$INSTALL_DIR"
68
- for binary in delegate delegate-run; do
68
+ for binary in delegate delegate-run pueue pueued; do
69
69
  [ -f "$TEMP_DIR/$binary" ] || fail "release archive does not contain $binary"
70
70
  install -m 0755 "$TEMP_DIR/$binary" "$INSTALL_DIR/$binary"
71
71
  done
72
72
 
73
- printf 'Installed delegate and delegate-run %s in %s\n' "$VERSION" "$INSTALL_DIR"
73
+ printf 'Installed delegation-layer CLI and bundled supervisor %s in %s\n' "$VERSION" "$INSTALL_DIR"
74
74
  case ":${PATH:-}:" in
75
75
  *:"$INSTALL_DIR":*) ;;
76
76
  *) printf 'Add %s to PATH if it is not already there.\n' "$INSTALL_DIR" ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "delegation-layer",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Multi-provider AI agent delegation CLI and Agent Skill for running bounded coding tasks across provider CLIs.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -24,7 +24,7 @@
24
24
  "LICENSE",
25
25
  "docs/assets/delegation-layer-hero.jpg",
26
26
  "install.sh",
27
- "skills/agent-integration/SKILL.md",
27
+ "skills/agent-integration/",
28
28
  "scripts/install-agent-integration.mjs",
29
29
  "scripts/install-cli.mjs"
30
30
  ],
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { accessSync, constants } from "node:fs";
4
- import { copyFile, lstat, mkdir, readFile } from "node:fs/promises";
4
+ import { copyFile, lstat, mkdir, readdir, readFile } from "node:fs/promises";
5
5
  import { emitKeypressEvents } from "node:readline";
6
6
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
7
7
  import { homedir } from "node:os";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
10
10
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
11
- const sourcePath = join(packageRoot, "skills", "agent-integration", "SKILL.md");
11
+ const sourceDirectory = join(packageRoot, "skills", "agent-integration");
12
12
  const harnesses = [
13
13
  {
14
14
  name: "universal",
@@ -215,6 +215,93 @@ async function existingPath(path) {
215
215
  }
216
216
  }
217
217
 
218
+ async function assertNoSymlinkComponents(path) {
219
+ let current = resolve(path);
220
+ while (true) {
221
+ const stat = await existingPath(current);
222
+ if (stat?.isSymbolicLink()) {
223
+ throw new Error(`skill destination must not contain symlink components: ${current}`);
224
+ }
225
+ const parent = dirname(current);
226
+ if (parent === current) {
227
+ return;
228
+ }
229
+ current = parent;
230
+ }
231
+ }
232
+
233
+ async function skillFiles(directory, relative = "") {
234
+ const entries = await readdir(join(directory, relative), { withFileTypes: true });
235
+ const files = [];
236
+ for (const entry of entries) {
237
+ const child = join(relative, entry.name);
238
+ const path = join(directory, child);
239
+ const stat = await lstat(path);
240
+ if (stat.isSymbolicLink()) {
241
+ throw new Error(`skill source or destination must not contain symlinks: ${path}`);
242
+ }
243
+ if (stat.isDirectory()) {
244
+ files.push(...(await skillFiles(directory, child)));
245
+ continue;
246
+ }
247
+ if (!stat.isFile()) {
248
+ throw new Error(`skill entry must be a regular file: ${path}`);
249
+ }
250
+ files.push(child);
251
+ }
252
+ return files.sort();
253
+ }
254
+
255
+ async function skillTreeMatches(target, files) {
256
+ for (const relative of files) {
257
+ const destination = join(target, relative);
258
+ await assertNoSymlinkComponents(destination);
259
+ const stat = await existingPath(destination);
260
+ if (!stat || !stat.isFile() || stat.isSymbolicLink()) {
261
+ return false;
262
+ }
263
+ const [source, existing] = await Promise.all([
264
+ readFile(join(sourceDirectory, relative)),
265
+ readFile(destination),
266
+ ]);
267
+ if (!source.equals(existing)) {
268
+ return false;
269
+ }
270
+ }
271
+ return true;
272
+ }
273
+
274
+ async function skillTreeHasExistingFiles(target, files) {
275
+ for (const relative of files) {
276
+ const destination = join(target, relative);
277
+ await assertNoSymlinkComponents(destination);
278
+ if (await existingPath(destination)) {
279
+ return true;
280
+ }
281
+ }
282
+ return false;
283
+ }
284
+
285
+ async function copySkillTree(target, files, force) {
286
+ for (const relative of files) {
287
+ const destination = join(target, relative);
288
+ const parent = dirname(destination);
289
+ await assertNoSymlinkComponents(destination);
290
+ await mkdir(parent, { recursive: true });
291
+ const stat = await existingPath(destination);
292
+ if (stat?.isSymbolicLink()) {
293
+ throw new Error(`existing skill file must not be a symlink: ${destination}`);
294
+ }
295
+ if (stat && !stat.isFile()) {
296
+ throw new Error(`existing skill entry is not a regular file: ${destination}`);
297
+ }
298
+ if (stat && !force) {
299
+ throw new Error(`skill files already exist; use --force to replace them: ${target}`);
300
+ }
301
+ await copyFile(join(sourceDirectory, relative), destination);
302
+ }
303
+ }
304
+
218
305
  function executableOnPath(command) {
219
306
  const pathEntries = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
220
307
  const candidates = process.platform === "win32" ? [command, `${command}.exe`, `${command}.cmd`] : [command];
@@ -465,6 +552,7 @@ function targetFor(harness, scope, cwd, home) {
465
552
  }
466
553
 
467
554
  async function install({ force, target }) {
555
+ await assertNoSymlinkComponents(target);
468
556
  const targetStat = await existingPath(target);
469
557
  if (targetStat?.isSymbolicLink()) {
470
558
  throw new Error(`target directory must not be a symlink: ${target}`);
@@ -473,33 +561,31 @@ async function install({ force, target }) {
473
561
  throw new Error(`target must be a directory: ${target}`);
474
562
  }
475
563
 
564
+ const files = await skillFiles(sourceDirectory);
476
565
  await mkdir(target, { recursive: true });
477
566
 
478
- const destination = join(target, "SKILL.md");
479
- const destinationStat = await existingPath(destination);
480
- if (destinationStat?.isSymbolicLink()) {
481
- throw new Error(`existing SKILL.md must not be a symlink: ${destination}`);
567
+ if (!force && (await skillTreeMatches(target, files))) {
568
+ console.log(`Skill already installed: ${join(target, "SKILL.md")}`);
569
+ return;
482
570
  }
483
- if (destinationStat && !destinationStat.isFile()) {
484
- throw new Error(`existing SKILL.md is not a regular file: ${destination}`);
571
+ if (!force && (await skillTreeHasExistingFiles(target, files))) {
572
+ throw new Error(`skill files already exist; use --force to replace them: ${target}`);
485
573
  }
486
574
 
487
- if (destinationStat && !force) {
488
- const [source, existing] = await Promise.all([
489
- readFile(sourcePath),
490
- readFile(destination),
491
- ]);
492
- if (source.equals(existing)) {
493
- console.log(`Skill already installed: ${destination}`);
494
- return;
495
- }
496
- throw new Error(`SKILL.md already exists; use --force to replace it: ${destination}`);
497
- }
575
+ await copySkillTree(target, files, force);
576
+ console.log(`Installed agent integration skill: ${join(target, "SKILL.md")}`);
577
+ }
498
578
 
499
- await copyFile(sourcePath, destination);
500
- console.log(`Installed agent integration skill: ${destination}`);
579
+ async function validateSourceTree() {
580
+ const files = await skillFiles(sourceDirectory);
581
+ if (!files.includes("SKILL.md")) {
582
+ throw new Error(`skill package is missing SKILL.md: ${sourceDirectory}`);
583
+ }
584
+ return files;
501
585
  }
502
586
 
587
+ await validateSourceTree();
588
+
503
589
  const argumentsList = process.argv.slice(2);
504
590
  if (argumentsList[0] === "install-cli") {
505
591
  try {
@@ -1,182 +1,200 @@
1
1
  ---
2
2
  name: agent-integration
3
- description: Use Delegation Layer from a provider-agnostic agent workflow, including capability discovery, bounded dispatch, terminal collection, and live authentication handling.
3
+ description: Use the installed Delegation Layer CLI to select a ready provider, run one bounded agent turn, collect durable JSON evidence, and continue a timed out session safely.
4
4
  ---
5
5
 
6
- # Agent integration
6
+ # Delegation Layer
7
+
8
+ Use this skill when a task should be performed by another coding agent through
9
+ the local `delegate` CLI. The CLI is the only integration surface. Do not call,
10
+ inspect, authenticate, or install a provider CLI directly.
11
+
12
+ ## Rules
13
+
14
+ 1. Check whether `delegate` is already installed. If it is missing, report the
15
+ missing prerequisite and stop. This skill never installs or repairs the CLI.
16
+ 2. Use `dispatch --auto` unless the user explicitly selected a provider ID.
17
+ Provider selection, executable checks, compatibility checks, authentication
18
+ gates, and supervisor admission belong inside Delegation Layer.
19
+ 3. Give every turn an absolute workspace, a separate state root, a finite
20
+ permission mode, and a finite budget.
21
+ 4. Use `--json` and save the returned `task_id`. Read `admission`, `liveness`,
22
+ `publication`, `status`, `outcome`, `continuation`, and `error` as separate
23
+ fields.
24
+ 5. Observe with `status` and collect with `collect`. Collection may recover the
25
+ saved supervisor only to observe an already-requested budget stop; it never
26
+ launches a provider or retries a task.
27
+ 6. If a task is timed out and its JSON says `continuation.resumable: true`, use
28
+ `continue --task TASK_ID`, optionally with a new `--brief`. Do not create a
29
+ fresh unrelated dispatch to replace a resumable task.
30
+ 7. Treat `blocked` authentication as unresolved. Do not call it compatible,
31
+ successful, or failed provider behavior, and do not invent login commands.
32
+ 8. Never put credentials, tokens, or unrestricted provider output in a brief,
33
+ option, receipt, or agent message.
34
+
35
+ ## Operating procedure
36
+
37
+ ### 1. Verify the control CLI
38
+
39
+ Run a quick local check:
7
40
 
8
- This skill is self-contained as an operating guide. It gives an agent enough
9
- information to prepare a safe run, choose a provider from observed
10
- capabilities, and collect a terminal result when the host exposes the
11
- Delegation Layer CLI. It applies to every provider profile and does not encode
12
- provider names, release versions, or native CLI syntax.
41
+ ```sh
42
+ command -v delegate && delegate --version
43
+ ```
13
44
 
14
- ## Preconditions
45
+ If this fails, tell the caller that Delegation Layer is not installed and
46
+ stop. Do not substitute a provider command.
15
47
 
16
- The host is responsible for installing and configuring the CLI and supervisor.
17
- This skill must not install, build, upgrade, or repair either one. It may check
18
- whether `delegate` is available, use a configured private supervisor
19
- configuration and socket, and create separate task and state directories that
20
- it is authorized to use.
21
- Authentication is a separate live gate; do not invent a provider-specific
22
- login check or classify a missing login as static incompatibility.
48
+ ### 2. Discover only when useful
23
49
 
24
- Keep the supervisor configuration, Delegation Layer state root, and task
25
- workspace as separate canonical paths. Do not put credentials in a brief, task
26
- option, environment value recorded by the task, or agent-visible output.
50
+ For the normal path, use automatic selection directly. If you need to explain
51
+ what the installed build knows, run:
27
52
 
28
- ## Start safely
53
+ ```sh
54
+ delegate providers --json
55
+ ```
29
56
 
30
- First check that the already-installed CLI is available, then discover compiled
31
- profiles:
57
+ This is a catalog, not proof that a provider is installed, authenticated, or
58
+ ready. If the caller named one provider ID and wants a side-effect-free check,
59
+ run:
32
60
 
33
61
  ```sh
34
- command -v delegate
35
- delegate help
36
- delegate providers --json
62
+ delegate preflight --provider PROVIDER_ID --cwd ABSOLUTE_WORKSPACE --json
37
63
  ```
38
64
 
39
- If `command -v delegate` fails, stop and report that the host prerequisite is
40
- missing. Do not install or build the CLI from this skill.
65
+ Preflight reports static admission only. It does not create a task, start the
66
+ supervisor, or launch a provider. Do not keep retrying preflight when it says
67
+ `blocked` or `unsupported`; report the bounded reason.
41
68
 
42
- `providers --json` is static discovery. It lists provider IDs, supported
43
- permission modes and options, and the help arguments and flags required by the
44
- compiled adapters. It does not inspect the host, authenticate a provider, or
45
- prove that a provider is launchable.
69
+ ### 3. Prepare separate paths and a finite brief
46
70
 
47
- Choose a listed `PROFILE`, then inspect its provider-neutral contract:
71
+ Use an existing task brief when one is supplied. Otherwise create a bounded
72
+ brief file that contains the requested work and its acceptance criteria. Keep
73
+ the state root outside the workspace:
48
74
 
49
75
  ```sh
50
- delegate capabilities --provider PROFILE --json
76
+ STATE_ROOT="${DELEGATE_ROOT:-$HOME/.local/state/delegation-layer}"
77
+ WORKSPACE="$PWD"
78
+ BRIEF="$(mktemp)"
79
+ cat >"$BRIEF" <<'EOF'
80
+ Describe the delegated task, the files or behavior to change, and the checks
81
+ that must pass. Keep the response concise and report validation evidence.
82
+ EOF
51
83
  ```
52
84
 
53
- This command is side-effect-free. It does not create state, contact Pueue, or
54
- launch a provider. A successful catalog response has
55
- `status: "unknown"`, `verification: "catalog"`, and
56
- `live_acceptance.status: "not_run"`. Unknown here means that dispatch has not
57
- run the host probe yet; it is neither a compatibility failure nor live proof.
85
+ Replace the example brief with the actual user request. Do not place the state
86
+ root inside the workspace or place secrets in either path’s task data.
87
+
88
+ ### 4. Dispatch one bounded turn
58
89
 
59
- Choose a listed `PROFILE`, ensure its provider executable is available to the
60
- dispatch environment, and prepare a finite brief and two separate absolute
61
- directories. Then dispatch one bounded task:
90
+ Use automatic selection unless the user explicitly supplied `PROVIDER_ID`:
62
91
 
63
92
  ```sh
64
- mkdir -p "${HOME}/delegation-workspace" "${HOME}/delegation-state"
65
- printf '%s\n' 'Inspect the workspace and return a short summary.' > "${HOME}/delegation-brief.txt"
66
-
67
- delegate --root "${HOME}/delegation-state" \
68
- --pueue-config /absolute/path/to/private-pueue.yml \
69
- dispatch \
70
- --provider PROFILE \
71
- --brief "${HOME}/delegation-brief.txt" \
72
- --cwd "${HOME}/delegation-workspace" \
93
+ delegate --root "$STATE_ROOT" dispatch --auto \
94
+ --brief "$BRIEF" \
95
+ --cwd "$WORKSPACE" \
73
96
  --permission read-only \
74
97
  --budget 30m \
75
98
  --json
76
99
  ```
77
100
 
78
- Use the host's configured private supervisor path (or its
79
- `DELEGATE_PUEUE_CONFIG` equivalent); the placeholder above is not a path to
80
- create. If the supervisor or provider executable is unavailable, stop on the
81
- CLI's structured error. For live acceptance, an unavailable authentication
82
- prerequisite is `blocked`; preserve that evidence and never relabel it as
83
- incompatible or passed.
101
+ For an authorized code change, use `--permission workspace-write`. Keep the
102
+ budget finite. A successful dispatch means the request was durably admitted;
103
+ it does not mean the delegated work finished.
84
104
 
85
- Save the returned `task_id`. Admission records one immutable request and at
86
- most one provider launch attempt; the command does not wait for completion.
87
- Observe and collect with that exact ID:
105
+ Save the exact `task_id` from the JSON response. If dispatch returns an error,
106
+ use its `status`, `capability`, and `error` fields to report whether the request
107
+ was invalid, unsupported, blocked by authentication, or operationally
108
+ uncertain. Do not silently try another provider after a task has been
109
+ admitted.
110
+
111
+ ### 5. Observe and collect
112
+
113
+ Use the same state root and task ID:
88
114
 
89
115
  ```sh
90
- delegate --root "${HOME}/delegation-state" status TASK_ID --json
91
- delegate --root "${HOME}/delegation-state" collect TASK_ID --watch 5s --json
116
+ delegate --root "$STATE_ROOT" status TASK_ID --json
117
+ delegate --root "$STATE_ROOT" collect TASK_ID --watch 5s --json
92
118
  ```
93
119
 
94
- Repeat `collect` while the task is pending. Collection is observational: it
95
- may wait for the bounded watch interval and clean up a validated reservation,
96
- but it never launches, retries, or resumes provider work. A continuation is a
97
- new task with an exact predecessor ID when the selected profile advertises
98
- continuation support.
120
+ If `collect` exits with code `3` because publication is still pending, wait a
121
+ short interval and run `collect` again. Do not dispatch again. Continue until
122
+ the response has a terminal `outcome` or a bounded operational error that
123
+ requires the caller’s attention.
124
+
125
+ Accept a result only when the terminal outcome is present and its publication
126
+ is committed. A rejected outcome is terminal evidence and must be reported as
127
+ rejected. Payload and raw output are returned as validated descriptors; do not
128
+ copy unrestricted provider output into the control response.
99
129
 
100
- ## Read the JSON contract
130
+ ### 6. Continue a timed out turn
101
131
 
102
- The capability response has this stable shape. The two arrays are
103
- profile-specific and can be non-empty; empty arrays below only keep the example
104
- provider-agnostic:
132
+ When `status` or `collect` returns `status: "timed_out"`, inspect the nested
133
+ continuation object:
105
134
 
106
135
  ```json
107
136
  {
108
- "schema_version": 1,
109
- "command": "capabilities",
110
- "capability": {
111
- "contract": "runtime-capability-v1",
112
- "provider": "PROFILE",
113
- "status": "unknown",
114
- "verification": "catalog",
115
- "help_args": [],
116
- "required_flags": [],
117
- "reason_code": "runtime_probe_not_run",
118
- "live_acceptance": {
119
- "status": "not_run",
120
- "authentication": "unknown",
121
- "reason_code": "runtime_probe_not_run"
122
- }
137
+ "status": "timed_out",
138
+ "continuation": {
139
+ "resumable": true,
140
+ "mode": "native",
141
+ "predecessor_task_id": "TASK_ID",
142
+ "continue_command": "delegate continue --task TASK_ID --json"
123
143
  }
124
144
  }
125
145
  ```
126
146
 
127
- Interpret the fields as follows:
128
-
129
- - `status=unknown` with `verification=catalog` is a declaration from the
130
- compiled catalog.
131
- - `status=ready` with `verification=runtime` appears in a dispatch response
132
- only after the supervised probe observed the required behavior and exact
133
- executable identity for that admission boundary.
134
- - `status=unsupported` is a machine-readable refusal. Use `reason_code` and
135
- the bounded error to explain it; do not try another profile without a new
136
- explicit selection.
137
- - `version` is diagnostic observation. Accept any valid nonempty trimmed UTF-8
138
- version text. Never use a release allowlist, semantic-version comparison, or
139
- web lookup as a compatibility decision. Required flags, executable identity,
140
- and provider behavior decide compatibility. An unusually large version may
141
- be omitted from the bounded projection.
142
- - `live_acceptance` is separate from compatibility. `not_run` does not claim a
143
- live test; `passed` requires authenticated provider evidence; `blocked` means
144
- authentication or another prerequisite was unavailable and is neutral. A
145
- blocked result must never be reported as a pass or as incompatible behavior.
146
-
147
- Dispatch JSON contains the same `capability` projection plus separate task
148
- `admission`, `liveness`, and `publication` fields. Read those fields
149
- independently. Trust a terminal `outcome` only after it has been returned by
150
- the validated collection path and its payload and sealed evidence descriptors
151
- match the task records.
152
-
153
- ## Integration acceptance contract
154
-
155
- Apply these criteria to every provider and every agent integration:
156
-
157
- - required behavior is verified at the runtime boundary;
158
- - arbitrary valid provider version text remains acceptable;
159
- - missing flags, executable identity drift, malformed facts, and timeouts fail
160
- closed;
161
- - authentication is represented separately from compatibility;
162
- - missing authentication is `blocked`, never a passing skip;
163
- - admission is at most once and collection is observational;
164
- - terminal results come from durable validated evidence; and
165
- - JSON output is bounded, deterministic for observational commands, versioned,
166
- and free of credentials.
167
-
168
- Do not expose credentials, tokens, private raw provider output, or unrestricted
169
- diagnostics in prompts, tool results, receipts, or task metadata. After an
170
- uncertain admission or lost response, inspect the original task with
171
- `status`/`collect`; do not relaunch it. Stop and report the durable state when
172
- the task cannot be resolved from validated evidence.
173
-
174
- ## Completion record
175
-
176
- An integration is complete when it can provide:
177
-
178
- - the selected profile and original task ID;
179
- - the bounded capability and dispatch JSON responses;
180
- - the terminal committed or rejected outcome and validated evidence
181
- descriptors; and
182
- - a sanitized live-acceptance receipt marked `passed`, `failed`, or `blocked`.
147
+ If `resumable` is true, create the linked successor through the public
148
+ continuation command. Supply a new follow-up brief when the next turn needs a
149
+ new instruction:
150
+
151
+ ```sh
152
+ delegate --root "$STATE_ROOT" continue \
153
+ --task TASK_ID \
154
+ --brief "$FOLLOW_UP_BRIEF" \
155
+ --budget 30m \
156
+ --json
157
+ ```
158
+
159
+ The command preserves the exact provider session and creates a new task ID.
160
+ Observe and collect the successor by repeating step 5. If `resumable` is
161
+ false, report the reason and stop; a fresh dispatch would lose the original
162
+ conversation and is not an equivalent recovery.
163
+
164
+ ## JSON decisions
165
+
166
+ The capability projection uses the stable `runtime-capability-v1` contract.
167
+ Catalog responses have `verification: "catalog"` and `status: "unknown"`.
168
+ Preflight responses have `verification: "preflight"` and describe static
169
+ readiness. A dispatch response can report `verification: "runtime"` only after
170
+ the supervised runtime probe has observed the required behavior and executable
171
+ identity.
172
+
173
+ Versions and help text are observations. Accept any valid nonempty version
174
+ reported by the CLI; never apply a semantic-version allowlist or web lookup.
175
+ The adapter’s required flags and runtime behavior decide compatibility.
176
+
177
+ Authentication is a separate live gate. `live_acceptance.status` values are
178
+ `not_run`, `passed`, or `blocked`; a blocked authentication prerequisite is
179
+ neutral evidence and must not be relabeled.
180
+
181
+ The task response separates:
182
+
183
+ - `admission`: whether the request was accepted by the durable boundary;
184
+ - `liveness`: the observed supervisor state;
185
+ - `publication`: whether validated evidence was committed or rejected;
186
+ - `outcome`: the terminal authority, when available;
187
+ - `continuation`: whether the exact session can make a linked successor; and
188
+ - `error`: bounded operational diagnostics.
189
+
190
+ See [references/command-reference.md](references/command-reference.md) for
191
+ the command matrix, [references/result-handling.md](references/result-handling.md)
192
+ for decision rules, and [references/continuation.md](references/continuation.md)
193
+ for timeout recovery. The examples and machine-readable checks in `examples/`
194
+ and `evals/` are part of this skill package.
195
+
196
+ ## Completion report
197
+
198
+ Report the original task ID, selected provider ID when the CLI returns one,
199
+ the final admission/liveness/publication fields, the terminal outcome and
200
+ evidence descriptors, and any continuation or blocked-authentication state.
@@ -0,0 +1,31 @@
1
+ {
2
+ "skill": "agent-integration",
3
+ "version": 1,
4
+ "cases": [
5
+ {
6
+ "name": "missing-cli",
7
+ "prompt": "Delegate this read-only task.",
8
+ "expected": ["check delegate", "report missing prerequisite", "do not install a provider"]
9
+ },
10
+ {
11
+ "name": "normal-dispatch",
12
+ "prompt": "Run one bounded task through the available agent provider.",
13
+ "expected": ["dispatch --auto", "--json", "save task_id", "status", "collect"]
14
+ },
15
+ {
16
+ "name": "timeout",
17
+ "prompt": "The result says status timed_out and continuation.resumable is true.",
18
+ "expected": ["delegate continue --task", "new task id", "collect successor", "no fresh unrelated dispatch"]
19
+ },
20
+ {
21
+ "name": "blocked-auth",
22
+ "prompt": "Capability output says live_acceptance.status is blocked.",
23
+ "expected": ["preserve blocked", "do not call a provider login command", "do not claim success"]
24
+ },
25
+ {
26
+ "name": "provider-neutrality",
27
+ "prompt": "Use the skill without a provider name.",
28
+ "expected": ["delegate only", "dispatch --auto", "no direct provider CLI"]
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,14 @@
1
+ # JSON decision sketch
2
+
3
+ ```sh
4
+ delegate --root "$STATE_ROOT" status "$TASK_ID" --json
5
+ delegate --root "$STATE_ROOT" collect "$TASK_ID" --watch 5s --json
6
+ ```
7
+
8
+ Decision order:
9
+
10
+ 1. Save `task_id`.
11
+ 2. Check `admission` and `error`.
12
+ 3. Wait while publication is pending.
13
+ 4. Accept only a committed terminal `outcome`.
14
+ 5. If `status` is `timed_out`, follow `continuation.resumable`.
@@ -0,0 +1,12 @@
1
+ # Read-only task
2
+
3
+ ```sh
4
+ STATE_ROOT="${DELEGATE_ROOT:-$HOME/.local/state/delegation-layer}"
5
+ BRIEF="$(mktemp)"
6
+ printf '%s\n' 'Inspect the workspace and summarize the current test failures. Do not edit files.' >"$BRIEF"
7
+ delegate --root "$STATE_ROOT" dispatch --auto \
8
+ --brief "$BRIEF" --cwd "$PWD" --permission read-only --budget 15m --json
9
+ ```
10
+
11
+ Save the returned `task_id`, then use `status` and `collect` with the same
12
+ state root.
@@ -0,0 +1,12 @@
1
+ # Authorized workspace write task
2
+
3
+ ```sh
4
+ STATE_ROOT="${DELEGATE_ROOT:-$HOME/.local/state/delegation-layer}"
5
+ delegate --root "$STATE_ROOT" dispatch --auto \
6
+ --brief "$PWD/delegated-task.md" --cwd "$PWD" \
7
+ --permission workspace-write --budget 30m --json
8
+ ```
9
+
10
+ Use `workspace-write` only when the caller authorized edits in the selected
11
+ workspace. The CLI still owns provider selection, policy, admission, and
12
+ publication.
@@ -0,0 +1,38 @@
1
+ # Command reference
2
+
3
+ All paths passed to `--root`, `--cwd`, and `--brief` must be absolute. Add
4
+ `--json` to commands that return an agent-consumable response.
5
+
6
+ | Command | Purpose | Launches provider work? |
7
+ | --- | --- | --- |
8
+ | `delegate --version` | Confirm the installed control CLI | No |
9
+ | `delegate providers --json` | List compiled provider metadata | No |
10
+ | `delegate capabilities --provider ID --json` | Read the catalog contract | No |
11
+ | `delegate preflight --provider ID --cwd ABS --json` | Check static provider admission | No |
12
+ | `delegate dispatch --auto ... --json` | Select and admit one bounded turn | Yes, at most once |
13
+ | `delegate status TASK --json` | Reconcile and observe task state | No provider launch |
14
+ | `delegate collect TASK --watch 5s --json` | Read or wait for publication | No provider launch |
15
+ | `delegate continue --task TASK ... --json` | Create an exact-session successor | Yes, at most once for the successor |
16
+ | `delegate logs TASK --json` | Return validated output descriptors | No |
17
+ | `delegate cancel TASK --json` | Record one explicit stop request | Stops, does not relaunch |
18
+
19
+ The bundled supervisor is managed by the released CLI under the state root.
20
+ Collection remains observational. Provider authentication remains the
21
+ provider’s own responsibility and is never performed by this skill.
22
+
23
+ Dispatch flags:
24
+
25
+ - `--brief FILE`: bounded task instructions;
26
+ - `--cwd ABS`: workspace for the delegated turn;
27
+ - `--permission read-only|workspace-write`: requested containment;
28
+ - `--budget DURATION`: finite turn budget;
29
+ - `--auto`: let the CLI select a ready provider;
30
+ - `--provider ID`: use only when the caller explicitly selected an ID;
31
+ - `--model MODEL` and `--effort VALUE`: optional provider-advertised choices;
32
+ - `--native-timeout DURATION`: optional provider-native timeout where advertised.
33
+
34
+ Continuation flags:
35
+
36
+ - `--task TASK`: predecessor task ID;
37
+ - `--brief FILE`: optional new follow-up instruction;
38
+ - `--budget DURATION`, `--model MODEL`, and `--effort VALUE`: optional overrides.
@@ -0,0 +1,42 @@
1
+ # Continuation and timeout recovery
2
+
3
+ Each task is one provider turn. A time limit ends that turn; it does not grant
4
+ permission to launch the same task again. The durable task record keeps the
5
+ exact provider session identity and the stop evidence.
6
+
7
+ When `status` or `collect` reports a timed-out turn with:
8
+
9
+ ```json
10
+ "continuation": {
11
+ "resumable": true,
12
+ "mode": "native",
13
+ "predecessor_task_id": "..."
14
+ }
15
+ ```
16
+
17
+ run:
18
+
19
+ ```sh
20
+ delegate --root "$STATE_ROOT" continue \
21
+ --task PREDECESSOR_TASK_ID \
22
+ --brief FOLLOW_UP_BRIEF \
23
+ --budget 30m \
24
+ --json
25
+ ```
26
+
27
+ The follow-up brief is optional. If it is omitted, Delegation Layer reuses the
28
+ validated predecessor brief. A successful continuation returns a new task ID
29
+ and links it to the predecessor. Observe and collect the new ID separately.
30
+
31
+ `mode` describes the provider contract:
32
+
33
+ - `native` uses the provider’s exact persisted conversation/session ID;
34
+ - `checkpoint` requires a provider checkpoint and is resumable only when the
35
+ response explicitly says so;
36
+ - `unsupported` cannot be continued safely.
37
+
38
+ Do not use a guessed session ID, a provider-specific resume command, or a new
39
+ independent dispatch to recover a timeout. The CLI accepts either a validated
40
+ terminal outcome or durable supervisor-ended budget-stop evidence for the
41
+ session handoff; without one of those proofs it refuses continuation. Inspect
42
+ the predecessor again rather than bypassing that boundary.
@@ -0,0 +1,25 @@
1
+ # Result handling
2
+
3
+ Use the response fields independently. A successful control command is not
4
+ the same as a successful provider result.
5
+
6
+ 1. Save `task_id` immediately after dispatch or continue.
7
+ 2. If `admission` is not admitted, report the bounded `error` and do not claim
8
+ that provider work ran.
9
+ 3. If `liveness` is queued or running, call `status` or `collect` again with a
10
+ finite watch interval.
11
+ 4. If `publication` is committed and `outcome.verdict` is committed, return
12
+ the result and its payload/evidence descriptors.
13
+ 5. If `outcome.verdict` is rejected, report a terminal rejected result with its
14
+ refusal and evidence. Do not retry automatically.
15
+ 6. If `status` is `timed_out`, follow the continuation procedure. A timeout is
16
+ recoverable only when `continuation.resumable` is true.
17
+ 7. If authentication is `blocked`, preserve that neutral state and tell the
18
+ caller what prerequisite is unavailable without inventing a login command.
19
+ 8. If a command reports an operational uncertainty, inspect the original task
20
+ with `status` and `collect` before considering any action. Never relaunch an
21
+ uncertain task by guessing.
22
+
23
+ Exit codes are bounded control signals: `0` is a completed command, `1` is an
24
+ operational error, `2` is an invalid or unsupported request, `3` is a pending
25
+ collection watch, and `4` is a validated rejected outcome.