@tangle-network/agent-interface 2.10.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
@@ -172,6 +172,33 @@ When caller environment values merge into a bridge or harness process, reject na
172
172
  Use `isCredentialBearingProfileConfigName(name)` before retaining public config.
173
173
  These checks do not apply to a replacement environment owned by caller code.
174
174
 
175
+ ## Profile knowledge base
176
+
177
+ `@tangle-network/agent-interface/profile-kb` holds how to get the best from each frontier harness and model this platform runs.
178
+ Every entry cites a vendor source or a command run on a dated check.
179
+ Guidance is specific to one harness or model and never compares models.
180
+
181
+ `withProfileKb(profile)` composes harness guidance, then model guidance, then the profile's own text into the profile's prompt:
182
+
183
+ ```ts
184
+ import { withProfileKb } from "@tangle-network/agent-interface/profile-kb";
185
+
186
+ const worker = withProfileKb({
187
+ harness: "claude-code",
188
+ model: { default: "claude-opus-5-5" },
189
+ prompt: { appendSystemPrompt: "Cite the file you read." },
190
+ });
191
+ ```
192
+
193
+ Guidance goes into `appendSystemPrompt` where the harness owns an additive system-prompt control, and into `instructions` otherwise.
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`).
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.
199
+
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.
201
+
175
202
  ## Failed execution accounting
176
203
 
177
204
  An adapter can reject with `AgentExecutionError` and retain observed usage and timing in its immutable `receipt`.
@@ -519,4 +519,48 @@ export interface AgentProfileValidationResult {
519
519
  * base-first with a blank line between them.
520
520
  */
521
521
  export declare function mergeAgentProfiles(base: AgentProfile | undefined, overlay: AgentProfile | undefined): AgentProfile | undefined;
522
+ /**
523
+ * One block of standing guidance composed into a profile's prompt.
524
+ *
525
+ * `source` names the layer the text came from (`harness`, `model`, or another
526
+ * knowledge layer) and `id` names the subject, so a composed block can be
527
+ * found and replaced later. The profile's own prompt text is never a block:
528
+ * it is what the blocks are composed in front of.
529
+ */
530
+ export interface AgentProfileGuidanceBlock {
531
+ source: string;
532
+ id: string;
533
+ text: string;
534
+ }
535
+ /**
536
+ * Where composed guidance lands. `appendSystemPrompt` keeps the harness's
537
+ * system prompt and adds the guidance to it; `instructions` uses the
538
+ * harness's caller-instruction surface, for harnesses that own no additive
539
+ * system-prompt control.
540
+ */
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
+ }
552
+ /**
553
+ * Compose harness, model, and other layered guidance into a profile's prompt.
554
+ *
555
+ * The blocks come first, in the order given, and the profile's own text comes
556
+ * last, so the most specific instruction (the profile's) is the one a model
557
+ * reads after the general guidance. Composition replaces the blocks a previous
558
+ * composition added, on both channels, so recomposing after a harness or
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.
564
+ */
565
+ export declare function composeAgentProfileGuidance(profile: AgentProfile, blocks: readonly AgentProfileGuidanceBlock[], channel: AgentProfileGuidanceChannel, options?: AgentProfileGuidanceOptions): AgentProfile;
522
566
  export {};
@@ -173,3 +173,145 @@ export function mergeAgentProfiles(base, overlay) {
173
173
  extensions: mergeRecord(base?.extensions, overlay?.extensions),
174
174
  });
175
175
  }
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;
188
+ function renderGuidanceBlock(block) {
189
+ if (/["\n]/.test(block.source) || /["\n]/.test(block.id)) {
190
+ throw new TypeError("profile guidance source and id must not contain quotes or newlines");
191
+ }
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");
196
+ }
197
+ return `<profile-guidance source="${block.source}" id="${block.id}">\n${block.text}\n</profile-guidance>`;
198
+ }
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) {
237
+ if (text === undefined || text === "")
238
+ return text;
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
+ }
260
+ return stripped === "" ? undefined : stripped;
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
+ }
270
+ /**
271
+ * Compose harness, model, and other layered guidance into a profile's prompt.
272
+ *
273
+ * The blocks come first, in the order given, and the profile's own text comes
274
+ * last, so the most specific instruction (the profile's) is the one a model
275
+ * reads after the general guidance. Composition replaces the blocks a previous
276
+ * composition added, on both channels, so recomposing after a harness or
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.
282
+ */
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);
288
+ const prompt = profile.prompt ?? {};
289
+ const ownAppend = stripGuidanceText(prompt.appendSystemPrompt, owned);
290
+ const ownInstructions = prompt.instructions?.filter((line) => !isOwnedGuidanceLine(line, owned));
291
+ const rendered = blocks.map(renderGuidanceBlock);
292
+ const next = { ...prompt };
293
+ delete next.appendSystemPrompt;
294
+ delete next.instructions;
295
+ if (channel === "appendSystemPrompt") {
296
+ const parts = [...rendered, ...(ownAppend ? [ownAppend] : [])];
297
+ if (parts.length > 0)
298
+ next.appendSystemPrompt = parts.join("\n\n");
299
+ else if (ownAppend !== undefined)
300
+ next.appendSystemPrompt = ownAppend;
301
+ if (ownInstructions !== undefined)
302
+ next.instructions = ownInstructions;
303
+ }
304
+ else {
305
+ if (ownAppend !== undefined)
306
+ next.appendSystemPrompt = ownAppend;
307
+ const lines = [...rendered, ...(ownInstructions ?? [])];
308
+ if (lines.length > 0 || prompt.instructions !== undefined) {
309
+ next.instructions = lines;
310
+ }
311
+ }
312
+ const result = { ...profile };
313
+ if (Object.keys(next).length > 0 || profile.prompt !== undefined) {
314
+ result.prompt = next;
315
+ }
316
+ return result;
317
+ }
@@ -0,0 +1,9 @@
1
+ import type { ProfileKbHarness } from "./types.js";
2
+ /**
3
+ * The frontier coding harnesses this platform runs, checked against the
4
+ * installed CLI on 2026-09-22.
5
+ *
6
+ * `prompt` lines go into the agent's prompt; `operator` lines are launch
7
+ * facts. Each harness is described on its own terms.
8
+ */
9
+ export declare const profileKbHarnesses: readonly ProfileKbHarness[];
@@ -0,0 +1,123 @@
1
+ const CHECKED = "2026-09-22";
2
+ /**
3
+ * The frontier coding harnesses this platform runs, checked against the
4
+ * installed CLI on 2026-09-22.
5
+ *
6
+ * `prompt` lines go into the agent's prompt; `operator` lines are launch
7
+ * facts. Each harness is described on its own terms.
8
+ */
9
+ export const profileKbHarnesses = [
10
+ {
11
+ id: "claude-code",
12
+ name: "Claude Code",
13
+ version: "2.1.280",
14
+ sources: [
15
+ {
16
+ url: "https://code.claude.com/docs/en/cli-reference",
17
+ checkedAt: CHECKED,
18
+ },
19
+ { url: "cli:claude --help", checkedAt: CHECKED, note: "2.1.280" },
20
+ ],
21
+ prompt: [
22
+ "Delegate independent slices to subagents, several in one turn so they run in parallel, then synthesize their results.",
23
+ "Use the skills listed in your session for procedures they cover.",
24
+ "Run long commands in the background and keep working while they finish.",
25
+ ],
26
+ operator: [
27
+ "Headless: `claude -p <prompt> --model <id> --effort <level> --output-format stream-json`.",
28
+ "`--append-system-prompt` adds to the built-in prompt; `--system-prompt` replaces it.",
29
+ "`--max-budget-usd` caps spend; `--json-schema` validates structured output; `--mcp-config` loads MCP servers.",
30
+ "`--bg` starts a background session; `--bare` skips hooks, plugins, memory, and CLAUDE.md discovery.",
31
+ ],
32
+ },
33
+ {
34
+ id: "codex",
35
+ name: "Codex CLI",
36
+ version: "0.156.1",
37
+ sources: [
38
+ { url: "https://learn.chatgpt.com/docs/models", checkedAt: CHECKED },
39
+ {
40
+ url: "cli:codex exec --help; codex features list",
41
+ checkedAt: CHECKED,
42
+ note: "0.156.1; multi_agent stable and enabled",
43
+ },
44
+ ],
45
+ prompt: [
46
+ "Split independent work across child agents with spawn_agent, and join them with wait_agent.",
47
+ "Edit with apply_patch and search with rg.",
48
+ "Carry the task end to end in this turn: gather context, implement, run the checks, and report the evidence.",
49
+ ],
50
+ operator: [
51
+ "Headless: `codex exec <prompt> -m <model> -c model_reasoning_effort=<level> --json -o <last-message-file>`.",
52
+ "`--output-schema <file>` enforces structured output. `codex review` runs a non-interactive review.",
53
+ "Reasoning levels are per model; read them from `$CODEX_HOME/models_cache.json`.",
54
+ "System-prompt replacement uses the `model_instructions_file` config key; standing instructions go in AGENTS.md.",
55
+ "`/goal` runs long unattended work; `resume` and `fork` continue a session.",
56
+ ],
57
+ },
58
+ {
59
+ id: "opencode",
60
+ name: "OpenCode",
61
+ version: "1.18.32",
62
+ sources: [
63
+ { url: "https://opencode.ai/docs/cli/", checkedAt: CHECKED },
64
+ { url: "cli:opencode run --help", checkedAt: CHECKED, note: "1.18.32" },
65
+ ],
66
+ prompt: [
67
+ "Delegate a focused subtask to a named agent when its persona or model fits the subtask.",
68
+ "Use the mounted MCP tools for external actions.",
69
+ ],
70
+ operator: [
71
+ "Headless: `opencode run <message> -m <provider/model> --agent <name> --variant <effort> --format json`.",
72
+ "Standing text goes through `instructions` files or appended system text; the built-in prompt stays in place.",
73
+ "`opencode serve` with `run --attach <url>`, or `opencode acp`, gives a parent live control.",
74
+ ],
75
+ },
76
+ {
77
+ id: "pi",
78
+ name: "Pi",
79
+ version: "0.87.1",
80
+ sources: [
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" },
90
+ ],
91
+ prompt: [
92
+ "Expect steering messages mid-run and fold each one into the current plan.",
93
+ "Fan out independent work by running `pi -p <task> --mode json` from bash, several at once, and read the results back.",
94
+ ],
95
+ operator: [
96
+ "Headless: `pi -p <prompt> --model <provider/id[:thinking]> --mode json`.",
97
+ "`--mode rpc` takes JSONL commands on stdin (steer, follow-up, set model, compact, fork) for live control.",
98
+ "`--thinking` takes off, minimal, low, medium, high, xhigh, or max.",
99
+ "`--system-prompt` replaces the default; `--append-system-prompt` adds to it.",
100
+ ],
101
+ },
102
+ {
103
+ id: "kimi-code",
104
+ name: "Kimi Code CLI",
105
+ version: "2.0.2",
106
+ sources: [
107
+ { url: "https://moonshotai.github.io/kimi-code/", checkedAt: CHECKED },
108
+ {
109
+ url: "cli:kimi --help; ~/.kimi-code/config.toml",
110
+ checkedAt: CHECKED,
111
+ note: "2.0.2",
112
+ },
113
+ ],
114
+ prompt: [
115
+ "Plan the change first, then edit and verify each step against tests and runtime output.",
116
+ ],
117
+ operator: [
118
+ "Headless: `kimi -p <prompt> -m kimi-code/k3 --output-format stream-json`.",
119
+ "`--auto` runs fully autonomous; `--agent-file <path>` loads an agent definition; `--plan` starts in plan mode.",
120
+ "Effort is the `[thinking] effort` key in `~/.kimi-code/config.toml`: low, high, or max.",
121
+ ],
122
+ },
123
+ ];
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Profile knowledge base: how to get the best from each frontier harness and
3
+ * model this platform runs, from current vendor sources, composed into an
4
+ * {@link AgentProfile}'s prompt.
5
+ *
6
+ * The data is plain, dated, and sourced. Composition is pure: the same profile
7
+ * yields the same composed profile, so profile identity stays deterministic.
8
+ */
9
+ import { composeAgentProfileGuidance, type AgentProfile, type AgentProfileGuidanceBlock } from "../agent-profile.js";
10
+ import type { HarnessType } from "../harness.js";
11
+ import { profileKbHarnesses } from "./harnesses.js";
12
+ import { profileKbModels } from "./models.js";
13
+ import { profileKbDiscrepancies, profileKbLearnings } from "./records.js";
14
+ import type { ProfileKbHarness, ProfileKbModel } from "./types.js";
15
+ export type * from "./types.js";
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"];
23
+ /** Date the knowledge base was last checked against its sources. */
24
+ export declare const PROFILE_KB_CHECKED_AT = "2026-09-22";
25
+ /** Find a harness entry by its {@link HarnessType}. */
26
+ export declare function findProfileKbHarness(harness: HarnessType | string | undefined): ProfileKbHarness | undefined;
27
+ /**
28
+ * Find a model entry by id or alias.
29
+ *
30
+ * Accepts the spellings harnesses and routers use: a provider prefix
31
+ * (`anthropic/claude-opus-5-5`), a route prefix
32
+ * (`pi/tangle-router/deepseek/deepseek-v4.1-flash`), and a trailing `:suffix`
33
+ * such as pi's thinking level or a router's `:batch`. A different version is a
34
+ * different model and never matches.
35
+ */
36
+ export declare function findProfileKbModel(model: string | undefined): ProfileKbModel | undefined;
37
+ /** Which harness and model to compose guidance for. */
38
+ export interface ProfileKbSelection {
39
+ harness?: HarnessType | string;
40
+ model?: string;
41
+ }
42
+ /**
43
+ * The guidance blocks for one harness and model: the harness's, then the
44
+ * model's, then any reproduced platform learnings for either. Unknown names
45
+ * contribute nothing.
46
+ */
47
+ export declare function profileKbGuidance(selection: ProfileKbSelection): AgentProfileGuidanceBlock[];
48
+ /**
49
+ * Compose harness, model, and profile guidance into the profile's prompt.
50
+ *
51
+ * The harness and model default to the profile's own `harness` and
52
+ * `model.default`; pass a selection to compose for an executor's override.
53
+ * Guidance goes into `appendSystemPrompt` where the harness owns an additive
54
+ * system-prompt control and into `instructions` otherwise, so a harness that
55
+ * refuses appended system text still receives it. The profile's own text
56
+ * stays last. Recomposing replaces the guidance this function wrote earlier,
57
+ * so the result is stable; blocks from other sources are kept.
58
+ */
59
+ export declare function withProfileKb(profile: AgentProfile, selection?: ProfileKbSelection): AgentProfile;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Profile knowledge base: how to get the best from each frontier harness and
3
+ * model this platform runs, from current vendor sources, composed into an
4
+ * {@link AgentProfile}'s prompt.
5
+ *
6
+ * The data is plain, dated, and sourced. Composition is pure: the same profile
7
+ * yields the same composed profile, so profile identity stays deterministic.
8
+ */
9
+ import { composeAgentProfileGuidance, } from "../agent-profile.js";
10
+ import { deepFreeze } from "../deep-freeze.js";
11
+ import { harnessSystemPromptIntents } from "../harness-capabilities.js";
12
+ import { profileKbHarnesses } from "./harnesses.js";
13
+ import { profileKbModels } from "./models.js";
14
+ import { profileKbDiscrepancies, profileKbLearnings } from "./records.js";
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"];
21
+ /** Date the knowledge base was last checked against its sources. */
22
+ export const PROFILE_KB_CHECKED_AT = "2026-09-22";
23
+ /** Find a harness entry by its {@link HarnessType}. */
24
+ export function findProfileKbHarness(harness) {
25
+ if (!harness)
26
+ return undefined;
27
+ return profileKbHarnesses.find((entry) => entry.id === harness);
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
+ }
41
+ const modelIndex = (() => {
42
+ const index = new Map();
43
+ modelSnapshot.forEach((model, position) => {
44
+ for (const name of [model.id, ...model.aliases]) {
45
+ const key = name.toLowerCase();
46
+ if (index.has(key)) {
47
+ throw new Error(`profile-kb: model name ${name} is declared twice`);
48
+ }
49
+ index.set(key, position);
50
+ }
51
+ });
52
+ return index;
53
+ })();
54
+ /**
55
+ * Find a model entry by id or alias.
56
+ *
57
+ * Accepts the spellings harnesses and routers use: a provider prefix
58
+ * (`anthropic/claude-opus-5-5`), a route prefix
59
+ * (`pi/tangle-router/deepseek/deepseek-v4.1-flash`), and a trailing `:suffix`
60
+ * such as pi's thinking level or a router's `:batch`. A different version is a
61
+ * different model and never matches.
62
+ */
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) {
73
+ if (!model)
74
+ return undefined;
75
+ const colon = model.lastIndexOf(":");
76
+ let candidate = (colon > 0 ? model.slice(0, colon) : model)
77
+ .trim()
78
+ .toLowerCase();
79
+ for (;;) {
80
+ const found = lookup(candidate);
81
+ if (found !== undefined)
82
+ return found;
83
+ const slash = candidate.indexOf("/");
84
+ if (slash < 0)
85
+ return undefined;
86
+ candidate = candidate.slice(slash + 1);
87
+ }
88
+ }
89
+ function modelPosition(model) {
90
+ return matchModelName(model, (candidate) => modelIndex.get(candidate)) ?? -1;
91
+ }
92
+ function learningsFor(harness, model) {
93
+ return learningSnapshot.filter((learning) => {
94
+ const scope = learning.appliesTo;
95
+ if (scope.harness !== undefined && scope.harness !== harness?.id) {
96
+ return false;
97
+ }
98
+ if (scope.model !== undefined && scope.model !== model?.id)
99
+ return false;
100
+ return scope.harness !== undefined || scope.model !== undefined;
101
+ });
102
+ }
103
+ function bullets(lines) {
104
+ return lines.map((line) => `- ${line}`).join("\n");
105
+ }
106
+ /**
107
+ * The guidance blocks for one harness and model: the harness's, then the
108
+ * model's, then any reproduced platform learnings for either. Unknown names
109
+ * contribute nothing.
110
+ */
111
+ export function profileKbGuidance(selection) {
112
+ const harness = selection.harness
113
+ ? harnessSnapshot[harnessPosition(selection.harness)]
114
+ : undefined;
115
+ const model = modelSnapshot[modelPosition(selection.model)];
116
+ const blocks = [];
117
+ if (harness && harness.prompt.length > 0) {
118
+ blocks.push({
119
+ source: "harness",
120
+ id: harness.id,
121
+ text: `You are running in ${harness.name}.\n${bullets(harness.prompt)}`,
122
+ });
123
+ }
124
+ if (model && model.prompt.length > 0) {
125
+ blocks.push({
126
+ source: "model",
127
+ id: model.id,
128
+ text: `You are ${model.name}.\n${bullets(model.prompt)}`,
129
+ });
130
+ }
131
+ const learnings = learningsFor(harness, model);
132
+ if (learnings.length > 0) {
133
+ blocks.push({
134
+ source: "learning",
135
+ id: learnings.map((learning) => learning.id).join(","),
136
+ text: bullets(learnings.map((learning) => learning.text)),
137
+ });
138
+ }
139
+ return blocks;
140
+ }
141
+ /**
142
+ * Compose harness, model, and profile guidance into the profile's prompt.
143
+ *
144
+ * The harness and model default to the profile's own `harness` and
145
+ * `model.default`; pass a selection to compose for an executor's override.
146
+ * Guidance goes into `appendSystemPrompt` where the harness owns an additive
147
+ * system-prompt control and into `instructions` otherwise, so a harness that
148
+ * refuses appended system text still receives it. The profile's own text
149
+ * stays last. Recomposing replaces the guidance this function wrote earlier,
150
+ * so the result is stable; blocks from other sources are kept.
151
+ */
152
+ export function withProfileKb(profile, selection = {}) {
153
+ const harness = selection.harness ?? profile.harness;
154
+ const model = selection.model ?? profile.model?.default;
155
+ const blocks = profileKbGuidance({ harness, model });
156
+ const known = harness ? harnessSnapshot[harnessPosition(harness)] : undefined;
157
+ const channel = harnessSystemPromptIntents(known?.id ?? harness).append
158
+ ? "appendSystemPrompt"
159
+ : "instructions";
160
+ return composeAgentProfileGuidance(profile, blocks, channel, {
161
+ replaceSources: PROFILE_KB_SOURCES,
162
+ });
163
+ }
@@ -0,0 +1,9 @@
1
+ import type { ProfileKbModel } from "./types.js";
2
+ /**
3
+ * Current frontier models, from vendor sources read on 2026-09-22.
4
+ *
5
+ * `prompt` lines go into the model's prompt; `operator` lines configure the
6
+ * run. Vendor facts carry their source. Nothing here compares one model with
7
+ * another.
8
+ */
9
+ export declare const profileKbModels: readonly ProfileKbModel[];
@@ -0,0 +1,412 @@
1
+ const CHECKED = "2026-09-22";
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
+ }
12
+ /**
13
+ * Current frontier models, from vendor sources read on 2026-09-22.
14
+ *
15
+ * `prompt` lines go into the model's prompt; `operator` lines configure the
16
+ * run. Vendor facts carry their source. Nothing here compares one model with
17
+ * another.
18
+ */
19
+ export const profileKbModels = [
20
+ {
21
+ id: "claude-opus-5-5",
22
+ name: "Claude Opus 5.5",
23
+ vendor: "Anthropic",
24
+ surfaces: ["api"],
25
+ aliases: ["anthropic/claude-opus-5-5", "anthropic.claude-opus-5-5"],
26
+ defaultEffort: "medium",
27
+ sources: [
28
+ { url: `${claudeDocs}/models/opus-5-5/overview`, checkedAt: CHECKED },
29
+ {
30
+ url: `${claudeDocs}/build-with-claude/prompt-engineering/prompting-claude-opus-5-5`,
31
+ checkedAt: CHECKED,
32
+ },
33
+ {
34
+ url: "https://www.anthropic.com/claude-opus-5-5-system-card",
35
+ checkedAt: CHECKED,
36
+ note: "system card, linked from the model overview",
37
+ },
38
+ ],
39
+ prompt: [
40
+ "Keep the task's parts in a checklist you update, and keep working until every item is done or blocked.",
41
+ "Put status notes and recommendations in the same message as your next tool call; end the turn only when the work is complete or nothing can move without the user.",
42
+ "Before acting on a loosely specified task, explore the relevant files, records, and sources, including ones the task does not name, and use what you find.",
43
+ "When a lead agent can delegate, run independent slices in parallel subagents and pace the work to finish early.",
44
+ "For dense charts, diagrams, or screenshots, crop and zoom with the available image tools before reading values.",
45
+ ],
46
+ operator: [
47
+ "Model id claude-opus-5-5; 1M context, 128K max output; released 2026-09-22.",
48
+ "Adaptive thinking is always on. Start at effort medium, measure low for cost, and reserve xhigh and max for measured gains.",
49
+ "Set max_tokens to 128000 for long agentic turns: thinking counts toward it.",
50
+ "Keep history append-only and pass thinking blocks back unchanged; change instructions with mid-conversation system messages.",
51
+ "Set thinking display to updates to receive progress notes between tool calls.",
52
+ "For unattended loops, treat a text-only end of turn as a report: name the open items in a short user message, at most two or three times.",
53
+ "For multi-agent runs, append elapsed time against a budget (for example `elapsed 340s / 1200s`) to each message.",
54
+ ],
55
+ },
56
+ {
57
+ id: "claude-fable-5-1",
58
+ name: "Claude Fable 5.1",
59
+ vendor: "Anthropic",
60
+ surfaces: ["api"],
61
+ aliases: ["anthropic/claude-fable-5-1", "anthropic.claude-fable-5-1"],
62
+ defaultEffort: "high",
63
+ sources: [
64
+ { url: `${claudeDocs}/models/fable-5-1/overview`, checkedAt: CHECKED },
65
+ {
66
+ url: `${claudeDocs}/build-with-claude/prompt-engineering/prompting-claude-fable-5-1`,
67
+ checkedAt: CHECKED,
68
+ },
69
+ ],
70
+ prompt: [
71
+ "You are operating autonomously: for reversible actions that follow from the request, proceed without asking; stop only for destructive actions or scope changes the user must decide.",
72
+ "Before ending your turn, check your last paragraph: if it is a plan, a list of next steps, or a promise, do that work now with tool calls.",
73
+ "The request sets the scope: deliver all of it, keep changes to what it needs, and report other findings as follow-ups.",
74
+ "First privately list what you need next; then request every item that does not depend on another's result in one response.",
75
+ "Say in a line what you are about to do, give brief updates while you work, and close with a recap that stands on its own.",
76
+ "Edit files surgically when a targeted edit gives the same result.",
77
+ "Write plainly: when a literal phrase is available, use it.",
78
+ ],
79
+ operator: [
80
+ "Model id claude-fable-5-1; 1M context, 128K max output; released 2026-09-01. For demanding reasoning and long-horizon agentic work.",
81
+ "Adaptive thinking only. Start at effort high and sweep low, medium, xhigh, and max against your evals.",
82
+ "Keep history append-only; send per-turn reminders as turn-scoped system messages (clear_at next_user_message).",
83
+ "Let the lead agent keep working while subagents run: return from the spawn tool immediately and deliver results in a later user message.",
84
+ "At xhigh and max, leave max_tokens room for thinking plus the deliverable.",
85
+ ],
86
+ },
87
+ {
88
+ id: "claude-sonnet-5",
89
+ name: "Claude Sonnet 5",
90
+ vendor: "Anthropic",
91
+ surfaces: ["api", "router"],
92
+ aliases: ["anthropic/claude-sonnet-5", "anthropic.claude-sonnet-5"],
93
+ defaultEffort: "high",
94
+ sources: [
95
+ { url: `${claudeDocs}/models/overview`, checkedAt: CHECKED },
96
+ {
97
+ url: `${claudeDocs}/build-with-claude/prompt-engineering/prompting-claude-sonnet-5`,
98
+ checkedAt: CHECKED,
99
+ },
100
+ routerCheck("claude-sonnet-5 returned HTTP 200"),
101
+ ],
102
+ prompt: [
103
+ "Apply each instruction to every case it names; where an instruction should apply broadly, the prompt says so.",
104
+ "For review work, report every issue you find with a confidence and severity; a later step filters them.",
105
+ "Give regular, short progress updates through long agentic work.",
106
+ ],
107
+ operator: [
108
+ "Model id claude-sonnet-5; 1M context, 128K max output.",
109
+ "Adaptive thinking is on by default. Effort defaults to high; use xhigh for the hardest coding and agentic tasks.",
110
+ "Instructions are followed literally, especially at low effort: state scope explicitly.",
111
+ "Give the task, intent, and constraints up front in the first turn.",
112
+ "Temperature, top_p, and top_k stay at defaults; steer tone and variety in the prompt.",
113
+ ],
114
+ },
115
+ {
116
+ id: "claude-haiku-4-5",
117
+ name: "Claude Haiku 4.5",
118
+ vendor: "Anthropic",
119
+ surfaces: ["api"],
120
+ aliases: [
121
+ "claude-haiku-4-5-20251001",
122
+ "anthropic/claude-haiku-4-5",
123
+ "anthropic.claude-haiku-4-5",
124
+ ],
125
+ sources: [
126
+ { url: `${claudeDocs}/models/haiku-4-5/overview`, checkedAt: CHECKED },
127
+ ],
128
+ prompt: [
129
+ "Answer directly and keep each step scoped to the task in hand.",
130
+ ],
131
+ operator: [
132
+ "Model id claude-haiku-4-5-20251001 (alias claude-haiku-4-5); 200K context, 64K max output.",
133
+ "Uses manual extended thinking (thinking.type enabled with budget_tokens); it takes no effort parameter.",
134
+ "Retirement date: 2026-10-15 at the earliest.",
135
+ ],
136
+ },
137
+ {
138
+ id: "gpt-6-pro",
139
+ name: "GPT-6 Pro",
140
+ vendor: "OpenAI",
141
+ surfaces: ["chatgpt"],
142
+ aliases: ["GPT-6 Pro"],
143
+ sources: [
144
+ {
145
+ url: "https://help.openai.com/en/articles/20001354-gpt-56-and-gpt-6-pro-in-chatgpt",
146
+ checkedAt: CHECKED,
147
+ note: "read through search results; direct fetch returned HTTP 403",
148
+ },
149
+ { url: "https://learn.chatgpt.com/docs/prompting", checkedAt: CHECKED },
150
+ ],
151
+ prompt: [
152
+ "Start with the result you want, then give the goal, the context that helps, the output format, and what must stay unchanged.",
153
+ "Describe a process only when the process itself matters; otherwise leave room to search, compare, and adjust.",
154
+ "End with a final check: confirm each deliverable and flag anything you could not verify.",
155
+ ],
156
+ operator: [
157
+ "A ChatGPT model mode powered by GPT-6 Astra, on Pro, Business, and Enterprise plans. It has a weekly usage limit.",
158
+ "No API model id; reach it through ChatGPT (chatgpt-fleet).",
159
+ ],
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
+ },
251
+ {
252
+ id: "gpt-5.6-sol",
253
+ name: "GPT-5.6 Sol",
254
+ vendor: "OpenAI",
255
+ surfaces: ["codex", "api", "router"],
256
+ aliases: ["openai/gpt-5.6-sol"],
257
+ defaultEffort: "medium",
258
+ sources: [
259
+ {
260
+ url: "https://developers.openai.com/api/docs/models/gpt-5.6-sol",
261
+ checkedAt: CHECKED,
262
+ },
263
+ {
264
+ url: "https://openai.com/index/builders-guide-to-gpt-5-6/",
265
+ checkedAt: CHECKED,
266
+ note: "read through search results; direct fetch returned HTTP 403",
267
+ },
268
+ {
269
+ url: "file://~/.codex/models_cache.json",
270
+ checkedAt: CHECKED,
271
+ note: "codex-cli 0.156.1 served list; `codex exec -m gpt-5.6-sol` returned OK",
272
+ },
273
+ routerCheck("gpt-5.6-sol returned HTTP 200"),
274
+ ],
275
+ prompt: [
276
+ "Work from the outcome: know what good looks like and the stopping condition, then choose the method yourself.",
277
+ "Carry the task to completion: gather context, plan, implement, verify, and report the evidence.",
278
+ "Run independent reads and checks in parallel.",
279
+ ],
280
+ operator: [
281
+ "Flagship GPT-5.6 tier for complex professional work: architecture, security review, repo-wide debugging.",
282
+ "Keep the system prompt lean: objective, context, hard constraints, approval boundaries, success criteria, required evidence, and output format.",
283
+ "Efforts: none, low, medium (API default), high, xhigh, max; Codex adds ultra, which runs parallel agents. Codex lists low as its default for this model.",
284
+ "1,050,000-token context, 128K max output.",
285
+ ],
286
+ },
287
+ {
288
+ id: "gpt-5.6-terra",
289
+ name: "GPT-5.6 Terra",
290
+ vendor: "OpenAI",
291
+ surfaces: ["codex", "api", "router"],
292
+ aliases: ["openai/gpt-5.6-terra"],
293
+ defaultEffort: "medium",
294
+ sources: [
295
+ {
296
+ url: "https://developers.openai.com/api/docs/models/gpt-5.6-terra",
297
+ checkedAt: CHECKED,
298
+ },
299
+ {
300
+ url: "file://~/.codex/models_cache.json",
301
+ checkedAt: CHECKED,
302
+ note: "codex-cli 0.156.1 served list; `codex exec -m gpt-5.6-terra` returned OK",
303
+ },
304
+ routerCheck("gpt-5.6-terra returned HTTP 200"),
305
+ ],
306
+ prompt: [
307
+ "Work from the outcome and the stopping condition; choose the method yourself and verify before you report.",
308
+ ],
309
+ operator: [
310
+ "Balanced GPT-5.6 tier for everyday professional coding.",
311
+ "Efforts: none, low, medium (default), high, xhigh, max; Codex adds ultra.",
312
+ "1,050,000-token context, 128K max output.",
313
+ ],
314
+ },
315
+ {
316
+ id: "gpt-5.6-luna",
317
+ name: "GPT-5.6 Luna",
318
+ vendor: "OpenAI",
319
+ surfaces: ["codex", "api", "router"],
320
+ aliases: ["openai/gpt-5.6-luna"],
321
+ defaultEffort: "medium",
322
+ sources: [
323
+ {
324
+ url: "https://developers.openai.com/api/docs/models/gpt-5.6-luna",
325
+ checkedAt: CHECKED,
326
+ },
327
+ {
328
+ url: "file://~/.codex/models_cache.json",
329
+ checkedAt: CHECKED,
330
+ note: "codex-cli 0.156.1 served list; `codex exec -m gpt-5.6-luna` returned OK",
331
+ },
332
+ routerCheck("gpt-5.6-luna returned HTTP 200"),
333
+ ],
334
+ prompt: [
335
+ "Follow the stated format exactly and finish each item before moving to the next.",
336
+ ],
337
+ operator: [
338
+ "GPT-5.6 tier for cost-sensitive, high-volume work: summarizing, labeling, extraction, scaffolds.",
339
+ "Efforts: none, low, medium (default), high, xhigh, max. Raise effort for harder items.",
340
+ "1,050,000-token context, 128K max output.",
341
+ ],
342
+ },
343
+ {
344
+ id: "deepseek-v4.1-flash",
345
+ name: "DeepSeek V4.1 Flash",
346
+ vendor: "DeepSeek",
347
+ surfaces: ["api", "router"],
348
+ aliases: ["deepseek-flash", "deepseek/deepseek-v4.1-flash"],
349
+ sources: [
350
+ {
351
+ url: "https://api-docs.deepseek.com/news/news260910/",
352
+ checkedAt: CHECKED,
353
+ },
354
+ {
355
+ url: "https://api-docs.deepseek.com/quick_start/pricing",
356
+ checkedAt: CHECKED,
357
+ },
358
+ routerCheck("deepseek/deepseek-v4.1-flash returned HTTP 200 and served that id"),
359
+ ],
360
+ prompt: [
361
+ "State the goal and the finished result up front, and use the tools you are given to check your work.",
362
+ ],
363
+ operator: [
364
+ "Vendor API id deepseek-flash (released 2026-09-10); on the Tangle router, deepseek/deepseek-v4.1-flash.",
365
+ "1M context, 384K max output; thinking mode is the default and non-thinking is available. Native vision.",
366
+ "OpenCode supports it as an official partner harness.",
367
+ ],
368
+ },
369
+ {
370
+ id: "glm-5.3",
371
+ name: "GLM-5.3",
372
+ vendor: "Z.ai",
373
+ surfaces: ["api", "router"],
374
+ aliases: ["z-ai/glm-5.3", "zai-coding-plan/glm-5.3"],
375
+ defaultEffort: "ultracode",
376
+ sources: [
377
+ { url: "https://docs.z.ai/guides/llm/glm-5.3", checkedAt: CHECKED },
378
+ routerCheck("glm-5.3 returned HTTP 200, served as z-ai/glm-5.3"),
379
+ ],
380
+ prompt: [
381
+ "Work the task through to a verified result, using the tools to run and check each change.",
382
+ ],
383
+ operator: [
384
+ "Model id glm-5.3; text only; 1M context, 128K max output. Built for long-horizon software engineering.",
385
+ "Reasoning is always on: thinking.type enabled with reasoning_effort low, high, or max (default). Use max for coding.",
386
+ ],
387
+ },
388
+ {
389
+ id: "kimi-k3",
390
+ name: "Kimi K3",
391
+ vendor: "Moonshot AI",
392
+ surfaces: ["api", "router"],
393
+ aliases: ["moonshotai/kimi-k3", "kimi-code/k3"],
394
+ defaultEffort: "ultracode",
395
+ sources: [
396
+ {
397
+ url: "https://platform.kimi.ai/docs/guide/kimi-k3-quickstart",
398
+ checkedAt: CHECKED,
399
+ },
400
+ routerCheck("kimi-k3 returned HTTP 200, served as moonshotai/kimi-k3"),
401
+ ],
402
+ prompt: [
403
+ "Navigate the repository, run the code, and iterate against tests, logs, and runtime output until the result holds.",
404
+ ],
405
+ operator: [
406
+ "Model id kimi-k3; 1M context; native vision. For long-horizon coding, knowledge work, and reasoning.",
407
+ "Thinking is always on: reasoning_effort low, high, or max (default).",
408
+ "Sampling is fixed (temperature 1.0, top_p 0.95); omit sampling parameters.",
409
+ "Return the complete assistant message unchanged in multi-turn and tool-call histories.",
410
+ ],
411
+ },
412
+ ];
@@ -0,0 +1,12 @@
1
+ import type { ProfileKbDiscrepancy, ProfileKbLearning } from "./types.js";
2
+ /**
3
+ * Lessons this platform measured itself. A lesson enters only after an
4
+ * agent-eval check reproduced it strongly; none has met that bar yet, so the
5
+ * list is empty rather than filled with single-run observations.
6
+ */
7
+ export declare const profileKbLearnings: readonly ProfileKbLearning[];
8
+ /**
9
+ * Names the platform asked for that vendor sources state differently, as read
10
+ * on 2026-09-22. Recorded instead of guessed.
11
+ */
12
+ export declare const profileKbDiscrepancies: readonly ProfileKbDiscrepancy[];
@@ -0,0 +1,62 @@
1
+ const CHECKED = "2026-09-22";
2
+ /**
3
+ * Lessons this platform measured itself. A lesson enters only after an
4
+ * agent-eval check reproduced it strongly; none has met that bar yet, so the
5
+ * list is empty rather than filled with single-run observations.
6
+ */
7
+ export const profileKbLearnings = [];
8
+ /**
9
+ * Names the platform asked for that vendor sources state differently, as read
10
+ * on 2026-09-22. Recorded instead of guessed.
11
+ */
12
+ export const profileKbDiscrepancies = [
13
+ {
14
+ subject: "OpenAI Codex models",
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 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.",
19
+ sources: [
20
+ { url: "https://learn.chatgpt.com/docs/models", checkedAt: CHECKED },
21
+ {
22
+ url: "cli:codex exec -m <model> 'Reply with exactly: OK'",
23
+ checkedAt: CHECKED,
24
+ note: "codex-cli 0.156.1",
25
+ },
26
+ ],
27
+ },
28
+ {
29
+ subject: "DeepSeek V4.1 Pro",
30
+ requested: "DeepSeek V4.1 Pro",
31
+ observed: "DeepSeek has released V4.1 Flash only (2026-09-10). Its changelog says the V4 Pro API continues, its pricing page maps " +
32
+ "deepseek-v4-pro to DeepSeek-V4-Pro-0813, and its V4.1 Flash announcement says deepseek-v4-pro routes to V4.1 Flash from 2026-09-14. " +
33
+ "On the Tangle router, deepseek-v4-pro returned HTTP 503 (provider quota exhausted). No V4.1 Pro entry exists until DeepSeek ships it.",
34
+ sources: [
35
+ { url: "https://api-docs.deepseek.com/updates/", checkedAt: CHECKED },
36
+ { url: "https://api-docs.deepseek.com/quick_start/pricing", checkedAt: CHECKED },
37
+ { url: "https://api-docs.deepseek.com/news/news260910/", checkedAt: CHECKED },
38
+ ],
39
+ },
40
+ {
41
+ subject: "DeepSeek V4.1 Flash router id",
42
+ requested: "deepseek-flash (the vendor id)",
43
+ observed: "On the Tangle router, deepseek-flash returned HTTP 503 (provider_pricing_unavailable); deepseek/deepseek-v4.1-flash returned HTTP 200.",
44
+ sources: [
45
+ {
46
+ url: "https://router.tangle.tools/v1/chat/completions",
47
+ checkedAt: CHECKED,
48
+ },
49
+ ],
50
+ },
51
+ {
52
+ subject: "GPT-6 Pro",
53
+ requested: "GPT-6 Pro in ChatGPT",
54
+ observed: "OpenAI's help center names GPT-6 Pro as a ChatGPT mode powered by GPT-6 Astra; it has no API model id.",
55
+ sources: [
56
+ {
57
+ url: "https://help.openai.com/en/articles/20001354-gpt-56-and-gpt-6-pro-in-chatgpt",
58
+ checkedAt: CHECKED,
59
+ },
60
+ ],
61
+ },
62
+ ];
@@ -0,0 +1,76 @@
1
+ import type { HarnessType } from "../harness.js";
2
+ import type { ReasoningEffort } from "../agent-profile.js";
3
+ /** A vendor or measured source, with the date someone read or ran it. */
4
+ export interface ProfileKbSource {
5
+ url: string;
6
+ /** ISO date (YYYY-MM-DD) the source was read or the command was run. */
7
+ checkedAt: string;
8
+ /** What the source is, when the URL alone does not say. */
9
+ note?: string;
10
+ }
11
+ /**
12
+ * How to get the best from one harness.
13
+ *
14
+ * `prompt` lines are addressed to the agent running inside the harness and
15
+ * are composed into its profile. `operator` lines are for whoever launches
16
+ * the harness; they are never sent to a model.
17
+ */
18
+ export interface ProfileKbHarness {
19
+ id: HarnessType;
20
+ name: string;
21
+ /** Installed version the guidance was checked against. */
22
+ version: string;
23
+ sources: ProfileKbSource[];
24
+ prompt: string[];
25
+ operator: string[];
26
+ }
27
+ /** Where a model is reached. A model id means different things on each surface. */
28
+ export type ProfileKbSurface = "api" | "codex" | "chatgpt" | "router";
29
+ /**
30
+ * How to get the best from one model.
31
+ *
32
+ * Vendor guidance only, each entry citing its source and date. The guidance is
33
+ * positive and specific to this model; it never ranks the model against
34
+ * another.
35
+ */
36
+ export interface ProfileKbModel {
37
+ /** Canonical vendor id. */
38
+ id: string;
39
+ name: string;
40
+ vendor: string;
41
+ surfaces: ProfileKbSurface[];
42
+ /** Other spellings that resolve to this model: router ids, harness aliases. */
43
+ aliases: string[];
44
+ /** Vendor default reasoning effort, mapped onto the portable scale. */
45
+ defaultEffort?: ReasoningEffort;
46
+ sources: ProfileKbSource[];
47
+ prompt: string[];
48
+ operator: string[];
49
+ }
50
+ /**
51
+ * A lesson this platform learned itself. It enters the knowledge base only
52
+ * after an agent-eval check reproduced it, and it stays scoped to the
53
+ * harness or model it was measured on.
54
+ */
55
+ export interface ProfileKbLearning {
56
+ id: string;
57
+ appliesTo: {
58
+ harness?: HarnessType;
59
+ model?: string;
60
+ };
61
+ text: string;
62
+ evidence: {
63
+ /** The agent-eval check that reproduced the lesson. */
64
+ check: string;
65
+ /** Independent reproductions that passed. */
66
+ reproductions: number;
67
+ source: ProfileKbSource;
68
+ };
69
+ }
70
+ /** A name the platform asked for that a vendor source does not confirm as stated. */
71
+ export interface ProfileKbDiscrepancy {
72
+ subject: string;
73
+ requested: string;
74
+ observed: string;
75
+ sources: ProfileKbSource[];
76
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
@@ -66,6 +66,11 @@
66
66
  "import": "./dist/interaction.js",
67
67
  "types": "./dist/interaction.d.ts",
68
68
  "default": "./dist/interaction.js"
69
+ },
70
+ "./profile-kb": {
71
+ "import": "./dist/profile-kb/index.js",
72
+ "types": "./dist/profile-kb/index.d.ts",
73
+ "default": "./dist/profile-kb/index.js"
69
74
  }
70
75
  },
71
76
  "repository": {