delegation-layer 0.1.18 → 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 +20 -7
- package/package.json +1 -1
- package/plugins/delegation-layer/.claude-plugin/plugin.json +2 -2
- package/plugins/delegation-layer/.codex-plugin/plugin.json +2 -2
- package/plugins/delegation-layer/hooks/hooks.json +18 -0
- package/plugins/delegation-layer/skills/agent-integration/SKILL.md +84 -50
- package/plugins/delegation-layer/skills/agent-integration/evals/routing.json +95 -0
- package/plugins/delegation-layer/skills/agent-integration/references/ccr.md +33 -0
- package/plugins/delegation-layer/skills/agent-integration/references/collaboration.md +77 -0
- package/plugins/delegation-layer/skills/agent-integration/references/command-reference.md +27 -4
- 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 +84 -50
- package/skills/agent-integration/evals/routing.json +95 -0
- package/skills/agent-integration/references/ccr.md +33 -0
- package/skills/agent-integration/references/collaboration.md +77 -0
- package/skills/agent-integration/references/command-reference.md +27 -4
- package/skills/agent-integration/references/routing-rule.json +1 -0
- package/skills/agent-integration/scripts/delegate-routing-context.sh +12 -0
|
@@ -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
|
|
@@ -13,6 +13,9 @@ external harness to do work. Requests such as “ask Codex to review this”,
|
|
|
13
13
|
collaboration even when the user never mentions Delegate. The same applies
|
|
14
14
|
when you decide external expertise is useful to fulfill the user's task.
|
|
15
15
|
These names are examples; discover supported destinations through the CLI.
|
|
16
|
+
“Claude Code Router” and “CCR” mean `ccr:print`, not ordinary Claude Code.
|
|
17
|
+
For that request, use Delegate with a CCR model alias; a Claude-specific skill
|
|
18
|
+
or subscription model does not fulfill the requested route.
|
|
16
19
|
|
|
17
20
|
Ordinary subagents within the current harness remain available. Naming the
|
|
18
21
|
current harness (for example, “have Claude investigate” while using Claude)
|
|
@@ -28,6 +31,16 @@ replace it with a provider command, a provider-specific plugin, or an API call.
|
|
|
28
31
|
Do not inspect, authenticate, install, or repair provider CLIs to bypass a
|
|
29
32
|
Delegate blocker.
|
|
30
33
|
|
|
34
|
+
A reported blocker is a valid final response to a delegation request. It does
|
|
35
|
+
not turn that request into an environment-repair task. When a response says
|
|
36
|
+
`failure.next_action: "stop_and_report"`, that attempt is finished. Include its
|
|
37
|
+
failure in your final response without opening a clarification dialog to choose
|
|
38
|
+
a fallback. For `task_record: not_created`, identify the requested destination
|
|
39
|
+
and quote the returned `task_id` (if present), `task_record`, `failure`, and
|
|
40
|
+
`error`. Do not append inferred state paths or provider-setup diagnoses. Do not run diagnostic commands, retry it, or use another integration.
|
|
41
|
+
Troubleshooting requires a separate user request. Other independently requested
|
|
42
|
+
tasks may proceed; the failed attempt must remain reported as failed or blocked.
|
|
43
|
+
|
|
31
44
|
## Rules
|
|
32
45
|
|
|
33
46
|
1. Check whether `delegate` is already installed. A plugin SessionStart hook
|
|
@@ -39,16 +52,24 @@ Delegate blocker.
|
|
|
39
52
|
name rather than a provider ID. Use `dispatch --auto` when none was chosen.
|
|
40
53
|
Provider selection, executable checks, compatibility checks, authentication
|
|
41
54
|
gates, and supervisor admission belong inside Delegation Layer.
|
|
42
|
-
3. Give every turn an absolute workspace,
|
|
43
|
-
|
|
44
|
-
`DELEGATE_ROOT
|
|
45
|
-
|
|
55
|
+
3. Give every turn an absolute workspace, an explicit permission mode, and a
|
|
56
|
+
finite budget. Keep the state root outside the workspace. Reuse a supplied
|
|
57
|
+
`DELEGATE_ROOT` for all providers and tasks; do not invent per-provider or
|
|
58
|
+
per-task state roots. Task IDs separate work within that root.
|
|
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`,
|
|
46
62
|
`publication`, `status`, `outcome`, `continuation`, `task_record`, `failure`,
|
|
47
63
|
and `error` as separate fields.
|
|
48
64
|
Delegate owns the supervisor lifecycle. Never install, configure, start,
|
|
49
65
|
inspect, or repair Pueue, and never poll a task unless the response says its
|
|
50
66
|
`task_record` is `created`.
|
|
51
|
-
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
|
|
52
73
|
saved supervisor only to observe an already-requested budget stop; it never
|
|
53
74
|
launches a provider or retries a task.
|
|
54
75
|
6. If a task is timed out and its JSON says `continuation.resumable: true`, use
|
|
@@ -95,19 +116,9 @@ Use the matching catalog ID with `--provider`; do not silently substitute
|
|
|
95
116
|
`--auto` or another provider. If the name is ambiguous, clarify the intended
|
|
96
117
|
destination. If it is absent, report that this build does not support it.
|
|
97
118
|
The catalog is not proof that a provider is installed, authenticated, or ready.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
```sh
|
|
102
|
-
delegate preflight --provider PROVIDER_ID --cwd ABSOLUTE_WORKSPACE \
|
|
103
|
-
--permission REQUESTED_MODE --json
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Replace `REQUESTED_MODE` with `read-only` for investigation or
|
|
107
|
-
`workspace-write` for authorized edits. Preflight reports static admission only.
|
|
108
|
-
It does not create a task, start the supervisor, or launch a provider. Do not keep
|
|
109
|
-
retrying preflight when it says
|
|
110
|
-
`blocked` or `unsupported`; report the bounded reason.
|
|
119
|
+
Dispatch performs admission checks; a separate preflight is not needed for a
|
|
120
|
+
normal task. For an explicitly requested readiness check, see
|
|
121
|
+
[preflight guidance](references/command-reference.md#checking-readiness).
|
|
111
122
|
|
|
112
123
|
The catalog and `capabilities` response expose supported permission modes,
|
|
113
124
|
request options, continuation, and the read-only mechanism. A read-only
|
|
@@ -116,10 +127,21 @@ behavior but is not an independent delegate-owned sandbox. Static discovery
|
|
|
116
127
|
does not prove authentication or live acceptance; use the dispatch or live
|
|
117
128
|
acceptance result for that evidence.
|
|
118
129
|
|
|
119
|
-
|
|
130
|
+
Read `required_options` in provider discovery before dispatch. Supply every
|
|
131
|
+
required option; a provider's optional defaults do not replace required inputs.
|
|
132
|
+
For CCR / Claude Code Router or a CCR model request, read
|
|
133
|
+
[CCR model selection](references/ccr.md). A CCR route is external collaboration
|
|
134
|
+
even when you are already using Claude. Do not assume every open-source model
|
|
135
|
+
name belongs to CCR; clarify only if discovery leaves the destination ambiguous.
|
|
136
|
+
|
|
137
|
+
Keep provider defaults when no model or effort was requested and neither is required. When an explicit
|
|
120
138
|
choice is needed, read [model and effort guidance](references/command-reference.md#choosing-models-and-effort).
|
|
121
|
-
Pass requested
|
|
122
|
-
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.
|
|
123
145
|
|
|
124
146
|
### 3. Prepare separate paths and a finite brief
|
|
125
147
|
|
|
@@ -128,22 +150,18 @@ brief file that contains the requested work and its acceptance criteria. Keep
|
|
|
128
150
|
the state root outside the workspace and provider-writable trees (which can
|
|
129
151
|
include the system temporary directory). Use `DELEGATE_ROOT` when supplied:
|
|
130
152
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
cat >"$BRIEF" <<'EOF'
|
|
136
|
-
Describe the delegated task, the files or behavior to change, and the checks
|
|
137
|
-
that must pass. Keep the response concise and report validation evidence.
|
|
138
|
-
EOF
|
|
139
|
-
```
|
|
153
|
+
Run path preparation and dispatch in **one shell/tool invocation**, as shown
|
|
154
|
+
below. Shell variables may disappear between tool calls. For later commands,
|
|
155
|
+
use the actual saved absolute paths or assign them again in that invocation;
|
|
156
|
+
never pass an empty `--root`, `--brief`, or `--cwd`.
|
|
140
157
|
|
|
141
|
-
Replace the example brief with the actual user request. Do not place the state
|
|
158
|
+
Replace the example brief below with the actual user request. Do not place the state
|
|
142
159
|
root inside the workspace or place secrets in either path’s task data. Keep it
|
|
143
160
|
outside provider configuration and runtime directories as well. If delegate
|
|
144
|
-
rejects
|
|
145
|
-
|
|
146
|
-
|
|
161
|
+
rejects a caller-supplied state root, report the placement error rather than
|
|
162
|
+
silently replacing that root. If no root was supplied, choose one outside the
|
|
163
|
+
workspace and reuse it. Do not change provider settings. Retain the selected
|
|
164
|
+
root for every observation and continuation.
|
|
147
165
|
|
|
148
166
|
### 4. Dispatch one bounded turn
|
|
149
167
|
|
|
@@ -152,6 +170,13 @@ For a named harness or provider resolved in step 2, replace `--auto` with `--pro
|
|
|
152
170
|
combine the two:
|
|
153
171
|
|
|
154
172
|
```sh
|
|
173
|
+
STATE_ROOT="${DELEGATE_ROOT:-$HOME/delegation-state}"
|
|
174
|
+
WORKSPACE="$PWD"
|
|
175
|
+
BRIEF="$(mktemp)"
|
|
176
|
+
cat >"$BRIEF" <<'EOF'
|
|
177
|
+
Describe the delegated task, the files or behavior to change, and the checks
|
|
178
|
+
that must pass. Keep the response concise and report validation evidence.
|
|
179
|
+
EOF
|
|
155
180
|
delegate --root "$STATE_ROOT" dispatch --auto \
|
|
156
181
|
--brief "$BRIEF" \
|
|
157
182
|
--cwd "$WORKSPACE" \
|
|
@@ -171,21 +196,26 @@ For independent work, the model may run several complete `dispatch` commands
|
|
|
171
196
|
concurrently. Keep each task's brief, workspace, permission, and task ID
|
|
172
197
|
independent. Do not configure Pueue or wait for one task before dispatching the
|
|
173
198
|
next solely to serialize ordinary work. Inspect each returned task separately
|
|
174
|
-
with `status` and `collect`.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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.
|
|
203
|
+
|
|
204
|
+
Save the exact `task_id` from the JSON response. Apply this decision separately
|
|
205
|
+
for **each attempt**, including requests issued together:
|
|
206
|
+
|
|
207
|
+
| Response | Next action |
|
|
208
|
+
|---|---|
|
|
209
|
+
| Usage error, no JSON response | Correct the reported syntax using Delegate help or `providers --json`, then invoke that request again. Parsing failed before admission. |
|
|
210
|
+
| `task_record: not_created` and `failure.next_action: correct_request` | Correct that request and retry it. |
|
|
211
|
+
| `task_record: not_created` with any other next action | Report that attempt's failure and stop it. Do not diagnose provider setup or substitute an integration. |
|
|
212
|
+
| `task_record: created` | Use `status` before taking any other action. Do not dispatch again. |
|
|
213
|
+
| `task_record: unknown` or an ambiguous response | Stop and report; do not retry an uncertain task creation. |
|
|
214
|
+
|
|
215
|
+
One attempt's `stop_and_report` does not prevent correcting another independent
|
|
216
|
+
request's usage error. Keep the failed attempt reported as failed. With an
|
|
217
|
+
explicit task ID, absence can race another dispatch creating the same ID;
|
|
218
|
+
never retry that uncertain ID or run Pueue commands.
|
|
189
219
|
|
|
190
220
|
### 5. Observe and collect
|
|
191
221
|
|
|
@@ -246,6 +276,10 @@ recruit more agents unless that is explicitly part of the task.
|
|
|
246
276
|
|
|
247
277
|
## Completion report
|
|
248
278
|
|
|
279
|
+
Build the report from observed CLI responses and collected evidence. Copy
|
|
280
|
+
returned values; do not reconstruct paths from example commands or shell
|
|
281
|
+
defaults. Omit unobserved configuration details.
|
|
282
|
+
|
|
249
283
|
Report the original task ID, selected provider ID when the CLI returns one,
|
|
250
284
|
the final admission/liveness/publication fields, the terminal outcome and
|
|
251
285
|
evidence descriptors, and any continuation or blocked-authentication state.
|
|
@@ -150,6 +150,101 @@
|
|
|
150
150
|
"external": false,
|
|
151
151
|
"provider": null,
|
|
152
152
|
"split": "validation"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "ccr-review",
|
|
156
|
+
"prompt": "Ask Claude Code Router to review pricing.py for correctness without editing. Choose an appropriate configured model and report which one you used.",
|
|
157
|
+
"external": true,
|
|
158
|
+
"provider": "ccr:print",
|
|
159
|
+
"split": "development"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"name": "ccr-explicit-alias",
|
|
163
|
+
"prompt": "Ask CCR using the alias local-review to investigate pricing.py. Do not edit files.",
|
|
164
|
+
"external": true,
|
|
165
|
+
"provider": "ccr:print",
|
|
166
|
+
"model": "local-review",
|
|
167
|
+
"split": "validation"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"name": "ccr-write",
|
|
171
|
+
"prompt": "Have CCR with the alias local-review fix pricing.py so discounts are percentages, and test the correction. Authorize edits in this workspace.",
|
|
172
|
+
"external": true,
|
|
173
|
+
"provider": "ccr:print",
|
|
174
|
+
"model": "local-review",
|
|
175
|
+
"split": "validation"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "ccr-follow-up",
|
|
179
|
+
"prompt": "Ask CCR using local-review to critique pricing.py, then ask that same session one follow-up about its recommendation. No edits.",
|
|
180
|
+
"external": true,
|
|
181
|
+
"provider": "ccr:print",
|
|
182
|
+
"model": "local-review",
|
|
183
|
+
"split": "validation"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "ccr-explain",
|
|
187
|
+
"prompt": "Explain what Claude Code Router does. Do not ask it to perform work.",
|
|
188
|
+
"external": false,
|
|
189
|
+
"provider": null,
|
|
190
|
+
"split": "validation"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "ccr-compare",
|
|
194
|
+
"prompt": "Ask CCR using local-review and a separate Codex session for independent reviews of pricing.py, then compare their findings. No edits.",
|
|
195
|
+
"external": true,
|
|
196
|
+
"provider": [
|
|
197
|
+
"ccr:print",
|
|
198
|
+
"codex:exec"
|
|
199
|
+
],
|
|
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
|
|
153
248
|
}
|
|
154
249
|
]
|
|
155
250
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# CCR model selection
|
|
2
|
+
|
|
3
|
+
Use this reference when asked to consult Claude Code Router (CCR), use one of
|
|
4
|
+
its configured model aliases, or compare its models with other harnesses.
|
|
5
|
+
CCR runs Claude Code against a configured third-party or local provider.
|
|
6
|
+
|
|
7
|
+
1. Resolve CCR through `delegate providers --json`; its profile is `ccr:print`.
|
|
8
|
+
2. Preserve an explicitly requested CCR alias as `--model ALIAS`. Do not replace
|
|
9
|
+
it with an upstream model ID or reject it solely because discovery omitted it.
|
|
10
|
+
3. If no alias was chosen, run
|
|
11
|
+
`delegate models --provider ccr:print --cwd ABSOLUTE_WORKSPACE --json`.
|
|
12
|
+
Choose a suitable configured alias from the available metadata and report
|
|
13
|
+
the choice. Clarify when the intended model remains ambiguous. If discovery
|
|
14
|
+
is unavailable and no alias is known, report the blocker; do not guess.
|
|
15
|
+
4. Dispatch through Delegate with `--provider ccr:print --model ALIAS`, the brief,
|
|
16
|
+
workspace, permission, finite budget, and `--json`. Use the normal status,
|
|
17
|
+
collect, and continuation procedure from the main skill.
|
|
18
|
+
|
|
19
|
+
CCR requires an explicit alias. Unspecified external collaboration still uses
|
|
20
|
+
`dispatch --auto`; do not prefer CCR unless the request calls for it. Multiple
|
|
21
|
+
aliases can be assigned independent tasks for comparison or debate.
|
|
22
|
+
|
|
23
|
+
Read-only selects native Claude plan behavior; workspace-write selects native
|
|
24
|
+
unattended tool execution. Neither is an independent Delegate sandbox. Preserve
|
|
25
|
+
requested effort through `--effort`; unknown effort metadata is not an allowlist
|
|
26
|
+
or a reason to invent a level. Leave effort unspecified unless requested.
|
|
27
|
+
|
|
28
|
+
Delegate uses CCR's foreground provider-only route. Model/provider setup and
|
|
29
|
+
credentials belong to CCR. Never run `ccr launch`, configure CCR, inspect its
|
|
30
|
+
SQLite database, call Claude directly, or select another integration to bypass a
|
|
31
|
+
Delegate error. Ordinary questions about CCR or installation do not trigger
|
|
32
|
+
external collaboration. An explicit request to use another integration remains
|
|
33
|
+
an explicit user choice.
|