Say what a delegation lane is: a harness and a model, named separately

ff63313ff305 · AtlantisPleb · · parent ae4256df4216

Say what a delegation lane is: a harness and a model, named separately

Asked what it could delegate to, a session answered with a table that had
`opencode/x-preview-f-free` as a "fast preview / experimental" model beside
`ox-alpha` as the flagship. They are the same model. opencode's own
normalization maps `x-preview-f` to `ox-alpha`; what differs is who runs the
child and whose credential pays.

The enum caused it. It reads as a list of models and it is not one — it mixes
two independent things, and nothing said which was which. `gemini` names a
model and not the harness that runs it; `devin` names a harness and not the
model it brings; `ox-alpha` and `opencode/x-preview-f-free` name one model
twice.

`CHILD_LANES` says both for every lane, and where the model is served from, so
a caller picking between two lanes knows it is picking a harness:

- `ox-alpha` — harness: openagents, this process, one `shell` tool. Model: Ox
  Alpha, through the OpenAgents inference proxy, routed to OpenRouter
  `stealth/ox-alpha`, on this session's thread grant.
- `opencode/x-preview-f-free` — harness: opencode, a separate CLI with its own
  tools. Model: Ox Alpha, the same model, under opencode's name for it, served
  by OpenCode Zen on this machine's opencode credential.
- `gemini` — harness: opencode. Model: Gemini 3.7 Flash, also OpenCode Zen.
- `devin` — harness: the Devin CLI, its own tools, its own model, its own
  credentials. It spends nothing of this account's.

Devin's model is named as unreported rather than guessed: it picks from its own
configuration and print mode does not say which.

A test holds the part that was actually wrong — that the two Ox Alpha lanes are
described as one model differing in harness, and that the opencode lane is
never called fast or experimental.

Also fixes a test that was already red on this tip, and whose failure was worth
reading: it registered a child at `Date.now()` and completed it at epoch 4,000,
so the child finished fifty-six years before it began and its duration clamped
to zero. Both instants are fixed now, which is what made the pair a fake clock
in the first place.

719 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012TRDRrfL1khQhQtNr3SRrA
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified packages/openagents-cli/src/coder-delegate.ts
  • modified packages/openagents-cli/src/coder-tools.ts
  • modified packages/openagents-cli/test/coder-delegate-lanes.test.ts
  • modified packages/openagents-cli/test/coder-delegate.test.ts

Diff

4 files changed, +161 -8

packages/openagents-cli/src/coder-delegate.ts modified +67 -3

