@oisincoveney/pipeline 2.2.0 → 2.3.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 (38) hide show
  1. package/.agents/skills/orchestrate/SKILL.md +80 -0
  2. package/defaults/profiles.yaml +5 -2
  3. package/dist/argo-submit.d.ts +0 -1
  4. package/dist/argo-submit.js +1 -4
  5. package/dist/argo-workflow.d.ts +1 -2
  6. package/dist/argo-workflow.js +0 -2
  7. package/dist/cli/program.js +2 -3
  8. package/dist/cli/submit-options.js +1 -2
  9. package/dist/cluster-doctor.js +0 -12
  10. package/dist/commands/pipeline-command.js +1 -1
  11. package/dist/config/schemas.d.ts +4 -4
  12. package/dist/install-commands/opencode.js +17 -7
  13. package/dist/moka-global-config.d.ts +0 -1
  14. package/dist/moka-global-config.js +0 -1
  15. package/dist/moka-submit.d.ts +1 -4
  16. package/dist/moka-submit.js +1 -4
  17. package/dist/pipeline-runtime.d.ts +9 -0
  18. package/dist/planned-node.js +2 -5
  19. package/dist/{workflow-planner.d.ts → planning/compile.d.ts} +2 -2
  20. package/dist/{workflow-planner.js → planning/compile.js} +6 -83
  21. package/dist/{schedule/planner.d.ts → planning/generate.d.ts} +17 -3
  22. package/dist/{schedule/planner.js → planning/generate.js} +24 -56
  23. package/dist/planning/graph.js +138 -0
  24. package/dist/runner-command/lifecycle-context.js +2 -3
  25. package/dist/runner-command/run.js +2 -3
  26. package/dist/runner.d.ts +27 -0
  27. package/dist/runtime/context/context.js +1 -1
  28. package/dist/runtime/contracts/contracts.d.ts +16 -1
  29. package/dist/schedule/passes/coverage.js +7 -51
  30. package/dist/schedule/passes/ids.js +3 -23
  31. package/dist/schedule/scheduling-roles.js +19 -0
  32. package/dist/strings.js +30 -1
  33. package/docs/config-architecture.md +32 -0
  34. package/docs/operator-guide.md +2 -3
  35. package/docs/pipeline-console-runner-contract.md +3 -4
  36. package/package.json +5 -5
  37. package/dist/schedule-planner.d.ts +0 -2
  38. package/dist/schedule-planner.js +0 -2
