@a-t-h-i/bot-lobby 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +150 -12
  2. package/package.json +12 -3
  3. package/prompts/backend.md +3 -1
  4. package/prompts/designer.md +24 -1
  5. package/prompts/global.md +18 -0
  6. package/prompts/master.md +122 -20
  7. package/prompts/quickfix.md +16 -3
  8. package/prompts/worker.md +7 -2
  9. package/src/agents/backend.ts +2 -2
  10. package/src/agents/designer.ts +2 -2
  11. package/src/ask/dialog.ts +11 -1
  12. package/src/classifier/triage.ts +24 -7
  13. package/src/index.ts +4 -1
  14. package/src/lobby/feed.ts +7 -2
  15. package/src/lobby/keys.ts +0 -1
  16. package/src/lobby/quickfix.ts +29 -3
  17. package/src/lobby/runtime.ts +29 -26
  18. package/src/lobby/tabs/home.ts +6 -42
  19. package/src/lobby/tabs/quickfix.ts +1 -1
  20. package/src/lobby/tabs/tasks.ts +3 -2
  21. package/src/lobby/view.ts +18 -23
  22. package/src/master/decisions.ts +10 -2
  23. package/src/pi/activity.ts +1 -33
  24. package/src/pi/commands.ts +22 -11
  25. package/src/pi/events.ts +3 -17
  26. package/src/pi/plan-checklist.ts +305 -0
  27. package/src/pi/route.ts +180 -0
  28. package/src/pi/run-summary.ts +12 -3
  29. package/src/pi/settings-ui.ts +2 -2
  30. package/src/pi/start-task.ts +51 -5
  31. package/src/pi/tools.ts +13 -7
  32. package/src/pi/ui.ts +18 -284
  33. package/src/roles/worker.ts +11 -3
  34. package/src/schemas/configuration.ts +19 -6
  35. package/src/schemas/task.ts +31 -0
  36. package/src/workflow/brief.ts +59 -0
  37. package/src/workflow/track.ts +436 -0
  38. package/src/workflow/workflow.ts +151 -10
  39. package/src/pi/expressions.ts +0 -169
  40. package/src/pi/kaomoji.ts +0 -227
  41. package/src/pi/mascot-art.ts +0 -359
  42. package/src/pi/zen-large.ts +0 -699
  43. package/src/pi/zen-metrics.ts +0 -130
  44. package/src/pi/zen.ts +0 -659
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Where a new request goes before any task exists. The classifier (Jev, or
3
+ * the rules while it is off) reads whether one agent can do it alone, right
4
+ * away. When it can, the oracle confirms with `route_request`: the lobby hands
5
+ * the request to the quick-fix agent and shows the Quick fix tab. Otherwise,
6
+ * or when the oracle says it needs the team, the task starts as before.
7
+ *
8
+ * Nothing is routed without the lobby (it runs quick fixes), when routing is
9
+ * off (`workflow.routeQuickFixes`), or for a request the user already placed
10
+ * (`--task`, `--fast`, `--full`) or a planned task.
11
+ */
12
+ import { StringEnum } from "@earendil-works/pi-ai";
13
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
14
+ import { Container, Text } from "@earendil-works/pi-tui";
15
+ import { Type } from "typebox";
16
+ import type { Domain } from "../schemas/agent.ts";
17
+ import type { TaskTriage } from "../schemas/task.ts";
18
+ import { chooseRoute, type RequestRoute } from "../workflow/track.ts";
19
+ import { triageFor } from "../classifier/instance.ts";
20
+ import { ownedTask } from "../state/persistence.ts";
21
+ import { detectProjectRoot, loadConfig } from "../state/project.ts";
22
+ import { lobbyFeed } from "../lobby/feed.ts";
23
+ import { startTask, type StartOptions } from "./start-task.ts";
24
+ import { isQuiet, isSubagentProcess } from "./quiet.ts";
25
+
26
+ export const ROUTE_TOOL = "route_request";
27
+
28
+ /**
29
+ * Hands a request to the lobby's quick-fix agent and shows the Quick fix tab;
30
+ * the job's id, or undefined without a lobby. `builder` is set for a quick
31
+ * feature: it runs on that domain's model, thinking and time limit.
32
+ */
33
+ export type QuickFixHandoff = (request: string, builder: Domain | undefined, reason: string) => string | undefined;
34
+
35
+ let handoff: QuickFixHandoff | undefined;
36
+
37
+ /** The lobby registers its quick-fix hand-off while it runs. */
38
+ export function setQuickFixHandoff(next: QuickFixHandoff | undefined): void {
39
+ handoff = next;
40
+ }
41
+
42
+ interface Pending {
43
+ request: string;
44
+ options: StartOptions;
45
+ route: RequestRoute;
46
+ at: number;
47
+ }
48
+
49
+ let pending: Pending | undefined;
50
+
51
+ /** A request the oracle has not routed in this long is forgotten: the next one starts afresh. */
52
+ const STALE_MS = 30 * 60_000;
53
+
54
+ /** The request waiting for the oracle to route it, if any. */
55
+ export function pendingRequest(now = Date.now()): { request: string; route: RequestRoute } | undefined {
56
+ if (pending && now - pending.at >= STALE_MS) pending = undefined;
57
+ return pending ? { request: pending.request, route: pending.route } : undefined;
58
+ }
59
+
60
+ /** A quick feature (bigger than a small change, but self-contained) runs with its builder's settings; a quick fix with the quick-fix agent's. */
61
+ function builderFor(route: RequestRoute): Domain | undefined {
62
+ return route.size === "medium" || route.size === "large" ? route.builder : undefined;
63
+ }
64
+
65
+ /** What the oracle is asked: one step, from the request alone. */
66
+ export function routeMessage(request: string, route: RequestRoute): string {
67
+ return [
68
+ "bot-lobby: a new request, not a task yet.",
69
+ `Request: ${request}`,
70
+ `Read: a ${builderFor(route) ? "quick feature" : "quick fix"} — ${route.reasons.join("; ")} (${route.source}).`,
71
+ "",
72
+ "Confirm where it goes with route_request, in one step and from the request alone: do not read files, plan or ask.",
73
+ "- to=quickfix: one agent does it now and the lobby shows it on the Quick fix tab. Right for anything one person can just do, even a rich piece of work in one file.",
74
+ "- to=task: the team (bot-lobby's workflow), when it needs several areas agreeing on a contract, a survey of an unfamiliar codebase, a risky change, or decisions the user must make first.",
75
+ "Then tell the user in one line where it went.",
76
+ ].join("\n");
77
+ }
78
+
79
+ export interface RequestOptions extends StartOptions {
80
+ /** `--task`: always a task. */
81
+ task?: boolean;
82
+ }
83
+
84
+ /**
85
+ * A new request from the lobby or `/bot-lobby <request>`: a task at once, or
86
+ * (when it reads as one agent's work) a question to the oracle first.
87
+ * Resolves with what happened.
88
+ */
89
+ export async function startRequest(pi: ExtensionAPI, ctx: ExtensionContext, configDir: string, request: string, options: RequestOptions = {}): Promise<"task" | "routing" | undefined> {
90
+ const { task: asTask, ...start } = options;
91
+ const config = loadConfig();
92
+ const root = detectProjectRoot(ctx.cwd, configDir);
93
+ const placed = asTask || start.track || start.approvedPlan || !config.workflow.routeQuickFixes || !handoff;
94
+ // A session that already drives a task gets startTask's own warning.
95
+ if (placed || ownedTask(root, configDir, ctx.sessionManager.getSessionId())) {
96
+ return (await startTask(pi, ctx, configDir, request, start)) ? "task" : undefined;
97
+ }
98
+ const triage = await triageFor({ cwd: ctx.cwd, root, configDir }, request);
99
+ const route = chooseRoute(request, triage, config.classifier.thresholds.quickFixAt);
100
+ const read: StartOptions = { ...start, triaged: true, ...(triage ? { triage } : {}) };
101
+ if (route.to === "task") return (await startTask(pi, ctx, configDir, request, read)) ? "task" : undefined;
102
+ pending = { request, options: read, route, at: Date.now() };
103
+ lobbyFeed.log("LOBBY", `reads as a ${builderFor(route) ? "quick feature" : "quick fix"} (${route.source}): ${route.reasons[0]} — the oracle confirms`, "info");
104
+ pi.sendUserMessage(routeMessage(request, route), ctx.isIdle() ? undefined : { deliverAs: "followUp" });
105
+ return "routing";
106
+ }
107
+
108
+ const RouteSchema = Type.Object({
109
+ to: StringEnum(["quickfix", "task"] as const, { description: "quickfix: one agent does it now, on the lobby's Quick fix tab; task: the team (bot-lobby's workflow)" }),
110
+ reason: Type.Optional(Type.String({ description: "one line: why it goes there" })),
111
+ request: Type.Optional(Type.String({ description: "the request as it should be done, when the user refined it since; their words otherwise" })),
112
+ });
113
+
114
+ /** The oracle's answer to a routing question: the quick-fix agent, or a task. */
115
+ export async function routeRequest(pi: ExtensionAPI, ctx: ExtensionContext, configDir: string, params: { to: "quickfix" | "task"; reason?: string; request?: string }): Promise<{ ok: boolean; text: string }> {
116
+ const waiting = pendingRequest();
117
+ if (!waiting || !pending) return { ok: false, text: "No request is waiting to be routed. Start a task with /bot-lobby <request>." };
118
+ const { options, route } = pending;
119
+ const request = params.request?.trim() || pending.request;
120
+ pending = undefined;
121
+ const why = params.reason?.trim();
122
+ if (params.to === "quickfix") {
123
+ const id = handoff?.(request, builderFor(route), why || route.reasons[0] || "one agent can do it alone");
124
+ if (id) {
125
+ lobbyFeed.log("LOBBY", `the oracle sent it to the quick-fix agent: ${id}${why ? ` — ${why}` : ""}`, "success");
126
+ return { ok: true, text: `${id} is running on the lobby's Quick fix tab, which is now open. Tell the user in one line that this looks like a ${builderFor(route) ? "quick feature" : "quick fix"} and the quick-fix agent is on it; nothing else to do.` };
127
+ }
128
+ // No lobby to run it any more: it becomes a task.
129
+ }
130
+ lobbyFeed.log("LOBBY", `the oracle sent it to the team${why ? ` — ${why}` : ""}`, "info");
131
+ const task = await startTask(pi, ctx, configDir, request, { ...options, routed: true });
132
+ return task
133
+ ? { ok: true, text: `Started ${task.id} as a task; its kickoff follows as the next message. Say nothing more now.` }
134
+ : { ok: false, text: "Could not start a task: this session already drives one (finish or cancel it first)." };
135
+ }
136
+
137
+ /**
138
+ * `route_request`, active only while a request waits for the oracle, so it
139
+ * never sits in the tool list of plain pi or of a running task.
140
+ */
141
+ export function registerRouteTool(pi: ExtensionAPI, configDir: string): void {
142
+ if (isSubagentProcess()) return;
143
+ pi.on("session_start", () => {
144
+ pending = undefined;
145
+ });
146
+ pi.on("before_agent_start", () => {
147
+ const active = pi.getActiveTools();
148
+ const want = Boolean(pendingRequest());
149
+ if (want === active.includes(ROUTE_TOOL)) return;
150
+ pi.setActiveTools(want ? [...active, ROUTE_TOOL] : active.filter((name) => name !== ROUTE_TOOL));
151
+ });
152
+ pi.registerTool({
153
+ name: ROUTE_TOOL,
154
+ label: "Route request",
155
+ description: "Send a new bot-lobby request to the quick-fix agent (to=quickfix: one agent does it now, shown on the lobby's Quick fix tab) or to the team (to=task: bot-lobby's workflow). Only while a request waits to be routed.",
156
+ promptSnippet: "Route a new bot-lobby request: quickfix (one agent now) or task (the team)",
157
+ parameters: RouteSchema,
158
+ renderShell: "self",
159
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
160
+ const result = await routeRequest(pi, ctx, configDir, params as { to: "quickfix" | "task"; reason?: string; request?: string });
161
+ return { content: [{ type: "text", text: result.text }], details: { ok: result.ok, to: (params as { to: string }).to } };
162
+ },
163
+ renderCall(args, theme) {
164
+ if (isQuiet()) return new Container();
165
+ const call = args as { to?: string };
166
+ return new Text(`${theme.fg("toolTitle", theme.bold("route_request"))} ${theme.fg("accent", call.to ?? "")}`, 0, 0);
167
+ },
168
+ renderResult(result, _options, theme) {
169
+ if (isQuiet()) return new Container();
170
+ const details = result.details as { ok?: boolean; to?: string } | undefined;
171
+ const icon = details?.ok ? theme.fg("success", "✓") : theme.fg("warning", "!");
172
+ return new Text(`${icon} ${theme.fg("toolTitle", theme.bold("route_request"))} ${theme.fg("muted", `→ ${details?.to === "quickfix" ? "Quick fix tab" : "a task"}`)}`, 0, 0);
173
+ },
174
+ });
175
+ }
176
+
177
+ /** Tests: forget any request waiting for the oracle. */
178
+ export function clearPendingRequest(): void {
179
+ pending = undefined;
180
+ }
@@ -6,16 +6,25 @@
6
6
  import type { AgentRun } from "../schemas/findings.ts";
