cyberlegion 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/.codex-plugin/plugin.json +20 -0
  3. package/.plugin/pins.json +3 -0
  4. package/agents/headless-legate.md +59 -0
  5. package/bin/cyberlegion.mjs +18 -1
  6. package/dist/cli.mjs +7823 -72
  7. package/dist/index.d.mts +262 -11
  8. package/dist/index.mjs +2298 -4
  9. package/package.json +10 -3
  10. package/plugin.json +24 -0
  11. package/readme.md +63 -0
  12. package/skills/dispatch-governance/README.md +29 -0
  13. package/skills/dispatch-governance/SKILL.md +143 -0
  14. package/skills/dispatch-governance/scripts/cyberlegion.mjs +31 -0
  15. package/skills/init-cyberlegion/README.md +14 -0
  16. package/skills/init-cyberlegion/SKILL.md +112 -0
  17. package/skills/init-cyberlegion/scripts/cyberlegion.mjs +31 -0
  18. package/skills/legate/README.md +22 -0
  19. package/skills/legate/SKILL.md +62 -0
  20. package/skills/legate/scripts/cyberlegion.mjs +31 -0
  21. package/skills/manage-inbox/README.md +12 -0
  22. package/skills/manage-inbox/SKILL.md +90 -0
  23. package/skills/manage-inbox/scripts/cyberlegion.mjs +31 -0
  24. package/skills/relay-governance/README.md +36 -0
  25. package/skills/relay-governance/SKILL.md +204 -0
  26. package/skills/relay-governance/scripts/cyberlegion.mjs +31 -0
  27. package/skills/session-adapter-governance/README.md +17 -0
  28. package/skills/session-adapter-governance/SKILL.md +74 -0
  29. package/skills/subagent-backend-governance/README.md +35 -0
  30. package/skills/subagent-backend-governance/SKILL.md +99 -0
  31. package/skills/subagent-backend-governance/scripts/cyberlegion.mjs +31 -0
  32. package/dist/cli.d.mts +0 -12
  33. package/dist/watch-D33fQcUI.mjs +0 -2044
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberlegion",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Harness-agnostic agent session spawning and messaging over the filesystem",
5
5
  "keywords": [
6
6
  "agent",
@@ -30,7 +30,13 @@
30
30
  },
31
31
  "files": [
32
32
  "bin",
33
- "dist"
33
+ "dist",
34
+ "plugin.json",
35
+ ".claude-plugin",
36
+ ".codex-plugin",
37
+ ".plugin",
38
+ "skills",
39
+ "agents"
34
40
  ],
35
41
  "dependencies": {
36
42
  "commander": "^14.0.3",
@@ -38,6 +44,7 @@
38
44
  },
