trantor 0.17.42 → 0.17.44

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + context-handoff for independent AI coding agents (Claude, Codex, Gemini, …)",
9
- "version": "0.17.42"
9
+ "version": "0.17.44"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "trantor",
14
14
  "source": "./",
15
- "description": "The hub-world for AI agent crews. Say \"fire up the crew\" and Claude becomes the architect: a plan-aware Advisor routes the work (solo / cheap inline calls / live crew of Codex, Gemini, Kimi & DeepSeek in their own terminal windows), a Kanban/flow command center with a testing gate tracks it, and an economics brain (Scrooge) keeps the receipts. Includes the relay MCP, a SessionStart auto-discovery hook, and a PreCompact context-handoff so a fresh session can take over a full window instead of compacting.",
16
- "version": "0.17.42",
15
+ "description": "The hub-world for AI agent crews. Say \"fire up the crew\" and Claude becomes the architect: a plan-aware Advisor routes the work (solo / cheap inline calls / live crew of Codex, GLM, Kimi & DeepSeek in their own terminal windows), a Kanban/flow command center with a testing gate tracks it, and an economics brain (Scrooge) keeps the receipts. Includes the relay MCP, a SessionStart auto-discovery hook, and a PreCompact context-handoff so a fresh session can take over a full window instead of compacting.",
16
+ "version": "0.17.44",
17
17
  "author": {
18
18
  "name": "Sasha Bogojevic"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.42",
3
+ "version": "0.17.44",
4
4
  "description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + crew orchestration for independent AI coding agents (Claude, Codex, Gemini, Kimi, DeepSeek)",
5
5
  "mcpServers": {
6
6
  "relay": {
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  ### The hub-world for AI agent crews.
13
13
 
14
14
  **One Advisor decides how your work runs — solo, cheap inline calls, or a live crew of
15
- Claude Code, Codex, Gemini, Kimi & DeepSeek in their own terminal windows — routed by your
15
+ Claude Code, Codex, GLM, Kimi & DeepSeek in their own terminal windows — routed by your
16
16
  actual plans, supervised on a live + historical board you can scroll back through, learning
17
17
  from every failure.**
18
18
 
@@ -134,7 +134,7 @@ project takes over with a full window (and a PreCompact hook does this automatic
134
134
  (`solo | scrooge | crew | hybrid`), a routing table with a **reason per package**, why
135
135
  that many seats ("seats follow the work, not the install list"), and a real-money estimate
136
136
  with quota-pool accounting. You say go.
137
- 2. **Windows open.** `trantor up codex gemini kimi deepseek:deepseek-v4-pro` spawns one titled
137
+ 2. **Windows open.** `trantor up codex kimi deepseek:deepseek opencode:zai-coding-plan` spawns one titled
138
138
  terminal window per agent. `agent:model` pins a model; `agent:provider --difficulty hard`
139
139
  picks the **best live model** for the work at spawn (capability × cost), enumerated from the
140
140
  CLI itself — never a guessed endpoint. **Serialized and then verified on the bus** — the
@@ -194,7 +194,7 @@ Trantor's economics engine ([Scrooge](https://github.com/sashabogi/token-scrooge
194
194
  automatically by `trantor setup`) knows model capabilities, per-1M costs, and keeps the
195
195
  ledger; Trantor turns that into decisions:
196
196
 
197
- - **Declare your plans once:** `trantor profile set claude=max codex=plus gemini=tier kimi=coding-plan deepseek=api`
197
+ - **Declare your plans once:** `trantor profile set claude=max codex=plus zai=coding-plan kimi=coding-plan deepseek=api`
198
198
  - The Advisor routes by **your economics**: API-billed orchestrator → offload everything;
199
199
  $20-tier plan → the crew *is* the only way a real build fits; max-tier → context horizon
200
200
  decides. **Quota pooling**: one build spread across your separate subscription buckets —
package/bin/advise.mjs CHANGED
@@ -19,12 +19,38 @@ import { pathToFileURL } from "node:url";
19
19
  const H = homedir();
20
20
  const read = (p, fb) => { try { return JSON.parse(readFileSync(p, "utf8")); } catch { return fb; } };
21
21
 
22
+ // ---- canonical crew roster (single source of truth, mirrors bin/doctor.mjs) ----
23
+ // Each token maps to: the CLI binary that must exist · the `trantor up` LAUNCH spec the
24
+ // orchestrator runs · the bus SESSION name (= spec's agent part) · the profile PROVIDER
25
+ // key that gates the seat and supplies the tier.
26
+ //
27
+ // GEMINI is deliberately ABSENT: Google retired the free Gemini CLI seat (2026-06-18), so
28
+ // even though the `gemini` binary is usually still on PATH, `gemini --yolo` crashes (exit 1)
29
+ // for everyone without a paid enterprise key. Its replacement seat is GLM via opencode.
30
+ // (Gemini still works as a Scrooge cheap-model via GEMINI_API_KEY — a separate path.) A
31
+ // paid-key holder can still force it with an explicit `trantor up gemini`; we just never
32
+ // auto-recommend a dead seat.
33
+ export const ROSTER = {
34
+ codex: { cli: "codex", launch: "codex", session: "codex", provider: "codex" },
35
+ kimi: { cli: "kimi", launch: "kimi", session: "kimi", provider: "kimi" },
36
+ deepseek: { cli: "opencode", launch: "deepseek:deepseek", session: "deepseek", provider: "deepseek" },
37
+ glm: { cli: "opencode", launch: "opencode:zai-coding-plan", session: "opencode", provider: "zai" },
38
+ };
39
+
22
40
  export function loadWorld() {
23
41
  const profile = read(join(H, ".agent-bus", "profile.json"), { providers: {} });
24
42
  const registry = read(join(H, ".token-scrooge", "registry.json"), { models: {}, tasks: {} });
25
43
  const caps = read(join(H, ".token-scrooge", "capabilities.json"), {});
26
44
  const has = (c) => { try { execSync(`command -v ${c}`, { stdio: "ignore", shell: "/bin/sh" }); return true; } catch { return false; } };
27
- const agents = ["codex", "gemini", "kimi", "deepseek"].filter(a => has(a === "deepseek" ? "opencode" : a));
45
+ const opencodeKey = () => !!read(join(H, ".config", "opencode", "opencode.json"), {})?.provider?.["zai-coding-plan"]?.options?.apiKey;
46
+ // a seat is available only if its CLI exists AND its provider is actually set up — a present
47
+ // binary with a dead/missing seat (gemini, or opencode with no zai key) must NOT be recommended.
48
+ const hasSeat = (tok) => {
49
+ const r = ROSTER[tok]; if (!r || !has(r.cli)) return false;
50
+ if (tok === "glm") return !!profile?.providers?.zai || opencodeKey();
51
+ return true;
52
+ };
53
+ const agents = Object.keys(ROSTER).filter(hasSeat);
28
54
  return { profile, registry, caps, agents, scrooge: has("scrooge") };
29
55
  }
30
56
 
@@ -42,8 +68,9 @@ export function scroogeModelFor(registry, caps, kind = "code", difficulty = "eas
42
68
 
43
69
  // crude per-package token forecast (input+output through the executor)
44
70
  const FORECAST = { easy: 0.3e6, medium: 1.5e6, hard: 6e6 }; // tokens
45
- // crew agent preference per difficulty: frontier subs take hard, cheap takes easy
46
- const CREW_PREF = { hard: ["codex", "gemini", "kimi", "deepseek"], medium: ["kimi", "gemini", "codex", "deepseek"], easy: ["deepseek", "kimi", "gemini", "codex"] };
71
+ // crew agent preference per difficulty: frontier subs take hard, cheap takes easy.
72
+ // (gemini retired its slot goes to glm, a strong coding-plan seat on $0 marginal quota.)
73
+ const CREW_PREF = { hard: ["codex", "glm", "kimi", "deepseek"], medium: ["kimi", "glm", "codex", "deepseek"], easy: ["deepseek", "kimi", "glm", "codex"] };
47
74
 
48
75
  export function advise(input, world = loadWorld()) {
49
76
  const { profile, registry, caps, agents, scrooge } = world;
@@ -82,9 +109,9 @@ export function advise(input, world = loadWorld()) {
82
109
  if (p.owner === "self") return { ...p, executor: "orchestrator", pool: tierOf(profile, "claude"), reason: "architect-owned (foundation/integration doctrine) — the orchestrator keeps the shared contract in its own hands" };
83
110
  if (mode === "solo") return { ...p, executor: "orchestrator", pool: tierOf(profile, "claude"), reason: "small enough to do inline" };
84
111
  const pref = CREW_PREF[p.difficulty].filter(a => agents.includes(a));
85
- const agent = pref.sort((a, b) => (used[a] || 0) - (used[b] || 0))[0] || "deepseek";
112
+ const agent = pref.sort((a, b) => (used[a] || 0) - (used[b] || 0))[0] || agents[0] || "deepseek";
86
113
  used[agent] = (used[agent] || 0) + 1;
87
- const pool = tierOf(profile, agent === "deepseek" ? "deepseek" : agent);
114
+ const pool = tierOf(profile, ROSTER[agent]?.provider || agent);
88
115
  let est = null;
89
116
  if (pool === "api") { // deepseek API etc — estimate real $ via registry
90
117
  const m = registry.models?.["deepseek-v4-flash"] || { cost_in: 0.14, cost_out: 0.28 };
@@ -120,17 +147,26 @@ export function advise(input, world = loadWorld()) {
120
147
  // creation order — the architect substitutes real ids as it creates them.)
121
148
  const selfPkgs = routing.filter(r => r.executor === "orchestrator");
122
149
  const foundationIdx = selfPkgs.length ? [1] : [];
123
- const cards = routing.map((r, i) => ({
150
+ const cards = routing.map((r, i) => {
151
+ const seat = ROSTER[r.executor];
152
+ return {
124
153
  order: i + 1, title: r.title, difficulty: r.difficulty,
125
- assignee: r.executor === "scrooge" || r.executor === "orchestrator" ? undefined : `${r.executor}:<project>`,
126
- // "auto" = resolve a LIVE model at spawn (the orchestrator runs `trantor up <agent>:<provider>
127
- // --task --difficulty`, which picks the best live model). Was `<cli>-default` a stale default.
154
+ // bus identity = the runner's session name (spec's agent part) — glm rides the `opencode`
155
+ // runner so its session/assignee is `opencode:<project>`, NOT `glm:<project>`.
156
+ assignee: r.executor === "scrooge" || r.executor === "orchestrator" ? undefined : `${seat?.session || r.executor}:<project>`,
157
+ // launch = the EXACT `trantor up` spec to spawn this seat; the orchestrator runs
158
+ // `trantor up <launch> --task <task> --difficulty <difficulty>`. Carrying it explicitly is
159
+ // what teaches the orchestrator the GLM path (`opencode:zai-coding-plan`) instead of guessing.
160
+ launch: ["scrooge", "orchestrator"].includes(r.executor) ? undefined : (seat?.launch || r.executor),
161
+ // "auto" = resolve a LIVE model at spawn (the launch spec already pins the provider; the
162
+ // runner picks the best live model for it). Was `<cli>-default` — a stale default.
128
163
  model: r.model || (["scrooge", "orchestrator"].includes(r.executor) ? undefined : "auto"),
129
164
  task: ["scrooge", "orchestrator"].includes(r.executor) ? undefined : r.kind,
130
165
  via: r.executor === "scrooge" ? "relay_scrooge" : "relay_task_add",
131
166
  deps_orders: r.executor === "orchestrator" && /integrat/i.test(r.title)
132
167
  ? routing.map((x, j) => j + 1).filter(j => j !== i + 1)
133
- : (r.executor !== "orchestrator" ? foundationIdx.filter(f => f !== i + 1) : []) }));
168
+ : (r.executor !== "orchestrator" ? foundationIdx.filter(f => f !== i + 1) : []) };
169
+ });
134
170
  return { mode, why, crew, routing, routing_table_md: table, card_args: cards, est_api_cost_usd: apiCost, quota_pools: pools, summary, orchestrator_tier: orchTier, agents_available: agents };
135
171
  }
136
172
 
package/bin/cli.mjs CHANGED
@@ -49,7 +49,7 @@ switch (cmd) {
49
49
  trantor doctor where do I stand? hub/plugin/CLIs/auth/keys/profile, with copy-paste fixes
50
50
  trantor connect (re)wire every installed AI CLI to the bus
51
51
  trantor profile declare your plans: trantor profile set claude=max codex=plus deepseek=api
52
- trantor up … spawn a crew here: trantor up codex gemini kimi deepseek:deepseek-v4-pro
52
+ trantor up … spawn a crew here: trantor up codex kimi deepseek:deepseek opencode:zai-coding-plan
53
53
  trantor down tear the crew down (kills processes, closes windows, no dialogs)
54
54
  trantor ui open the live dashboard (board + flow views)
55
55
  trantor catchup "where are we?" — the continuous board + git, with a synthesized brief
package/hooks/hooks.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
- "description": "trantor — auto-register each session + inject live roster (SessionStart); post an in-flight 'doing' card when a sub-agent is dispatched (PreToolUse); heartbeat presence on every tool call + deliver unread bus messages to a busy session mid-turn + mirror the session's TodoWrite list onto the board as cards (PostToolUse); write a handoff before compaction (PreCompact); card each sub-agent's notional API cost when it finishes (SubagentStop)",
2
+ "description": "trantor — auto-register each session + inject live roster (SessionStart); turn each substantive user prompt into the session's live 'focus' card so a regular session's own work shows IN PROGRESS (UserPromptSubmit); post an in-flight 'doing' card when a sub-agent is dispatched (PreToolUse); heartbeat presence on every tool call + deliver unread bus messages to a busy session mid-turn + mirror the session's TodoWrite list onto the board as cards (PostToolUse); write a handoff before compaction (PreCompact); card each sub-agent's notional API cost when it finishes (SubagentStop)",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  { "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart.mjs" } ] }
6
6
  ],
7
+ "UserPromptSubmit": [
8
+ { "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/prompt-focus.mjs" } ] }
9
+ ],
7
10
  "PreToolUse": [
8
11
  { "matcher": "Task|Agent", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-start.mjs" } ] }
9
12
  ],
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ // trantor UserPromptSubmit hook — turn each substantive user prompt into the session's live "focus" card,
3
+ // so a REGULAR (non-crew) Claude session's OWN work shows IN PROGRESS on the board as it happens — not only
4
+ // when it commits or dispatches a sub-agent. ONE rolling card per session (the hub re-titles it as the focus
5
+ // shifts and closes it to "done" when the session goes offline). Trivial acks ("yes", "go ahead") don't
6
+ // refocus. Fail-silent + fast: NO LLM call (the title is a heuristic clean of the prompt; Scrooge-summarized
7
+ // titles are a follow-up). Never blocks or delays the turn.
8
+ import { readFileSync, existsSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { homedir } from "node:os";
11
+ import { resolveProject, hostId } from "../lib/project.mjs";
12
+
13
+ function readStdin() {
14
+ return new Promise(res => { let d = ""; process.stdin.setEncoding("utf8");
15
+ process.stdin.on("data", c => (d += c)); process.stdin.on("end", () => res(d));
16
+ setTimeout(() => res(d), 100); });
17
+ }
18
+ function relayUrl() {
19
+ if (process.env.RELAY_URL) return process.env.RELAY_URL;
20
+ try { const c = join(homedir(), ".agent-bus", "config.json"); if (existsSync(c)) { const u = JSON.parse(readFileSync(c, "utf8")).url; if (u) return u; } } catch {}
21
+ return "http://127.0.0.1:4477";
22
+ }
23
+ // A prompt that is JUST an acknowledgement/continuation (anchored to end) — not a new focus.
24
+ const ACK = /^(y|yes|yep|yeah|ok|okay|sure|go|go ahead|continue|proceed|do it|please|thanks|thank you|ty|next|k|cool|nice|great|perfect|sounds good|👍)[\s.!]*$/i;
25
+ function titleFrom(prompt) {
26
+ let s = String(prompt || "").replace(/\s+/g, " ").trim();
27
+ // strip a leading politeness/imperative wrapper so the card reads as the WORK, not "can you please…"
28
+ s = s.replace(/^(please|can you|could you|would you|hey,?|ok,?|now,?|let's|lets|i want you to|i'd like you to|i need you to|go ahead and)\s+/i, "");
29
+ return s.slice(0, 120);
30
+ }
31
+
32
+ try {
33
+ if (process.env.TRANTOR_NO_FOCUS === "1") { process.stdout.write("{}"); process.exit(0); } // opt-out
34
+ const input = JSON.parse((await readStdin()) || "{}");
35
+ const prompt = String(input.prompt || "");
36
+ const cwd = input.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
37
+ // don't card home-dir sessions (matches sessionstart's phantom-project guard)
38
+ if (!process.env.RELAY_SESSION && !process.env.RELAY_PROJECT && cwd === homedir()) { process.stdout.write("{}"); process.exit(0); }
39
+ const trimmed = prompt.replace(/\s+/g, " ").trim();
40
+ // skip empties, tiny continuations, and pure acks — they're not a new focus
41
+ if (!trimmed || trimmed.length < 12 || ACK.test(trimmed)) { process.stdout.write("{}"); process.exit(0); }
42
+ const project = resolveProject(cwd);
43
+ const session = process.env.RELAY_SESSION
44
+ || (process.env.RELAY_AGENT ? `${process.env.RELAY_AGENT}:${project}` : `${hostId()}:${project}`);
45
+ await fetch(`${relayUrl()}/focus`, {
46
+ method: "POST", headers: { "content-type": "application/json" },
47
+ body: JSON.stringify({ session, project, title: titleFrom(trimmed), by: session }),
48
+ signal: AbortSignal.timeout(1500),
49
+ }).catch(() => {});
50
+ } catch (e) {
51
+ process.stderr.write(`[trantor] prompt-focus error: ${e?.message || e}\n`);
52
+ }
53
+ process.stdout.write("{}");
54
+ process.exit(0);
package/hub.mjs CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.42",
3
+ "version": "0.17.44",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"
@@ -10,7 +10,7 @@
10
10
  "zod": "^4.4.3"
11
11
  },
12
12
  "scripts": {
13
- "test": "node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-handoff.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-balances.mjs && node test-subagent-cost.mjs && node test-inbox.mjs && node test-inflight.mjs"
13
+ "test": "node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-handoff.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-balances.mjs && node test-subagent-cost.mjs && node test-inbox.mjs && node test-inflight.mjs && node test-focus.mjs"
14
14
  },
15
15
  "description": "The hub-world for AI agent crews — orchestrate Claude Code, Codex, Gemini, Kimi & DeepSeek as live crews with a plan-aware Advisor, a Kanban/flow command center, a testing gate, and an economics brain (Scrooge).",
16
16
  "files": [
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: crew
3
- description: Orchestrate a multi-agent build with Trantor — get an Advisor recommendation (solo/scrooge/crew/hybrid based on the user's plans and the work), fire up helper AI CLIs (Codex, Gemini, Kimi, DeepSeek) with pinned models in visible terminal windows, assign difficulty-tagged work over the bus, track it on the Kanban dashboard with a testing gate, delegate grunt to Scrooge, supervise actively, integrate, ship. Use when the user wants several AI agents building something together, says "fire up the crew/agents", "build it with trantor / with the crew", or asks to coordinate other coding CLIs on a task.
3
+ description: Orchestrate a multi-agent build with Trantor — get an Advisor recommendation (solo/scrooge/crew/hybrid based on the user's plans and the work), fire up helper AI CLIs (Codex, GLM, Kimi, DeepSeek) with pinned models in visible terminal windows, assign difficulty-tagged work over the bus, track it on the Kanban dashboard with a testing gate, delegate grunt to Scrooge, supervise actively, integrate, ship. Use when the user wants several AI agents building something together, says "fire up the crew/agents", "build it with trantor / with the crew", or asks to coordinate other coding CLIs on a task.
4
4
  ---
5
5
 
6
6
  # Trantor crew — the unified playbook (brain × body)
@@ -39,12 +39,32 @@ explicit EVENT/INTERFACE CONTRACT — cross-agent bugs come from contract drift.
39
39
  3. Open the dashboard: `trantor ui` (or `open -na "Google Chrome" --args --new-window <hub-url>`)
40
40
 
41
41
  ## Phase 2 — fire up the crew (with the Advisor's models)
42
- `trantor up codex:gpt-5.5 gemini kimi deepseek:deepseek-v4-pro`
43
- `agent:model` pins a model (omit to use that CLI's default; use what relay_advise routed).
42
+ **Each crew card from `relay_advise` carries a `launch` spec — run it VERBATIM; never invent a
43
+ CLI invocation or run an agent "in a terminal" yourself.** Spawn every seat in one call:
44
+ `trantor up <launch> <launch> … --task <kind> --difficulty <diff>`. The live roster + the
45
+ EXACT launch spec per provider (do not improvise these):
46
+
47
+ | seat | launch spec | notes |
48
+ |---|---|---|
49
+ | Codex | `codex` (or `codex:gpt-5.5` to pin) | OpenAI CLI |
50
+ | Kimi | `kimi` | Moonshot coding-plan |
51
+ | DeepSeek | `deepseek:deepseek` | runs via opencode; `deepseek` alone = CLI default |
52
+ | **GLM (Z.ai)** | **`opencode:zai-coding-plan`** | **runs via opencode, NOT a bare `glm`/`zai` terminal command.** `opencode:zai-coding-plan/glm-5.2` pins a model; `opencode:zai-coding-plan` live-selects. |
53
+
54
+ `agent:provider` live-selects the best model now; `agent:provider/model` pins one. Example:
55
+ `trantor up codex kimi deepseek:deepseek opencode:zai-coding-plan --task code --difficulty hard`.
56
+
57
+ ⚠️ **Gemini CLI is RETIRED (Google killed the free seat 2026-06-18).** The advisor no longer
58
+ offers it and you must NOT fire up `gemini` — `gemini --yolo` exits 1 and crash-loops on the
59
+ bus. Its replacement seat is **GLM via `opencode:zai-coding-plan`**. (Gemini still serves as a
60
+ Scrooge cheap-model via `GEMINI_API_KEY` — that's a separate, working path, not a crew seat.)
61
+ Only a holder of a paid Gemini enterprise key should ever `trantor up gemini`.
62
+
44
63
  (If `trantor` isn't on PATH, the same launcher is `bash <plugin-root>/bin/crew.sh up …`.)
45
64
  The launcher auto-wires configs, spawns serialized runner windows, then **VERIFIES each agent
46
65
  on the bus with one retry**. READ ITS OUTPUT: it ends "crew verified" or "✗✗ CREW INCOMPLETE"
47
- naming no-shows. **Never assign work to an unverified agent.** The bus is the truth.
66
+ naming no-shows. **Never assign work to an unverified agent.** The bus is the truth. If a seat
67
+ fails to verify, run `trantor doctor` — it shows each CLI's wired/auth state and the exact fix.
48
68
 
49
69
  ## Phase 3 — contracts over the bus
50
70
  Build the shared foundation yourself first, then `relay_send` each agent its contract