7
7
  import type { RunLogEntry } from "../schemas/task.ts";
8
8
  import { shortDuration } from "../text.ts";
9
- import { SLOT_LABELS, type SlotId } from "./mascot-art.ts";
10
9
 
11
- /** The zen column a run belongs to; a researcher reports under RESEARCH from any domain. */
10
+ export type SlotId = "dev" | "design" | "research" | "qa";
11
+
12
+ /** Uppercase agent names. */
13
+ const SLOT_LABELS: Record<SlotId, string> = {
14
+ dev: "DEV",
15
+ design: "DESIGN",
16
+ research: "RESEARCH",
17
+ qa: "QA",
18
+ };
19
+
20
+ /** The agent slot a run belongs to; a researcher reports under RESEARCH from any domain. */
12
21
  export function slotOf(run: Pick<AgentRun, "domain" | "role">): SlotId {
13
22
  if (run.role === "researcher") return "research";
14
23
  if (run.domain === "backend") return "dev";
15
24
  return run.domain === "designer" ? "design" : "qa";
16
25
  }
17
26
 
18
- /** Upper-case agent name as the scene shows it (DEV, DESIGN, RESEARCH, QA). */
27
+ /** Upper-case agent name (DEV, DESIGN, RESEARCH, QA). */
19
28
  export function agentName(run: Pick<AgentRun, "domain" | "role">): string {
20
29
  return SLOT_LABELS[slotOf(run)];
21
30
  }
