@tangle-network/agent-interface 2.11.0 → 2.12.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.
package/README.md CHANGED
@@ -191,8 +191,11 @@ const worker = withProfileKb({
191
191
  ```
192
192
 
193
193
  Guidance goes into `appendSystemPrompt` where the harness owns an additive system-prompt control, and into `instructions` otherwise.
194
- Recomposing replaces earlier guidance, so a second call, or a call with an executor's harness or model override, yields a stable profile.
194
+ Recomposing replaces the guidance `withProfileKb` wrote earlier, so a second call, or a call with an executor's harness or model override, yields a stable profile.
195
+ The knowledge base owns the block sources `harness`, `model`, and `learning` (`PROFILE_KB_SOURCES`).
195
196
  `composeAgentProfileGuidance` is the underlying composer for other knowledge layers.
197
+ Give each layer its own source name and pass it as `replaceSources`: the composition then replaces only those sources and keeps every other block in place. Without `replaceSources`, it replaces every block.
198
+ The block marker is reserved for composers: a hand-written `<profile-guidance source="model" ...>` block counts as a knowledge-base block and is replaced.
196
199
 
197
200
  The module also exports the data (`profileKbHarnesses`, `profileKbModels`), operator notes for launching each harness and model, platform learnings (admitted only after an agent-eval check reproduced them), and `profileKbDiscrepancies`, which records where a vendor source states a requested name differently.
198
201
 
@@ -539,15 +539,28 @@ export interface AgentProfileGuidanceBlock {
539
539
  * system-prompt control.
540
540
  */
541
541
  export type AgentProfileGuidanceChannel = "appendSystemPrompt" | "instructions";
542
+ /** Options for {@link composeAgentProfileGuidance}. */
543
+ export interface AgentProfileGuidanceOptions {
544
+ /**
545
+ * The block sources this composition owns. Existing blocks from these
546
+ * sources are removed before the new blocks are composed; blocks from any
547
+ * other source stay where they are. When omitted, every previously composed
548
+ * block is removed, as in 2.11. Pass it to keep other layers in place.
549
+ */
550
+ replaceSources?: readonly string[];
551
+ }
542
552
  /**
543
553
  * Compose harness, model, and other layered guidance into a profile's prompt.
544
554
  *
545
555
  * The blocks come first, in the order given, and the profile's own text comes
546
556
  * last, so the most specific instruction (the profile's) is the one a model
547
- * reads after the general guidance. Composition replaces any blocks a previous
557
+ * reads after the general guidance. Composition replaces the blocks a previous
548
558
  * composition added, on both channels, so recomposing after a harness or
549
- * model change never stacks stale guidance. Composing the same blocks twice
550
- * yields the same profile, and with it the same canonical identity.
559
+ * model change never stacks stale guidance. By default it replaces every
560
+ * block; with `options.replaceSources` it replaces only those sources and
561
+ * keeps the others, such as a team's own layer, in place. Composing the same
562
+ * blocks twice yields the same profile, and with it the same canonical
563
+ * identity.
551
564
  */
552
- export declare function composeAgentProfileGuidance(profile: AgentProfile, blocks: readonly AgentProfileGuidanceBlock[], channel: AgentProfileGuidanceChannel): AgentProfile;
565
+ export declare function composeAgentProfileGuidance(profile: AgentProfile, blocks: readonly AgentProfileGuidanceBlock[], channel: AgentProfileGuidanceChannel, options?: AgentProfileGuidanceOptions): AgentProfile;
553
566
  export {};
@@ -173,38 +173,121 @@ export function mergeAgentProfiles(base, overlay) {
173
173
  extensions: mergeRecord(base?.extensions, overlay?.extensions),
174
174
  });
175
175
  }
176
- const GUIDANCE_OPEN = /^<profile-guidance source="[^"]*" id="[^"]*">\n/;
177
- const GUIDANCE_BLOCK = /<profile-guidance source="[^"]*" id="[^"]*">\n[\s\S]*?\n<\/profile-guidance>(\n\n)?/g;
176
+ const GUIDANCE_LINE = /^<profile-guidance source="([^"]*)" id="[^"]*">\n(?:(?!<\/profile-guidance>)[\s\S])*\n<\/profile-guidance>$/;
177
+ // A block runs from an opening marker to the first closing marker after it,
178
+ // which is how every released composer wrote blocks (2.11 text could carry an
179
+ // opener but never a closer). When a match holds a second opener and the
180
+ // markers balance from its start, the match may be an outer block cut short by
181
+ // an inner one. That extent is ambiguous, so the balanced extent is kept whole:
182
+ // recomposition may leave a stale block in such caller text, but it never
183
+ // truncates caller text. When the markers never balance, the match is a 2.11
184
+ // block whose text carried an opener, and it is replaced.
185
+ const GUIDANCE_OPEN_MARKER = "<profile-guidance ";
186
+ const GUIDANCE_CLOSE_MARKER = "</profile-guidance>";
187
+ const GUIDANCE_BLOCK = /<profile-guidance source="([^"]*)" id="[^"]*">\n[\s\S]*?\n<\/profile-guidance>(\n\n)?/g;
178
188
  function renderGuidanceBlock(block) {
179
189
  if (/["\n]/.test(block.source) || /["\n]/.test(block.id)) {
180
190
  throw new TypeError("profile guidance source and id must not contain quotes or newlines");
181
191
  }
182
- if (block.text.includes("</profile-guidance>")) {
183
- throw new TypeError("profile guidance text must not contain the closing block marker");
192
+ // Only a closer can end a block early. An opener in the text is the 2.11
193
+ // input contract, and stripGuidanceText keeps such a block whole.
194
+ if (block.text.includes(GUIDANCE_CLOSE_MARKER)) {
195
+ throw new TypeError("profile guidance text must not contain a closing block marker");
184
196
  }
185
197
  return `<profile-guidance source="${block.source}" id="${block.id}">\n${block.text}\n</profile-guidance>`;
186
198
  }
187
- /** Remove every previously composed guidance block from appended prompt text. */
188
- function stripGuidanceText(text) {
199
+ /** True when the block's body, after its opening tag line, holds an opener. */
200
+ function nestsOpener(block) {
201
+ return block.includes(GUIDANCE_OPEN_MARKER, block.indexOf("\n"));
202
+ }
203
+ /**
204
+ * The end of the block that opens at `offset`, counting nested markers, or -1
205
+ * when the markers never balance (a 2.11 block whose text carried an opener).
206
+ */
207
+ function balancedBlockEnd(text, offset) {
208
+ // As in GUIDANCE_BLOCK, a closer counts only at the start of a line, and an
209
+ // opener's attributes (one line, since source and id hold no newline) are
210
+ // skipped, so marker text inside them cannot end a block.
211
+ const closer = `\n${GUIDANCE_CLOSE_MARKER}`;
212
+ let depth = 0;
213
+ let at = offset;
214
+ for (;;) {
215
+ const open = text.indexOf(GUIDANCE_OPEN_MARKER, at);
216
+ const close = text.indexOf(closer, at);
217
+ if (close === -1)
218
+ return -1;
219
+ if (open !== -1 && open < close) {
220
+ depth += 1;
221
+ const lineEnd = text.indexOf("\n", open);
222
+ if (lineEnd === -1)
223
+ return -1;
224
+ at = lineEnd;
225
+ }
226
+ else {
227
+ depth -= 1;
228
+ at = close + closer.length;
229
+ if (depth === 0)
230
+ return at;
231
+ }
232
+ }
233
+ }
234
+ const ALL_SOURCES = { has: () => true };
235
+ /** Remove previously composed guidance blocks from the owned sources. */
236
+ function stripGuidanceText(text, owned) {
189
237
  if (text === undefined || text === "")
190
238
  return text;
191
- const stripped = text.replace(GUIDANCE_BLOCK, "");
239
+ let strippedLast = false;
240
+ // A kept block that nests markers extends to its balanced end; every match
241
+ // inside it is caller text and is kept too.
242
+ let keptUntil = 0;
243
+ let stripped = text.replace(GUIDANCE_BLOCK, (block, source, gap, offset) => {
244
+ if (offset < keptUntil)
245
+ return block;
246
+ const end = nestsOpener(block) ? balancedBlockEnd(text, offset) : -1;
247
+ if (!owned.has(source) || end !== -1) {
248
+ keptUntil = Math.max(keptUntil, end);
249
+ return block;
250
+ }
251
+ if (gap === undefined && offset + block.length === text.length) {
252
+ strippedLast = true;
253
+ }
254
+ return "";
255
+ });
256
+ // A removed final block leaves the separator written before it.
257
+ if (strippedLast && stripped.endsWith("\n\n")) {
258
+ stripped = stripped.slice(0, -2);
259
+ }
192
260
  return stripped === "" ? undefined : stripped;
193
261
  }
262
+ /**
263
+ * True only for a whole line that is one complete composed block from an owned
264
+ * source. A line that merely starts with an opening marker is caller text.
265
+ */
266
+ function isOwnedGuidanceLine(line, owned) {
267
+ const match = GUIDANCE_LINE.exec(line);
268
+ return match !== null && owned.has(match[1]);
269
+ }
194
270
  /**
195
271
  * Compose harness, model, and other layered guidance into a profile's prompt.
196
272
  *
197
273
  * The blocks come first, in the order given, and the profile's own text comes
198
274
  * last, so the most specific instruction (the profile's) is the one a model
199
- * reads after the general guidance. Composition replaces any blocks a previous
275
+ * reads after the general guidance. Composition replaces the blocks a previous
200
276
  * composition added, on both channels, so recomposing after a harness or
201
- * model change never stacks stale guidance. Composing the same blocks twice
202
- * yields the same profile, and with it the same canonical identity.
277
+ * model change never stacks stale guidance. By default it replaces every
278
+ * block; with `options.replaceSources` it replaces only those sources and
279
+ * keeps the others, such as a team's own layer, in place. Composing the same
280
+ * blocks twice yields the same profile, and with it the same canonical
281
+ * identity.
203
282
  */
204
- export function composeAgentProfileGuidance(profile, blocks, channel) {
283
+ export function composeAgentProfileGuidance(profile, blocks, channel, options = {}) {
284
+ // Without replaceSources, every composed block is replaced, as in 2.11.
285
+ const owned = options.replaceSources === undefined
286
+ ? ALL_SOURCES
287
+ : new Set(options.replaceSources);
205
288
  const prompt = profile.prompt ?? {};
206
- const ownAppend = stripGuidanceText(prompt.appendSystemPrompt);
207
- const ownInstructions = prompt.instructions?.filter((line) => !GUIDANCE_OPEN.test(line));
289
+ const ownAppend = stripGuidanceText(prompt.appendSystemPrompt, owned);
290
+ const ownInstructions = prompt.instructions?.filter((line) => !isOwnedGuidanceLine(line, owned));
208
291
  const rendered = blocks.map(renderGuidanceBlock);
209
292
  const next = { ...prompt };
210
293
  delete next.appendSystemPrompt;
@@ -33,13 +33,13 @@ export const profileKbHarnesses = [
33
33
  {
34
34
  id: "codex",
35
35
  name: "Codex CLI",
36
- version: "0.152.1",
36
+ version: "0.156.1",
37
37
  sources: [
38
38
  { url: "https://learn.chatgpt.com/docs/models", checkedAt: CHECKED },
39
39
  {
40
40
  url: "cli:codex exec --help; codex features list",
41
41
  checkedAt: CHECKED,
42
- note: "0.152.1; multi_agent stable and enabled",
42
+ note: "0.156.1; multi_agent stable and enabled",
43
43
  },
44
44
  ],
45
45
  prompt: [
@@ -58,10 +58,10 @@ export const profileKbHarnesses = [
58
58
  {
59
59
  id: "opencode",
60
60
  name: "OpenCode",
61
- version: "1.18.18",
61
+ version: "1.18.32",
62
62
  sources: [
63
63
  { url: "https://opencode.ai/docs/cli/", checkedAt: CHECKED },
64
- { url: "cli:opencode run --help", checkedAt: CHECKED, note: "1.18.18" },
64
+ { url: "cli:opencode run --help", checkedAt: CHECKED, note: "1.18.32" },
65
65
  ],
66
66
  prompt: [
67
67
  "Delegate a focused subtask to a named agent when its persona or model fits the subtask.",
@@ -76,9 +76,17 @@ export const profileKbHarnesses = [
76
76
  {
77
77
  id: "pi",
78
78
  name: "Pi",
79
- version: "0.83.0",
79
+ version: "0.87.1",
80
80
  sources: [
81
- { url: "cli:pi --help", checkedAt: CHECKED, note: "0.83.0" },
81
+ {
82
+ url: "https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/cli.md",
83
+ checkedAt: CHECKED,
84
+ },
85
+ {
86
+ url: "https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/rpc.md",
87
+ checkedAt: CHECKED,
88
+ },
89
+ { url: "cli:pi --help", checkedAt: CHECKED, note: "0.87.1" },
82
90
  ],
83
91
  prompt: [
84
92
  "Expect steering messages mid-run and fold each one into the current plan.",
@@ -94,13 +102,13 @@ export const profileKbHarnesses = [
94
102
  {
95
103
  id: "kimi-code",
96
104
  name: "Kimi Code CLI",
97
- version: "0.36.1",
105
+ version: "2.0.2",
98
106
  sources: [
99
107
  { url: "https://moonshotai.github.io/kimi-code/", checkedAt: CHECKED },
100
108
  {
101
109
  url: "cli:kimi --help; ~/.kimi-code/config.toml",
102
110
  checkedAt: CHECKED,
103
- note: "0.36.1",
111
+ note: "2.0.2",
104
112
  },
105
113
  ],
106
114
  prompt: [
@@ -6,14 +6,20 @@
6
6
  * The data is plain, dated, and sourced. Composition is pure: the same profile
7
7
  * yields the same composed profile, so profile identity stays deterministic.
8
8
  */
9
- import { type AgentProfile, type AgentProfileGuidanceBlock } from "../agent-profile.js";
9
+ import { composeAgentProfileGuidance, type AgentProfile, type AgentProfileGuidanceBlock } from "../agent-profile.js";
10
10
  import type { HarnessType } from "../harness.js";
11
11
  import { profileKbHarnesses } from "./harnesses.js";
12
12
  import { profileKbModels } from "./models.js";
13
13
  import { profileKbDiscrepancies, profileKbLearnings } from "./records.js";
14
14
  import type { ProfileKbHarness, ProfileKbModel } from "./types.js";
15
15
  export type * from "./types.js";
16
- export { profileKbDiscrepancies, profileKbHarnesses, profileKbLearnings, profileKbModels, };
16
+ export type { AgentProfileGuidanceBlock, AgentProfileGuidanceChannel, AgentProfileGuidanceOptions, } from "../agent-profile.js";
17
+ export { composeAgentProfileGuidance, profileKbDiscrepancies, profileKbHarnesses, profileKbLearnings, profileKbModels, };
18
+ /**
19
+ * The guidance block sources the knowledge base owns. {@link withProfileKb}
20
+ * replaces blocks from these sources and keeps blocks any other layer added.
21
+ */
22
+ export declare const PROFILE_KB_SOURCES: readonly ["harness", "model", "learning"];
17
23
  /** Date the knowledge base was last checked against its sources. */
18
24
  export declare const PROFILE_KB_CHECKED_AT = "2026-09-22";
19
25
  /** Find a harness entry by its {@link HarnessType}. */
@@ -47,6 +53,7 @@ export declare function profileKbGuidance(selection: ProfileKbSelection): AgentP
47
53
  * Guidance goes into `appendSystemPrompt` where the harness owns an additive
48
54
  * system-prompt control and into `instructions` otherwise, so a harness that
49
55
  * refuses appended system text still receives it. The profile's own text
50
- * stays last. Recomposing replaces earlier guidance, so the result is stable.
56
+ * stays last. Recomposing replaces the guidance this function wrote earlier,
57
+ * so the result is stable; blocks from other sources are kept.
51
58
  */
52
59
  export declare function withProfileKb(profile: AgentProfile, selection?: ProfileKbSelection): AgentProfile;
@@ -7,11 +7,17 @@
7
7
  * yields the same composed profile, so profile identity stays deterministic.
8
8
  */
9
9
  import { composeAgentProfileGuidance, } from "../agent-profile.js";
10
+ import { deepFreeze } from "../deep-freeze.js";
10
11
  import { harnessSystemPromptIntents } from "../harness-capabilities.js";
11
12
  import { profileKbHarnesses } from "./harnesses.js";
12
13
  import { profileKbModels } from "./models.js";
13
14
  import { profileKbDiscrepancies, profileKbLearnings } from "./records.js";
14
- export { profileKbDiscrepancies, profileKbHarnesses, profileKbLearnings, profileKbModels, };
15
+ export { composeAgentProfileGuidance, profileKbDiscrepancies, profileKbHarnesses, profileKbLearnings, profileKbModels, };
16
+ /**
17
+ * The guidance block sources the knowledge base owns. {@link withProfileKb}
18
+ * replaces blocks from these sources and keeps blocks any other layer added.
19
+ */
20
+ export const PROFILE_KB_SOURCES = ["harness", "model", "learning"];
15
21
  /** Date the knowledge base was last checked against its sources. */
16
22
  export const PROFILE_KB_CHECKED_AT = "2026-09-22";
17
23
  /** Find a harness entry by its {@link HarnessType}. */
@@ -20,17 +26,29 @@ export function findProfileKbHarness(harness) {
20
26
  return undefined;
21
27
  return profileKbHarnesses.find((entry) => entry.id === harness);
22
28
  }
29
+ /*
30
+ * Composition reads a frozen snapshot taken at load, never the exported
31
+ * records. A consumer that edits an exported entry therefore cannot change
32
+ * later compositions or desynchronize the lookup index, and the exported
33
+ * types stay as they were published.
34
+ */
35
+ const harnessSnapshot = deepFreeze(structuredClone(profileKbHarnesses));
36
+ const modelSnapshot = deepFreeze(structuredClone(profileKbModels));
37
+ const learningSnapshot = deepFreeze(structuredClone(profileKbLearnings));
38
+ function harnessPosition(harness) {
39
+ return harnessSnapshot.findIndex((entry) => entry.id === harness);
40
+ }
23
41
  const modelIndex = (() => {
24
42
  const index = new Map();
25
- for (const model of profileKbModels) {
43
+ modelSnapshot.forEach((model, position) => {
26
44
  for (const name of [model.id, ...model.aliases]) {
27
45
  const key = name.toLowerCase();
28
46
  if (index.has(key)) {
29
47
  throw new Error(`profile-kb: model name ${name} is declared twice`);
30
48
  }
31
- index.set(key, model);
49
+ index.set(key, position);
32
50
  }
33
- }
51
+ });
34
52
  return index;
35
53
  })();
36
54
  /**
@@ -43,6 +61,15 @@ const modelIndex = (() => {
43
61
  * different model and never matches.
44
62
  */
45
63
  export function findProfileKbModel(model) {
64
+ // Public lookups search the exported records as they are now; composition
65
+ // alone reads the frozen snapshot, so neither can desynchronize the other.
66
+ return matchModelName(model, (candidate) => profileKbModels.find((entry) => [entry.id, ...entry.aliases].some((name) => name.toLowerCase() === candidate)));
67
+ }
68
+ /**
69
+ * Try the name as given, then without each leading `provider/` or route
70
+ * segment, after dropping a trailing `:suffix`.
71
+ */
72
+ function matchModelName(model, lookup) {
46
73
  if (!model)
47
74
  return undefined;
48
75
  const colon = model.lastIndexOf(":");
@@ -50,8 +77,8 @@ export function findProfileKbModel(model) {
50
77
  .trim()
51
78
  .toLowerCase();
52
79
  for (;;) {
53
- const found = modelIndex.get(candidate);
54
- if (found)
80
+ const found = lookup(candidate);
81
+ if (found !== undefined)
55
82
  return found;
56
83
  const slash = candidate.indexOf("/");
57
84
  if (slash < 0)
@@ -59,8 +86,11 @@ export function findProfileKbModel(model) {
59
86
  candidate = candidate.slice(slash + 1);
60
87
  }
61
88
  }
89
+ function modelPosition(model) {
90
+ return matchModelName(model, (candidate) => modelIndex.get(candidate)) ?? -1;
91
+ }
62
92
  function learningsFor(harness, model) {
63
- return profileKbLearnings.filter((learning) => {
93
+ return learningSnapshot.filter((learning) => {
64
94
  const scope = learning.appliesTo;
65
95
  if (scope.harness !== undefined && scope.harness !== harness?.id) {
66
96
  return false;
@@ -79,8 +109,10 @@ function bullets(lines) {
79
109
  * contribute nothing.
80
110
  */
81
111
  export function profileKbGuidance(selection) {
82
- const harness = findProfileKbHarness(selection.harness);
83
- const model = findProfileKbModel(selection.model);
112
+ const harness = selection.harness
113
+ ? harnessSnapshot[harnessPosition(selection.harness)]
114
+ : undefined;
115
+ const model = modelSnapshot[modelPosition(selection.model)];
84
116
  const blocks = [];
85
117
  if (harness && harness.prompt.length > 0) {
86
118
  blocks.push({
@@ -114,14 +146,18 @@ export function profileKbGuidance(selection) {
114
146
  * Guidance goes into `appendSystemPrompt` where the harness owns an additive
115
147
  * system-prompt control and into `instructions` otherwise, so a harness that
116
148
  * refuses appended system text still receives it. The profile's own text
117
- * stays last. Recomposing replaces earlier guidance, so the result is stable.
149
+ * stays last. Recomposing replaces the guidance this function wrote earlier,
150
+ * so the result is stable; blocks from other sources are kept.
118
151
  */
119
152
  export function withProfileKb(profile, selection = {}) {
120
153
  const harness = selection.harness ?? profile.harness;
121
154
  const model = selection.model ?? profile.model?.default;
122
155
  const blocks = profileKbGuidance({ harness, model });
123
- const channel = harnessSystemPromptIntents(findProfileKbHarness(harness)?.id ?? harness).append
156
+ const known = harness ? harnessSnapshot[harnessPosition(harness)] : undefined;
157
+ const channel = harnessSystemPromptIntents(known?.id ?? harness).append
124
158
  ? "appendSystemPrompt"
125
159
  : "instructions";
126
- return composeAgentProfileGuidance(profile, blocks, channel);
160
+ return composeAgentProfileGuidance(profile, blocks, channel, {
161
+ replaceSources: PROFILE_KB_SOURCES,
162
+ });
127
163
  }
@@ -1,5 +1,14 @@
1
1
  const CHECKED = "2026-09-22";
2
2
  const claudeDocs = "https://platform.claude.com/docs/en";
3
+ const openaiDocs = "https://developers.openai.com/api/docs";
4
+ /** A router surface is listed only with a dated check that the router served the id. */
5
+ function routerCheck(note) {
6
+ return {
7
+ url: "https://router.tangle.tools/v1/chat/completions",
8
+ checkedAt: CHECKED,
9
+ note,
10
+ };
11
+ }
3
12
  /**
4
13
  * Current frontier models, from vendor sources read on 2026-09-22.
5
14
  *
@@ -12,7 +21,7 @@ export const profileKbModels = [
12
21
  id: "claude-opus-5-5",
13
22
  name: "Claude Opus 5.5",
14
23
  vendor: "Anthropic",
15
- surfaces: ["api", "router"],
24
+ surfaces: ["api"],
16
25
  aliases: ["anthropic/claude-opus-5-5", "anthropic.claude-opus-5-5"],
17
26
  defaultEffort: "medium",
18
27
  sources: [
@@ -48,7 +57,7 @@ export const profileKbModels = [
48
57
  id: "claude-fable-5-1",
49
58
  name: "Claude Fable 5.1",
50
59
  vendor: "Anthropic",
51
- surfaces: ["api", "router"],
60
+ surfaces: ["api"],
52
61
  aliases: ["anthropic/claude-fable-5-1", "anthropic.claude-fable-5-1"],
53
62
  defaultEffort: "high",
54
63
  sources: [
@@ -88,6 +97,7 @@ export const profileKbModels = [
88
97
  url: `${claudeDocs}/build-with-claude/prompt-engineering/prompting-claude-sonnet-5`,
89
98
  checkedAt: CHECKED,
90
99
  },
100
+ routerCheck("claude-sonnet-5 returned HTTP 200"),
91
101
  ],
92
102
  prompt: [
93
103
  "Apply each instruction to every case it names; where an instruction should apply broadly, the prompt says so.",
@@ -106,7 +116,7 @@ export const profileKbModels = [
106
116
  id: "claude-haiku-4-5",
107
117
  name: "Claude Haiku 4.5",
108
118
  vendor: "Anthropic",
109
- surfaces: ["api", "router"],
119
+ surfaces: ["api"],
110
120
  aliases: [
111
121
  "claude-haiku-4-5-20251001",
112
122
  "anthropic/claude-haiku-4-5",
@@ -119,9 +129,9 @@ export const profileKbModels = [
119
129
  "Answer directly and keep each step scoped to the task in hand.",
120
130
  ],
121
131
  operator: [
122
- "Model id claude-haiku-4-5-20251001 (alias claude-haiku-4-5); 200K context, 64K max output. The fastest model in the current lineup.",
132
+ "Model id claude-haiku-4-5-20251001 (alias claude-haiku-4-5); 200K context, 64K max output.",
123
133
  "Uses manual extended thinking (thinking.type enabled with budget_tokens); it takes no effort parameter.",
124
- "Retirement not sooner than 2026-10-15.",
134
+ "Retirement date: 2026-10-15 at the earliest.",
125
135
  ],
126
136
  },
127
137
  {
@@ -148,6 +158,96 @@ export const profileKbModels = [
148
158
  "No API model id; reach it through ChatGPT (chatgpt-fleet).",
149
159
  ],
150
160
  },
161
+ {
162
+ id: "gpt-6-astra",
163
+ name: "GPT-6 Astra",
164
+ vendor: "OpenAI",
165
+ surfaces: ["codex", "api"],
166
+ aliases: ["openai/gpt-6-astra"],
167
+ defaultEffort: "medium",
168
+ sources: [
169
+ { url: `${openaiDocs}/models/gpt-6-astra`, checkedAt: CHECKED },
170
+ {
171
+ url: `${openaiDocs}/guides/latest-model?model=gpt-6-astra`,
172
+ checkedAt: CHECKED,
173
+ note: "Using GPT-6: prompting guidance",
174
+ },
175
+ { url: "https://learn.chatgpt.com/docs/models", checkedAt: CHECKED },
176
+ {
177
+ url: "cli:codex exec -m gpt-6-astra 'Reply with exactly: OK'",
178
+ checkedAt: CHECKED,
179
+ note: "codex-cli 0.156.1 on a ChatGPT account returned OK",
180
+ },
181
+ ],
182
+ prompt: [
183
+ "Infer the user's intent and task scope from the instructions and prior context, bias toward action, and carry the intended task to completion.",
184
+ "When you can parallelize work by delegating tasks to another agent, do so with the collaboration tools.",
185
+ "Write clear, concise paragraphs that each develop one idea; use lists only for parallel or sequential items.",
186
+ ],
187
+ operator: [
188
+ "Built for end-to-end work with sustained reasoning across many tools: coding, computer use, research, and documents.",
189
+ "API: 1,050,000-token context, 128K max output; efforts low, medium, high, xhigh, max.",
190
+ "Codex: 272K context; efforts low to max plus ultra, which delegates tasks automatically; default medium.",
191
+ ],
192
+ },
193
+ {
194
+ id: "gpt-6-sol",
195
+ name: "GPT-6 Sol",
196
+ vendor: "OpenAI",
197
+ surfaces: ["codex", "api"],
198
+ aliases: ["openai/gpt-6-sol"],
199
+ defaultEffort: "medium",
200
+ sources: [
201
+ { url: `${openaiDocs}/models/gpt-6-sol`, checkedAt: CHECKED },
202
+ {
203
+ url: `${openaiDocs}/guides/latest-model?model=gpt-6-sol`,
204
+ checkedAt: CHECKED,
205
+ note: "Using GPT-6: prompting guidance",
206
+ },
207
+ {
208
+ url: "cli:codex exec -m gpt-6-sol 'Reply with exactly: OK'",
209
+ checkedAt: CHECKED,
210
+ note: "codex-cli 0.156.1 on a ChatGPT account returned OK",
211
+ },
212
+ ],
213
+ prompt: [
214
+ "Treat a request such as 'can you', 'I want to', or 'help me' as an instruction to do the work.",
215
+ "The user's explicit instructions take precedence over a skill's instructions; if a skill makes you pause or ask, name the SKILL.md file.",
216
+ "Carry the task to completion and report the checks you ran.",
217
+ ],
218
+ operator: [
219
+ "Built for complex coding and agentic workflows, and for ambiguous or high-value tasks that need analysis and polish.",
220
+ "API: 1,050,000-token context, 128K max output; efforts none, low, medium (default), high, xhigh, max. Inputs over 272K tokens bill at 2x input and 1.5x output.",
221
+ "Codex: 272K context; efforts low to max plus ultra; default medium.",
222
+ "Use the Responses API for built-in tools; Chat Completions supports function calling only at effort none.",
223
+ ],
224
+ },
225
+ {
226
+ id: "gpt-6-luna",
227
+ name: "GPT-6 Luna",
228
+ vendor: "OpenAI",
229
+ surfaces: ["codex", "api"],
230
+ aliases: ["openai/gpt-6-luna"],
231
+ defaultEffort: "medium",
232
+ sources: [
233
+ { url: `${openaiDocs}/models/gpt-6-luna`, checkedAt: CHECKED },
234
+ { url: "https://learn.chatgpt.com/docs/models", checkedAt: CHECKED },
235
+ {
236
+ url: "cli:codex exec -m gpt-6-luna 'Reply with exactly: OK'",
237
+ checkedAt: CHECKED,
238
+ note: "codex-cli 0.156.1 on a ChatGPT account returned OK",
239
+ },
240
+ ],
241
+ prompt: [
242
+ "Work to the stated success criteria and output format for each item.",
243
+ "Scale testing to the change: once the targeted checks pass, test further only when new changes or failures justify it.",
244
+ ],
245
+ operator: [
246
+ "Built for focused, high-volume tasks with known success criteria: summarization, extraction, and focused coding.",
247
+ "API: 1,050,000-token context, 128K max output; efforts none, low, medium (default), high, xhigh, max.",
248
+ "Codex: 272K context; efforts low to max; default medium.",
249
+ ],
250
+ },
151
251
  {
152
252
  id: "gpt-5.6-sol",
153
253
  name: "GPT-5.6 Sol",
@@ -168,8 +268,9 @@ export const profileKbModels = [
168
268
  {
169
269
  url: "file://~/.codex/models_cache.json",
170
270
  checkedAt: CHECKED,
171
- note: "codex-cli 0.152.1 served list; `codex exec -m gpt-5.6-sol` returned OK",
271
+ note: "codex-cli 0.156.1 served list; `codex exec -m gpt-5.6-sol` returned OK",
172
272
  },
273
+ routerCheck("gpt-5.6-sol returned HTTP 200"),
173
274
  ],
174
275
  prompt: [
175
276
  "Work from the outcome: know what good looks like and the stopping condition, then choose the method yourself.",
@@ -198,8 +299,9 @@ export const profileKbModels = [
198
299
  {
199
300
  url: "file://~/.codex/models_cache.json",
200
301
  checkedAt: CHECKED,
201
- note: "codex-cli 0.152.1 served list; `codex exec -m gpt-5.6-terra` returned OK",
302
+ note: "codex-cli 0.156.1 served list; `codex exec -m gpt-5.6-terra` returned OK",
202
303
  },
304
+ routerCheck("gpt-5.6-terra returned HTTP 200"),
203
305
  ],
204
306
  prompt: [
205
307
  "Work from the outcome and the stopping condition; choose the method yourself and verify before you report.",
@@ -225,8 +327,9 @@ export const profileKbModels = [
225
327
  {
226
328
  url: "file://~/.codex/models_cache.json",
227
329
  checkedAt: CHECKED,
228
- note: "codex-cli 0.152.1 served list; `codex exec -m gpt-5.6-luna` returned OK",
330
+ note: "codex-cli 0.156.1 served list; `codex exec -m gpt-5.6-luna` returned OK",
229
331
  },
332
+ routerCheck("gpt-5.6-luna returned HTTP 200"),
230
333
  ],
231
334
  prompt: [
232
335
  "Follow the stated format exactly and finish each item before moving to the next.",
@@ -252,11 +355,7 @@ export const profileKbModels = [
252
355
  url: "https://api-docs.deepseek.com/quick_start/pricing",
253
356
  checkedAt: CHECKED,
254
357
  },
255
- {
256
- url: "https://router.tangle.tools/v1/chat/completions",
257
- checkedAt: CHECKED,
258
- note: "deepseek/deepseek-v4.1-flash returned HTTP 200 and served that id",
259
- },
358
+ routerCheck("deepseek/deepseek-v4.1-flash returned HTTP 200 and served that id"),
260
359
  ],
261
360
  prompt: [
262
361
  "State the goal and the finished result up front, and use the tools you are given to check your work.",
@@ -276,11 +375,7 @@ export const profileKbModels = [
276
375
  defaultEffort: "ultracode",
277
376
  sources: [
278
377
  { url: "https://docs.z.ai/guides/llm/glm-5.3", checkedAt: CHECKED },
279
- {
280
- url: "https://router.tangle.tools/v1/chat/completions",
281
- checkedAt: CHECKED,
282
- note: "glm-5.3 returned HTTP 200, served as z-ai/glm-5.3",
283
- },
378
+ routerCheck("glm-5.3 returned HTTP 200, served as z-ai/glm-5.3"),
284
379
  ],
285
380
  prompt: [
286
381
  "Work the task through to a verified result, using the tools to run and check each change.",
@@ -302,11 +397,7 @@ export const profileKbModels = [
302
397
  url: "https://platform.kimi.ai/docs/guide/kimi-k3-quickstart",
303
398
  checkedAt: CHECKED,
304
399
  },
305
- {
306
- url: "https://router.tangle.tools/v1/chat/completions",
307
- checkedAt: CHECKED,
308
- note: "kimi-k3 returned HTTP 200, served as moonshotai/kimi-k3",
309
- },
400
+ routerCheck("kimi-k3 returned HTTP 200, served as moonshotai/kimi-k3"),
310
401
  ],
311
402
  prompt: [
312
403
  "Navigate the repository, run the code, and iterate against tests, logs, and runtime output until the result holds.",
@@ -13,15 +13,15 @@ export const profileKbDiscrepancies = [
13
13
  {
14
14
  subject: "OpenAI Codex models",
15
15
  requested: "gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna as the current Codex models",
16
- observed: "OpenAI's Codex model page lists GPT-6 Astra, GPT-6 Sol, and GPT-6 Luna as the current recommended models. " +
17
- "On this box, codex-cli 0.152.1 on a ChatGPT account serves gpt-5.6-sol, gpt-5.6-terra, and gpt-5.6-luna (each returned OK) " +
18
- "and labels them 'Older'; gpt-6-sol and gpt-6-luna return 'not supported when using Codex with a ChatGPT account', " +
19
- "and gpt-6-astra returns 'requires a newer version of Codex'. The knowledge base keeps the gpt-5.6 tiers because they are what this box can run.",
16
+ observed: "OpenAI's Codex model page lists GPT-6 Astra, GPT-6 Sol, and GPT-6 Luna as the current models. " +
17
+ "codex-cli 0.156.1 on a ChatGPT account serves all three (each returned OK) and labels the gpt-5.6 tiers 'Older'. " +
18
+ "The knowledge base carries the GPT-6 tiers as the current Codex models and keeps the requested gpt-5.6 tiers, which Codex and the API still serve.",
20
19
  sources: [
21
20
  { url: "https://learn.chatgpt.com/docs/models", checkedAt: CHECKED },
22
21
  {
23
22
  url: "cli:codex exec -m <model> 'Reply with exactly: OK'",
24
23
  checkedAt: CHECKED,
24
+ note: "codex-cli 0.156.1",
25
25
  },
26
26
  ],
27
27
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "2.11.0",
3
+ "version": "2.12.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",