@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 +4 -1
- package/dist/agent-profile.d.ts +17 -4
- package/dist/agent-profile.js +96 -13
- package/dist/profile-kb/harnesses.js +16 -8
- package/dist/profile-kb/index.d.ts +10 -3
- package/dist/profile-kb/index.js +48 -12
- package/dist/profile-kb/models.js +114 -23
- package/dist/profile-kb/records.js +4 -4
- package/package.json +1 -1
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
|
|
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
|
|
package/dist/agent-profile.d.ts
CHANGED
|
@@ -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
|
|
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.
|
|
550
|
-
*
|
|
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 {};
|
package/dist/agent-profile.js
CHANGED
|
@@ -173,38 +173,121 @@ export function mergeAgentProfiles(base, overlay) {
|
|
|
173
173
|
extensions: mergeRecord(base?.extensions, overlay?.extensions),
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
|
-
const
|
|
177
|
-
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
/**
|
|
188
|
-
function
|
|
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
|
-
|
|
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
|
|
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.
|
|
202
|
-
*
|
|
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) => !
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
79
|
+
version: "0.87.1",
|
|
80
80
|
sources: [
|
|
81
|
-
{
|
|
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.
|
|
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.
|
|
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 {
|
|
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
|
|
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;
|
package/dist/profile-kb/index.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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 =
|
|
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
|
|
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 =
|
|
83
|
-
|
|
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
|
|
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
|
|
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"
|
|
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"
|
|
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"
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
17
|
-
"
|
|
18
|
-
"
|
|
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
|
},
|