@oisincoveney/pipeline 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -152,22 +152,36 @@ Generate native host files from the YAML config:
152
152
  pipe install-commands --host all
153
153
  ```
154
154
 
155
- Generated resources are projections of the three config files; they are not
156
- separate sources of truth. The pipeline `orchestrator.profile` configures the
157
- host orchestrator surface, while workflow node `profile` values configure
158
- delegated agent boundaries.
159
-
160
- | Host | Generated files | Invocation |
161
- | --- | --- | --- |
162
- | Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md` | `/pipe <task>` |
163
- | Codex | `.agents/skills/pipe/SKILL.md`, `.codex/agents/*.toml` | `$pipe <task>` |
164
- | OpenCode | `.opencode/commands/pipe.md`, `.opencode/agents/*.md` | `/pipe <task>` |
165
- | Kimi | `.kimi/commands/pipe.md`, `.kimi/agents/*.md` | `/pipe <task>` |
166
- | Pi | `.pi/extensions/pipe.ts`, `.pi/prompts/pipe.md` | `/pipe <task>` |
155
+ Generated resources are derived from the three config files; they are not
156
+ separate sources of truth. Host resources use exact native agents when the node
157
+ runner matches the host. Cross-runner nodes use host-native execution only when
158
+ the host can run the requested model explicitly, such as OpenCode subagents with
159
+ a resolved `model:`; otherwise generated instructions dispatch to that runner's
160
+ CLI instead of doing instruction-only translation.
161
+
162
+ | Host | Generated files | Invocation |
163
+ | ----------- | ------------------------------------------------------ | -------------------- |
164
+ | Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md` | `/pipe <task>` |
165
+ | Codex | `.agents/skills/pipe/SKILL.md`, `.codex/agents/*.toml` | `$pipe <task>` |
166
+ | OpenCode | `.opencode/commands/pipe.md`, `.opencode/agents/*.md` | `/pipe <task>` |
167
+ | Kimi | `.kimi/skills/pipe/SKILL.md`, `.kimi/agents/*.yaml` | `/skill:pipe <task>` |
168
+ | Pi | `.pi/prompts/pipe.md`, `.pi/extensions/pipe.ts` | `/pipe <task>` |
167
169
 
168
170
  The installer is idempotent, supports `--check` and `--dry-run`, and refuses to
169
171
  overwrite manually edited files unless `--force` is supplied.
170
172
 
173
+ Runner `model` is the canonical model id. Optional
174
+ `host_models.<host>` entries are only needed when a host uses a different model
175
+ identifier:
176
+
177
+ ```yaml
178
+ runners:
179
+ kimi:
180
+ type: kimi
181
+ command: kimi
182
+ model: moonshot/kimi-k2.6
183
+ ```
184
+
171
185
  ## Runtime Guarantees
172
186
 
173
187
  - `pipe run` fails without `.pipeline/pipeline.yaml`,
@@ -189,9 +203,16 @@ External apps can import the stable config, planner, and runtime surfaces
189
203
  without deep-importing private source paths:
190
204
 
191
205
  ```ts
192
- import { loadPipelineConfig, parsePipelineConfigParts } from "@oisincoveney/pipeline/config";
206
+ import {
207
+ loadPipelineConfig,
208
+ parsePipelineConfigParts,
209
+ } from "@oisincoveney/pipeline/config";
193
210
  import { compileWorkflowPlan } from "@oisincoveney/pipeline/planner";
194
- import { runPipelineFromConfig, type PipelineRuntimeResult, type PipelineTaskContext } from "@oisincoveney/pipeline/runtime";
211
+ import {
212
+ runPipelineFromConfig,
213
+ type PipelineRuntimeResult,
214
+ type PipelineTaskContext,
215
+ } from "@oisincoveney/pipeline/runtime";
195
216
  ```
196
217
 
197
218
  ## Verification
package/dist/config.d.ts CHANGED
@@ -74,6 +74,7 @@ declare const configSchema: z.ZodObject<{
74
74
  "workspace-write": "workspace-write";
75
75
  }>;
76
76
  }, z.core.$strict>>;
77
+ host_models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
77
78
  instructions: z.ZodObject<{
78
79
  inline: z.ZodOptional<z.ZodString>;
79
80
  path: z.ZodOptional<z.ZodString>;
@@ -150,6 +151,7 @@ declare const configSchema: z.ZodObject<{
150
151
  }>>>;
151
152
  }, z.core.$strict>;
152
153
  command: z.ZodOptional<z.ZodString>;
154
+ host_models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
153
155
  model: z.ZodOptional<z.ZodString>;
154
156
  type: z.ZodEnum<{
155
157
  claude: "claude";
package/dist/config.js CHANGED
@@ -21357,6 +21357,7 @@ var runnerSchema = exports_external.object({
21357
21357
  args: exports_external.array(exports_external.string()).optional(),
21358
21358
  capabilities: runnerCapabilitiesSchema,
21359
21359
  command: exports_external.string().optional(),
21360
+ host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
21360
21361
  model: exports_external.string().optional(),
21361
21362
  type: exports_external.enum(RUNNER_TYPES)
21362
21363
  }).strict();
@@ -21422,6 +21423,7 @@ var retriesSchema = exports_external.object({
21422
21423
  var profileSchema = exports_external.object({
21423
21424
  description: exports_external.string().optional(),
21424
21425
  filesystem: filesystemSchema.optional(),
21426
+ host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
21425
21427
  instructions: instructionsSchema,
21426
21428
  mcp_servers: exports_external.array(exports_external.string()).optional(),
21427
21429
  model: exports_external.string().optional(),
package/dist/index.js CHANGED
@@ -27430,6 +27430,7 @@ var runnerSchema = exports_external.object({
27430
27430
  args: exports_external.array(exports_external.string()).optional(),
27431
27431
  capabilities: runnerCapabilitiesSchema,
27432
27432
  command: exports_external.string().optional(),
27433
+ host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
27433
27434
  model: exports_external.string().optional(),
27434
27435
  type: exports_external.enum(RUNNER_TYPES)
27435
27436
  }).strict();
@@ -27495,6 +27496,7 @@ var retriesSchema = exports_external.object({
27495
27496
  var profileSchema = exports_external.object({
27496
27497
  description: exports_external.string().optional(),
27497
27498
  filesystem: filesystemSchema.optional(),
27499
+ host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
27498
27500
  instructions: instructionsSchema,
27499
27501
  mcp_servers: exports_external.array(exports_external.string()).optional(),
27500
27502
  model: exports_external.string().optional(),
@@ -27938,8 +27940,8 @@ function validationError(issues) {
27938
27940
  // src/install-commands.ts
27939
27941
  var import_gray_matter = __toESM(require_gray_matter(), 1);
27940
27942
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
27941
- import { mkdir, writeFile } from "node:fs/promises";
27942
- import { dirname, join as join2 } from "node:path";
27943
+ import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
27944
+ import { dirname, join as join2, relative } from "node:path";
27943
27945
 
27944
27946
  // node_modules/smol-toml/dist/error.js
27945
27947
  /*!
@@ -28663,8 +28665,10 @@ function codeForIssue(message) {
28663
28665
  // src/install-commands.ts
28664
28666
  var GENERATED_MARKER = "<!-- Generated by @oisincoveney/pipeline. -->";
28665
28667
  var GENERATED_TS_MARKER = "// Generated by @oisincoveney/pipeline.";
28668
+ var GENERATED_YAML_MARKER = "# Generated by @oisincoveney/pipeline.";
28666
28669
  var OWNER_MARKER_PREFIX = "<!-- @oisincoveney/pipeline:";
28667
28670
  var OWNER_TS_MARKER_PREFIX = "// @oisincoveney/pipeline:";
28671
+ var OWNER_YAML_MARKER_PREFIX = "# @oisincoveney/pipeline:";
28668
28672
  var COMMAND_HOSTS = [
28669
28673
  "claude",
28670
28674
  "opencode",
@@ -28684,6 +28688,17 @@ function tsHeader(host) {
28684
28688
  ].join(`
28685
28689
  `);
28686
28690
  }
28691
+ function yamlHeader(host) {
28692
+ return [
28693
+ GENERATED_YAML_MARKER,
28694
+ `${OWNER_YAML_MARKER_PREFIX}host=${host}`,
28695
+ ""
28696
+ ].join(`
28697
+ `);
28698
+ }
28699
+ function hashHeader(host) {
28700
+ return yamlHeader(host);
28701
+ }
28687
28702
  function markdown(data, body) {
28688
28703
  return `${import_gray_matter.default.stringify(body.trimEnd(), data).trimEnd()}
28689
28704
  `;
@@ -28699,6 +28714,9 @@ function profileEntries(config2) {
28699
28714
  }
28700
28715
  return [...profileIds].sort((a, b) => a.localeCompare(b)).map((id) => [id, config2.profiles[id]]);
28701
28716
  }
28717
+ function nativeProfileEntries(host, config2) {
28718
+ return profileEntries(config2).filter(([_, profile]) => canRunNatively(host, config2, profile));
28719
+ }
28702
28720
  function hasAgentWorkflowNodes(config2) {
28703
28721
  return compileWorkflowPlan(config2).topologicalOrder.some((node) => node.kind === "agent");
28704
28722
  }
@@ -28726,6 +28744,7 @@ function workflowSummary(config2) {
28726
28744
  `- ${node.id}`,
28727
28745
  `kind=${node.kind}`,
28728
28746
  node.profile ? `profile=${node.profile}` : "",
28747
+ runnerForNode(config2, node.profile) ? `runner=${runnerForNode(config2, node.profile)}` : "",
28729
28748
  node.needs.length ? `needs=${node.needs.join(",")}` : "needs=none"
28730
28749
  ].filter(Boolean);
28731
28750
  return parts.join(" ");
@@ -28733,6 +28752,62 @@ function workflowSummary(config2) {
28733
28752
  ].join(`
28734
28753
  `);
28735
28754
  }
28755
+ function runnerForNode(config2, profileId) {
28756
+ return profileId ? config2.profiles[profileId]?.runner : undefined;
28757
+ }
28758
+ function resolvedHostModel(config2, host, profile) {
28759
+ const runner = config2.runners[profile.runner];
28760
+ return profile.host_models?.[host] ?? runner?.host_models?.[host] ?? profile.model ?? runner?.model;
28761
+ }
28762
+ function canRunNatively(host, config2, profile) {
28763
+ if (profile.runner === host) {
28764
+ return true;
28765
+ }
28766
+ return host === "opencode" && isModelRunner(profile.runner) && resolvedHostModel(config2, host, profile) !== undefined;
28767
+ }
28768
+ function isModelRunner(runnerId) {
28769
+ return COMMAND_HOSTS.some((host) => host === runnerId);
28770
+ }
28771
+ function runnerCliLabel(runnerId) {
28772
+ return `${runnerId} CLI`;
28773
+ }
28774
+ function dispatchSummary(host, config2) {
28775
+ const plan = compileWorkflowPlan(config2);
28776
+ const lines = plan.topologicalOrder.flatMap((node) => {
28777
+ if (!(node.kind === "agent" && node.profile)) {
28778
+ return [];
28779
+ }
28780
+ const profile = config2.profiles[node.profile];
28781
+ if (!profile) {
28782
+ return [];
28783
+ }
28784
+ return [dispatchLineForAgent(host, config2, node.id, node.profile, profile)];
28785
+ });
28786
+ return ["Node dispatch:", ...lines.length > 0 ? lines : ["- none"]].join(`
28787
+ `);
28788
+ }
28789
+ function dispatchLineForAgent(host, config2, nodeId, profileId, profile) {
28790
+ if (profile.runner === host) {
28791
+ return sameHostDispatchLine(host, nodeId, profileId, profile.runner);
28792
+ }
28793
+ if (host === "opencode" && isModelRunner(profile.runner)) {
28794
+ const model = resolvedHostModel(config2, host, profile);
28795
+ if (model) {
28796
+ return `- ${nodeId}: OpenCode native subagent profile=${profileId} model=${model} runner=${profile.runner}`;
28797
+ }
28798
+ }
28799
+ return `- ${nodeId}: ${runnerCliLabel(profile.runner)} profile=${profileId} runner=${profile.runner}`;
28800
+ }
28801
+ function sameHostDispatchLine(host, nodeId, profileId, runnerId) {
28802
+ const labels = {
28803
+ claude: `Claude native subagent subagent_type=${profileId}`,
28804
+ codex: `Codex native worker subagent profile=${profileId} agent_type=worker`,
28805
+ kimi: `Kimi native Agent subagent subagent_type=${profileId}`,
28806
+ opencode: `OpenCode native subagent subagent_type=${profileId}`,
28807
+ pi: `Pi native subagent chain profile=${profileId}`
28808
+ };
28809
+ return `- ${nodeId}: ${labels[host]} runner=${runnerId}`;
28810
+ }
28736
28811
  function entrypointSummary(config2) {
28737
28812
  const entries = Object.entries(config2.entrypoints);
28738
28813
  if (entries.length === 0) {
@@ -28772,9 +28847,36 @@ function nativeDelegationInstruction(host, config2) {
28772
28847
  return;
28773
28848
  }
28774
28849
  if (host === "opencode") {
28775
- return "Use OpenCode's task tool to delegate each agent workflow node to its configured profile. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28850
+ return "Dispatch each agent workflow node by its runner. For lines marked `OpenCode native subagent`, use OpenCode's task tool with `subagent_type` exactly equal to the configured profile id. For cross-runner nodes with an explicit resolved OpenCode model, use the generated OpenCode native subagent. For lines marked CLI, invoke that runner's CLI directly. Do not substitute a default subagent. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28851
+ }
28852
+ if (host === "codex") {
28853
+ return 'Dispatch each agent workflow node by its runner. For lines marked `Codex native worker subagent`, call `spawn_agent` with `agent_type: "worker"` and `fork_context: false`, then pass a node prompt containing the task, workflow id, node id, profile id, runner id, configured profile instructions, grants, and dependency outputs. Codex `exec` exposes native built-in agent types (`default`, `explorer`, `worker`) rather than arbitrary generated project profile ids, so do not call `spawn_agent` with the profile id as `agent_type`. Do not spawn the default agent for configured Codex nodes. For lines marked CLI, invoke that runner\'s CLI directly. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.';
28854
+ }
28855
+ if (host === "claude") {
28856
+ return "Dispatch each agent workflow node by its runner. For lines marked `Claude native subagent`, use Claude's Task tool with `subagent_type` exactly equal to the configured profile id. For lines marked CLI, invoke that runner's CLI directly. Do not substitute a default subagent. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28776
28857
  }
28777
- return "Use this host's native subagent mechanism for agent workflow nodes. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28858
+ if (host === "kimi") {
28859
+ return "Dispatch each agent workflow node by its runner. For lines marked `Kimi native Agent subagent`, use Kimi's Agent tool with `subagent_type` exactly equal to the configured profile id when the current Kimi root agent exposes that subagent type. If that native subagent type is not available, invoke Kimi CLI with the generated `.kimi/agents/<profile>.yaml` agent spec. For non-Kimi nodes, invoke that runner's CLI directly. Do not substitute a default subagent. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28860
+ }
28861
+ if (host === "pi") {
28862
+ return "Dispatch each agent workflow node by its runner. Use Pi's native subagent chain only when every agent node is a Pi runner node and the Pi subagent commands are available. If Pi native subagent commands are unavailable, invoke each configured runner through its CLI directly. For mixed-runner workflows, invoke each configured runner through its CLI directly. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28863
+ }
28864
+ return "Dispatch each agent workflow node by its runner. Use this host's native subagent mechanism only when it can actually run the requested runner/model. For cross-runner nodes that cannot be represented natively, invoke that runner's CLI directly. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28865
+ }
28866
+ function cliDispatchInstructions(config2) {
28867
+ if (!hasAgentWorkflowNodes(config2)) {
28868
+ return;
28869
+ }
28870
+ return [
28871
+ "For CLI-dispatched nodes, construct the node prompt with the task, workflow id, node id, profile id, runner id, and dependency outputs.",
28872
+ "CLI dispatch command shapes:",
28873
+ "- codex: `codex exec --json -C <repo-root> --sandbox <mode> --config 'approval_policy=\"never\"' --skip-git-repo-check <node prompt>`",
28874
+ "- kimi: `kimi --print --agent-file .kimi/agents/<profile>.yaml --work-dir <repo-root> --final-message-only --prompt <node prompt>`",
28875
+ "- opencode: `opencode run --agent <profile> --format json --dir <repo-root> <node prompt>`",
28876
+ "- claude: `claude --print -p <node prompt>`",
28877
+ "- pi: `pi --print --no-session <node prompt>`"
28878
+ ].join(`
28879
+ `);
28778
28880
  }
28779
28881
  function compactLines(lines) {
28780
28882
  return lines.filter((line) => line !== undefined);
@@ -28805,10 +28907,14 @@ function claudeDefinitions(config2) {
28805
28907
  "",
28806
28908
  workflowSummary(config2),
28807
28909
  "",
28910
+ dispatchSummary("claude", config2),
28911
+ "",
28808
28912
  orchestratorBlock(config2),
28809
28913
  "",
28810
28914
  nativeDelegationInstruction("claude", config2),
28811
28915
  "",
28916
+ cliDispatchInstructions(config2),
28917
+ "",
28812
28918
  `Delegate work only to configured profiles: ${profileNames(config2)}.`
28813
28919
  ]).join(`
28814
28920
  `)),
@@ -28816,7 +28922,7 @@ function claudeDefinitions(config2) {
28816
28922
  invocation: "/pipe <task description>",
28817
28923
  path: ".claude/commands/pipe.md"
28818
28924
  },
28819
- ...profileEntries(config2).map(([id, profile]) => ({
28925
+ ...nativeProfileEntries("claude", config2).map(([id, profile]) => ({
28820
28926
  content: markdown({
28821
28927
  description: profile.description ?? id,
28822
28928
  name: id,
@@ -28863,10 +28969,14 @@ function opencodeDefinitions(config2) {
28863
28969
  "",
28864
28970
  workflowSummary(config2),
28865
28971
  "",
28972
+ dispatchSummary("opencode", config2),
28973
+ "",
28866
28974
  orchestratorBlock(config2),
28867
28975
  "",
28868
28976
  nativeDelegationInstruction("opencode", config2),
28869
28977
  "",
28978
+ cliDispatchInstructions(config2),
28979
+ "",
28870
28980
  `Delegate work only to configured profiles: ${profileNames(config2)}.`
28871
28981
  ]).join(`
28872
28982
  `)),
@@ -28886,17 +28996,22 @@ function opencodeDefinitions(config2) {
28886
28996
  "",
28887
28997
  orchestratorBlock(config2),
28888
28998
  "",
28889
- nativeDelegationInstruction("opencode", config2)
28999
+ dispatchSummary("opencode", config2),
29000
+ "",
29001
+ nativeDelegationInstruction("opencode", config2),
29002
+ "",
29003
+ cliDispatchInstructions(config2)
28890
29004
  ]).join(`
28891
29005
  `)),
28892
29006
  host: "opencode",
28893
29007
  invocation: "/pipe <task description>",
28894
29008
  path: ".opencode/agents/pipeline-orchestrator.md"
28895
29009
  },
28896
- ...profileEntries(config2).map(([id, profile]) => ({
29010
+ ...nativeProfileEntries("opencode", config2).map(([id, profile]) => ({
28897
29011
  content: markdown({
28898
29012
  description: profile.description ?? id,
28899
29013
  mode: "subagent",
29014
+ ...opencodeModelProjection(config2, profile),
28900
29015
  permission: opencodePermission(profile)
28901
29016
  }, [
28902
29017
  header("opencode").trimEnd(),
@@ -28928,10 +29043,14 @@ function codexDefinitions(config2) {
28928
29043
  "",
28929
29044
  workflowSummary(config2),
28930
29045
  "",
29046
+ dispatchSummary("codex", config2),
29047
+ "",
28931
29048
  orchestratorBlock(config2),
28932
29049
  "",
28933
29050
  nativeDelegationInstruction("codex", config2),
28934
29051
  "",
29052
+ cliDispatchInstructions(config2),
29053
+ "",
28935
29054
  `Use separate configured profiles: ${profileNames(config2)}.`
28936
29055
  ]).join(`
28937
29056
  `)),
@@ -28939,8 +29058,8 @@ function codexDefinitions(config2) {
28939
29058
  invocation: "$pipe <task description>",
28940
29059
  path: ".agents/skills/pipe/SKILL.md"
28941
29060
  },
28942
- ...profileEntries(config2).map(([id, profile]) => ({
28943
- content: `${stringify({
29061
+ ...nativeProfileEntries("codex", config2).map(([id, profile]) => ({
29062
+ content: `${hashHeader("codex")}${stringify({
28944
29063
  description: profile.description ?? id,
28945
29064
  developer_instructions: [
28946
29065
  profile.description ?? id,
@@ -28963,39 +29082,164 @@ function kimiDefinitions(config2) {
28963
29082
  return [
28964
29083
  {
28965
29084
  content: markdown({
29085
+ name: "pipe",
28966
29086
  description: "Run the configured pipeline workflow with Kimi agents"
28967
29087
  }, compactLines([
28968
29088
  header("kimi").trimEnd(),
28969
29089
  "",
28970
29090
  workflowSummary(config2),
28971
29091
  "",
29092
+ dispatchSummary("kimi", config2),
29093
+ "",
28972
29094
  orchestratorBlock(config2),
28973
29095
  "",
28974
29096
  nativeDelegationInstruction("kimi", config2),
28975
29097
  "",
29098
+ cliDispatchInstructions(config2),
29099
+ "",
28976
29100
  `Use separate configured profiles: ${profileNames(config2)}.`
28977
29101
  ]).join(`
28978
29102
  `)),
28979
29103
  host: "kimi",
28980
- invocation: "/pipe <task description>",
28981
- path: ".kimi/commands/pipe.md"
29104
+ invocation: "/skill:pipe <task description>",
29105
+ path: ".kimi/skills/pipe/SKILL.md"
28982
29106
  },
28983
- ...profileEntries(config2).map(([id, profile]) => ({
28984
- content: markdown({ description: profile.description ?? id, name: id }, [
29107
+ ...kimiOrchestratorAgentDefinitions(config2),
29108
+ ...nativeProfileEntries("kimi", config2).flatMap(([id, profile]) => kimiAgentDefinitions(id, profile))
29109
+ ];
29110
+ }
29111
+ function opencodeModelProjection(config2, profile) {
29112
+ const model = resolvedHostModel(config2, "opencode", profile);
29113
+ return model ? { model } : {};
29114
+ }
29115
+ function kimiAgentDefinitions(id, profile) {
29116
+ const agentPath = `.kimi/agents/${id}.yaml`;
29117
+ const promptPath = `.kimi/agents/${id}.prompt.md`;
29118
+ const promptDefinitions = profile.instructions.inline ? [
29119
+ {
29120
+ content: [
28985
29121
  header("kimi").trimEnd(),
28986
29122
  "",
28987
- profile.description ?? id,
29123
+ profile.instructions.inline,
29124
+ ""
29125
+ ].join(`
29126
+ `),
29127
+ host: "kimi",
29128
+ invocation: "/skill:pipe <task description>",
29129
+ path: promptPath
29130
+ }
29131
+ ] : [];
29132
+ const systemPromptPath = profile.instructions.path ? relative(dirname(agentPath), profile.instructions.path).replaceAll("\\", "/") : `./${id}.prompt.md`;
29133
+ return [
29134
+ {
29135
+ content: [
29136
+ yamlHeader("kimi").trimEnd(),
28988
29137
  "",
28989
- "Configured grants:",
28990
- grants(profile),
29138
+ $stringify({
29139
+ version: 1,
29140
+ agent: {
29141
+ allowed_tools: kimiAllowedTools(profile),
29142
+ extend: "default",
29143
+ name: id,
29144
+ system_prompt_path: systemPromptPath
29145
+ }
29146
+ }).trimEnd(),
29147
+ ""
29148
+ ].join(`
29149
+ `),
29150
+ host: "kimi",
29151
+ invocation: "/skill:pipe <task description>",
29152
+ path: agentPath
29153
+ },
29154
+ ...promptDefinitions
29155
+ ];
29156
+ }
29157
+ function kimiOrchestratorAgentDefinitions(config2) {
29158
+ const agentPath = ".kimi/agents/pipeline-orchestrator.yaml";
29159
+ const promptPath = ".kimi/agents/pipeline-orchestrator.prompt.md";
29160
+ const nativeKimiProfiles = nativeProfileEntries("kimi", config2);
29161
+ const subagents = Object.fromEntries(nativeKimiProfiles.map(([id, profile]) => [
29162
+ id,
29163
+ {
29164
+ description: profile.description ?? id,
29165
+ path: `./${id}.yaml`
29166
+ }
29167
+ ]));
29168
+ const plan = compileWorkflowPlan(config2);
29169
+ const hasKimiNodes = plan.topologicalOrder.some((node) => {
29170
+ if (!(node.kind === "agent" && node.profile)) {
29171
+ return false;
29172
+ }
29173
+ return config2.profiles[node.profile]?.runner === "kimi";
29174
+ });
29175
+ const hasNonKimiNodes = plan.topologicalOrder.some((node) => {
29176
+ if (!(node.kind === "agent" && node.profile)) {
29177
+ return false;
29178
+ }
29179
+ return config2.profiles[node.profile]?.runner !== "kimi";
29180
+ });
29181
+ const allowedTools = [
29182
+ ...hasKimiNodes ? ["kimi_cli.tools.agent:Agent"] : [],
29183
+ ...hasNonKimiNodes ? ["kimi_cli.tools.shell:Shell"] : []
29184
+ ];
29185
+ return [
29186
+ {
29187
+ content: [
29188
+ yamlHeader("kimi").trimEnd(),
28991
29189
  "",
28992
- instructionsPointer(profile)
29190
+ $stringify({
29191
+ version: 1,
29192
+ agent: {
29193
+ allowed_tools: allowedTools,
29194
+ extend: "default",
29195
+ name: "pipeline-orchestrator",
29196
+ ...Object.keys(subagents).length > 0 ? { subagents } : {},
29197
+ system_prompt_path: "./pipeline-orchestrator.prompt.md"
29198
+ }
29199
+ }).trimEnd(),
29200
+ ""
28993
29201
  ].join(`
28994
- `)),
29202
+ `),
28995
29203
  host: "kimi",
28996
- invocation: "/pipe <task description>",
28997
- path: `.kimi/agents/${id}.md`
28998
- }))
29204
+ invocation: 'kimi --agent-file .kimi/agents/pipeline-orchestrator.yaml --work-dir <repo-root> --prompt "/skill:pipe <task description>"',
29205
+ path: agentPath
29206
+ },
29207
+ {
29208
+ content: compactLines([
29209
+ header("kimi").trimEnd(),
29210
+ "",
29211
+ workflowSummary(config2),
29212
+ "",
29213
+ dispatchSummary("kimi", config2),
29214
+ "",
29215
+ orchestratorBlock(config2),
29216
+ "",
29217
+ nativeDelegationInstruction("kimi", config2),
29218
+ "",
29219
+ cliDispatchInstructions(config2),
29220
+ "",
29221
+ "This agent file is the Kimi-native orchestrator surface. Launch Kimi with `--agent-file .kimi/agents/pipeline-orchestrator.yaml` before using `/skill:pipe` when you want Kimi runner nodes to run through Kimi's native Agent tool."
29222
+ ]).join(`
29223
+ `),
29224
+ host: "kimi",
29225
+ invocation: 'kimi --agent-file .kimi/agents/pipeline-orchestrator.yaml --work-dir <repo-root> --prompt "/skill:pipe <task description>"',
29226
+ path: promptPath
29227
+ }
29228
+ ];
29229
+ }
29230
+ function kimiAllowedTools(profile) {
29231
+ const mapped = new Map([
29232
+ ["bash", "kimi_cli.tools.shell:Shell"],
29233
+ ["edit", "kimi_cli.tools.file:StrReplaceFile"],
29234
+ ["glob", "kimi_cli.tools.file:Glob"],
29235
+ ["grep", "kimi_cli.tools.file:Grep"],
29236
+ ["list", "kimi_cli.tools.file:Glob"],
29237
+ ["read", "kimi_cli.tools.file:ReadFile"],
29238
+ ["task", "kimi_cli.tools.agent:Agent"],
29239
+ ["write", "kimi_cli.tools.file:WriteFile"]
29240
+ ]);
29241
+ return [
29242
+ ...new Set((profile.tools ?? []).flatMap((tool) => mapped.get(tool) ?? []))
28999
29243
  ];
29000
29244
  }
29001
29245
  function piDefinitions(config2) {
@@ -29009,9 +29253,13 @@ function piDefinitions(config2) {
29009
29253
  "",
29010
29254
  workflowSummary(config2),
29011
29255
  "",
29256
+ dispatchSummary("pi", config2),
29257
+ "",
29012
29258
  orchestratorBlock(config2),
29013
29259
  "",
29014
- nativeDelegationInstruction("pi", config2)
29260
+ nativeDelegationInstruction("pi", config2),
29261
+ "",
29262
+ cliDispatchInstructions(config2)
29015
29263
  ]).join(`
29016
29264
  `)),
29017
29265
  host: "pi",
@@ -29022,58 +29270,12 @@ function piDefinitions(config2) {
29022
29270
  content: [
29023
29271
  tsHeader("pi").trimEnd(),
29024
29272
  "",
29025
- "interface PiCommand {",
29026
- " name: string;",
29027
- "}",
29028
- "interface PiCommandContext {",
29029
- " sendUserMessage(message: string): Promise<void> | void;",
29030
- " ui: { notify(message: string, type?: string): void };",
29031
- "}",
29032
- "interface PiExtensionApi {",
29033
- " getCommands?(): PiCommand[];",
29034
- " registerCommand(",
29035
- " name: string,",
29036
- " command: {",
29037
- " description: string;",
29038
- " handler(args: string, ctx: PiCommandContext): Promise<void> | void;",
29039
- " }",
29040
- " ): void;",
29041
- "}",
29042
- "",
29043
- ...piOrchestratorComment(config2),
29044
- "",
29045
- ...piWorkflowNodesLiteral(config2),
29046
- "",
29047
- "function renderSubagentCommand(task: string): string {",
29048
- ' const chain = WORKFLOW_NODES.filter((node) => node.kind === "agent")',
29049
- " .map((node) => `" + "$" + "{String(node.profile)}: " + "$" + "{node.id} " + "$" + "{task}" + "`)",
29050
- ' .join(" -> ");',
29051
- [" return `/chain ", "$", "{JSON.stringify(chain)}`;"].join(""),
29052
- "}",
29053
- "",
29054
- "export default function pipelineWorkNext(pi: PiExtensionApi): void {",
29055
- ' pi.registerCommand("pipe", {',
29056
- ' description: "Run the configured pipeline with Pi subagents",',
29057
- " handler: async (args: string, ctx) => {",
29058
- ' const task = String(args ?? "").trim();',
29059
- " if (!task) {",
29060
- ' ctx.ui.notify("Usage: /pipe <task description>", "error");',
29061
- " return;",
29062
- " }",
29063
- " const commands =",
29064
- ' typeof pi.getCommands === "function" ? pi.getCommands() : [];',
29065
- " const hasPiSubagents = commands.some((command) =>",
29066
- ' ["run", "chain", "parallel", "run-chain", "subagents-doctor"].some(',
29067
- " (name) => command.name === name || command.name.startsWith(`" + "$" + "{name}:" + "`)",
29068
- " )",
29069
- " );",
29070
- " if (!hasPiSubagents) {",
29071
- ' ctx.ui.notify("Install pi-subagents before running /pipe.", "error");',
29072
- " return;",
29073
- " }",
29074
- " await ctx.sendUserMessage(renderSubagentCommand(task));",
29075
- " },",
29076
- " });",
29273
+ "// Pi exposes project prompt templates as slash commands.",
29274
+ "// The real /pipe command is .pi/prompts/pipe.md.",
29275
+ "// This generated shim intentionally registers no command so it cannot",
29276
+ "// intercept /pipe in print mode before the prompt-template expansion runs.",
29277
+ "export default function pipelineWorkNext(): void {",
29278
+ " return;",
29077
29279
  "}",
29078
29280
  ""
29079
29281
  ].join(`
@@ -29084,30 +29286,6 @@ function piDefinitions(config2) {
29084
29286
  }
29085
29287
  ];
29086
29288
  }
29087
- function piWorkflowNodesLiteral(config2) {
29088
- const plan = compileWorkflowPlan(config2);
29089
- return [
29090
- "const WORKFLOW_NODES = [",
29091
- ...plan.topologicalOrder.flatMap((node) => [
29092
- " {",
29093
- ` id: ${JSON.stringify(node.id)},`,
29094
- ` kind: ${JSON.stringify(node.kind)},`,
29095
- ` needs: ${JSON.stringify(node.needs)},`,
29096
- ` profile: ${JSON.stringify(node.profile ?? null)},`,
29097
- " },"
29098
- ]),
29099
- "] as const;"
29100
- ];
29101
- }
29102
- function piOrchestratorComment(config2) {
29103
- return compactLines([
29104
- "/*",
29105
- orchestratorBlock(config2),
29106
- "",
29107
- nativeDelegationInstruction("pi", config2),
29108
- "*/"
29109
- ]);
29110
- }
29111
29289
  function instructionsPointer(actor) {
29112
29290
  if (actor.instructions.path) {
29113
29291
  return `Instructions: ${actor.instructions.path}`;
@@ -29126,6 +29304,67 @@ function definitionsFor(host, config2) {
29126
29304
  const hosts = host === "all" ? COMMAND_HOSTS : [host];
29127
29305
  return hosts.flatMap((name) => definitions[name]());
29128
29306
  }
29307
+ function selectedHosts(host) {
29308
+ return host === "all" ? [...COMMAND_HOSTS] : [host];
29309
+ }
29310
+ var GENERATED_RESOURCE_ROOTS = {
29311
+ claude: [".claude/commands", ".claude/agents"],
29312
+ codex: [".agents/skills", ".codex/agents"],
29313
+ kimi: [".kimi/skills", ".kimi/agents", ".kimi/commands"],
29314
+ opencode: [".opencode/commands", ".opencode/agents"],
29315
+ pi: [".pi/prompts", ".pi/extensions"]
29316
+ };
29317
+ async function listFiles(root) {
29318
+ if (!existsSync2(root)) {
29319
+ return [];
29320
+ }
29321
+ const entries = await readdir(root, { withFileTypes: true });
29322
+ const files = await Promise.all(entries.map((entry) => {
29323
+ const path = join2(root, entry.name);
29324
+ if (entry.isDirectory()) {
29325
+ return listFiles(path);
29326
+ }
29327
+ return [path];
29328
+ }));
29329
+ return files.flat();
29330
+ }
29331
+ function generatedHostFor(content) {
29332
+ return COMMAND_HOSTS.find((host) => content.includes(`${OWNER_MARKER_PREFIX}host=${host} -->`) || content.includes(`${OWNER_TS_MARKER_PREFIX}host=${host}`) || content.includes(`${OWNER_YAML_MARKER_PREFIX}host=${host}`));
29333
+ }
29334
+ async function obsoleteGeneratedItems(cwd, host, wantedPaths) {
29335
+ const hosts = new Set(selectedHosts(host));
29336
+ const roots = selectedHosts(host).flatMap((selectedHost) => GENERATED_RESOURCE_ROOTS[selectedHost]);
29337
+ const files = await Promise.all(roots.map((root) => listFiles(join2(cwd, root))));
29338
+ return files.flat().flatMap((absolutePath) => {
29339
+ const content = readFileSync2(absolutePath, "utf8");
29340
+ const generatedHost = generatedHostFor(content);
29341
+ if (!(generatedHost && hosts.has(generatedHost))) {
29342
+ return [];
29343
+ }
29344
+ const path = relative(cwd, absolutePath).replaceAll("\\", "/");
29345
+ if (wantedPaths.has(path)) {
29346
+ return [];
29347
+ }
29348
+ return [
29349
+ {
29350
+ action: "delete",
29351
+ host: generatedHost,
29352
+ invocation: invocationForHost(generatedHost),
29353
+ path
29354
+ }
29355
+ ];
29356
+ }).sort((a, b) => a.path.localeCompare(b.path));
29357
+ }
29358
+ function invocationForHost(host) {
29359
+ const invocations = {
29360
+ claude: "/pipe <task description>",
29361
+ codex: "$pipe <task description>",
29362
+ kimi: "/skill:pipe <task description>",
29363
+ opencode: "/pipe <task description>",
29364
+ pi: "/pipe <task description>"
29365
+ };
29366
+ return invocations[host];
29367
+ }
29129
29368
  function actionFor(path, content, force) {
29130
29369
  if (!existsSync2(path)) {
29131
29370
  return "create";
@@ -29134,7 +29373,7 @@ function actionFor(path, content, force) {
29134
29373
  if (current === content) {
29135
29374
  return "unchanged";
29136
29375
  }
29137
- if (!(current.includes(GENERATED_MARKER) || current.includes(GENERATED_TS_MARKER) || force)) {
29376
+ if (!(current.includes(GENERATED_MARKER) || current.includes(GENERATED_TS_MARKER) || current.includes(GENERATED_YAML_MARKER) || force)) {
29138
29377
  return "conflict";
29139
29378
  }
29140
29379
  return "update";
@@ -29144,7 +29383,9 @@ async function installCommands(options2 = {}) {
29144
29383
  const host = options2.host ?? "all";
29145
29384
  const config2 = loadPipelineConfig(cwd);
29146
29385
  const items = [];
29147
- for (const definition of definitionsFor(host, config2)) {
29386
+ const definitions = definitionsFor(host, config2);
29387
+ const wantedPaths = new Set(definitions.map((definition) => definition.path));
29388
+ for (const definition of definitions) {
29148
29389
  const target = join2(cwd, definition.path);
29149
29390
  const action = actionFor(target, definition.content, Boolean(options2.force));
29150
29391
  items.push({
@@ -29162,6 +29403,13 @@ async function installCommands(options2 = {}) {
29162
29403
  await mkdir(dirname(target), { recursive: true });
29163
29404
  await writeFile(target, definition.content);
29164
29405
  }
29406
+ const obsoleteItems = await obsoleteGeneratedItems(cwd, host, wantedPaths);
29407
+ items.push(...obsoleteItems);
29408
+ if (!(options2.check || options2.dryRun)) {
29409
+ for (const item of obsoleteItems) {
29410
+ await rm(join2(cwd, item.path), { force: true });
29411
+ }
29412
+ }
29165
29413
  if (!options2.dryRun && items.some((item) => item.action === "conflict")) {
29166
29414
  throw new Error([
29167
29415
  "Refusing to overwrite manually edited command files.",
@@ -29619,7 +29867,7 @@ function hostResourceInput(host) {
29619
29867
  return [
29620
29868
  `# ${host} Resource Input`,
29621
29869
  "",
29622
- "This file is scaffolded input for host-specific resource projection.",
29870
+ "This file is scaffolded input for host-specific generated resources.",
29623
29871
  "The source of truth is `.pipeline/pipeline.yaml` plus `.pipeline/profiles.yaml` and `.pipeline/runners.yaml`; generated host resources must preserve the profiles, prompts, rules, tools, filesystem policy, network policy, and output contracts declared there.",
29624
29872
  ""
29625
29873
  ].join(`
@@ -36912,8 +37160,6 @@ function createActorLaunchPlan(config2, input, actor, runnerId) {
36912
37160
  function piArgv(prompt, config2, actor, worktreePath = process.cwd(), runner) {
36913
37161
  return [
36914
37162
  "--print",
36915
- "--mode",
36916
- "json",
36917
37163
  ...optionalModelArgs("pi", runner, actor),
36918
37164
  ...piToolArgs(actor?.tools ?? []),
36919
37165
  ...skillArgsFor("pi", config2, actor, worktreePath),
@@ -38642,7 +38888,10 @@ function createCliProgram() {
38642
38888
  console.log(formatPipelineInitResult(result));
38643
38889
  });
38644
38890
  program2.command("install-commands").description("Install generated slash-command adapters into this repository").addOption(new Option("--host <host>", "host command set to install").choices(["all", "claude", "opencode", "codex", "kimi", "pi"]).default("all").argParser(parseCommandHost)).option("--dry-run", "show planned changes without writing files").option("--check", "fail if generated command files are missing or stale").option("--force", "overwrite manually edited command files").action(async (flags) => {
38645
- const result = await installCommands(flags);
38891
+ const result = await installCommands({
38892
+ ...flags,
38893
+ cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd()
38894
+ });
38646
38895
  console.log(formatInstallCommandsResult(result));
38647
38896
  });
38648
38897
  return program2;
@@ -1,7 +1,7 @@
1
1
  export declare const COMMAND_HOSTS: readonly ["claude", "opencode", "codex", "kimi", "pi"];
2
2
  export type CommandHost = (typeof COMMAND_HOSTS)[number];
3
3
  export type CommandHostSelection = CommandHost | "all";
4
- export type InstallAction = "conflict" | "create" | "unchanged" | "update";
4
+ export type InstallAction = "conflict" | "create" | "delete" | "unchanged" | "update";
5
5
  export interface CommandInstallPlanItem {
6
6
  action: InstallAction;
7
7
  host: CommandHost;
@@ -28560,6 +28560,7 @@ var runnerSchema = exports_external.object({
28560
28560
  args: exports_external.array(exports_external.string()).optional(),
28561
28561
  capabilities: runnerCapabilitiesSchema,
28562
28562
  command: exports_external.string().optional(),
28563
+ host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
28563
28564
  model: exports_external.string().optional(),
28564
28565
  type: exports_external.enum(RUNNER_TYPES)
28565
28566
  }).strict();
@@ -28625,6 +28626,7 @@ var retriesSchema = exports_external.object({
28625
28626
  var profileSchema = exports_external.object({
28626
28627
  description: exports_external.string().optional(),
28627
28628
  filesystem: filesystemSchema.optional(),
28629
+ host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
28628
28630
  instructions: instructionsSchema,
28629
28631
  mcp_servers: exports_external.array(exports_external.string()).optional(),
28630
28632
  model: exports_external.string().optional(),
@@ -29627,8 +29629,6 @@ function createActorLaunchPlan(config2, input, actor, runnerId) {
29627
29629
  function piArgv(prompt, config2, actor, worktreePath = process.cwd(), runner) {
29628
29630
  return [
29629
29631
  "--print",
29630
- "--mode",
29631
- "json",
29632
29632
  ...optionalModelArgs("pi", runner, actor),
29633
29633
  ...piToolArgs(actor?.tools ?? []),
29634
29634
  ...skillArgsFor("pi", config2, actor, worktreePath),
package/dist/runner.js CHANGED
@@ -7381,8 +7381,6 @@ async function execaHarnessPi(prompt, contextFile, worktreePath) {
7381
7381
  ${prompt}` : prompt;
7382
7382
  const argv = [
7383
7383
  "--print",
7384
- "--mode",
7385
- "json",
7386
7384
  ...optionalModelArgs("pi"),
7387
7385
  "--no-session",
7388
7386
  effectivePrompt
@@ -7477,8 +7475,6 @@ function createActorLaunchPlan(config, input, actor, runnerId) {
7477
7475
  function piArgv(prompt, config, actor, worktreePath = process.cwd(), runner) {
7478
7476
  return [
7479
7477
  "--print",
7480
- "--mode",
7481
- "json",
7482
7478
  ...optionalModelArgs("pi", runner, actor),
7483
7479
  ...piToolArgs(actor?.tools ?? []),
7484
7480
  ...skillArgsFor("pi", config, actor, worktreePath),
@@ -189,14 +189,14 @@ timeouts, output limits, sanitized env, and explicit trust flags.
189
189
 
190
190
  ## Host Support Matrix
191
191
 
192
- | Runner | Native subagents | Rules | Skills | MCP | Outputs | Generated resources |
193
- | --- | --- | --- | --- | --- | --- | --- |
194
- | Claude | yes | yes | projected as text when declared | yes | text, JSON, schema | command plus `.claude/agents` |
195
- | Codex | yes | yes | yes | yes | text, JSON, JSONL, schema | skill plus `.codex/agents` |
196
- | OpenCode | yes | yes | projected as text when declared | yes | text, JSON, JSONL, schema | command plus `.opencode/agents` |
197
- | Kimi | yes | yes | projected as text when declared | no | text, JSON | command plus `.kimi/agents` |
198
- | Pi | yes, with pi-subagents | yes | projected as text when declared | no | text, JSON | extension plus prompt |
199
- | command | no | no | no | no | declared by runner | subprocess argv |
192
+ | Runner | Native subagents | Rules | Skills | MCP | Outputs | Generated resources |
193
+ | -------- | ---------------------- | ----- | --------------------------------------------- | --- | ------------------------- | -------------------------------- |
194
+ | Claude | yes | yes | included in generated profile text | yes | text, JSON, schema | command plus `.claude/agents` |
195
+ | Codex | yes | yes | yes | yes | text, JSON, JSONL, schema | skill plus `.codex/agents` |
196
+ | OpenCode | yes | yes | included in generated profile text | yes | text, JSON, JSONL, schema | command plus `.opencode/agents` |
197
+ | Kimi | yes | yes | surfaced through project skills when declared | no | text, JSON | skill plus `.kimi/agents/*.yaml` |
198
+ | Pi | yes, with pi-subagents | yes | included in generated prompt text | no | text, JSON | prompt plus no-op extension shim |
199
+ | command | no | no | no | no | declared by runner | subprocess argv |
200
200
 
201
201
  The runtime prefers native subagents when the runner advertises
202
202
  `native_subagents: true` and the configured permissions, runner, output, and
@@ -204,6 +204,15 @@ resource grants can be represented safely. Otherwise it uses a subprocess for
204
204
  the agent node. In both cases each agent node records a separate invocation
205
205
  boundary; multi-agent workflows are never collapsed into one prompt.
206
206
 
207
+ Generated host resources follow a native-first, runner-correct rule. Same-host
208
+ agent nodes use exact native subagents. Cross-runner nodes use host-native
209
+ execution only when the host can explicitly run the requested model; OpenCode
210
+ does this through per-agent `model:` values resolved from profile or runner
211
+ `model`, with optional `host_models.opencode` overrides when model ids differ.
212
+ If native execution cannot represent the requested runner/model, generated
213
+ instructions dispatch to that runner's CLI instead of doing instruction-only
214
+ translation.
215
+
207
216
  ## Troubleshooting
208
217
 
209
218
  - Missing config: run `pipe init`; `pipe run` requires
@@ -212,8 +221,9 @@ boundary; multi-agent workflows are never collapsed into one prompt.
212
221
  - Capability error: reduce the profile grants or choose a runner whose declared
213
222
  capabilities include the requested tools, filesystem, network, output, rules,
214
223
  skills, or MCP access.
215
- - Pi native execution error: install and enable `pi-subagents`; generated Pi
216
- resources check for its commands before sending a chain.
224
+ - Pi native execution unavailable: install and enable `pi-subagents` if you want
225
+ Pi-native chains. Otherwise `/pipe` uses the CLI dispatch instructions in the
226
+ generated Pi prompt.
217
227
  - Gate failure: inspect `pipe run` output for node, gate, reason, and evidence.
218
228
  Dependent nodes are not executed after a required gate fails.
219
229
  - Schema failure: ensure the agent emits valid JSON and that `schema_path`
@@ -1,8 +1,9 @@
1
1
  # Host Resource Adapter Contract
2
2
 
3
- Generated host resources are projections of `.pipeline/runners.yaml`,
4
- `.pipeline/profiles.yaml`, and `.pipeline/pipeline.yaml`. They do not shell out
5
- to a legacy command and do not maintain independent profile definitions.
3
+ Generated host resources are derived from `.pipeline/runners.yaml`,
4
+ `.pipeline/profiles.yaml`, and `.pipeline/pipeline.yaml`. They do not maintain
5
+ independent profile definitions or silently translate one runner into another
6
+ host's default agent.
6
7
 
7
8
  Install or check generated resources with:
8
9
 
@@ -13,23 +14,30 @@ pipe install-commands --host all --check
13
14
 
14
15
  ## Host Mappings
15
16
 
16
- | Host | Generated resources | Invocation | Mechanical path |
17
- | --- | --- | --- | --- |
18
- | Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md` | `/pipe <task>` | Project command delegates to configured Claude agents. |
19
- | Codex | `.agents/skills/pipe/SKILL.md`, `.codex/agents/*.toml` | `$pipe <task>` | Skill instructs Codex to use generated Codex agents. |
20
- | OpenCode | `.opencode/commands/pipe.md`, `.opencode/agents/*.md` | `/pipe <task>` | Project command runs a primary orchestrator and configured subagents. |
21
- | Kimi | `.kimi/commands/pipe.md`, `.kimi/agents/*.md` | `/pipe <task>` | Project command and agent specs mirror YAML grants. |
22
- | Pi | `.pi/extensions/pipe.ts`, `.pi/prompts/pipe.md` | `/pipe <task>` | Extension requires `pi-subagents` before sending a chain. |
17
+ | Host | Generated resources | Invocation | Mechanical path |
18
+ | ----------- | ------------------------------------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------ |
19
+ | Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md` | `/pipe <task>` | Project command delegates to configured Claude agents. |
20
+ | Codex | `.agents/skills/pipe/SKILL.md`, `.codex/agents/*.toml` | `$pipe <task>` | Skill instructs Codex to use generated Codex agents for Codex runner nodes. |
21
+ | OpenCode | `.opencode/commands/pipe.md`, `.opencode/agents/*.md` | `/pipe <task>` | Project command runs a primary orchestrator and native subagents when the requested model is resolved. |
22
+ | Kimi | `.kimi/skills/pipe/SKILL.md`, `.kimi/agents/*.yaml` | `/skill:pipe <task>` | Kimi discovers project skills as `/skill:<name>` commands; Kimi agents are generated as YAML specs. |
23
+ | Pi | `.pi/prompts/pipe.md`, `.pi/extensions/pipe.ts` | `/pipe <task>` | Pi discovers project prompt templates as slash commands; the generated extension is a no-op shim. |
23
24
 
24
25
  ## Projection Rules
25
26
 
26
27
  - Profile names, descriptions, instructions, tools, rules, skills, MCP servers,
27
28
  filesystem mode, network mode, and output contracts are read from YAML.
29
+ - Exact native dispatch is used when a node runner matches the host.
30
+ - OpenCode can run mixed native subagents when the node runner has a resolved
31
+ model from `profile.host_models.opencode`, `runner.host_models.opencode`,
32
+ `profile.model`, or `runner.model`.
33
+ - Cross-runner nodes that cannot be represented natively are dispatched through
34
+ that runner's CLI. Instruction-only translation is not runner-correct and is
35
+ not used as an implicit fallback.
28
36
  - Host-specific formats can omit unsupported capabilities, but they must not
29
37
  grant broader access than requested.
30
38
  - Regeneration is idempotent for generated files. Manual edits are protected
31
39
  unless `--force` is supplied.
32
40
 
33
- The CLI runtime and host projections share the same workflow plan. Multi-agent
41
+ The CLI runtime and generated host resources share the same workflow plan. Multi-agent
34
42
  workflows require separate agent boundaries; host resources must not collapse
35
43
  the workflow into a single prompt.
package/package.json CHANGED
@@ -73,7 +73,7 @@
73
73
  "prepack": "bun run build:cli"
74
74
  },
75
75
  "type": "module",
76
- "version": "1.5.1",
76
+ "version": "1.5.2",
77
77
  "description": "",
78
78
  "main": "index.js",
79
79
  "keywords": [],