@@ -358,7 +358,7 @@ function entryDescription(kind: SettingsKind, view: EntryView): string {
358
358
  /** The lobby's on/off settings as the settings menu lists them; `panel:*` are the Lobby tab's panes. */
359
359
  export type LobbySwitch = "autoOpen" | "autoAsk" | "mouse" | "issues" | `panel:${LobbyPanel}`;
360
360
 
361
- const PANEL_SWITCH_LABELS: Record<LobbyPanel, string> = { animations: "Animations in the lobby", conversation: "Conversation pane", activity: "Activity log pane", thinking: "Thinking pane" };
361
+ const PANEL_SWITCH_LABELS: Record<LobbyPanel, string> = { conversation: "Conversation pane", activity: "Activity log pane", thinking: "Thinking pane" };
362
362
 
363
363
  export const LOBBY_SWITCHES: ReadonlyArray<{ id: LobbySwitch; label: string; help: string }> = [
364
364
  { id: "autoOpen", label: "Open with a task", help: "open the lobby when this session starts or resumes a task" },
@@ -368,7 +368,7 @@ export const LOBBY_SWITCHES: ReadonlyArray<{ id: LobbySwitch; label: string; hel
368
368
  ...LOBBY_PANELS.map((panel) => ({
369
369
  id: `panel:${panel}` as const,
370
370
  label: PANEL_SWITCH_LABELS[panel],
371
- help: panel === "animations" ? "the animated oracle and agents on the Lobby tab; off keeps only the task's status (they still show above pi's editor while the lobby is hidden); alt+z toggles it too" : "shown on the Lobby tab; its key in the lobby toggles it too",
371
+ help: "shown on the Lobby tab; its key in the lobby toggles it too",
372
372
  })),
373
373
  ];
374
374
 
@@ -5,7 +5,10 @@
5
5
  */
6
6
  import { existsSync } from "node:fs";
7
7
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
8
- import { createTask, taskRequest, type Task } from "../schemas/task.ts";
8
+ import { createTask, taskRequest, type Task, type TaskTrack, type TaskTriage, type TrackPath } from "../schemas/task.ts";
9
+ import type { Domain } from "../schemas/agent.ts";
10
+ import { chooseTrack, trackLine, trackSummary } from "../workflow/track.ts";
11
+ import { lobbyFeed } from "../lobby/feed.ts";
9
12
  import { createTaskDir, ensureProjectStructure, nextTaskId, ownedTask, saveTask, taskDirFor } from "../state/persistence.ts";
10
13
  import { detectProjectRoot, loadConfig } from "../state/project.ts";
11
14
  import { transition } from "../state/task-state.ts";
@@ -26,19 +29,51 @@ function uniqueTaskId(root: string, configDir: string, request: string): string
26
29
  return id;
27
30
  }
28
31
 
29
- export function kickoff(task: Task, budgetMinutes = 0): string {
32
+ /** In a kickoff after the oracle routed the request to the team (the lobby then shows the request once). */
33
+ export const ROUTED_LINE = "Routed: you sent this request to the team.";
34
+
35
+ /** The fast track's steps: straight to the agents the request needs, QA only for tests, then complete. */
36
+ function fastSteps(track: TaskTrack): string[] {
37
+ const building = track.roster.filter((member): member is Domain => member === "designer" || member === "backend");
38
+ const builders = building.length > 1
39
+ ? `one call with assignments for ${building.join(" and ")}, each task stating the contract between them`
40
+ : building.length === 1 ? `domain ${building[0]}` : track.roster.includes("qa") ? "domain qa (the change is its tests)" : "the one domain that owns the files";
41
+ const steps = [
42
+ ...(track.roster.includes("researcher") ? ["First summon the researcher for the outside facts it needs: orchestrate action=research with a domain and the question."] : []),
43
+ `Delegate now with orchestrate action=implement: ${builders}; open each task with "Step 1:" (the engine keeps the plan).`,
44
+ ...(track.roster.includes("qa") && building.length > 0 ? ["QA takes part (tests): once the change is in, give qa the tests as the last step (action=implement domain=qa), or run action=qa."] : []),
45
+ "Check `git diff --stat` and the report, then orchestrate action=complete with a one-line summary.",
46
+ ];
30
47
  return [
48
+ "Fast track: the request reads small and clear, so skip the ceremony: no scouts, no proposal, no plan document.",
49
+ ...steps.map((step, index) => `${index + 1}. ${step}`),
50
+ track.roster.includes("qa") ? "" : "No QA gate on this track: nothing here needs tests.",
51
+ "If the request is bigger, riskier or less clear than it reads, switch before delegating: orchestrate action=track track=full with a reason. Otherwise do not deliberate over the track.",
52
+ ].filter(Boolean);
53
+ }
54
+
55
+ export function kickoff(task: Task, budgetMinutes = 0, options: { fastTrack?: boolean; routed?: boolean } = {}): string {
56
+ const track = task.track;
57
+ const head = [
31
58
  `A bot-lobby task is active: ${task.id}`,
32
59
  `Title: ${task.title}`,
33
60
  `Request: ${taskRequest(task)}`,
34
61
  `State: ${task.state}`,
62
+ ...(options.routed ? [ROUTED_LINE] : []),
63
+ ...(track ? [trackSummary(track)] : []),
35
64
  ...(budgetMinutes > 0 ? [`Time budget: ${budgetMinutes} minutes of work, for you and every agent. Size the plan to fit it and divide it by scope (see Time budget in your prompt).`] : []),
36
65
  "",
66
+ ];
67
+ if (track?.path === "fast") return [...head, ...fastSteps(track)].join("\n");
68
+ const fastAllowed = Boolean(track) && options.fastTrack !== false && track?.userChoice !== "full" && track?.source !== "plan";
69
+ return [
70
+ ...head,
37
71
  "Drive it with the orchestrate tool:",
38
72
  "1. clarify if the request is genuinely ambiguous,",
39
73
  "2. scout the domains the request touches,",
40
74
  "3. synthesize the findings and propose a short `- ` bullet list for approval.",
41
75
  "Do not implement anything before the user approves the proposal.",
76
+ ...(fastAllowed ? ["If it is in fact a small, clear, low-risk change, take the fast track instead: orchestrate action=track track=fast with a reason."] : []),
42
77
  ].join("\n");
43
78
  }
44
79
 
@@ -56,6 +91,13 @@ export interface StartOptions {
56
91
  title?: string;
57
92
  /** Minutes of work time the task gets; `workflow.taskBudgetMinutes` when absent. */
58
93
  budget?: number;
94
+ /** The user's `--fast` or `--full`: the path the task takes, whatever its request reads as. */
95
+ track?: TrackPath;
96
+ /** The classifier already read the request (routing did): `triage` is its read, or absent when it was off. */
97
+ triaged?: boolean;
98
+ triage?: TaskTriage;
99
+ /** The oracle sent this request to the team after the classifier read it as a quick fix. */
100
+ routed?: boolean;
59
101
  }
60
102
 
61
103
  export async function startTask(pi: ExtensionAPI, ctx: ExtensionContext, configDir: string, request: string, options: StartOptions = {}): Promise<Task | undefined> {
@@ -72,10 +114,14 @@ export async function startTask(pi: ExtensionAPI, ctx: ExtensionContext, configD
72
114
  createTaskDir(root, configDir, task);
73
115
  transition(task, "clarifying");
74
116
  // The classifier's read of the request (when it is on) reaches the Master's very first turn.
75
- const triage = await triageFor({ cwd: ctx.cwd, root, configDir }, request);
117
+ const triage = options.triaged ? options.triage : await triageFor({ cwd: ctx.cwd, root, configDir }, request);
76
118
  if (triage) task.triage = triage;
119
+ // How serious the request reads: who takes part, and whether it takes the fast track or the full workflow.
120
+ const config = loadConfig();
121
+ task.track = chooseTrack(request, triage, { fastTrack: config.workflow.fastTrack, ...(options.track ? { forced: options.track } : {}), ...(options.approvedPlan ? { approvedPlan: true } : {}) });
122
+ lobbyFeed.log("LOBBY", trackLine(task.track), "info");
77
123
  saveTask(root, configDir, task);
78
- const budgetMinutes = options.budget ?? loadConfig().workflow.taskBudgetMinutes;
124
+ const budgetMinutes = options.budget ?? config.workflow.taskBudgetMinutes;
79
125
  if (budgetMinutes > 0) setBudget(root, configDir, task.id, budgetMinutes);
80
126
  if (options.auto) setAutoMode(root, configDir, task.id, true, sessionId);
81
127
  // A session that starts a task is named after it, so /resume and the lobby list it by name.
@@ -86,7 +132,7 @@ export async function startTask(pi: ExtensionAPI, ctx: ExtensionContext, configD
86
132
  // The oracle takes the task on with a clean context: nothing said before the kickoff is sent to its model.
87
133
  if (freshContextOn()) markContext(pi, { kind: "start", taskId: task.id, at: Date.now() });
88
134
  // A kickoff while pi is still busy (another turn) queues behind it instead of throwing.
89
- pi.sendUserMessage(kickoff(task, budgetMinutes), ctx.isIdle() ? undefined : { deliverAs: "followUp" });
135
+ pi.sendUserMessage(kickoff(task, budgetMinutes, { fastTrack: config.workflow.fastTrack, ...(options.routed ? { routed: true } : {}) }), ctx.isIdle() ? undefined : { deliverAs: "followUp" });
90
136
  return task;
91
137
  }
92
138
 
package/src/pi/tools.ts CHANGED
@@ -29,17 +29,17 @@ const OrchestrateSchema = Type.Object({
29
29
  question: Type.Optional(Type.String({ description: "clarify: question for the user" })),
30
30
  options: Type.Optional(Type.Array(Type.String(), { description: "clarify: optional answer choices; put your recommended one first and mark it (Recommended)" })),
31
31
  domains: Type.Optional(Type.Array(Type.String(), { description: "scout: any of designer, backend, qa" })),
32
- instruction: Type.Optional(Type.String({ description: "scout/research: what to investigate (for scout, also used to target-verify a claim)" })),
32
+ instruction: Type.Optional(Type.String({ description: "scout/research: a self-contained brief: the specific questions, where to look, the answer format you want (paths, names, versions, evidence) and what you will do with it. The agent may be a small model: assume nothing" })),
33
33
  proposal: Type.Optional(Type.String({ description: "propose: the user-facing proposal as a short `- ` bullet list, one line per change" })),
34
34
  concerns: Type.Optional(Type.Array(Type.String(), { description: "propose: concerns raised while challenging the request" })),
35
- plan: Type.Optional(Type.String({ description: "plan: the detailed internal plan (while implementing or reviewing, the full revised plan that replaces it)" })),
35
+ plan: Type.Optional(Type.String({ description: "plan: the detailed internal plan (while implementing or reviewing, the full revised plan that replaces it). Every step names its files, its concrete actions and its done criteria, and the contracts between domains are written out; no decision is left to the workers" })),
36
36
  domain: Type.Optional(Type.String({ description: "implement/research: designer, backend, or qa" })),
37
- task: Type.Optional(Type.String({ description: "implement: the concrete step for that domain's worker" })),
37
+ task: Type.Optional(Type.String({ description: "implement: a self-contained brief for that domain's worker (which may be a small, literal model): Goal, exact Files, numbered What to do with names/shapes/values, Contracts, Constraints, Done when (checkable criteria and commands), If stuck. Decide everything yourself; leave nothing to be assumed" })),
38
38
  assignments: Type.Optional(
39
39
  Type.Array(
40
40
  Type.Object({
41
41
  domain: Type.String({ description: "designer, backend, or qa" }),
42
- task: Type.String({ description: "the concrete step(s) for that domain's worker" }),
42
+ task: Type.String({ description: "a self-contained brief for that domain's worker: Goal, exact Files, numbered What to do, Contracts (in full), Constraints, Done when, If stuck. No assumptions" }),
43
43
  minutes: Type.Optional(Type.Number({ description: "under a time budget: minutes for this worker, by its scope" })),
44
44
  }),
45
45
  { description: "implement: run several domains in parallel (distinct domains); workers share files through the file desk" },
@@ -51,7 +51,11 @@ const OrchestrateSchema = Type.Object({
51
51
  StringEnum(["approved", "rejected"] as const, { description: "resolve_approval: approve or reject the request" }),
52
52
  ),
53
53
  note: Type.Optional(Type.String({ description: "resolve_approval: rationale, or what to do instead when rejected" })),
54
- reason: Type.Optional(Type.String({ description: "block: why the task cannot continue; budget: why the task needs more time" })),
54
+ reason: Type.Optional(Type.String({ description: "block: why the task cannot continue; budget: why the task needs more time; track: why the task takes that path or needs those members" })),
55
+ track: Type.Optional(
56
+ StringEnum(["fast", "full"] as const, { description: "track: fast (straight to the roster's agents, no scouts, proposal or plan; QA only for tests) or full (the whole workflow)" }),
57
+ ),
58
+ roster: Type.Optional(Type.Array(Type.String(), { description: "track: who takes part — any of designer, backend, qa, researcher" })),
55
59
  text: Type.Optional(Type.String({ description: "decide/complete/knowledge: the decision, completion summary, or knowledge text" })),
56
60
  file: Type.Optional(Type.String({ description: "compact: the knowledge file to rewrite, e.g. knowledge.md" })),
57
61
  kind: Type.Optional(
@@ -71,7 +75,9 @@ const DESCRIPTION = [
71
75
  "compact (replace a knowledge file with a rewritten version, archiving the old one),",
72
76
  "resolve_approval (approve or reject a request), complete (declare the task done after the gates",
73
77
  "pass), block/resume (escalate or continue), budget (under a time budget: where it stands, or ask the",
74
- "user for more minutes with a reason), status, cancel.",
78
+ "user for more minutes with a reason), track (the task's path and who takes part: show it, or correct it with",
79
+ "track=fast|full, roster and a reason), status, cancel.",
80
+ "Every instruction you give an agent is read by a possibly smaller, cheaper model that cannot infer intent: write each one as a complete, explicit brief with the goal, files, actions, contracts, constraints and done criteria, then check the report against it.",
75
81
  "The engine validates every step against the task state machine, so a rejected action means the workflow is not at that step yet.",
76
82
  ].join(" ");
77
83
 
@@ -184,7 +190,7 @@ export function registerOrchestrateTool(pi: ExtensionAPI, configDir: string, run
184
190
  name: "orchestrate",
185
191
  label: "Orchestrate",
186
192
  description: DESCRIPTION,
187
- promptSnippet: "Run a bot-lobby workflow step (clarify, scout, propose, plan, decide, status, cancel)",
193
+ promptSnippet: "Run a bot-lobby workflow step (clarify, scout, propose, plan, implement, track, decide, status, cancel)",
188
194
  promptGuidelines: [
189
195
  "Use orchestrate for every bot-lobby workflow step; it enforces the task state machine and records results.",
190
196
  ],