@@ -0,0 +1,80 @@
1
+ ---
2
+ description: Local multi-agent orchestrator — decompose a task and fan it out to the MoKa specialist roster on the current machine instead of submitting to the remote Moka pipeline. The local twin of the MoKa Orchestrator. On Claude Code, dispatch each agent with `opencode run`; on OpenCode, spawn native Task subagents. Use when the user wants parallel specialist agents driven locally rather than as Argo/k8s jobs.
3
+ name: orchestrate
4
+ ---
5
+
6
+ # Orchestrate
7
+
8
+ The **local twin of the MoKa Orchestrator**. The MoKa Orchestrator decomposes a task and submits a schedule to Argo/k8s via `moka submit`, where the runtime executes it as DAG jobs on the cluster. Orchestrate runs the **same roster, same loop, on the current machine** — no schedule, no `moka submit`, no Argo. It is the hands-on, here-and-now path for getting work done through specialist agents.
9
+
10
+ Use this skill when the user wants real work driven through **parallel specialist agents locally**: a task large enough to decompose into research / test / implement / verify lanes, where you stay the controller and the agents do the labor.
11
+
12
+ ## When NOT to use
13
+
14
+ - **Durable, reproducible, or cluster-scale runs** → use [[quick]] or [[execute]] (these submit through `moka submit`). Orchestrate is ephemeral and local; it leaves no schedule artifact.
15
+ - **Trivial single-threaded work** → just do it inline. Spawning agents for a one-line change is pure overhead.
16
+ - **You need package gates enforced as part of a pipeline run** → that is the remote path's job. Orchestrate still *uses* the gate agents (Verifier, Acceptance Reviewer) but does not replace pipeline-level gating.
17
+
18
+ ## The roster
19
+
20
+ The same specialist agents the MoKa pipeline uses, mirrored locally. Each is `mode: all`, so it works both as an `opencode run --agent` subprocess and as a native Task subagent.
21
+
22
+ | Role | Agent name | Writes | Job |
23
+ |-------------|-------------------------|---------------|-----|
24
+ | Research | `MoKa Researcher` | `research.json` only | Read-only; map the codebase, gather context, extract acceptance criteria. |
25
+ | Test | `MoKa Test Writer` | `*.test.ts` only | Write failing tests that describe the desired behaviour. |
26
+ | Implement | `MoKa Code Writer` | `src/**` only | Smallest production change that makes the failing tests pass. |
27
+ | Verify | `MoKa Verifier` | nothing | Run checks, judge diff against AC, emit `PASS`/`FAIL` with evidence. |
28
+ | Review | `MoKa Acceptance Reviewer` | nothing | Acceptance/quality gate before declaring done. |
29
+ | Inspect | `MoKa Inspector` | nothing | Read-only repository inspection / explanation. |
30
+
31
+ Keep each agent inside its lane — never ask the Code Writer to touch tests, or the Verifier to write files. The lane boundaries are what make fan-out safe.
32
+
33
+ ## Dispatch by host
34
+
35
+ The orchestration **doctrine below is identical on every host**. Only the spawn mechanism differs — select the branch for the host you are actually running in.
36
+
37
+ ### On Claude Code → `opencode run`
38
+
39
+ Spawn each roster member as a headless OpenCode subprocess and read its JSON back:
40
+
41
+ ```sh
42
+ opencode run --agent "MoKa Code Writer" --format json \
43
+ "<scoped task + acceptance criteria + paths to read>"
44
+ ```
45
+
46
+ - Select the roster member with `--agent "<exact name>"` (names from the table above).
47
+ - Use `--format json` so the agent's structured result comes back machine-readable; parse it, do not eyeball it.
48
+ - **Parallelize independent lanes**: launch each `opencode run` as a background Bash process (one tool call per lane in the same turn), then collect. Run dependent lanes only after their inputs land.
49
+ - Pass context by path, not by paste — agents read the worktree directly. Hand them the files/AC the Researcher produced.
50
+ - `--model` / `--variant` only when a lane genuinely needs a different tier; otherwise inherit.
51
+
52
+ ### On OpenCode → native Task subagents
53
+
54
+ You are already inside OpenCode — do not shell out to `opencode run`. Spawn the roster directly with the native **Task** tool, selecting the agent by the same name:
55
+
56
+ - `task` → `MoKa Researcher`, `MoKa Test Writer`, `MoKa Code Writer`, `MoKa Verifier`, `MoKa Acceptance Reviewer`.
57
+ - Issue independent Task calls together so they run concurrently; sequence dependent ones.
58
+ - Each subagent's structured output returns to you as the controller — gather, do not re-do their work.
59
+
60
+ ## The loop
61
+
62
+ Whichever host you are on, run the same five steps:
63
+
64
+ 1. **Plan** — Decompose the task into a DAG of agent lanes. Model parallelism *structurally*: independent lanes fan out together, dependents wait on their inputs (research → tests → implementation → verify). Do not invent JSON-pointer fanout; nest the work as real lanes.
65
+ 2. **Dispatch** — Fan out per the host branch above. Scope each agent tightly: one job, its lane's write boundary, explicit acceptance criteria.
66
+ 3. **Gather** — Collect each agent's structured output (`research.json`, the Verifier's verdict JSON, etc.). Treat the returned artifact as the source of truth.
67
+ 4. **Gate** — Run `MoKa Verifier`, then `MoKa Acceptance Reviewer`. Do **not** accept work on a `FAIL`. Loop the relevant lane (re-dispatch Code Writer with the failure evidence) rather than papering over it.
68
+ 5. **Synthesize** — Report only the evidence the agents actually returned: what passed, what the diff is, what the verifier proved. Never fabricate or assume an outcome an agent did not report.
69
+
70
+ ## Rules
71
+
72
+ - **Doctrine is host-neutral; only the Dispatch section is host-specific.** Do not leak `opencode run` syntax into an OpenCode run or Task-tool talk into a Claude run.
73
+ - **You are the controller, not a worker.** Decompose, dispatch, gate, synthesize. Let the specialists do the labor inside their lanes.
74
+ - **Evidence only.** Report what agents returned. A green claim needs a Verifier `PASS` with evidence behind it — see [[verify]].
75
+ - **Respect lane write boundaries.** Researcher/Verifier/Reviewer write nothing; Test Writer touches only tests; Code Writer touches only `src/`. Mixed lanes corrupt parallel fan-out.
76
+ - **Local, not durable.** If the user needs a reproducible cluster run or a schedule artifact, route to [[quick]] / [[execute]] instead.
77
+
78
+ ## The short version
79
+
80
+ Orchestrate is `moka submit` brought home: same roster, same decompose → dispatch → gather → gate → synthesize loop, run on this machine. On Claude Code each agent is an `opencode run --agent` subprocess; on OpenCode each is a native Task subagent. You stay the orchestrator — fan out the lanes, gate on real verifier evidence, and report only what the agents proved.
@@ -30,6 +30,9 @@ mcp_gateway:
30
30
  workspace_path_source: PIPELINE_TARGET_PATH
31
31
  tool_prefixes: [backlog]
32
32
  skills:
33
+ orchestrate:
34
+ path: .agents/skills/orchestrate/SKILL.md
35
+ source_root: package
33
36
  execute:
34
37
  path: .agents/skills/execute/SKILL.md
35
38
  source_root: package
@@ -85,8 +88,8 @@ profiles:
85
88
  moka-orchestrator:
86
89
  runner: opencode
87
90
  description: Orchestrate the configured pipeline and enforce gates.
