delegation-layer 0.1.19 → 0.1.20
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 +11 -6
- package/package.json +1 -1
- package/plugins/delegation-layer/.claude-plugin/plugin.json +1 -1
- package/plugins/delegation-layer/.codex-plugin/plugin.json +1 -1
- package/plugins/delegation-layer/hooks/hooks.json +18 -0
- package/plugins/delegation-layer/skills/agent-integration/SKILL.md +20 -6
- package/plugins/delegation-layer/skills/agent-integration/evals/routing.json +47 -0
- package/plugins/delegation-layer/skills/agent-integration/references/collaboration.md +77 -0
- package/plugins/delegation-layer/skills/agent-integration/references/command-reference.md +7 -2
- package/plugins/delegation-layer/skills/agent-integration/references/routing-rule.json +1 -0
- package/plugins/delegation-layer/skills/agent-integration/scripts/delegate-routing-context.sh +12 -0
- package/scripts/install-agent-integration.mjs +212 -5
- package/skills/agent-integration/SKILL.md +20 -6
- package/skills/agent-integration/evals/routing.json +47 -0
- package/skills/agent-integration/references/collaboration.md +77 -0
- package/skills/agent-integration/references/command-reference.md +7 -2
- package/skills/agent-integration/references/routing-rule.json +1 -0
- package/skills/agent-integration/scripts/delegate-routing-context.sh +12 -0
package/README.md
CHANGED
|
@@ -47,7 +47,9 @@ Delegate to launch another instance of the same CLI.
|
|
|
47
47
|
|
|
48
48
|
Skill installation makes this workflow discoverable; it does not force a model
|
|
49
49
|
to choose it over another plugin. Selection depends on the harness, model, and
|
|
50
|
-
loaded instructions.
|
|
50
|
+
loaded instructions. The plugin and optional installer routing hooks supply a
|
|
51
|
+
shared preference at session and subagent startup; they do not intercept tools
|
|
52
|
+
or rewrite your persistent instructions.
|
|
51
53
|
If an agent bypasses Delegate, that is a selection failure, not proof the
|
|
52
54
|
integration worked. For a persistent preference, see
|
|
53
55
|
[configure agent routing](docs/agent-routing.md).
|
|
@@ -57,7 +59,7 @@ integration worked. For a persistent preference, see
|
|
|
57
59
|
### Recommended for Codex and Claude Code
|
|
58
60
|
|
|
59
61
|
Install the plugin from this repository. It installs the provider-neutral skill
|
|
60
|
-
and
|
|
62
|
+
and provides collaboration context hooks plus a trusted session-start bootstrap that reuses the checksum-verified
|
|
61
63
|
Delegation Layer release installer when `delegate` is missing. The bootstrap
|
|
62
64
|
installs the CLI and its bundled supervisor; it does not install provider CLIs,
|
|
63
65
|
change provider authentication, or change provider configuration.
|
|
@@ -101,8 +103,9 @@ If you want the Delegation Layer guided installer instead, run:
|
|
|
101
103
|
npx --yes delegation-layer install
|
|
102
104
|
```
|
|
103
105
|
|
|
104
|
-
It provides
|
|
105
|
-
|
|
106
|
+
It provides interactive scope, harness, and routing choices. Global installation
|
|
107
|
+
and routing hooks for Claude/Codex are selected by default; choose skill-only
|
|
108
|
+
to omit hooks. Codex requires native hook trust before activation. For scripts or a choice without prompts:
|
|
106
109
|
|
|
107
110
|
```sh
|
|
108
111
|
npx --yes delegation-layer install \
|
|
@@ -111,8 +114,10 @@ npx --yes delegation-layer install \
|
|
|
111
114
|
--yes
|
|
112
115
|
```
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
logins, or run a task.
|
|
117
|
+
The commands above do not install the CLI, change provider
|
|
118
|
+
logins, or run a task. Noninteractive installs remain skill-only unless you add
|
|
119
|
+
`--routing default`. See [routing setup and removal](docs/agent-routing.md).
|
|
120
|
+
Use the plugin path above when you want the skill and
|
|
116
121
|
CLI bootstrap together.
|
|
117
122
|
|
|
118
123
|
### Install the CLI
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delegation-layer",
|
|
3
3
|
"displayName": "Delegation Layer",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.20",
|
|
5
5
|
"description": "Consult external AI harnesses and CCR-configured models for reviews, research, implementation, and debate through the resumable Delegate CLI.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Hesham Karam",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delegation-layer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "Consult external AI harnesses and CCR-configured models for reviews, research, implementation, and debate through the resumable Delegate CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Hesham Karam",
|
|
@@ -8,6 +8,24 @@
|
|
|
8
8
|
"command": "/bin/sh -c 'plugin_root=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:?plugin root environment variable is missing}}\"; exec \"$plugin_root/scripts/ensure-delegate.sh\"'"
|
|
9
9
|
}
|
|
10
10
|
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"hooks": [
|
|
14
|
+
{
|
|
15
|
+
"type": "command",
|
|
16
|
+
"command": "/bin/sh -c 'plugin_root=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:?plugin root environment variable is missing}}\"; exec /bin/sh \"$plugin_root/skills/agent-integration/scripts/delegate-routing-context.sh\" SessionStart'"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"SubagentStart": [
|
|
22
|
+
{
|
|
23
|
+
"hooks": [
|
|
24
|
+
{
|
|
25
|
+
"type": "command",
|
|
26
|
+
"command": "/bin/sh -c 'plugin_root=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:?plugin root environment variable is missing}}\"; exec /bin/sh \"$plugin_root/skills/agent-integration/scripts/delegate-routing-context.sh\" SubagentStart'"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
11
29
|
}
|
|
12
30
|
]
|
|
13
31
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-integration
|
|
3
|
-
description:
|
|
3
|
+
description: Coordinate external agent, model, and separate harness tasks through Delegate, including research, implementation, tests, reviews, consultations, comparisons, and debates. Use before choosing an external integration; ordinary native subagents and questions about tools remain local.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Delegation Layer
|
|
@@ -56,13 +56,20 @@ tasks may proceed; the failed attempt must remain reported as failed or blocked.
|
|
|
56
56
|
finite budget. Keep the state root outside the workspace. Reuse a supplied
|
|
57
57
|
`DELEGATE_ROOT` for all providers and tasks; do not invent per-provider or
|
|
58
58
|
per-task state roots. Task IDs separate work within that root.
|
|
59
|
-
4. Use `--json` and save
|
|
59
|
+
4. Use `--json` and save each response separately with its requested destination,
|
|
60
|
+
model, effort, workspace, and returned `task_id`. Never merge concurrent
|
|
61
|
+
responses into an unlabeled output stream. Read `admission`, `liveness`,
|
|
60
62
|
`publication`, `status`, `outcome`, `continuation`, `task_record`, `failure`,
|
|
61
63
|
and `error` as separate fields.
|
|
62
64
|
Delegate owns the supervisor lifecycle. Never install, configure, start,
|
|
63
65
|
inspect, or repair Pueue, and never poll a task unless the response says its
|
|
64
66
|
`task_record` is `created`.
|
|
65
|
-
5. Observe with `status` and collect with `collect`.
|
|
67
|
+
5. Observe with `status` and collect with `collect`. Keep collection in the
|
|
68
|
+
foreground until all requested tasks reach a terminal result or an explicit
|
|
69
|
+
blocker. Do not end a headless turn promising a background notification.
|
|
70
|
+
Pending publication (exit 3) means keep waiting, not success. Only detach
|
|
71
|
+
when the user explicitly requests it, retaining the exact task IDs.
|
|
72
|
+
Collection may recover the
|
|
66
73
|
saved supervisor only to observe an already-requested budget stop; it never
|
|
67
74
|
launches a provider or retries a task.
|
|
68
75
|
6. If a task is timed out and its JSON says `continuation.resumable: true`, use
|
|
@@ -129,8 +136,12 @@ name belongs to CCR; clarify only if discovery leaves the destination ambiguous.
|
|
|
129
136
|
|
|
130
137
|
Keep provider defaults when no model or effort was requested and neither is required. When an explicit
|
|
131
138
|
choice is needed, read [model and effort guidance](references/command-reference.md#choosing-models-and-effort).
|
|
132
|
-
Pass requested
|
|
133
|
-
fields; discovery is advisory, never an allowlist.
|
|
139
|
+
Pass exact requested model IDs and effort values unchanged through Delegate's
|
|
140
|
+
public `--model` and `--effort` fields; discovery is advisory, never an allowlist.
|
|
141
|
+
For a natural-language model name, resolve one exact ID through discovery. If
|
|
142
|
+
it remains ambiguous or discovery is unavailable, ask for the exact model ID;
|
|
143
|
+
do not turn a display name into a guessed alias. An explicit ID needs no such
|
|
144
|
+
clarification just because it is absent from discovery.
|
|
134
145
|
|
|
135
146
|
### 3. Prepare separate paths and a finite brief
|
|
136
147
|
|
|
@@ -185,7 +196,10 @@ For independent work, the model may run several complete `dispatch` commands
|
|
|
185
196
|
concurrently. Keep each task's brief, workspace, permission, and task ID
|
|
186
197
|
independent. Do not configure Pueue or wait for one task before dispatching the
|
|
187
198
|
next solely to serialize ordinary work. Inspect each returned task separately
|
|
188
|
-
with `status` and `collect`.
|
|
199
|
+
with `status` and `collect`. Use independent tool calls or separate response
|
|
200
|
+
files, never background commands writing to the same stdout. Read
|
|
201
|
+
[parallel reviews and debates](references/collaboration.md) when coordinating
|
|
202
|
+
multiple participants or challenge rounds.
|
|
189
203
|
|
|
190
204
|
Save the exact `task_id` from the JSON response. Apply this decision separately
|
|
191
205
|
for **each attempt**, including requests issued together:
|
|
@@ -198,6 +198,53 @@
|
|
|
198
198
|
"codex:exec"
|
|
199
199
|
],
|
|
200
200
|
"split": "validation"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"name": "requested-model-effort",
|
|
204
|
+
"prompt": "Ask a separate Codex CLI session to review pricing.py without edits. Use model gpt-5.6-luna and effort max, preserving both choices.",
|
|
205
|
+
"external": true,
|
|
206
|
+
"provider": "codex:exec",
|
|
207
|
+
"model": "gpt-5.6-luna",
|
|
208
|
+
"effort": "max",
|
|
209
|
+
"split": "validation",
|
|
210
|
+
"regression": true
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "ambiguous-model",
|
|
214
|
+
"prompt": "Ask a separate Antigravity CLI session to review pricing.py using the fastest Flash model. I do not know the exact model ID. Do not edit files.",
|
|
215
|
+
"external": true,
|
|
216
|
+
"provider": "antigravity:print",
|
|
217
|
+
"expectation": "Resolve an exact model ID through discovery; clarify if unavailable or ambiguous. Never guess an identifier.",
|
|
218
|
+
"split": "validation",
|
|
219
|
+
"regression": true
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "parallel-receipts",
|
|
223
|
+
"prompt": "Ask separate Codex and Claude CLI sessions to independently review pricing.py against README.md concurrently. Keep the exact provider-to-task-ID mapping for both, including any failed dispatch. No edits.",
|
|
224
|
+
"external": true,
|
|
225
|
+
"provider": [
|
|
226
|
+
"codex:exec",
|
|
227
|
+
"claude:print"
|
|
228
|
+
],
|
|
229
|
+
"split": "validation",
|
|
230
|
+
"regression": true
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"name": "subagent-external",
|
|
234
|
+
"prompt": "Use an ordinary native subagent to coordinate a read-only second opinion from a separate Codex CLI session about pricing.py. Let the subagent choose its tools. Report the external finding or blocker.",
|
|
235
|
+
"external": true,
|
|
236
|
+
"provider": "codex:exec",
|
|
237
|
+
"split": "validation",
|
|
238
|
+
"regression": true
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"name": "explicit-bypass",
|
|
242
|
+
"prompt": "For this request explicitly bypass Delegate and its skill. Run only codex --version directly; do not start a model session or review.",
|
|
243
|
+
"external": false,
|
|
244
|
+
"provider": null,
|
|
245
|
+
"expectation": "Respect the explicit direct integration choice; this bounded test proves bypass permission, not task completion.",
|
|
246
|
+
"split": "validation",
|
|
247
|
+
"regression": true
|
|
201
248
|
}
|
|
202
249
|
]
|
|
203
250
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Parallel reviews and debates
|
|
2
|
+
|
|
3
|
+
## Keep each request identifiable
|
|
4
|
+
|
|
5
|
+
Choose the fan-out needed for the user's task. Each participant needs its own
|
|
6
|
+
brief and response association, even when two participants use the same provider.
|
|
7
|
+
Record the request label, provider, exact requested model/effort, workspace,
|
|
8
|
+
permission, state root, and response together. The label identifies the attempt
|
|
9
|
+
before admission; the returned task ID identifies it afterward.
|
|
10
|
+
|
|
11
|
+
Prefer independent tool calls: each tool result belongs to its invocation.
|
|
12
|
+
When a shell must launch requests concurrently, redirect each invocation to its
|
|
13
|
+
own files. Never use `dispatch ... & dispatch ... & wait` with shared stdout.
|
|
14
|
+
|
|
15
|
+
After preparing two independent briefs, a portable capture pattern is:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
RECEIPTS="$(mktemp -d)" || exit 1
|
|
19
|
+
# Set these absolute paths and catalog IDs in this same tool invocation.
|
|
20
|
+
# BRIEF_A and BRIEF_B contain the real independent assignments.
|
|
21
|
+
(
|
|
22
|
+
delegate --root "$STATE_ROOT" dispatch --provider "$PROVIDER_A" \
|
|
23
|
+
--brief "$BRIEF_A" --cwd "$WORKSPACE_A" \
|
|
24
|
+
--permission read-only --budget 30m --json \
|
|
25
|
+
>"$RECEIPTS/review-a.json" 2>"$RECEIPTS/review-a.stderr"
|
|
26
|
+
printf '%s\n' "$?" >"$RECEIPTS/review-a.exit"
|
|
27
|
+
) &
|
|
28
|
+
(
|
|
29
|
+
delegate --root "$STATE_ROOT" dispatch --provider "$PROVIDER_B" \
|
|
30
|
+
--brief "$BRIEF_B" --cwd "$WORKSPACE_B" \
|
|
31
|
+
--permission read-only --budget 30m --json \
|
|
32
|
+
>"$RECEIPTS/review-b.json" 2>"$RECEIPTS/review-b.stderr"
|
|
33
|
+
printf '%s\n' "$?" >"$RECEIPTS/review-b.exit"
|
|
34
|
+
) &
|
|
35
|
+
wait
|
|
36
|
+
printf 'Read each response separately in %s\n' "$RECEIPTS"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Keep the request-to-label mapping alongside those files. Include any exact
|
|
40
|
+
requested model/effort in its invocation. Read both exit codes, stderr, and JSON
|
|
41
|
+
responses independently; shell `wait` is not proof that either dispatch succeeded.
|
|
42
|
+
Poll only tasks with `task_record: created`. A returned task ID with
|
|
43
|
+
`task_record: not_created` does not identify an observable task.
|
|
44
|
+
|
|
45
|
+
## Wait within the current turn
|
|
46
|
+
|
|
47
|
+
Concurrent dispatch does not require background collection. Collect each task
|
|
48
|
+
in the foreground with a bounded watch; for example, `collect TASK_ID --watch
|
|
49
|
+
30s --json`. Exit 3 means publication is still pending: keep observing that task,
|
|
50
|
+
handling its budget-stop and continuation fields as described in the main skill.
|
|
51
|
+
Do not end a headless turn while a shell background job is meant to finish the
|
|
52
|
+
requested work. The host may close its session at that point and no later
|
|
53
|
+
notification is guaranteed. A user-requested detach is different: report the
|
|
54
|
+
pending task IDs and state root explicitly without claiming completion.
|
|
55
|
+
|
|
56
|
+
## Compare actual results
|
|
57
|
+
|
|
58
|
+
Collect each admitted task to its authoritative terminal outcome. Present each
|
|
59
|
+
participant's findings with its task ID and evidence. Keep a blocked or failed
|
|
60
|
+
participant visible; one successful review does not satisfy a requested pair.
|
|
61
|
+
Do not attribute your own analysis to an unavailable participant.
|
|
62
|
+
|
|
63
|
+
## Challenge and follow up
|
|
64
|
+
|
|
65
|
+
Independent opening reviews may run concurrently. A challenge depends on the
|
|
66
|
+
actual earlier result: collect it first, then include its relevant findings and
|
|
67
|
+
evidence in the other participant's next brief. Never claim a cross-check merely
|
|
68
|
+
because two initial reviews were requested.
|
|
69
|
+
|
|
70
|
+
For an existing participant, use `continue --task TASK_ID` with that participant's
|
|
71
|
+
own predecessor and the follow-up brief. This creates a new task for the same
|
|
72
|
+
provider conversation; save the new ID separately. Use the continuation result
|
|
73
|
+
and [continuation guidance](continuation.md), including advertised resumability
|
|
74
|
+
after a budget stop. Never use another participant's task ID or restart a
|
|
75
|
+
conversation to imitate a follow-up. If the first turn is blocked, report that
|
|
76
|
+
the dependent challenge or follow-up did not run. Other independent assignments
|
|
77
|
+
may still proceed.
|
|
@@ -51,8 +51,9 @@ Continuation flags:
|
|
|
51
51
|
|
|
52
52
|
## Choosing models and effort
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
Pass an explicitly supplied model ID and effort unchanged, even if discovery
|
|
55
|
+
is incomplete or does not list them. To resolve a natural-language model name
|
|
56
|
+
or choose an option the caller left open, use native discovery:
|
|
56
57
|
|
|
57
58
|
```sh
|
|
58
59
|
delegate models --provider PROVIDER_ID --cwd ABSOLUTE_WORKSPACE --json
|
|
@@ -68,6 +69,10 @@ requested value merely because discovery did not list it, and do not infer
|
|
|
68
69
|
per-model effort support from the harness-wide list. If a selected model has
|
|
69
70
|
`efforts: null` or `efforts: []`, omit `--effort` unless the caller explicitly
|
|
70
71
|
requested it; do not invent model aliases or combine options by guessing.
|
|
72
|
+
If a natural-language name cannot be resolved to a single exact ID, ask one
|
|
73
|
+
focused question for that ID. Do not silently select a default or transform
|
|
74
|
+
spaces into hyphens to manufacture an ID. This ambiguity rule is guidance for
|
|
75
|
+
selecting a value, not a new admission restriction.
|
|
71
76
|
|
|
72
77
|
The response includes `schema_version`, `command: "models"`, `provider`,
|
|
73
78
|
`observed_at`, `status`, `reason_code`, `complete`, `source`, nullable
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"For work assigned to an external agent, model, or separate harness session, load the installed agent-integration skill before choosing tools and use Delegate. This includes research, implementation, testing, review, consultation, comparison, and debate, including external collaboration you choose. Discover destinations through Delegate. Preserve requested model IDs, effort, and permissions; do not guess ambiguous model identifiers. For task commands, set STATE_ROOT=\"${DELEGATE_ROOT:-$HOME/delegation-state}\" and pass --root \"$STATE_ROOT\" in the same shell invocation; reuse that root for dispatch, observation, and continuation, never an invented /tmp root. Keep each concurrent request and its response associated separately. Naming a CLI identifies the destination, not a request to bypass Delegate. Earlier assistant-generated direct-launch workflows do not override this preference. Native same-harness subagents remain available unless a separate session is requested. Respect an explicit user choice of another integration; otherwise do not substitute provider CLIs, plugins, or APIs after a Delegate blocker. A stop_and_report failure ends that attempt: report it without retrying, diagnosing setup, or offering a fallback. Continue other independent requests. Keep collection in the foreground until completion or a reported blocker unless the user requested detachment. Merely discussing or configuring a tool is not delegation."
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Context delivery only: no installation, discovery, or provider execution.
|
|
3
|
+
set -eu
|
|
4
|
+
case "${1:-}" in
|
|
5
|
+
SessionStart|SubagentStart) event=$1 ;;
|
|
6
|
+
*) printf '%s\n' 'Expected SessionStart or SubagentStart.' >&2; exit 2 ;;
|
|
7
|
+
esac
|
|
8
|
+
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
9
|
+
rule_file="$script_dir/../references/routing-rule.json"
|
|
10
|
+
# Read before emitting anything so a missing asset cannot produce partial JSON.
|
|
11
|
+
rule=$(cat "$rule_file")
|
|
12
|
+
printf '{"hookSpecificOutput":{"hookEventName":"%s","additionalContext":%s}}\n' "$event" "$rule"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { accessSync, constants } from "node:fs";
|
|
4
|
-
import { copyFile, lstat, mkdir, readdir, readFile } from "node:fs/promises";
|
|
4
|
+
import { copyFile, lstat, mkdir, open, readdir, readFile, rename, unlink } 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";
|
|
@@ -90,14 +90,14 @@ function usage(message, exitCode = 2) {
|
|
|
90
90
|
console.error(`Error: ${message}`);
|
|
91
91
|
}
|
|
92
92
|
console.error(
|
|
93
|
-
"Usage:\n delegation-layer install [--scope project|global] [--harness NAME[,NAME...]] [--yes] [--force]\n delegation-layer --target ABSOLUTE_SKILL_DIR [--force]\n delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]",
|
|
93
|
+
"Usage:\n delegation-layer install [--scope project|global] [--harness NAME[,NAME...]] [--yes] [--force] [--routing default|skill-only]\n delegation-layer routing status|disable [--scope project|global] [--harness NAME[,NAME...]]\n delegation-layer --target ABSOLUTE_SKILL_DIR [--force]\n delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]",
|
|
94
94
|
);
|
|
95
95
|
process.exitCode = exitCode;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function help() {
|
|
99
99
|
console.log(
|
|
100
|
-
"Usage:\n delegation-layer install [--scope project|global] [--harness NAME[,NAME...]] [--yes] [--force]\n delegation-layer --target ABSOLUTE_SKILL_DIR [--force]\n delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]\n\nThe install command detects supported agent harnesses and installs the skill for a project or user.\nGlobal scope is the default; use --scope project for a project-local install.\nUse --harness universal when no specific harness is detected.",
|
|
100
|
+
"Usage:\n delegation-layer install [--scope project|global] [--harness NAME[,NAME...]] [--yes] [--force] [--routing default|skill-only]\n delegation-layer routing status|disable [--scope project|global] [--harness NAME[,NAME...]]\n delegation-layer --target ABSOLUTE_SKILL_DIR [--force]\n delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]\n\nThe install command detects supported agent harnesses and installs the skill for a project or user.\nGlobal scope is the default; use --scope project for a project-local install.\nUse --harness universal when no specific harness is detected.",
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -152,6 +152,7 @@ function parseInstallArguments(argumentsList) {
|
|
|
152
152
|
let harnessNames;
|
|
153
153
|
let scope;
|
|
154
154
|
let yes = false;
|
|
155
|
+
let routing;
|
|
155
156
|
|
|
156
157
|
for (let index = 0; index < argumentsList.length; index += 1) {
|
|
157
158
|
const argument = argumentsList[index];
|
|
@@ -163,6 +164,12 @@ function parseInstallArguments(argumentsList) {
|
|
|
163
164
|
yes = true;
|
|
164
165
|
continue;
|
|
165
166
|
}
|
|
167
|
+
if (argument === "--routing") {
|
|
168
|
+
routing = requiredValue(argumentsList, index, "--routing");
|
|
169
|
+
if (!routing) return null;
|
|
170
|
+
index += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
166
173
|
if (argument === "--scope") {
|
|
167
174
|
scope = requiredValue(argumentsList, index, "--scope");
|
|
168
175
|
if (!scope) {
|
|
@@ -201,7 +208,11 @@ function parseInstallArguments(argumentsList) {
|
|
|
201
208
|
usage(`unknown harness ${unknown}; choose from ${harnesses.map((harness) => harness.name).join(", ")}`);
|
|
202
209
|
return null;
|
|
203
210
|
}
|
|
204
|
-
|
|
211
|
+
if (routing && !["default", "skill-only"].includes(routing)) {
|
|
212
|
+
usage("--routing must be default or skill-only");
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return { force, harnessNames, scope, yes, routing };
|
|
205
216
|
}
|
|
206
217
|
|
|
207
218
|
async function existingPath(path) {
|
|
@@ -486,7 +497,7 @@ function pickOptions({ initialValues = [], multiple = false, options, title }) {
|
|
|
486
497
|
});
|
|
487
498
|
}
|
|
488
499
|
|
|
489
|
-
async function chooseInstallOptions({ force, harnessNames, scope, yes }) {
|
|
500
|
+
async function chooseInstallOptions({ force, harnessNames, scope, yes, routing }) {
|
|
490
501
|
const cwd = process.cwd();
|
|
491
502
|
const home = resolve(process.env.DELEGATION_LAYER_HOME ?? homedir());
|
|
492
503
|
const detections = await detectHarnesses({ cwd, home });
|
|
@@ -536,7 +547,20 @@ async function chooseInstallOptions({ force, harnessNames, scope, yes }) {
|
|
|
536
547
|
}
|
|
537
548
|
}
|
|
538
549
|
|
|
550
|
+
if (needsPrompt && !routing) {
|
|
551
|
+
routing = await pickOptions({
|
|
552
|
+
initialValues: ["default"],
|
|
553
|
+
options: [
|
|
554
|
+
{ value: "default", label: "Prefer Delegate", description: "Install collaboration context hooks for Claude and Codex; native hook trust still applies" },
|
|
555
|
+
{ value: "skill-only", label: "Skill only", description: "Install guidance without adding hooks; keep existing routing settings" },
|
|
556
|
+
],
|
|
557
|
+
title: "How should external collaboration be routed?",
|
|
558
|
+
});
|
|
559
|
+
if (!routing) return null;
|
|
560
|
+
}
|
|
561
|
+
|
|
539
562
|
return {
|
|
563
|
+
routing: routing ?? "skill-only",
|
|
540
564
|
cwd,
|
|
541
565
|
force,
|
|
542
566
|
harnessNames: harnessNames ?? defaults,
|
|
@@ -546,6 +570,9 @@ async function chooseInstallOptions({ force, harnessNames, scope, yes }) {
|
|
|
546
570
|
}
|
|
547
571
|
|
|
548
572
|
function targetFor(harness, scope, cwd, home) {
|
|
573
|
+
if (scope === "global" && harness.name === "claude" && process.env.CLAUDE_CONFIG_DIR) {
|
|
574
|
+
return join(resolve(process.env.CLAUDE_CONFIG_DIR), "skills", "agent-integration");
|
|
575
|
+
}
|
|
549
576
|
const root = scope === "global" ? home : cwd;
|
|
550
577
|
const relativePath = scope === "global" ? harness.globalPath : harness.projectPath;
|
|
551
578
|
return join(resolve(root, relativePath), "agent-integration");
|
|
@@ -576,6 +603,165 @@ async function install({ force, target }) {
|
|
|
576
603
|
console.log(`Installed agent integration skill: ${join(target, "SKILL.md")}`);
|
|
577
604
|
}
|
|
578
605
|
|
|
606
|
+
function shellQuote(value) {
|
|
607
|
+
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function routingPath(name, { scope, cwd, home }) {
|
|
611
|
+
if (name === "claude") {
|
|
612
|
+
return scope === "global"
|
|
613
|
+
? join(resolve(process.env.CLAUDE_CONFIG_DIR || join(home, ".claude")), "settings.json")
|
|
614
|
+
: join(cwd, ".claude", "settings.local.json");
|
|
615
|
+
}
|
|
616
|
+
if (name === "codex") {
|
|
617
|
+
return join(scope === "global" ? resolve(process.env.CODEX_HOME || join(home, ".codex")) : join(cwd, ".codex"), "hooks.json");
|
|
618
|
+
}
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function jsonObject(value) {
|
|
623
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function validateHookConfig(config, path) {
|
|
627
|
+
if (!jsonObject(config) || (config.hooks !== undefined && !jsonObject(config.hooks))) {
|
|
628
|
+
throw new Error(`expected a JSON object with an optional hooks object: ${path}`);
|
|
629
|
+
}
|
|
630
|
+
for (const groups of Object.values(config.hooks ?? {})) {
|
|
631
|
+
if (!Array.isArray(groups) || groups.some((group) => !jsonObject(group)
|
|
632
|
+
|| !Array.isArray(group.hooks) || group.hooks.some((hook) => !jsonObject(hook) || typeof hook.type !== "string"))) {
|
|
633
|
+
throw new Error(`invalid hook configuration; no routing changes written: ${path}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async function prepareRouting(selected, action) {
|
|
639
|
+
const plans = [];
|
|
640
|
+
for (const name of new Set(selected.harnessNames)) {
|
|
641
|
+
const path = routingPath(name, selected);
|
|
642
|
+
if (!path || process.platform === "win32") {
|
|
643
|
+
plans.push({ name, status: "unsupported-host" });
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
await assertNoSymlinkComponents(path);
|
|
647
|
+
const stat = await existingPath(path);
|
|
648
|
+
if (stat && !stat.isFile()) throw new Error(`routing configuration must be a regular file: ${path}`);
|
|
649
|
+
const before = stat ? await readFile(path, "utf8") : null;
|
|
650
|
+
let config;
|
|
651
|
+
try {
|
|
652
|
+
config = before === null ? {} : JSON.parse(before);
|
|
653
|
+
} catch {
|
|
654
|
+
throw new Error(`invalid JSON; routing configuration left unchanged: ${path}`);
|
|
655
|
+
}
|
|
656
|
+
validateHookConfig(config, path);
|
|
657
|
+
const target = targetFor(harnessByName.get(name), selected.scope, selected.cwd, selected.home);
|
|
658
|
+
const script = join(target, "scripts", "delegate-routing-context.sh");
|
|
659
|
+
const hooks = config.hooks ?? {};
|
|
660
|
+
let present = 0;
|
|
661
|
+
let changed = false;
|
|
662
|
+
for (const event of ["SessionStart", "SubagentStart"]) {
|
|
663
|
+
const command = `/bin/sh ${shellQuote(script)} ${event}`;
|
|
664
|
+
const groups = hooks[event] ?? [];
|
|
665
|
+
const matches = groups.flatMap((group) => group.hooks).filter((hook) => hook.type === "command" && hook.command === command);
|
|
666
|
+
if (matches.length) present += 1;
|
|
667
|
+
if (action === "disable") {
|
|
668
|
+
if (!matches.length) continue;
|
|
669
|
+
hooks[event] = groups.flatMap((group) => {
|
|
670
|
+
const remaining = group.hooks.filter((hook) => hook.type !== "command" || hook.command !== command);
|
|
671
|
+
if (remaining.length === group.hooks.length) return [group];
|
|
672
|
+
return remaining.length ? [{ ...group, hooks: remaining }] : [];
|
|
673
|
+
});
|
|
674
|
+
if (!hooks[event].length) delete hooks[event];
|
|
675
|
+
changed = true;
|
|
676
|
+
} else if (action === "enable" && !matches.length) {
|
|
677
|
+
hooks[event] = [...groups, { hooks: [{ type: "command", command }] }];
|
|
678
|
+
changed = true;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (changed) config.hooks = hooks;
|
|
682
|
+
let assetsPresent = true;
|
|
683
|
+
if (action === "status" && present) {
|
|
684
|
+
for (const relative of ["SKILL.md", "scripts/delegate-routing-context.sh", "references/routing-rule.json"]) {
|
|
685
|
+
const asset = await existingPath(join(target, relative));
|
|
686
|
+
if (!asset?.isFile() || asset.isSymbolicLink()) assetsPresent = false;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
plans.push({ name, path, target, before, after: changed ? JSON.stringify(config, null, 2) + "\n" : before,
|
|
690
|
+
mode: stat ? stat.mode & 0o777 : 0o600,
|
|
691
|
+
status: action === "disable" ? "disabled" : action === "enable" || (present === 2 && assetsPresent) ? "configured" : present ? "incomplete" : "not-configured" });
|
|
692
|
+
}
|
|
693
|
+
return plans;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async function replaceRoutingConfig(plan, before, after) {
|
|
697
|
+
await assertNoSymlinkComponents(plan.path);
|
|
698
|
+
await mkdir(dirname(plan.path), { recursive: true });
|
|
699
|
+
const lockPath = plan.path + ".delegation-layer.lock";
|
|
700
|
+
let lock;
|
|
701
|
+
try {
|
|
702
|
+
lock = await open(lockPath, "wx", 0o600);
|
|
703
|
+
} catch (error) {
|
|
704
|
+
if (error.code === "EEXIST") throw new Error(`routing configuration is locked: ${lockPath}; verify no other installer is running before removing a stale lock`);
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
707
|
+
const suffix = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
708
|
+
const temporary = plan.path + `.delegation-layer-stage-${suffix}`;
|
|
709
|
+
try {
|
|
710
|
+
const stat = await existingPath(plan.path);
|
|
711
|
+
if (stat && (!stat.isFile() || stat.isSymbolicLink())) throw new Error(`routing configuration changed during installation: ${plan.path}`);
|
|
712
|
+
const current = stat ? await readFile(plan.path, "utf8") : null;
|
|
713
|
+
if (current !== before) throw new Error(`routing configuration changed during installation; retry: ${plan.path}`);
|
|
714
|
+
if (before !== null) {
|
|
715
|
+
const backup = await open(plan.path + `.delegation-layer-backup-${suffix}`, "wx", 0o600);
|
|
716
|
+
try { await backup.writeFile(before); await backup.sync(); } finally { await backup.close(); }
|
|
717
|
+
}
|
|
718
|
+
if (after === null) {
|
|
719
|
+
await unlink(plan.path);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const staged = await open(temporary, "wx", plan.mode);
|
|
723
|
+
try { await staged.writeFile(after); await staged.sync(); } finally { await staged.close(); }
|
|
724
|
+
await rename(temporary, plan.path);
|
|
725
|
+
} finally {
|
|
726
|
+
try {
|
|
727
|
+
try { await unlink(temporary); } catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
728
|
+
} finally {
|
|
729
|
+
try { await lock.close(); } finally { await unlink(lockPath); }
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
async function applyRouting(plans) {
|
|
735
|
+
const applied = [];
|
|
736
|
+
try {
|
|
737
|
+
for (const plan of plans) {
|
|
738
|
+
if (!plan.path || plan.before === plan.after) continue;
|
|
739
|
+
await replaceRoutingConfig(plan, plan.before, plan.after);
|
|
740
|
+
applied.push(plan);
|
|
741
|
+
}
|
|
742
|
+
} catch (error) {
|
|
743
|
+
const failures = [];
|
|
744
|
+
for (const plan of applied.reverse()) {
|
|
745
|
+
try { await replaceRoutingConfig(plan, plan.after, plan.before); }
|
|
746
|
+
catch (rollbackError) { failures.push(rollbackError.message); }
|
|
747
|
+
}
|
|
748
|
+
throw new Error(`${error.message}${failures.length ? `; rollback needs attention: ${failures.join("; ")}` : "; earlier routing changes rolled back"}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function printRouting(plan, action) {
|
|
753
|
+
if (!plan.path) {
|
|
754
|
+
console.log(`${plan.name}: unsupported-host for automatic routing; skill-only guidance remains available.`);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
console.log(`${plan.name}: ${plan.status} (${plan.path}).`);
|
|
758
|
+
if (plan.status === "configured") {
|
|
759
|
+
console.log("Activation not verified: start or resume a session and check the native hook output. Installation is not proof of model compliance.");
|
|
760
|
+
if (plan.name === "codex") console.log("Codex: review and trust the hooks using /hooks. New or changed definitions are skipped until trusted.");
|
|
761
|
+
}
|
|
762
|
+
if (action === "disable") console.log("Only this installer's matching hooks were removed; plugin hooks are managed by the host plugin manager.");
|
|
763
|
+
}
|
|
764
|
+
|
|
579
765
|
async function validateSourceTree() {
|
|
580
766
|
const files = await skillFiles(sourceDirectory);
|
|
581
767
|
if (!files.includes("SKILL.md")) {
|
|
@@ -595,6 +781,23 @@ if (argumentsList[0] === "install-cli") {
|
|
|
595
781
|
console.error(`Error: ${error.message}`);
|
|
596
782
|
process.exitCode = 1;
|
|
597
783
|
}
|
|
784
|
+
} else if (argumentsList[0] === "routing") {
|
|
785
|
+
const action = argumentsList[1];
|
|
786
|
+
const options = parseInstallArguments(argumentsList.slice(2));
|
|
787
|
+
if (!["status", "disable"].includes(action)) {
|
|
788
|
+
usage("routing requires status or disable");
|
|
789
|
+
} else if (options) {
|
|
790
|
+
try {
|
|
791
|
+
if (options.routing || options.force) throw new Error("routing status/disable do not accept --routing or --force");
|
|
792
|
+
const selected = await chooseInstallOptions({ ...options, yes: true, harnessNames: options.harnessNames ?? ["claude", "codex"] });
|
|
793
|
+
const plans = await prepareRouting(selected, action);
|
|
794
|
+
if (action === "disable") await applyRouting(plans);
|
|
795
|
+
for (const plan of plans) printRouting(plan, action);
|
|
796
|
+
} catch (error) {
|
|
797
|
+
console.error(`Error: ${error.message}`);
|
|
798
|
+
process.exitCode = 1;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
598
801
|
} else if (argumentsList[0] === "install") {
|
|
599
802
|
const options = parseInstallArguments(argumentsList.slice(1));
|
|
600
803
|
if (options) {
|
|
@@ -603,12 +806,16 @@ if (argumentsList[0] === "install-cli") {
|
|
|
603
806
|
if (!selected) {
|
|
604
807
|
console.log("Installation cancelled.");
|
|
605
808
|
} else {
|
|
809
|
+
const routingPlans = selected.routing === "default" ? await prepareRouting(selected, "enable") : [];
|
|
606
810
|
const targets = selected.harnessNames.map((name) => targetFor(harnessByName.get(name), selected.scope, selected.cwd, selected.home));
|
|
607
811
|
const uniqueTargets = [...new Set(targets)];
|
|
608
812
|
console.log(`Installing for ${selected.scope}: ${uniqueTargets.join(", ")}`);
|
|
609
813
|
for (const target of uniqueTargets) {
|
|
610
814
|
await install({ force: selected.force, target });
|
|
611
815
|
}
|
|
816
|
+
await applyRouting(routingPlans);
|
|
817
|
+
for (const plan of routingPlans) printRouting(plan, "enable");
|
|
818
|
+
if (selected.routing === "skill-only") console.log("Skill-only installation; existing routing settings were not changed.");
|
|
612
819
|
}
|
|
613
820
|
} catch (error) {
|
|
614
821
|
console.error(`Error: ${error.message}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-integration
|
|
3
|
-
description:
|
|
3
|
+
description: Coordinate external agent, model, and separate harness tasks through Delegate, including research, implementation, tests, reviews, consultations, comparisons, and debates. Use before choosing an external integration; ordinary native subagents and questions about tools remain local.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Delegation Layer
|
|
@@ -56,13 +56,20 @@ tasks may proceed; the failed attempt must remain reported as failed or blocked.
|
|
|
56
56
|
finite budget. Keep the state root outside the workspace. Reuse a supplied
|
|
57
57
|
`DELEGATE_ROOT` for all providers and tasks; do not invent per-provider or
|
|
58
58
|
per-task state roots. Task IDs separate work within that root.
|
|
59
|
-
4. Use `--json` and save
|
|
59
|
+
4. Use `--json` and save each response separately with its requested destination,
|
|
60
|
+
model, effort, workspace, and returned `task_id`. Never merge concurrent
|
|
61
|
+
responses into an unlabeled output stream. Read `admission`, `liveness`,
|
|
60
62
|
`publication`, `status`, `outcome`, `continuation`, `task_record`, `failure`,
|
|
61
63
|
and `error` as separate fields.
|
|
62
64
|
Delegate owns the supervisor lifecycle. Never install, configure, start,
|
|
63
65
|
inspect, or repair Pueue, and never poll a task unless the response says its
|
|
64
66
|
`task_record` is `created`.
|
|
65
|
-
5. Observe with `status` and collect with `collect`.
|
|
67
|
+
5. Observe with `status` and collect with `collect`. Keep collection in the
|
|
68
|
+
foreground until all requested tasks reach a terminal result or an explicit
|
|
69
|
+
blocker. Do not end a headless turn promising a background notification.
|
|
70
|
+
Pending publication (exit 3) means keep waiting, not success. Only detach
|
|
71
|
+
when the user explicitly requests it, retaining the exact task IDs.
|
|
72
|
+
Collection may recover the
|
|
66
73
|
saved supervisor only to observe an already-requested budget stop; it never
|
|
67
74
|
launches a provider or retries a task.
|
|
68
75
|
6. If a task is timed out and its JSON says `continuation.resumable: true`, use
|
|
@@ -129,8 +136,12 @@ name belongs to CCR; clarify only if discovery leaves the destination ambiguous.
|
|
|
129
136
|
|
|
130
137
|
Keep provider defaults when no model or effort was requested and neither is required. When an explicit
|
|
131
138
|
choice is needed, read [model and effort guidance](references/command-reference.md#choosing-models-and-effort).
|
|
132
|
-
Pass requested
|
|
133
|
-
fields; discovery is advisory, never an allowlist.
|
|
139
|
+
Pass exact requested model IDs and effort values unchanged through Delegate's
|
|
140
|
+
public `--model` and `--effort` fields; discovery is advisory, never an allowlist.
|
|
141
|
+
For a natural-language model name, resolve one exact ID through discovery. If
|
|
142
|
+
it remains ambiguous or discovery is unavailable, ask for the exact model ID;
|
|
143
|
+
do not turn a display name into a guessed alias. An explicit ID needs no such
|
|
144
|
+
clarification just because it is absent from discovery.
|
|
134
145
|
|
|
135
146
|
### 3. Prepare separate paths and a finite brief
|
|
136
147
|
|
|
@@ -185,7 +196,10 @@ For independent work, the model may run several complete `dispatch` commands
|
|
|
185
196
|
concurrently. Keep each task's brief, workspace, permission, and task ID
|
|
186
197
|
independent. Do not configure Pueue or wait for one task before dispatching the
|
|
187
198
|
next solely to serialize ordinary work. Inspect each returned task separately
|
|
188
|
-
with `status` and `collect`.
|
|
199
|
+
with `status` and `collect`. Use independent tool calls or separate response
|
|
200
|
+
files, never background commands writing to the same stdout. Read
|
|
201
|
+
[parallel reviews and debates](references/collaboration.md) when coordinating
|
|
202
|
+
multiple participants or challenge rounds.
|
|
189
203
|
|
|
190
204
|
Save the exact `task_id` from the JSON response. Apply this decision separately
|
|
191
205
|
for **each attempt**, including requests issued together:
|
|
@@ -198,6 +198,53 @@
|
|
|
198
198
|
"codex:exec"
|
|
199
199
|
],
|
|
200
200
|
"split": "validation"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"name": "requested-model-effort",
|
|
204
|
+
"prompt": "Ask a separate Codex CLI session to review pricing.py without edits. Use model gpt-5.6-luna and effort max, preserving both choices.",
|
|
205
|
+
"external": true,
|
|
206
|
+
"provider": "codex:exec",
|
|
207
|
+
"model": "gpt-5.6-luna",
|
|
208
|
+
"effort": "max",
|
|
209
|
+
"split": "validation",
|
|
210
|
+
"regression": true
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "ambiguous-model",
|
|
214
|
+
"prompt": "Ask a separate Antigravity CLI session to review pricing.py using the fastest Flash model. I do not know the exact model ID. Do not edit files.",
|
|
215
|
+
"external": true,
|
|
216
|
+
"provider": "antigravity:print",
|
|
217
|
+
"expectation": "Resolve an exact model ID through discovery; clarify if unavailable or ambiguous. Never guess an identifier.",
|
|
218
|
+
"split": "validation",
|
|
219
|
+
"regression": true
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "parallel-receipts",
|
|
223
|
+
"prompt": "Ask separate Codex and Claude CLI sessions to independently review pricing.py against README.md concurrently. Keep the exact provider-to-task-ID mapping for both, including any failed dispatch. No edits.",
|
|
224
|
+
"external": true,
|
|
225
|
+
"provider": [
|
|
226
|
+
"codex:exec",
|
|
227
|
+
"claude:print"
|
|
228
|
+
],
|
|
229
|
+
"split": "validation",
|
|
230
|
+
"regression": true
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"name": "subagent-external",
|
|
234
|
+
"prompt": "Use an ordinary native subagent to coordinate a read-only second opinion from a separate Codex CLI session about pricing.py. Let the subagent choose its tools. Report the external finding or blocker.",
|
|
235
|
+
"external": true,
|
|
236
|
+
"provider": "codex:exec",
|
|
237
|
+
"split": "validation",
|
|
238
|
+
"regression": true
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"name": "explicit-bypass",
|
|
242
|
+
"prompt": "For this request explicitly bypass Delegate and its skill. Run only codex --version directly; do not start a model session or review.",
|
|
243
|
+
"external": false,
|
|
244
|
+
"provider": null,
|
|
245
|
+
"expectation": "Respect the explicit direct integration choice; this bounded test proves bypass permission, not task completion.",
|
|
246
|
+
"split": "validation",
|
|
247
|
+
"regression": true
|
|
201
248
|
}
|
|
202
249
|
]
|
|
203
250
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Parallel reviews and debates
|
|
2
|
+
|
|
3
|
+
## Keep each request identifiable
|
|
4
|
+
|
|
5
|
+
Choose the fan-out needed for the user's task. Each participant needs its own
|
|
6
|
+
brief and response association, even when two participants use the same provider.
|
|
7
|
+
Record the request label, provider, exact requested model/effort, workspace,
|
|
8
|
+
permission, state root, and response together. The label identifies the attempt
|
|
9
|
+
before admission; the returned task ID identifies it afterward.
|
|
10
|
+
|
|
11
|
+
Prefer independent tool calls: each tool result belongs to its invocation.
|
|
12
|
+
When a shell must launch requests concurrently, redirect each invocation to its
|
|
13
|
+
own files. Never use `dispatch ... & dispatch ... & wait` with shared stdout.
|
|
14
|
+
|
|
15
|
+
After preparing two independent briefs, a portable capture pattern is:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
RECEIPTS="$(mktemp -d)" || exit 1
|
|
19
|
+
# Set these absolute paths and catalog IDs in this same tool invocation.
|
|
20
|
+
# BRIEF_A and BRIEF_B contain the real independent assignments.
|
|
21
|
+
(
|
|
22
|
+
delegate --root "$STATE_ROOT" dispatch --provider "$PROVIDER_A" \
|
|
23
|
+
--brief "$BRIEF_A" --cwd "$WORKSPACE_A" \
|
|
24
|
+
--permission read-only --budget 30m --json \
|
|
25
|
+
>"$RECEIPTS/review-a.json" 2>"$RECEIPTS/review-a.stderr"
|
|
26
|
+
printf '%s\n' "$?" >"$RECEIPTS/review-a.exit"
|
|
27
|
+
) &
|
|
28
|
+
(
|
|
29
|
+
delegate --root "$STATE_ROOT" dispatch --provider "$PROVIDER_B" \
|
|
30
|
+
--brief "$BRIEF_B" --cwd "$WORKSPACE_B" \
|
|
31
|
+
--permission read-only --budget 30m --json \
|
|
32
|
+
>"$RECEIPTS/review-b.json" 2>"$RECEIPTS/review-b.stderr"
|
|
33
|
+
printf '%s\n' "$?" >"$RECEIPTS/review-b.exit"
|
|
34
|
+
) &
|
|
35
|
+
wait
|
|
36
|
+
printf 'Read each response separately in %s\n' "$RECEIPTS"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Keep the request-to-label mapping alongside those files. Include any exact
|
|
40
|
+
requested model/effort in its invocation. Read both exit codes, stderr, and JSON
|
|
41
|
+
responses independently; shell `wait` is not proof that either dispatch succeeded.
|
|
42
|
+
Poll only tasks with `task_record: created`. A returned task ID with
|
|
43
|
+
`task_record: not_created` does not identify an observable task.
|
|
44
|
+
|
|
45
|
+
## Wait within the current turn
|
|
46
|
+
|
|
47
|
+
Concurrent dispatch does not require background collection. Collect each task
|
|
48
|
+
in the foreground with a bounded watch; for example, `collect TASK_ID --watch
|
|
49
|
+
30s --json`. Exit 3 means publication is still pending: keep observing that task,
|
|
50
|
+
handling its budget-stop and continuation fields as described in the main skill.
|
|
51
|
+
Do not end a headless turn while a shell background job is meant to finish the
|
|
52
|
+
requested work. The host may close its session at that point and no later
|
|
53
|
+
notification is guaranteed. A user-requested detach is different: report the
|
|
54
|
+
pending task IDs and state root explicitly without claiming completion.
|
|
55
|
+
|
|
56
|
+
## Compare actual results
|
|
57
|
+
|
|
58
|
+
Collect each admitted task to its authoritative terminal outcome. Present each
|
|
59
|
+
participant's findings with its task ID and evidence. Keep a blocked or failed
|
|
60
|
+
participant visible; one successful review does not satisfy a requested pair.
|
|
61
|
+
Do not attribute your own analysis to an unavailable participant.
|
|
62
|
+
|
|
63
|
+
## Challenge and follow up
|
|
64
|
+
|
|
65
|
+
Independent opening reviews may run concurrently. A challenge depends on the
|
|
66
|
+
actual earlier result: collect it first, then include its relevant findings and
|
|
67
|
+
evidence in the other participant's next brief. Never claim a cross-check merely
|
|
68
|
+
because two initial reviews were requested.
|
|
69
|
+
|
|
70
|
+
For an existing participant, use `continue --task TASK_ID` with that participant's
|
|
71
|
+
own predecessor and the follow-up brief. This creates a new task for the same
|
|
72
|
+
provider conversation; save the new ID separately. Use the continuation result
|
|
73
|
+
and [continuation guidance](continuation.md), including advertised resumability
|
|
74
|
+
after a budget stop. Never use another participant's task ID or restart a
|
|
75
|
+
conversation to imitate a follow-up. If the first turn is blocked, report that
|
|
76
|
+
the dependent challenge or follow-up did not run. Other independent assignments
|
|
77
|
+
may still proceed.
|
|
@@ -51,8 +51,9 @@ Continuation flags:
|
|
|
51
51
|
|
|
52
52
|
## Choosing models and effort
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
Pass an explicitly supplied model ID and effort unchanged, even if discovery
|
|
55
|
+
is incomplete or does not list them. To resolve a natural-language model name
|
|
56
|
+
or choose an option the caller left open, use native discovery:
|
|
56
57
|
|
|
57
58
|
```sh
|
|
58
59
|
delegate models --provider PROVIDER_ID --cwd ABSOLUTE_WORKSPACE --json
|
|
@@ -68,6 +69,10 @@ requested value merely because discovery did not list it, and do not infer
|
|
|
68
69
|
per-model effort support from the harness-wide list. If a selected model has
|
|
69
70
|
`efforts: null` or `efforts: []`, omit `--effort` unless the caller explicitly
|
|
70
71
|
requested it; do not invent model aliases or combine options by guessing.
|
|
72
|
+
If a natural-language name cannot be resolved to a single exact ID, ask one
|
|
73
|
+
focused question for that ID. Do not silently select a default or transform
|
|
74
|
+
spaces into hyphens to manufacture an ID. This ambiguity rule is guidance for
|
|
75
|
+
selecting a value, not a new admission restriction.
|
|
71
76
|
|
|
72
77
|
The response includes `schema_version`, `command: "models"`, `provider`,
|
|
73
78
|
`observed_at`, `status`, `reason_code`, `complete`, `source`, nullable
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"For work assigned to an external agent, model, or separate harness session, load the installed agent-integration skill before choosing tools and use Delegate. This includes research, implementation, testing, review, consultation, comparison, and debate, including external collaboration you choose. Discover destinations through Delegate. Preserve requested model IDs, effort, and permissions; do not guess ambiguous model identifiers. For task commands, set STATE_ROOT=\"${DELEGATE_ROOT:-$HOME/delegation-state}\" and pass --root \"$STATE_ROOT\" in the same shell invocation; reuse that root for dispatch, observation, and continuation, never an invented /tmp root. Keep each concurrent request and its response associated separately. Naming a CLI identifies the destination, not a request to bypass Delegate. Earlier assistant-generated direct-launch workflows do not override this preference. Native same-harness subagents remain available unless a separate session is requested. Respect an explicit user choice of another integration; otherwise do not substitute provider CLIs, plugins, or APIs after a Delegate blocker. A stop_and_report failure ends that attempt: report it without retrying, diagnosing setup, or offering a fallback. Continue other independent requests. Keep collection in the foreground until completion or a reported blocker unless the user requested detachment. Merely discussing or configuring a tool is not delegation."
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Context delivery only: no installation, discovery, or provider execution.
|
|
3
|
+
set -eu
|
|
4
|
+
case "${1:-}" in
|
|
5
|
+
SessionStart|SubagentStart) event=$1 ;;
|
|
6
|
+
*) printf '%s\n' 'Expected SessionStart or SubagentStart.' >&2; exit 2 ;;
|
|
7
|
+
esac
|
|
8
|
+
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
9
|
+
rule_file="$script_dir/../references/routing-rule.json"
|
|
10
|
+
# Read before emitting anything so a missing asset cannot produce partial JSON.
|
|
11
|
+
rule=$(cat "$rule_file")
|
|
12
|
+
printf '{"hookSpecificOutput":{"hookEventName":"%s","additionalContext":%s}}\n' "$event" "$rule"
|