@@ -566,10 +566,74 @@ export const CHILD_LANE_ALIASES: Readonly<Record<string, string>> = {

566 566
/**
567 567
 * Every lane a `delegate` call may name, by the name a caller would use.
568 568
 *
569
 * Devin is here because it brings its own credentials rather than spending this
570
 * session's grant. Offered as an enum so a call chooses from what exists rather
571
 * than from what it remembers.
569
 * The enum is the names; `CHILD_LANES` is what each one is. Offered as an enum
570
 * so a call chooses from what exists rather than from what it remembers.
572 571
 */
572
/**
573
 * What each lane actually is: who runs the child, and what answers it.
574
 *
575
 * The two are independent and the enum reads as one list, which is how a
576
 * session came to describe `opencode/x-preview-f-free` as a "fast preview /
577
 * experimental" model. It is not. It is **the same model as `ox-alpha`** —
578
 * opencode's own normalization maps `x-preview-f` to `ox-alpha` — reached
579
 * through a different harness on a different credential. A caller choosing
580
 * between them is choosing who runs the child, not which model thinks.
581
 *
582
 * So each lane says both, and says who pays.
583
 */
584
export interface ChildLane {
585
  /** The name a `delegate` call passes as `model`. */
586
  readonly name: string;
587
  /** What runs the child and gives it its tools. */
588
  readonly harness: string;
589
  /** What answers. */
590
  readonly model: string;
591
  /** Where that model is served from, and whose credential pays for it. */
592
  readonly served: string;
593
  readonly bestFor: string;
594
}
595
596
export const CHILD_LANES: ReadonlyArray<ChildLane> = [
597
  {
598
    name: "ox-alpha",
599
    harness: "openagents (this process, one `shell` tool)",
600
    model: "Ox Alpha",
601
    served: "the OpenAgents inference proxy, routed to OpenRouter `stealth/ox-alpha`, on this session's thread grant",
602
    bestFor: "work whose shape is the question: design, architecture, an open-ended refactor",
603
  },
604
  {
605
    name: "opencode/x-preview-f-free",
606
    harness: "opencode (a separate CLI on this machine, its own tools)",
607
    // Named as the same model on purpose. The difference between this lane and
608
    // the one above is the harness and the credential, and a description that
609
    // implies two models sends a caller here for the wrong reason.
610
    model: "Ox Alpha — the same model as `ox-alpha`, under opencode's name for it",
611
    served: "OpenCode Zen, on this machine's opencode credential",
612
    bestFor: "the same work as `ox-alpha`, when you want opencode's harness and tools instead of ours",
613
  },
614
  {
615
    name: "gemini",
616
    harness: "opencode (a separate CLI on this machine, its own tools)",
617
    model: "Gemini 3.7 Flash",
618
    served: "OpenCode Zen, on this machine's opencode credential",
619
    bestFor: "fast, straightforward coding and analysis",
620
  },
621
  {
622
    name: "devin",
623
    harness: "devin (the Devin CLI, its own tools)",
624
    // Devin picks its own model from its own configuration and print mode does
625
    // not report which, so naming one here would be inventing it.
626
    model: "Devin's own, not reported",
627
    served: "Devin, on its own credentials — it spends nothing of this account's",
628
    bestFor: "straightforward engineering with a clear shape: a named fix, a test, a migration",
629
  },
630
];
631
632
/** One lane as a line a model can read. */
633
export const describeChildLane = (lane: ChildLane): string =>
634
  `\`${lane.name}\` — harness: ${lane.harness}; model: ${lane.model}; served by ${lane.served}. ` +
635
  `Best for ${lane.bestFor}.`;
636
573 637
export const CHILD_MODELS: ReadonlyArray<string> = [
574 638
  // Deduplicated: `ox-alpha` and `openagents` are two names for one lane, and
575 639
  // offering both in the enum would read as two choices.
packages/openagents-cli/src/coder-tools.ts modified +16 -4

@@ -27,6 +27,7 @@ import {

27 27
  renderShell,
28 28
  runShell,
29 29
} from "./coder-shell.js";
30
import { CHILD_LANES, describeChildLane } from "./coder-delegate.js";
30 31
import { catalogEntry, renderSkill, type CoderSkill } from "./coder-skills.js";
31 32
import { describePrompt, MAX_DELEGATE_COUNT } from "./coder-delegate.js";
32 33
import type { DelegationOutcome } from "./coder-delegate.js";

@@ -78,7 +79,16 @@ export function delegateTool(delegation: CoderDelegation): CoderTool {

78 79
      `${String(MAX_DELEGATE_COUNT)} children.` +
79 80
      (models.length === 0
80 81
        ? ""
81
        : ` Children run on ${delegation.label} unless \`model\` names another: ${models.join(", ")}.`),
82
        : ` Children run on ${delegation.label} unless \`model\` names another lane.\n\n` +
83
          // Named in full, because the enum alone reads as a list of models and
84
          // it is not one: two of these lanes are the same model under
85
          // different harnesses, and a session that could not tell described
86
          // one of them as a "fast preview / experimental" model it is not.
87
          "A lane is a harness and a model together. The harness runs the child and gives it " +
88
          "its tools; the model is what answers. Choosing a lane chooses both:\n" +
89
          CHILD_LANES.filter((lane) => models.includes(lane.name))
90
            .map((lane) => `- ${describeChildLane(lane)}`)
91
            .join("\n")),
82 92
    parameters: {
83 93
      type: "object",
84 94
      properties: {

@@ -105,9 +115,11 @@ export function delegateTool(delegation: CoderDelegation): CoderTool {

105 115
                type: "string",
106 116
                enum: [...models],
107 117
                description:
108
                  "Which model the children run on. Defaults to " +
109
                  `${delegation.label}. Straightforward engineering suits a fast model; ` +
110
                  "work whose shape is the question suits a stronger one.",
118
                  "Which lane the children run on — a harness and a model together, not a " +
119
                  `model on its own. Defaults to ${delegation.label}. The tool's own ` +
120
                  "description says what each lane is; two of them are the same model under " +
121
                  "different harnesses, so pick by what the work needs and whose credential " +
122
                  "should pay.",
111 123
              },
112 124
            }),
113 125
      },
packages/openagents-cli/test/coder-delegate-lanes.test.ts modified +73

@@ -2,7 +2,10 @@ import { describe, expect, it } from "vitest";

2 2
3 3
import {
4 4
  CHILD_MODELS,
5
  CHILD_LANES,
6
  type ChildLane,
5 7
  childLaneName,
8
  describeChildLane,
6 9
  resolveChildLane,
7 10
  SELF_CHILD_LANE,
8 11
  selfChildLane,

@@ -96,3 +99,73 @@ describe("choosing a lane in the tool call", () => {

96 99
    expect(output).toContain("ox-alpha");
97 100
  });
98 101
});
102
103
describe("saying what a lane is", () => {
104
  const lane = (name: string) => CHILD_LANES.find((entry) => entry.name === name);
105
106
  it("names a harness and a model for every lane a call can pick", () => {
107
    // The enum reads as a list of models and is not one. A session that could
108
    // not tell described `opencode/x-preview-f-free` as a "fast preview /
109
    // experimental" model, which it is not: it is Ox Alpha.
110
    for (const name of CHILD_MODELS) {
111
      const resolved = resolveChildLane(name);
112
      expect(resolved).toBeDefined();
113
      // `openagents` and `ox-alpha` are two names for one lane, described once.
114
      const described = lane(name) ?? lane("ox-alpha");
115
      expect(described?.harness).toBeTruthy();
116
      expect(described?.model).toBeTruthy();
117
      expect(described?.served).toBeTruthy();
118
    }
119
  });
120
121
  it("says the two Ox Alpha lanes are the same model, differing in harness", () => {
122
    const ours = lane("ox-alpha");
123
    const theirs = lane("opencode/x-preview-f-free");
124
125
    expect(ours?.model).toContain("Ox Alpha");
126
    expect(theirs?.model).toContain("Ox Alpha");
127
    expect(theirs?.model).toContain("same model");
128
129
    // What actually differs.
130
    expect(ours?.harness).toContain("openagents");
131
    expect(theirs?.harness).toContain("opencode");
132
    expect(ours?.served).toContain("OpenRouter");
133
    expect(theirs?.served).toContain("OpenCode Zen");
134
  });
135
136
  it("does not call the opencode lane fast or experimental", () => {
137
    const theirs = describeChildLane(lane("opencode/x-preview-f-free") as ChildLane);
138
    expect(theirs).not.toMatch(/experimental|preview tier|lightweight/i);
139
  });
140
141
  it("says which harness the Gemini lane runs on, since the name does not", () => {
142
    const gemini = lane("gemini");
143
    expect(gemini?.harness).toContain("opencode");
144
    expect(gemini?.model).toContain("Gemini 3.7 Flash");
145
  });
146
147
  it("says Devin is a harness that brings its own model and credential", () => {
148
    const devin = lane("devin");
149
    expect(devin?.harness).toContain("Devin CLI");
150
    expect(devin?.served).toContain("its own credentials");
151
  });
152
153
  it("puts every offered lane's description in front of the model", () => {
154
    const registry = new CoderTaskRegistry();
155
    const fleet = { submit: () => Promise.resolve({ taskId: "t", status: "completed" } as never) };
156
    const { description } = delegateTool({
157
      registry,
158
      fleet,
159
      label: "openagents (ox-alpha)",
160
      models: CHILD_MODELS,
161
      fleetFor: () => ({ fleet, label: "x" }),
162
    } as unknown as CoderDelegation);
163
164
    for (const entry of CHILD_LANES) {
165
      if (!CHILD_MODELS.includes(entry.name)) continue;
166
      expect(description).toContain(entry.harness);
167
      expect(description).toContain(entry.model);
168
    }
169
    expect(description).toContain("A lane is a harness and a model together");
170
  });
171
});
packages/openagents-cli/test/coder-delegate.test.ts modified +5 -1

@@ -316,7 +316,11 @@ describe("fleet rendering", () => {

316 316
      cwd: "/tmp",
317 317
        background: true,
318 318
      },
319
      Date.now(),
319
      // A fixed start, because the completion below is a fixed instant too and
320
      // the pair is what makes the duration deterministic. Started at
321
      // `Date.now()` and completed at epoch 4000, the child finished
322
      // fifty-six years before it began and the duration clamped to zero.
323
      1_000,
320 324
    );
321 325
  registry.start(task.id, new AbortController());
322 326
  registry.recordToolUse(task.id, { toolName: "bash", target: "pnpm test" });

This page updates live while a promote is in flight · changelog