88
- instructions: { inline: "Orchestrate the configured pipeline through package-defined entrypoints, native agents, and gates. Do not bypass configured runner subprocesses or package-configured gates." }
89
- skills: [execute, quick, inspect]
91
+ instructions: { inline: "Orchestrate the configured pipeline locally. Load the `orchestrate` skill and spawn the roster as native Task subagents on this machine. Do not submit to Argo or run `moka submit`. Enforce only package-configured gates." }
92
+ skills: [orchestrate, execute, quick, inspect]
90
93
  mcp_servers: [pipeline-gateway]
91
94
  tools: [read, list, grep, glob, bash]
92
95
  filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
@@ -22,7 +22,6 @@ declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
22
22
  namespace: z.ZodString;
23
23
  opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
24
24
  payloadJson: z.ZodString;
25
- queueName: z.ZodOptional<z.ZodString>;
26
25
  scheduleYaml: z.ZodString;
27
26
  serviceAccountName: z.ZodOptional<z.ZodString>;
28
27
  }, z.core.$strict>;
@@ -2,9 +2,8 @@ import { ArgoGraphCompilerError, compileArgoExecutionGraph } from "./argo-graph.
2
2
  import { buildRunnerTaskDescriptor } from "./runner-command/task-descriptor.js";
3
3
  import { buildRunnerArgoWorkflowManifest, runnerArgoWorkflowManifestSchema } from "./argo-workflow.js";
4
4
  import { normalizeRunnerRepositoryForSubmit } from "./git-remote-url.js";