39
45
  "devDependencies": {
40
46
  "@types/node": "^24.10.1",
47
+ "smol-toml": "^1.8.0",
41
48
  "tsdown": "^0.22.0",
42
49
  "tsx": "^4.22.3",
43
50
  "typescript": "^7.0.2",
@@ -53,7 +60,7 @@
53
60
  "scripts": {
54
61
  "build": "tsdown",
55
62
  "check:metaphor-free": "tsx src/check-metaphor-free.ts",
56
- "dev": "tsx src/cli.ts",
63
+ "dev": "tsx src/dev.ts",
57
64
  "test": "vitest run src",
58
65
  "test:watch": "vitest",
59
66
  "typecheck": "tsc --noEmit"
package/plugin.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "cyberlegion",
4
+ "version": "0.5.0",
5
+ "description": "Agent session spawning, messaging, and dispatch — the Legion and its Legate.",
6
+ "author": {
7
+ "name": "unional"
8
+ },
9
+ "homepage": "https://github.com/cyberuni/cyberlegion",
10
+ "repository": "https://github.com/cyberuni/cyberlegion",
11
+ "keywords": ["agent", "dispatch", "inter-agent", "multiplexer"],
12
+ "extensions": {
13
+ "org.cyberuni.universal-plugin": {
14
+ "vendors": ["claude-code", "codex"],
15
+ "packagePath": ".",
16
+ "skills": "./skills",
17
+ "agents": ["./agents/headless-legate.md"],
18
+ "harnesses": {
19
+ "claude-code": {},
20
+ "codex": {}
21
+ }
22
+ }
23
+ }
24
+ }
package/readme.md CHANGED
@@ -98,6 +98,69 @@ Top-level shortcuts are provided for the common verbs: `spawn`, `send`, `inbox`,
98
98
  | `--space <path>` | Isolate the hub root (overrides the global hub / `$CYBERLEGION_ROOT`) |
99
99
  | `--format <format>` | Output format: `toon` (default) or `json` |
100
100
 
101
+ ## Agent plugin
102
+
103
+ Agent session spawning, messaging, and dispatch — harness-agnostic, MCP-free. The foundation both
104
+ SDD and cyberfleet build on: a **Legion** of addressable agent units, mustered and reaped, commanded
105
+ and communicating over the filesystem.
106
+
107
+ ### The console
108
+
109
+ The `cyberlegion` CLI is the cold, deterministic mechanism: identity (`unit register`, `unit claim`,
110
+ `unit who`), warm peer sessions in their own git worktrees (`unit spawn`, `unit close`,
111
+ `unit prune`), durable mail (`mail send`, `mail inbox`, `mail read`, `mail await`),
112
+ agent-definition resolution (`agent resolve`), hook registration (`init`), and
113
+ admin/diagnostics (`mux doctor`, `admin migrate`). It never decides *when* to spawn a peer
114
+ versus a subagent, and it carries no dispatch/result-slot primitives of its own — a cold subagent
115
+ returns via the caller's own Task-result, a warm peer via `mail await`.
116
+
117
+ ### The Legate
118
+
119
+ The **Legate** is the routing brain on top of the console — the judgment the CLI deliberately does
120
+ not carry. In an attended session it runs in-session as `dispatch-governance`; with no user channel
121
+ it is realized headless as the `headless-legate` agent. Given an intent (fulfill a role with a brief, expect a
122
+ verdict), it reads the target agent-definition's `warm`/`interactive` tags and the environment's
123
+ multiplexer availability, then picks exactly one strategy:
124
+
125
+ - **channel** — a warm, interactive peer in its own pane (`unit spawn` + `mail await` on the thread)
126
+ - **run-inline** — no multiplexer to host a peer, so the caller does the work itself, in-session
127
+ - **subagent** — a cold, one-shot unit realized via the caller's own Task tool, taking its
128
+ Task-result (final returned message) as the verdict
129
+
130
+ ### The gateway
131
+
132
+ `legate` (the skill) is the thin front door — classify the request (send mail, check inbox,
133
+ spawn/close a unit, wait for a reply, dispatch work) and either run the matching CLI call directly or
134
+ hand routing judgment to `dispatch-governance`. It loads no governance itself and writes no state.
135
+
136
+ ### Onboarding and the owner mailbox
137
+
138
+ Two more user-facing skills sit beside the gateway:
139
+
140
+ - **`init-cyberlegion`** — the onboarding front door: probe the environment, register the
141
+ mail-surfacing hook so incoming mail reaches you mid-session, and (only in a root session, only on
142
+ an explicit yes) bind this pane as the durable `legate` owner inbox. Every step is a
143
+ `cyberlegion` CLI call; the skill holds the conversation and the judgment.
144
+ - **`manage-inbox`** — the human's surface for that **owner mailbox**: the hub-level,
145
+ session-independent inbox a standing identity holds, where frameless agents (cron-started, no
146
+ parent frame) push their reports. It's how you read and ack those reports from whichever session
147
+ you happen to be in.
148
+
149
+ ### Installation
150
+
151
+ ```bash
152
+ npx skills add cyberuni/cyberplace --plugin cyberlegion --global
153
+ ```
154
+
155
+ This package doubles as the plugin root: `plugin.json`, the generated `.claude-plugin/` and
156
+ `.codex-plugin/` manifests, `skills/`, and `agents/` all ship in the npm tarball. The bundle
157
+ Each skill that runs the CLI carries a launcher, `scripts/cyberlegion.mjs`, that runs the CLI
158
+ shipped in this same package (`bin/cyberlegion.mjs` over the committed `dist/cli.mjs`), so a skill
159
+ never fetches a copy through npx and always runs the version its own text describes. Where the
160
+ launcher cannot be resolved, a skill names one fallback pinned to the version that shipped it,
161
+ `npx -y cyberlegion@<version>`. `pnpm version` rewrites those pins and `.plugin/pins.json` at every
162
+ release, and the test suite fails on a stale one.
163
+
101
164
  ## License
102
165
 
103
166
  MIT
@@ -0,0 +1,29 @@
1
+ # dispatch-governance
2
+
3
+ Partial Skill: invoke by name only — the Legate's routing brain. Not user-invocable — loaded by the `legate` gateway
4
+ in-session on a dispatch intent, and by the `headless-legate` agent when there is no user channel.
5
+
6
+ ## When it loads
7
+
8
+ - The `legate` gateway classified a request as "dispatch work to fulfill a role and expect a
9
+ verdict back."
10
+ - The `headless-legate` agent realizes the same flow headless.
11
+
12
+ ## What it does
13
+
14
+ - Resolves the target agent definition's `warm`/`interactive` tags (`cyberlegion agent resolve`).
15
+ - Probes the environment for a multiplexer (`cyberlegion mux doctor`).
16
+ - Picks exactly one of three strategies — **channel** (warm peer: `unit spawn` + `mail await`),
17
+ **run-inline** (caller does the work itself), or **subagent** (cold, one-shot, via
18
+ `subagent-backend-governance`) — and executes it by composing the `cyberlegion` CLI primitives.
19
+ - Folds in the wake-matrix decision (bounded await vs doorbell vs run-inline, gated on a verified
20
+ multiplexer) that the CLI no longer encodes.
21
+ - Defines the `subagent | channel` seam a dependent (SDD, ADR-0023) references by intent, never by
22
+ a literal command name.
23
+
24
+ ## What it does not do
25
+
26
+ - Never lets the CLI auto-route — there is no `--backend auto` flag, and no `dispatch` command
27
+ group at all; `unit spawn`/`mail await`/`unit nudge` are composed here deliberately.
28
+ - Never invokes a harness's Task tool directly (that happens inside `subagent-backend-governance`,
29
+ using the caller's own tool).
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: dispatch-governance
3
+ description: "Partial Skill: invoke by name only — the Legate's routing brain — picks and executes a dispatch strategy for a role intent. Loaded in-session by the legate gateway, and by the headless-legate agent headless. Not triggered by users directly."
4
+ user-invocable: false
5
+ ---
6
+
7
+ # Dispatch Governance
8
+
9
+ The judgment the `cyberlegion` CLI deliberately does not carry. The CLI is dumb hands — it never
10
+ auto-routes, never picks a backend, never invokes a Task tool itself. This governance is the one
11
+ place that judgment lives, loaded in-session by the `legate` gateway on a dispatch intent, and
12
+ realized headless by the `headless-legate` agent. Both loads run the exact same procedure below.
13
+
14
+ **Input:** an intent to fulfill role `R` with brief `B`, optionally expecting a result that
15
+ satisfies verdict schema `V`.
16
+
17
+ > **Running the CLI.** Every `node scripts/cyberlegion.mjs …` command below runs the `cyberlegion` CLI
18
+ > this plugin ships. The path is relative to this skill's own directory, not the working directory.
19
+ > If you cannot resolve it, run the published CLI of the version that shipped this skill instead,
20
+ > with the same arguments: `npx -y cyberlegion@0.5.0`.
21
+
22
+ ## 1. Resolve the agent definition
23
+
24
+ ```bash
25
+ node scripts/cyberlegion.mjs agent resolve <R> --format json
26
+ ```
27
+
28
+ Read `model`, `effort`, `harness`, `warm`, `interactive` off the resolved def. `warm` marks a role
29
+ that should run as a live peer session (not a fire-and-forget unit); `interactive` marks a role that
30
+ may need to grill the caller or converse over multiple rounds, not just return one result.
31
+
32
+ ## 2. Probe the environment
33
+
34
+ ```bash
35
+ node scripts/cyberlegion.mjs mux doctor --format json
36
+ ```
37
+
38
+ Read whether a multiplexer was ancestry-discovered (tmux/herdr) — a **channel** strategy needs one
39
+ to open a pane in; its absence rules that strategy out regardless of what the agent def wants.
40
+
41
+ ## 3. Pick exactly one strategy
42
+
43
+ | `warm` | `interactive` | mux present | Strategy |
44
+ |---|---|---|---|
45
+ | true | true | true | **channel** |
46
+ | true | true | false | **run-inline** |
47
+ | false or true | false | — | **subagent** |
48
+ | false | true | — | **error** — unroutable def |
49
+
50
+ - **channel** — warm, interactive, and a pane is available: spin up a live peer that can converse
51
+ and mail back over rounds. Compose the primitives directly — no single CLI command does this:
52
+
53
+ mint a thread id, weave it (and an instruction to reply on it) into brief `B`, then:
54
+
55
+ ```bash
56
+ node scripts/cyberlegion.mjs unit spawn --agent <R> --brief-file <B> --at <placement>
57
+ node scripts/cyberlegion.mjs mail await --thread <the-minted-thread-id> [--timeout <ms>] [--max-wait <s>]
58
+ ```
59
+
60
+ `unit spawn` launches the peer (its brief tells it what thread to reply on); `mail await` then
61
+ blocks on that reply thread. Use `--at` to place the pane, `--timeout`/`--max-wait` to bound the
62
+ wait; a `waiting` outcome at `--max-wait` means re-run `mail await` on the same thread to keep
63
+ polling, not a failure.
64
+
65
+ **The wake-matrix decision** (which sub-mode of channel to run) belongs here too, since the CLI
66
+ carries no `selectWakePath` helper of its own:
67
+
68
+ | Environment | Wake mode |
69
+ |---|---|
70
+ | Portable default (mux unknown, or nothing verified) | Bounded await — poll `mail await` with `--max-wait`, re-arming on `waiting` ("A-loop"). |
71
+ | Claude Code, with an observable background task | Bounded await plus a background-task check-in ("A-prime") — same polling, but the harness's own background-task surface can short-circuit a wait. |
72
+ | A live foreign session behind a **verified** multiplexer (`mux doctor` reports tmux/herdr, not `none`) | Doorbell — `unit nudge` the peer's pane, then `mail await` ("B"). |
73
+ | Multiplexer is `none` | **Never** pick doorbell (B) — there is no pane to ring. Fall back to bounded await (A). |
74
+
75
+ - **run-inline** — the role is warm and interactive (needs a live back-and-forth), but there is no
76
+ multiplexer to host a peer in. **Do not delegate.** Return a `run-inline` verdict and let the
77
+ caller (the session that loaded this governance — e.g. the SDD conductor) do the work itself,
78
+ in-session. A cold subagent is the wrong substitute here: it cold-reloads its whole context every
79
+ round and holds no user channel, so a role that needs to converse cannot be served that way.
80
+
81
+ - **subagent** — the role is cold and one-shot (no live conversation expected, one result back).
82
+ Realize it via `subagent-backend-governance`: resolve the agent def → the caller's **own** harness
83
+ Task/subagent tool → the subagent's Task-result (its own final returned message) is the verdict.
84
+ The CLI cannot invoke a Task tool itself — that is always the caller's own tool, never
85
+ `cyberlegion`'s — and it collects nothing on the caller's behalf; there is no result file.
86
+
87
+ - **error (unroutable def)** — `interactive` with `warm` unset (`false`) has no strategy. A role that
88
+ needs live back-and-forth (`interactive`) must be a live peer (`warm`); a cold one-shot must not be
89
+ `interactive` (`subagent-backend-governance` is one-shot by design). So this pair is a **malformed
90
+ def**, not a routing choice — **fail loud** here, naming the def and the contradictory tags, so the
91
+ author fixes it. It is **not** a `needsInput` (that is for an under-specified task, not a bad def),
92
+ and it is **never** silently swept into **subagent**. This is the Legate's judgment, not the CLI's:
93
+ `agent resolve` reports the raw tags; only this governance holds the table that knows the pair maps
94
+ to nothing.
95
+
96
+ The CLI never chooses between these on its own — `unit spawn` + `mail await` only run in this
97
+ sequence when this governance picked **channel**; the caller's Task tool only runs when it picked
98
+ **subagent**; picking **run-inline** means neither runs at all.
99
+
100
+ ## The `subagent | channel` seam
101
+
102
+ This is the seam a dependent (e.g. SDD, ADR-0023) references **by intent** — "dispatch a role
103
+ fulfillment and expect a verdict" — never by pinning `subagent` or `channel` as a literal command
104
+ name. The dependent states its intent (role, brief, verdict schema) and this governance decides the
105
+ mechanism; a dependent that hardcodes "always subagent" or "always channel" has broken the seam and
106
+ coupled itself to a mechanism instead of an intent.
107
+
108
+ ## Result shape — DispatchResult
109
+
110
+ Every strategy resolves to one shape the caller can handle uniformly:
111
+
112
+ ```jsonc
113
+ {
114
+ "strategy": "subagent" | "channel" | "run-inline",
115
+ "id": "<unit id, when one was spawned>", // omitted for run-inline
116
+ "verdict": "matched" | "waiting" | "timed-out" | "run-inline",
117
+ "result": { /* the subagent's Task-result, or the mail-await reply body, as-is */ },
118
+ "needsInput": ["<question>", "..."] // present only when the callee could not complete
119
+ }
120
+ ```
121
+
122
+ `result` is carried through unvalidated today — structured verdict-schema checking against `V` is a
123
+ deferred capability (`mail --verdict-schema`), not present in this CLI.
124
+
125
+ `waiting` (channel path, `--max-wait` cap hit) and `timed-out` (channel path, `--timeout` elapsed)
126
+ are re-armable and terminal respectively — treat them the same way `mail await`'s own three
127
+ outcomes are treated; never silently retry past a `timed-out`. A `needsInput` result on the subagent
128
+ path means the unit hit its own leash and returned a needs-input result rather than guessing.
129
+
130
+ **How you relay that `needsInput` (or your own) is `relay-governance`, not this skill.** Report/ask
131
+ transport is keyed on the reporting agent's own lifecycle — a framed callee returns `needsInput` to
132
+ its caller; a bare cron session with no frame pushes mail to the standing owner and exits. Load
133
+ `relay-governance` for the fork; this skill owns **strategy** choice (channel / run-inline /
134
+ subagent), relay owns how the result or an unanswerable question gets home.
135
+
136
+ ## Non-goals
137
+
138
+ - **No auto-routing in the CLI.** The CLI carries no `dispatch` command group at all — `unit spawn`,
139
+ `mail await`, `unit nudge`, and the caller's own Task tool are invoked deliberately by this
140
+ governance (or `subagent-backend-governance`), never by a `--backend auto` flag — no such flag
141
+ exists.
142
+ - **No mid-flight strategy switch.** Once a strategy is picked for one dispatch, it runs to its one
143
+ of the three outcomes above; a failed subagent is not silently retried as a channel.
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // Runs the cyberlegion CLI this plugin ships, so the skill beside it never fetches a copy through npx.
3
+ // The package root is three levels up from this file (skills/<skill>/scripts/), resolved from the
4
+ // file's own location: the working directory is the repository the agent is working on, not the plugin.
5
+ // Arguments, output, and the exit code pass straight through the package's bin.
6
+ //
7
+ // A skill folder installed on its own has no package around it. Then this runs the published CLI of
8
+ // the version that shipped the skill instead, so the skill still works, only slower. The version sync
9
+ // run at release keeps FALLBACK at the package version.
10
+ import { spawnSync } from 'node:child_process'
11
+ import { existsSync } from 'node:fs'
12
+ import { fileURLToPath } from 'node:url'
13
+
14
+ const FALLBACK = 'npx -y cyberlegion@0.5.0'
15
+ const bin = new URL('../../../bin/cyberlegion.mjs', import.meta.url)
16
+
17
+ if (existsSync(fileURLToPath(bin))) {
18
+ await import(bin.href)
19
+ } else {
20
+ process.stderr.write(`cyberlegion: no plugin package around this skill; running ${FALLBACK}\n`)
21
+ const [command, ...args] = FALLBACK.split(' ')
22
+ const res = spawnSync(command, [...args, ...process.argv.slice(2)], {
23
+ stdio: 'inherit',
24
+ shell: process.platform === 'win32',
25
+ })
26
+ if (res.error) {
27
+ process.stderr.write(`cyberlegion: could not run ${FALLBACK}: ${res.error.message}\n`)
28
+ process.exit(1)
29
+ }
30
+ process.exit(res.status ?? 1)
31
+ }
@@ -0,0 +1,14 @@
1
+ # init-cyberlegion
2
+
3
+ The onboarding front door to the Legion — walks a session through getting `cyberlegion` working in
4
+ this repo: probe the environment, register the surfacing hook, and (root session only, only on an
5
+ explicit yes) bind this pane as the durable `legate` owner inbox.
6
+
7
+ User-facing. Triggers on "set up cyberlegion", "onboard the legion", "register the cyberlegion
8
+ surfacing hook", "make this pane my main legion inbox", "get cyberlegion working in this repo". Wraps
9
+ `cyberlegion mux doctor` / `init` / `unit register --standing` / `attach` — every mechanic is a
10
+ CLI call, no filesystem or hub state touched directly.
11
+
12
+ Distinct from `legate` (spawn/mail/dispatch a peer) and `manage-inbox` (read/ack owner mail once
13
+ bound). See `SKILL.md` for the full flow (probe → register hook → detect root vs spawned →
14
+ consent-gated bind).
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: init-cyberlegion
3
+ description: "Use this skill to set up or onboard cyberlegion in this session or repo — probe the environment, register the mail-surfacing hook, and (in a root session, on your yes) bind this pane as the durable legate owner inbox. Triggers: 'set up cyberlegion', 'onboard the legion', 'register the cyberlegion surfacing hook', 'make this pane my main legion inbox', 'get cyberlegion working in this repo'. Not for spawning/messaging/dispatching a peer (that is legate), reading or acking owner mail (that is manage-inbox), or unrelated init like a git repo, npm package, or commit discipline."
4
+ ---
5
+
6
+ # init-cyberlegion
7
+
8
+ The onboarding front door to the Legion — a thin, user-invocable wrapper that walks a session through
9
+ getting `cyberlegion` working in this repo: probe the environment, register the surfacing hook, and
10
+ (only in a root session, only on an explicit yes) bind this pane as the durable `legate` owner inbox.
11
+ It is a **thin wrapper**: every mechanic is a `cyberlegion` CLI call. The skill holds the *conversation
12
+ and the judgment* — is this a root session? should we ask to bind? what does the environment look
13
+ like? — the CLI holds all the *mechanism*.
14
+
15
+ > **Running the CLI.** Every `node scripts/cyberlegion.mjs …` command below runs the `cyberlegion` CLI
16
+ > this plugin ships. The path is relative to this skill's own directory, not the working directory.
17
+ >
18
+ > **Version pin.** Resolve the CLI version **once, before the flow**, by reading the plugin's bundled
19
+ > map at `${CLAUDE_PLUGIN_ROOT}/.plugin/pins.json` — a flat `{ "<package>": "<version>" }` map the
20
+ > release version flow keeps equal to the shipped package version. Look up the `cyberlegion` key:
21
+ >
22
+ > - **A version is found** → pass it to the hook registration in step 2 as `init --pin <version>`
23
+ > so the installed surfacing hook is pinned to the same shipped version. If you cannot resolve
24
+ > `scripts/cyberlegion.mjs`, run `npx -y cyberlegion@<version>` in its place, with the same arguments.
25
+ > - **No `pins.json`, no `cyberlegion` key, or a malformed map** (an unbundled workspace checkout) →
26
+ > pass **no** `--pin`; if you cannot resolve `scripts/cyberlegion.mjs`, fall back to the unpinned
27
+ > `npx -y cyberlegion` form. **Never invent a version number.**
28
+ >
29
+ > Do not scrape the version from prose.
30
+
31
+ ## Flow
32
+
33
+ ### 1. Probe the environment
34
+
35
+ ```bash
36
+ node scripts/cyberlegion.mjs mux doctor
37
+ ```
38
+
39
+ Run this **before** touching the hook or any identity. It reports `harness`, `mux`, `pane`,
40
+ `hubRoot`, and `selfId` — read it to learn the environment (is there a multiplexer? a pane?) and to
41
+ detect root vs spawned (see step 3). Narrate a short, grounded summary of what it found — do not
42
+ invent facts the probe did not report.
43
+
44
+ ### 2. Register the surfacing hook
45
+
46
+ ```bash
47
+ node scripts/cyberlegion.mjs init --pin <version>
48
+ ```
49
+
50
+ Pass `--pin <version>` with the version resolved above so the installed hook is pinned to the shipped
51
+ version; **omit `--pin`** when the map yielded no version.
52
+
53
+ Auto-detect is the default — no `--agent` flag. Pass `--agent <name>` **only** when `mux doctor` could
54
+ not auto-detect the harness, or the user named one explicitly (it composes with `--pin`):
55
+
56
+ ```bash
57
+ node scripts/cyberlegion.mjs init --pin <version> --agent <name>
58
+ ```
59
+
60
+ This step is **idempotent**: if the hook is already registered, `init` reports `already present` —
61
+ that is a clean no-op, never a duplicate registration and never an error.
62
+
63
+ ### 3. Detect root vs spawned — derived, never asked
64
+
65
+ Read the probe's `selfId` from step 1. A **root** session has `spawnedBy` unset; a **spawned** unit
66
+ has it set. Derive this from the probe — never ask the user to declare it.
67
+
68
+ - **Spawned (non-root) unit** — stop here, right after the hook. Do not offer to bind; a spawned unit
69
+ is never the owner inbox.
70
+ - **A hook-only request in a root session** ("just register the surfacing hook") — also stop here.
71
+ Registering the hook satisfies the ask; do not proceed to the bind offer unasked.
72
+ - **Root session, broader onboarding intent, no `legate` owner bound yet** — continue to step 4.
73
+ - **Root session where a `legate` owner is already bound** — stop; do not re-ask, do not re-mint.
74
+
75
+ ### 4. Ask before binding — never silent
76
+
77
+ Only a root session with no `legate` owner bound is offered the bind. Ask plainly, e.g.:
78
+
79
+ > "This looks like a root session with no legate owner bound yet. Bind this pane as the main legate
80
+ > owner inbox?"
81
+
82
+ - **User declines** — the registered hook stays in place; nothing else runs. Do not mint or bind.
83
+ - **User agrees explicitly** — proceed to step 5.
84
+ - **Already bound** — never reach this ask (see step 3).
85
+
86
+ ### 5. On an explicit yes — mint and bind
87
+
88
+ ```bash
89
+ node scripts/cyberlegion.mjs unit register --standing --handle legate
90
+ node scripts/cyberlegion.mjs attach
91
+ ```
92
+
93
+ Run these **in this order** and only after the explicit yes: mint the durable, session-independent
94
+ `legate` owner inbox first, then bind the current pane as that owner's live presence.
95
+
96
+ **Non-mux parity.** If the probe reported no multiplexer or pane, `attach` is a no-op —
97
+ that is expected, not a failure. Still run `unit register --standing --handle legate` on yes, and complete
98
+ **without erroring**. The root session surfaces owner mail via the `!spawnedBy` fallback instead of a
99
+ bound pane.
100
+
101
+ ## Boundaries
102
+
103
+ - Every mechanic here is a `cyberlegion` CLI call — this skill writes no hub state and invents no
104
+ config format. Its only filesystem read is the plugin's own bundled `${CLAUDE_PLUGIN_ROOT}/.plugin/pins.json`
105
+ version map, to resolve the CLI pin.
106
+ - It never mints or binds an owner identity without an explicit user yes.
107
+ - It is distinct from `legate` — sending/spawning/dispatching to a peer is `legate`'s job, not this
108
+ skill's.
109
+ - It is distinct from `manage-inbox` — reading or acking owner mail once bound is `manage-inbox`'s
110
+ job, not this skill's.
111
+ - An unrelated `init` intent (a git repo, an npm package, commit discipline) is out of scope — defer
112
+ to the matching unrelated skill or decline.
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // Runs the cyberlegion CLI this plugin ships, so the skill beside it never fetches a copy through npx.
3
+ // The package root is three levels up from this file (skills/<skill>/scripts/), resolved from the
4
+ // file's own location: the working directory is the repository the agent is working on, not the plugin.
5
+ // Arguments, output, and the exit code pass straight through the package's bin.
6
+ //
7
+ // A skill folder installed on its own has no package around it. Then this runs the published CLI of
8
+ // the version that shipped the skill instead, so the skill still works, only slower. The version sync
9
+ // run at release keeps FALLBACK at the package version.
10
+ import { spawnSync } from 'node:child_process'
11
+ import { existsSync } from 'node:fs'
12
+ import { fileURLToPath } from 'node:url'
13
+
14
+ const FALLBACK = 'npx -y cyberlegion@0.5.0'
15
+ const bin = new URL('../../../bin/cyberlegion.mjs', import.meta.url)
16
+
17
+ if (existsSync(fileURLToPath(bin))) {
18
+ await import(bin.href)
19
+ } else {
20
+ process.stderr.write(`cyberlegion: no plugin package around this skill; running ${FALLBACK}\n`)
21
+ const [command, ...args] = FALLBACK.split(' ')
22
+ const res = spawnSync(command, [...args, ...process.argv.slice(2)], {
23
+ stdio: 'inherit',
24
+ shell: process.platform === 'win32',
25
+ })
26
+ if (res.error) {
27
+ process.stderr.write(`cyberlegion: could not run ${FALLBACK}: ${res.error.message}\n`)
28
+ process.exit(1)
29
+ }
30
+ process.exit(res.status ?? 1)
31
+ }
@@ -0,0 +1,22 @@
1
+ # legate (gateway)
2
+
3
+ The Legate's front door to the Legion — a thin classifier skill that routes an agent-to-agent
4
+ messaging or dispatch request to the matching `cyberlegion` CLI call, or to `dispatch-governance`
5
+ when the request needs routing judgment.
6
+
7
+ ## When to use
8
+
9
+ - You need to send or check mail with another agent session.
10
+ - You need to spawn, close, or nudge a peer session.
11
+ - You need to wait for a threaded reply.
12
+ - You need work done to fulfill a role and a verdict back — but do not yet know whether that should
13
+ be a warm peer, a cold subagent, or done inline.
14
+
15
+ ## What it does
16
+
17
+ - Classifies the request against a fixed intent → CLI-call table (mail, session, identity, admin).
18
+ - Hands any dispatch intent to `dispatch-governance` rather than guessing a strategy.
19
+ - Spawns the `headless-legate` agent when there is no user channel to relay through.
20
+ - Loads no other governance and writes no state — a front door only.
21
+
22
+ Every mechanic is a `cyberlegion` CLI call — harness-agnostic, MCP-free.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: legate
3
+ description: "Use this skill when you need to reach another agent session — send a message to a named peer or \"the pane on the right\", check your inbox, wait for a reply, spawn or close a peer session, or dispatch work to fulfill a role and get a verdict back."
4
+ ---
5
+
6
+ # legate
7
+
8
+ The Legate — gateway skill for the Legion, the front door to agent session spawning, messaging, and dispatch.
9
+ It **classifies** the request and either runs the matching `cyberlegion` CLI call directly or hands
10
+ routing judgment to `dispatch-governance`. It is a **thin classifier**: it holds no production
11
+ logic, loads no other governance, and writes no state of its own.
12
+
13
+ > **Running the CLI.** Every `node scripts/cyberlegion.mjs …` command below runs the `cyberlegion` CLI
14
+ > this plugin ships. The path is relative to this skill's own directory, not the working directory.
15
+ > If you cannot resolve it, run the published CLI of the version that shipped this skill instead,
16
+ > with the same arguments: `npx -y cyberlegion@0.5.0`.
17
+
18
+ ## Doorbell vs mailbox
19
+
20
+ Two different primitives, never confused: a **nudge** (`unit nudge <ref>`) is a dumb doorbell —
21
+ it rings a peer's pane and carries no content. The **payload always lives in the mailbox**
22
+ (`mail send`, or the brief file `unit spawn` drops for a new unit) — a peer that receives a nudge
23
+ reads its mail to learn *why* it was rung. Never encode meaning in a nudge itself; never skip the
24
+ mailbox because a nudge already fired.
25
+
26
+ ## Classification map
27
+
28
+ | User intent | Handling |
29
+ |---|---|
30
+ | Send a message to a named peer, "the pane on the right", a claude/cursor peer | `node scripts/cyberlegion.mjs mail send --to <handle>` (or `unit nudge <ref>` first if the peer needs waking, then `mail send`) |
31
+ | Check inbox / read unread mail | `node scripts/cyberlegion.mjs mail inbox` — `mail read <msg-id>` to peek, `mail ack <msg-id>` once handled |
32
+ | Spawn a new peer session | `node scripts/cyberlegion.mjs unit spawn --agent <name> ...` — cyberlegion owns worktree creation; pass `--cwd <dir>` only when the caller already has a directory to spawn into (e.g. reusing an existing worktree) |
33
+ | Close / tear down a peer session | `node scripts/cyberlegion.mjs unit close <id>` |
34
+ | Wait for a threaded reply | `node scripts/cyberlegion.mjs mail await --thread <id>` |
35
+ | Watch mail as it streams in (observer, never acks) | `node scripts/cyberlegion.mjs mail watch` |
36
+ | List addressable peers | `node scripts/cyberlegion.mjs unit who` |
37
+ | Sweep dead peers | `node scripts/cyberlegion.mjs unit prune` |
38
+ | Onboard / set up cyberlegion (register the surfacing hook, bind the main owner pane, first-run setup) | **invoke the `init-cyberlegion` skill** — the interactive onboarding front door over `cyberlegion init` / `unit register --standing` / `attach` |
39
+ | Diagnose the environment (harness, multiplexer, pane, hub root) | `node scripts/cyberlegion.mjs mux doctor` |
40
+ | Register the surfacing hook by hand (low-level, explicit harness) | `node scripts/cyberlegion.mjs init --agent <harness>` — the `init-cyberlegion` skill wraps this; use directly only when scripting a known harness |
41
+ | Dispatch work to fulfill a role with a brief and expect a verdict back (routing judgment needed — which strategy, which agent def, warm vs cold) | **hand off to `dispatch-governance`** — do not compose `unit spawn`/`mail await`/the Task tool yourself |
42
+ | No user channel at all (unattended trigger, multi-unit fan-out) | **spawn the `headless-legate` agent** by name — it realizes this same gateway + `dispatch-governance` flow headless |
43
+
44
+ ## Load the handling skill in-session
45
+
46
+ For an attended session (a user or peer session holds this gateway), classify and act directly —
47
+ spawn nothing. The one exception is a **dispatch** intent: routing which strategy to use (warm
48
+ channel, cold subagent, or run-inline) is judgment this gateway does not carry — load
49
+ `dispatch-governance` in-session and let it decide.
50
+
51
+ **Headless (no user channel): spawn `headless-legate`.** When this gateway is reached with no live
52
+ user or peer channel to relay through — an unattended scheduler, a multi-unit fan-out — spawn the
53
+ `headless-legate` agent by name. It is not a separate role: it is this same classify-then-route flow,
54
+ realized headless, batching `needs-input` instead of asking live.
55
+
56
+ ## Boundaries
57
+
58
+ This skill never talks to the filesystem or another session directly — every mechanic is a
59
+ `cyberlegion` CLI call. It never chooses a dispatch strategy itself (that is
60
+ `dispatch-governance`'s one job) and never invokes a harness's Task/subagent tool (that is the
61
+ caller's own tool, used only inside `dispatch-governance`'s subagent path via
62
+ `subagent-backend-governance`).
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // Runs the cyberlegion CLI this plugin ships, so the skill beside it never fetches a copy through npx.
3
+ // The package root is three levels up from this file (skills/<skill>/scripts/), resolved from the
4
+ // file's own location: the working directory is the repository the agent is working on, not the plugin.
5
+ // Arguments, output, and the exit code pass straight through the package's bin.
6
+ //
7
+ // A skill folder installed on its own has no package around it. Then this runs the published CLI of
8
+ // the version that shipped the skill instead, so the skill still works, only slower. The version sync
9
+ // run at release keeps FALLBACK at the package version.
10
+ import { spawnSync } from 'node:child_process'
11
+ import { existsSync } from 'node:fs'
12
+ import { fileURLToPath } from 'node:url'
13
+
14
+ const FALLBACK = 'npx -y cyberlegion@0.5.0'
15
+ const bin = new URL('../../../bin/cyberlegion.mjs', import.meta.url)
16
+
17
+ if (existsSync(fileURLToPath(bin))) {
18
+ await import(bin.href)
19
+ } else {
20
+ process.stderr.write(`cyberlegion: no plugin package around this skill; running ${FALLBACK}\n`)
21
+ const [command, ...args] = FALLBACK.split(' ')
22
+ const res = spawnSync(command, [...args, ...process.argv.slice(2)], {
23
+ stdio: 'inherit',
24
+ shell: process.platform === 'win32',
25
+ })
26
+ if (res.error) {
27
+ process.stderr.write(`cyberlegion: could not run ${FALLBACK}: ${res.error.message}\n`)
28
+ process.exit(1)
29
+ }
30
+ process.exit(res.status ?? 1)
31
+ }
@@ -0,0 +1,12 @@
1
+ # manage-inbox
2
+
3
+ The human's surface for the **owner mailbox** — the durable, session-independent inbox where
4
+ frameless (cron-started, no-parent-frame) agents push their reports via `relay-governance`.
5
+
6
+ User-facing. Triggers on "check my inbox", "any reports for me", "read that report", "ack that",
7
+ "clear my owner inbox", or when a surfaced owner-mail doorbell needs acting on. Wraps the
8
+ `cyberlegion` CLI's owner-scoped mail commands (`mail inbox/read/ack --owner <handle>`) so a human
9
+ roaming across sessions manages the one owner mailbox from anywhere.
10
+
11
+ Read is a deliberate `mail ack --owner` — surfacing shows a report inline but is never a read
12
+ receipt. See `SKILL.md` for the full flow (resolve owner → list → read → ack → reply).