5
+ import { compileScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
5
6
  import { parseRunnerCommandPayload, runnerCommandPayloadSchema } from "./runner-command-contract.js";
6
- import { compileScheduleArtifact, parseScheduleArtifact } from "./schedule/planner.js";
7
- import "./schedule-planner.js";
8
7
  import { workflowSubmitResultSchema } from "./workflow-submit-contract.js";
9
8
  import { stringify } from "yaml";
10
9
  import { z } from "zod";
@@ -40,7 +39,6 @@ const submitRunnerArgoWorkflowOptionsSchema = z.object({
40
39
  namespace: z.string().min(1),
41
40
  opencodeAuthSecretName: z.string().min(1).optional(),
42
41
  payloadJson: z.string().min(1),
43
- queueName: z.string().min(1).optional(),
44
42
  scheduleYaml: z.string().min(1),
45
43
  serviceAccountName: z.string().min(1).optional()
46
44
  }).strict().refine((options) => options.name !== void 0 || options.generateName !== void 0, { message: "Argo submit options must declare name or generateName" });
@@ -89,7 +87,6 @@ async function submitRunnerArgoWorkflow(rawOptions, dependencies = {}) {
89
87
  opencodeAuthSecretName: options.opencodeAuthSecretName,
90
88
  payloadConfigMapName,
91
89
  plan: compiled.plan,
92
- queueName: options.queueName,
93
90
  scheduleConfigMapName: scheduleArtifactConfigMapName,
94
91
  serviceAccountName: options.serviceAccountName,
95
92
  taskDescriptorConfigMapName
@@ -1,4 +1,4 @@
1
- import { WorkflowExecutionPlan } from "./workflow-planner.js";
1
+ import { WorkflowExecutionPlan } from "./planning/compile.js";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/argo-workflow.d.ts
@@ -143,7 +143,6 @@ declare const buildRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
143
143
  }, z.core.$strict>>;
144
144
  payloadConfigMapKey: z.ZodDefault<z.ZodString>;
145
145
  payloadConfigMapName: z.ZodString;
146
- queueName: z.ZodOptional<z.ZodString>;
147
146
  resources: z.ZodOptional<z.ZodObject<{
148
147
  limits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
149
148
  requests: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -153,7 +153,6 @@ const buildRunnerArgoWorkflowOptionsSchema = z.object({
153
153
  }).strict().optional(),
154
154
  payloadConfigMapKey: z.string().min(1).default("payload.json"),
155
155
  payloadConfigMapName: kubernetesNameSchema,
156
- queueName: kubernetesNameSchema.optional(),
157
156
  resources: argoWorkflowResourceRequirementsSchema.optional(),
158
157
  scheduleConfigMapKey: z.string().min(1).default("schedule.yaml"),
159
158
  scheduleConfigMapName: kubernetesNameSchema,
@@ -191,7 +190,6 @@ function buildRunnerArgoWorkflowManifest(rawOptions) {
191
190
  ...options.activeDeadlineSeconds ? { activeDeadlineSeconds: options.activeDeadlineSeconds } : {},
192
191
  entrypoint: RUNNER_WORKFLOW_ENTRYPOINT,
193
192
  ...options.imagePullSecretName ? { imagePullSecrets: [{ name: options.imagePullSecretName }] } : {},
194
- ...options.queueName ? { podMetadata: { labels: { "kueue.x-k8s.io/queue-name": options.queueName } } } : {},
195
193
  onExit: "pipeline-finalizer",
196
194
  serviceAccountName: options.serviceAccountName,
197
195
  templates: [
@@ -2,9 +2,8 @@ import { PipelineConfigError } from "../config/schemas.js";
2
2
  import { loadPipelineConfig } from "../config/load.js";
3
3
  import "../config.js";
4
4
  import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "../runner.js";
5
- import { compileWorkflowPlan } from "../workflow-planner.js";
6
- import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "../schedule/planner.js";
7
- import "../schedule-planner.js";
5
+ import { compileWorkflowPlan } from "../planning/compile.js";
6
+ import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "../planning/generate.js";
8
7
  import { loadMokaGlobalConfig } from "../moka-global-config.js";
9
8
  import { defaultClusterDoctorNamespace, runClusterDoctor } from "../cluster-doctor.js";
10
9
  import { formatCodexAuthSyncResult, syncLocalCodexAuth } from "../codex-auth-sync.js";
@@ -48,7 +48,6 @@ function mokaCommonSubmitOptions(input) {
48
48
  name: input.flags.name,
49
49
  namespace: input.flags.namespace ?? momokaya?.kubernetes.namespace,
50
50
  opencodeAuthSecretName: momokaya?.submit.opencodeAuthSecretName,
51
- queueName: input.flags.queueName ?? momokaya?.submit.queueName,
52
51
  serviceAccountName: input.flags.serviceAccount ?? momokaya?.submit.serviceAccountName,
53
52
  worktreePath: input.cwd
54
53
  };
@@ -77,7 +76,7 @@ function submitMokaGraphInput(input, flags, commonOptions) {
77
76
  function addRunnerArgoOptions(command, options = {}) {
78
77
  command.option("--name <name>", "Workflow metadata.name").option("--generate-name <prefix>", "Workflow metadata.generateName").option("--namespace <namespace>", "Workflow namespace");
79
78
  if (options.kubeconfig) command.option("--kubeconfig <path>", "kubeconfig path");
80
- return command.option("--queue-name <name>", "Kueue LocalQueue label for Workflow pods").option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").addOption(new Option("--image-pull-policy <policy>", "runner image pull policy").choices([
79
+ return command.option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").addOption(new Option("--image-pull-policy <policy>", "runner image pull policy").choices([
81
80
  "Always",
82
81
  "IfNotPresent",
83
82
  "Never"
@@ -10,7 +10,6 @@ const DEFAULT_RESOURCES = {
10
10
  githubAuthSecretName: "oisin-bot-github-auth",
11
11
  imagePullSecretName: "ghcr-pull-secret",
12
12
  opencodeAuthSecretName: "opencode-auth-1",
13
- queueName: "pipeline-runner",
14
13
  serviceAccountName: "pipeline-runner"
15
14
  };
16
15
  const FORBIDDEN_RE = /forbidden/i;
@@ -32,7 +31,6 @@ async function runClusterDoctor(options = {}) {
32
31
  verb: "create",
33
32
  ...kubectlOptions
34
33
  }),
35
- checkLocalQueue(namespace, resources.queueName, kubectlOptions),
36
34
  checkClusterResource("argo-workflow-crd", [
37
35
  "get",
38
36
  "crd",
@@ -63,7 +61,6 @@ function clusterResources() {
63
61
  githubAuthSecretName: configured.githubAuthSecretName,
64
62
  imagePullSecretName: configured.imagePullSecretName,
65
63
  opencodeAuthSecretName: configured.opencodeAuthSecretName,
66
- queueName: configured.queueName,
67
64
  serviceAccountName: configured.serviceAccountName
68
65
  } : DEFAULT_RESOURCES;
69
66
  }
@@ -169,15 +166,6 @@ async function checkWorkflowSubmitPermission(namespace, options) {
169
166
  passed: false
170
167
  };
171
168
  }
172
- function checkLocalQueue(namespace, queueName, kubectlOptions) {
173
- return checkNamespacedResource(`localqueue/${queueName}`, [
174
- "get",
175
- "localqueue",
176
- queueName,
177
- "-n",
178
- namespace
179
- ], `Kueue LocalQueue ${queueName} missing in ${namespace}; runner Workflow pods cannot be admitted to the expected queue.`, kubectlOptions);
180
- }
181
169
  async function checkClusterResource(name, args, kubectlOptions) {
182
170
  const result = await kubectl(args, kubectlOptions);
183
171
  return result.ok ? {
@@ -17,7 +17,7 @@ function registerConfiguredEntrypointCommands(program, config, runEntrypoint) {
17
17
  for (const [id, entrypoint] of Object.entries(config.entrypoints)) {
18
18
  if (reservedCommands.has(id)) continue;
19
19
  const command = program.command(id).description(entrypoint.description ?? `Run the ${id} workflow`).argument("<description...>", "task description");
20
- if ("schedule" in entrypoint) command.option("--namespace <namespace>", "Workflow namespace").option("--schedule <path>", "approved schedule YAML to submit").option("--kubeconfig <path>", "kubeconfig path").option("--queue-name <name>", "Kueue LocalQueue label for Workflow pods").option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").option("--image-pull-policy <policy>", "runner image pull policy").option("--image-pull-secret <name>", "imagePullSecret name").option("--event-url <url>", "runner event sink URL");
20
+ if ("schedule" in entrypoint) command.option("--namespace <namespace>", "Workflow namespace").option("--schedule <path>", "approved schedule YAML to submit").option("--kubeconfig <path>", "kubeconfig path").option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").option("--image-pull-policy <policy>", "runner image pull policy").option("--image-pull-secret <name>", "imagePullSecret name").option("--event-url <url>", "runner event sink URL");
21
21
  command.action(async (descriptionParts, flags) => {
22
22
  await runEntrypoint(id, descriptionParts.join(" "), flags);
23
23
  });
@@ -220,8 +220,8 @@ declare const configSchema: z.ZodObject<{
220
220
  policy: z.ZodOptional<z.ZodObject<{
221
221
  commands: z.ZodOptional<z.ZodEnum<{
222
222
  allow: "allow";
223
- "trusted-only": "trusted-only";
224
223
  deny: "deny";
224
+ "trusted-only": "trusted-only";
225
225
  }>>;
226
226
  modules: z.ZodOptional<z.ZodEnum<{
227
227
  allow: "allow";
@@ -245,8 +245,8 @@ declare const configSchema: z.ZodObject<{
245
245
  }, z.core.$strict>>>;
246
246
  default_profile: z.ZodOptional<z.ZodString>;
247
247
  mode: z.ZodEnum<{
248
- hosted: "hosted";
249
248
  local: "local";
249
+ hosted: "hosted";
250
250
  }>;
251
251
  provider: z.ZodLiteral<"toolhive">;
252
252
  authorization_env: z.ZodDefault<z.ZodString>;
@@ -289,10 +289,10 @@ declare const configSchema: z.ZodObject<{
289
289
  }, z.core.$strict>>;
290
290
  output: z.ZodOptional<z.ZodObject<{
291
291
  format: z.ZodEnum<{
292
+ json_schema: "json_schema";
292
293
  text: "text";
293
294
  json: "json";
294
295
  jsonl: "jsonl";
295
- json_schema: "json_schema";
296
296
  }>;
297
297
  repair: z.ZodOptional<z.ZodObject<{
298
298
  enabled: z.ZodOptional<z.ZodBoolean>;
@@ -361,10 +361,10 @@ declare const configSchema: z.ZodObject<{
361
361
  disabled: "disabled";
362
362
  }>>>;
363
363
  output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
364
+ json_schema: "json_schema";
364
365
  text: "text";
365
366
  json: "json";
366
367
  jsonl: "jsonl";
367
- json_schema: "json_schema";
368
368
  }>>>;
369
369
  rules: z.ZodOptional<z.ZodBoolean>;
370
370
  skills: z.ZodOptional<z.ZodBoolean>;
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST } from "../config/defaults.js";
2
2
  import { resolvePackageAssetPath } from "../package-assets.js";
3
3
  import "../config.js";
4
- import { compileWorkflowPlan } from "../workflow-planner.js";
4
+ import { compileWorkflowPlan } from "../planning/compile.js";
5
5
  import { mergeOpenCodeProjectConfig } from "../opencode-project-config.js";
6
6
  import { renderOpenCodeGatewayConfig } from "../mcp/gateway.js";
7
7
  import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
@@ -149,10 +149,19 @@ function scheduledEntrypointK8sNote(entrypoint) {
149
149
  if ("workflow" in entrypoint) return;
150
150
  return "Submit Momokaya work as Argo Workflows through `moka submit` and `moka submit --quick`.";
151
151
  }
152
- function orchestratorEntrypointDispatchBlock(host, config) {
153
- const scheduledEntrypoints = entrypointEntries(config).filter(([, entrypoint]) => !("workflow" in entrypoint));
154
- if (scheduledEntrypoints.length === 0) return dispatchBlock(host, config);
155
- return scheduledEntrypoints.map(([id, entrypoint]) => entrypointDispatchBlock(host, config, id, entrypoint)).filter((block) => Boolean(block)).join("\n\n");
152
+ function localRosterAgentIds(config) {
153
+ return nativeProfileEntries("opencode", config).map(([id]) => nativeAgentIdForHost("opencode", id));
154
+ }
155
+ function localOrchestratorDispatchBlock(config) {
156
+ return [
157
+ "Orchestrate locally. Load and follow the `orchestrate` skill.",
158
+ "Do not submit to Argo or run `moka submit`. Spawn the roster as native Task subagents on this machine and run nodes with satisfied dependencies in parallel.",
159
+ "",
160
+ "Roster (Task tool subagent_type):",
161
+ ...localRosterAgentIds(config).map((id) => `- ${id}`),
162
+ "",
163
+ "Gather each subagent's structured output, enforce only package-configured gates, and report only the evidence the subagents returned."
164
+ ].join("\n");
156
165
  }
157
166
  function nativeDispatchBlock(host, routes) {
158
167
  if (routes.length === 0) return;
@@ -325,13 +334,13 @@ function opencodeDefinitions(config, cwd) {
325
334
  description: "Orchestrate the configured pipeline and enforce gates.",
326
335
  mode: "primary",
327
336
  name: OPENCODE_ORCHESTRATOR_AGENT_ID,
328
- permission: opencodePermission(orchestrator, { allowedTaskAgents: agentDispatchRoutes("opencode", config).filter((route) => route.kind !== "cli").map((route) => route.nativeAgentId).filter((id) => Boolean(id)) })
337
+ permission: opencodePermission(orchestrator, { allowedTaskAgents: localRosterAgentIds(config) })
329
338
  }, compactLines([
330
339
  header("opencode").trimEnd(),
331
340
  "",
332
341
  orchestratorBlock(config),
333
342
  "",
334
- orchestratorEntrypointDispatchBlock("opencode", config)
343
+ localOrchestratorDispatchBlock(config)
335
344
  ]).join("\n")),
336
345
  host: "opencode",
337
346
  invocation: invocationForHost("opencode"),
@@ -382,6 +391,7 @@ function projectAgentsMdDefinition(cwd, host) {
382
391
  "- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
383
392
  "- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
384
393
  "- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
394
+ "- When the user needs to run a command, copy the command into the clipboard and tell the user what needs to be returned.",
385
395
  "",
386
396
  "## Pipeline Memory",
387
397
  "",
@@ -16,7 +16,6 @@ declare const mokaGlobalConfigSchema: z.ZodObject<{
16
16
  githubAuthSecretName: z.ZodString;
17
17
  imagePullSecretName: z.ZodString;
18
18
  opencodeAuthSecretName: z.ZodString;
19
- queueName: z.ZodString;
20
19
  serviceAccountName: z.ZodString;
21
20
  }, z.core.$strict>;
22
21
  }, z.core.$strict>;
@@ -15,7 +15,6 @@ const mokaSubmitGlobalConfigSchema = z.object({
15
15
  githubAuthSecretName: z.string().min(1),
16
16
  imagePullSecretName: z.string().min(1),
17
17
  opencodeAuthSecretName: z.string().min(1),
18
- queueName: z.string().min(1),
19
18
  serviceAccountName: z.string().min(1)
20
19
  }).strict();
21
20
  const mokaKubernetesGlobalConfigSchema = z.object({
@@ -1,5 +1,5 @@
1
1
  import { PipelineConfig } from "./config/schemas.js";
2
- import { generateScheduleArtifact } from "./schedule/planner.js";
2
+ import { generateScheduleArtifact } from "./planning/generate.js";
3
3
  import { z } from "zod";
4
4
 
5
5
  //#region src/moka-submit.d.ts
@@ -148,7 +148,6 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
148
148
  name: z.ZodOptional<z.ZodString>;
149
149
  namespace: z.ZodOptional<z.ZodString>;
150
150
  opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
151
- queueName: z.ZodOptional<z.ZodString>;
152
151
  repository: z.ZodOptional<z.ZodObject<{
153
152
  baseBranch: z.ZodString;
154
153
  sha: z.ZodOptional<z.ZodString>;
@@ -261,7 +260,6 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
261
260
  name: z.ZodOptional<z.ZodString>;
262
261
  namespace: z.ZodOptional<z.ZodString>;
263
262
  opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
264
- queueName: z.ZodOptional<z.ZodString>;
265
263
  repository: z.ZodOptional<z.ZodObject<{
266
264
  baseBranch: z.ZodString;
267
265
  sha: z.ZodOptional<z.ZodString>;
@@ -327,7 +325,6 @@ interface MokaWorkflowSubmitOptions {
327
325
  namespace: string;
328
326
  opencodeAuthSecretName?: string;
329
327
  payloadJson: string;
330
- queueName?: string;
331
328
  scheduleYaml: string;
332
329
  serviceAccountName?: string;
333
330
  }
@@ -1,7 +1,6 @@
1
1
  import { normalizeRunnerRepositoryForSubmit } from "./git-remote-url.js";
2
+ import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
2
3
  import { buildRunnerCommandPayload, runnerDeliverySchema, runnerHookPolicySchema, runnerRepositoryContextSchema, runnerRunIdentitySchema, runnerTaskSchema } from "./runner-command-contract.js";
3
- import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./schedule/planner.js";
4
- import "./schedule-planner.js";
5
4
  import { workflowSubmitResultSchema } from "./workflow-submit-contract.js";
6
5
  import { buildCommandScheduleYaml, submitRunnerArgoWorkflow } from "./argo-submit.js";
7
6
  import { z } from "zod";
@@ -79,7 +78,6 @@ const mokaSubmitBaseOptionsSchema = z.object({
79
78
  name: z.string().min(1).optional(),
80
79
  namespace: z.string().min(1).optional(),
81
80
  opencodeAuthSecretName: z.string().min(1).optional(),
82
- queueName: z.string().min(1).optional(),
83
81
  repository: runnerRepositoryContextSchema.optional(),
84
82
  run: runnerRunIdentitySchema.optional(),
85
83
  serviceAccountName: z.string().min(1).optional()
@@ -293,7 +291,6 @@ function workflowSubmitOptions(options) {
293
291
  name: options.name,
294
292
  namespace: requireSubmitOption(options.namespace, "namespace"),
295
293
  opencodeAuthSecretName: options.opencodeAuthSecretName,
296
- queueName: options.queueName,
297
294
  serviceAccountName: options.serviceAccountName
298
295
  };
299
296
  }
@@ -1,6 +1,15 @@
1
1
  import { PipelineConfigError } from "./config/schemas.js";
2
2
  import { AcceptanceCriterion, HookRuntimePolicy, NodeExecutionState, NodeStatus, PipelineRuntimeEvent, PipelineRuntimeObservabilityLevel, PipelineRuntimeOptions, PipelineRuntimeResult, PipelineTaskContext, RuntimeFailure, RuntimeGateResult, RuntimeNodeResult, RuntimeStructuredOutput } from "./runtime/contracts/contracts.js";
3
3
  //#region src/pipeline-runtime.d.ts
4
+ /**
5
+ * Top layer of the runtime-options stack (PIPE-74 B3). Extends
6
+ * {@link PipelineRuntimeOptions} for the schedule-driven path that runs a
7
+ * SINGLE workflow node (`nodeId`) in isolation, supplying that node's upstream
8
+ * `dependencyOutputs`. Full stack:
9
+ * RunnerExecutionOptions (src/runner.ts)
10
+ * < PipelineRuntimeOptions (src/runtime/contracts/contracts.ts)
11
+ * < ScheduledWorkflowTaskRuntimeOptions (this type)
12
+ */
4
13
  interface ScheduledWorkflowTaskRuntimeOptions extends PipelineRuntimeOptions {
5
14
  dependencyOutputs?: Map<string, string> | Record<string, string>;
6
15
  nodeId: string;
@@ -1,10 +1,7 @@
1
+ import { findNode } from "./planning/graph.js";
1
2
  //#region src/planned-node.ts
2
3
  function findPlannedNode(nodes, nodeId) {
3
- for (const node of nodes) {
4
- if (node.id === nodeId) return node;
5
- const child = findPlannedNode(node.children ?? [], nodeId);
6
- if (child) return child;
7
- }
4
+ return findNode(nodes, nodeId, (node) => node.children);
8
5
  }
9
6
  //#endregion
10
7
  export { findPlannedNode };
@@ -1,7 +1,7 @@
1
- import { PipelineConfig, WorkflowNodeKind } from "./config/schemas.js";
1
+ import { PipelineConfig, WorkflowNodeKind } from "../config/schemas.js";
2
2
  import { Graph } from "@dagrejs/graphlib";
3
3
 
4
- //#region src/workflow-planner.d.ts
4
+ //#region src/planning/compile.d.ts
5
5
  type WorkflowPlannerErrorCode = "WORKFLOW_CYCLE" | "WORKFLOW_DUPLICATE_NODE" | "WORKFLOW_GROUP_REFERENCE" | "WORKFLOW_MISSING_DEPENDENCY" | "WORKFLOW_MISSING_WORKFLOW";
6
6
  interface WorkflowPlannerIssue {
7
7
  message: string;
@@ -1,6 +1,7 @@
1
- import { uniqueStrings } from "./strings.js";
1
+ import { uniqueStrings } from "../strings.js";
2
+ import { findDependencyCycles } from "./graph.js";
2
3
  import { Graph } from "@dagrejs/graphlib";
3
- //#region src/workflow-planner.ts
4
+ //#region src/planning/compile.ts
4
5
  var WorkflowPlannerError = class extends Error {
5
6
  code;
6
7
  issues;
@@ -54,7 +55,7 @@ function validateNodeGraph(workflowId, nodes) {
54
55
  ...groupIssues(workflowId, nodes, nodeIds),
55
56
  ...dependencyIssues(workflowId, nodes, nodeIds)
56
57
  ];
57
- if (duplicateIssues.length === 0) return [...issues, ...cycleIssues(workflowId, nodes, nodeIds)];
58
+ if (duplicateIssues.length === 0) return [...issues, ...cycleIssues(workflowId, nodes)];
58
59
  return issues;
59
60
  }
60
61
  function duplicateNodeIssues(workflowId, nodes) {
@@ -100,92 +101,14 @@ function groupChildIssues(workflowId, node, nodeIds) {
100
101
  function isGroupNode(node) {
101
102
  return node.kind === "group";
102
103
  }
103
- function cycleIssues(workflowId, nodes, nodeIds) {
104
- return dependencyCycles(nodes, nodeIds).map((cycle) => {
104
+ function cycleIssues(workflowId, nodes) {
105
+ return findDependencyCycles(nodes).map((cycle) => {
105
106
  return {
106
107
  path: `workflows.${workflowId}.nodes.${cycle[0] ?? "nodes"}.needs`,
107
108
  message: `workflow '${workflowId}' contains dependency cycle: ${cycle.join(" -> ")}`
108
109
  };
109
110
  });
110
111
  }
111
- function dependencyCycles(nodes, nodeIds) {
112
- const dependentsByNeed = dependentsByNeedMap(nodes, nodeIds);
113
- const state = /* @__PURE__ */ new Map();
114
- const path = [];
115
- const pathIndex = /* @__PURE__ */ new Map();
116
- const cycles = [];
117
- const cycleKeys = /* @__PURE__ */ new Set();
118
- for (const node of nodes) {
119
- if (state.has(node.id)) continue;
120
- visitForCycles(node.id, {
121
- cycleKeys,
122
- cycles,
123
- dependentsByNeed,
124
- path,
125
- pathIndex,
126
- state
127
- });
128
- }
129
- return cycles;
130
- }
131
- function visitForCycles(startId, visitState) {
132
- const frames = [{
133
- index: 0,
134
- nodeId: startId
135
- }];
136
- markVisiting(startId, visitState);
137
- while (frames.length > 0) {
138
- const frame = frames.at(-1);
139
- if (!frame) return;
140
- const dependentId = (visitState.dependentsByNeed.get(frame.nodeId) ?? [])[frame.index];
141
- if (!dependentId) {
142
- markDone(frame.nodeId, visitState);
143
- frames.pop();
144
- continue;
145
- }
146
- frame.index += 1;
147
- const dependentState = visitState.state.get(dependentId);
148
- if (dependentState === "visiting") {
149
- recordCycle(dependentId, visitState);
150
- continue;
151
- }
152
- if (dependentState === "done") continue;
153
- markVisiting(dependentId, visitState);
154
- frames.push({
155
- index: 0,
156
- nodeId: dependentId
157
- });
158
- }
159
- }
160
- function markVisiting(nodeId, visitState) {
161
- visitState.state.set(nodeId, "visiting");
162
- visitState.pathIndex.set(nodeId, visitState.path.length);
163
- visitState.path.push(nodeId);
164
- }
165
- function markDone(nodeId, visitState) {
166
- visitState.state.set(nodeId, "done");
167
- visitState.pathIndex.delete(nodeId);
168
- visitState.path.pop();
169
- }
170
- function recordCycle(nodeId, visitState) {
171
- const startIndex = visitState.pathIndex.get(nodeId);
172
- if (startIndex === void 0) return;
173
- const cycle = visitState.path.slice(startIndex);
174
- const key = [...cycle].sort().join("\0");
175
- if (visitState.cycleKeys.has(key)) return;
176
- visitState.cycleKeys.add(key);
177
- visitState.cycles.push(cycle);
178
- }
179
- function dependentsByNeedMap(nodes, nodeIds) {
180
- const dependentsByNeed = /* @__PURE__ */ new Map();
181
- for (const node of nodes) for (const need of uniqueStrings(node.needs ?? [])) {
182
- if (!nodeIds.has(need)) continue;
183
- const dependents = dependentsByNeed.get(need) ?? [];
184
- dependents.push(node.id);
185
- dependentsByNeed.set(need, dependents);
186
- }
187
- return dependentsByNeed;
188
- }
189
112
  function topologicalOrderForPlan(graph) {
190
113
  const visited = /* @__PURE__ */ new Set();
191
114
  const inStack = /* @__PURE__ */ new Set();
@@ -1,9 +1,9 @@
1
1
  import { PipelineConfig } from "../config/schemas.js";
2
- import { WorkflowExecutionPlan } from "../workflow-planner.js";
2
+ import { WorkflowExecutionPlan } from "./compile.js";
3
3
  import { AgentResult, RunnerExecutionOptions, RunnerLaunchPlan } from "../runner.js";
4
4
  import { z } from "zod";
5
5
 
6
- //#region src/schedule/planner.d.ts
6
+ //#region src/planning/generate.d.ts
7
7
  declare const scheduleArtifactSchema: z.ZodObject<{
8
8
  generated_at: z.ZodString;
9
9
  kind: z.ZodLiteral<"pipeline-schedule">;
@@ -1575,6 +1575,20 @@ interface GenerateScheduleResult {
1575
1575
  artifact: ScheduleArtifact;
1576
1576
  path: string;
1577
1577
  }
1578
+ interface BacklogWorkUnit {
1579
+ acceptance_criteria: Array<{
1580
+ id: string;
1581
+ text: string;
1582
+ }>;
1583
+ dependencies?: string[];
1584
+ description?: string;
1585
+ id: string;
1586
+ title?: string;
1587
+ }
1588
+ interface SchedulePlanningContext {
1589
+ parentWorkUnits: BacklogWorkUnit[];
1590
+ workUnits: BacklogWorkUnit[];
1591
+ }
1578
1592
  declare function parseScheduleArtifact(source: string, sourcePath?: string): ScheduleArtifact;
1579
1593
  declare class ScheduleArtifactError extends Error {
1580
1594
  constructor(message: string);
@@ -1583,4 +1597,4 @@ declare function compileScheduleArtifact(config: PipelineConfig, artifact: Sched
1583
1597
  declare function generateScheduleArtifact(options: GenerateScheduleOptions): Promise<GenerateScheduleResult>;
1584
1598
  declare function scheduleArtifactPath(worktreePath: string, scheduleId: string): string;
1585
1599
  //#endregion
1586
- export { CompiledScheduleArtifact, GenerateScheduleOptions, GenerateScheduleResult, ScheduleArtifact, ScheduleArtifactError, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, scheduleArtifactPath };
1600
+ export { BacklogWorkUnit, CompiledScheduleArtifact, GenerateScheduleOptions, GenerateScheduleResult, ScheduleArtifact, ScheduleArtifactError, SchedulePlanningContext, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, scheduleArtifactPath };