Skip to content

Commit 491da78

Browse files
committed
Constrain guideline omit ids and validate profiles at load
Reject unknown promptSectionOmit ids in ProfileSchema via type.enumerated over GUIDELINE_SUB_BLOCK_IDS so profile typos fail closed at load instead of silently keeping the full guidelines. Type omit as GuidelineSubBlockId end to end and drop the unwired KEEPSTYLE_PROMPT_SECTION_OMIT export.
1 parent ecc28a8 commit 491da78

6 files changed

Lines changed: 38 additions & 34 deletions

File tree

src/agent/prompts.test.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
buildPromptDisciplineBlock,
77
buildSubAgentSystemPrompt,
88
GUIDELINE_SUB_BLOCK_IDS,
9-
KEEPSTYLE_PROMPT_SECTION_OMIT,
109
} from "./prompts.js";
1110
import { CORE_TOOL_NAMES, CATALOG_TOOL_NAMES } from "./tool-search.js";
1211

@@ -222,27 +221,17 @@ Orchestration:
222221
- If context is compacted automatically, do not stop tasks early due to token fear; persist progress via manage_tasks and worker reports.`);
223222
});
224223

225-
it("keepstyle omit keeps response style, drops tool-choice / ask-vs-proceed / orchestration", () => {
226-
expect([...KEEPSTYLE_PROMPT_SECTION_OMIT].sort()).toEqual([
227-
"askVsProceed",
228-
"orchestration",
229-
"toolChoice",
230-
]);
231-
const guidelines = buildGuidelines({ omit: KEEPSTYLE_PROMPT_SECTION_OMIT });
224+
it("omit keeps response style, drops tool-choice / ask-vs-proceed / orchestration", () => {
225+
const guidelines = buildGuidelines({
226+
omit: ["toolChoice", "askVsProceed", "orchestration"],
227+
});
232228
expect(guidelines).toContain("Response style:");
233229
expect(guidelines).toContain("Scope and conventions:");
234230
expect(guidelines).not.toContain("Tool choice:");
235231
expect(guidelines).not.toContain("Ask vs proceed:");
236232
expect(guidelines).not.toContain("Orchestration:");
237233
});
238234

239-
it("ignores unknown omit ids", () => {
240-
const guidelines = buildGuidelines({ omit: ["no-such-block"] });
241-
expect(guidelines).toContain("Response style:");
242-
expect(guidelines).toContain("Tool choice:");
243-
expect(guidelines).toContain("Orchestration:");
244-
});
245-
246235
it("threads guidelineConfig through the chat system prompt", () => {
247236
const full = buildChatSystemPrompt(
248237
undefined,
@@ -260,7 +249,7 @@ Orchestration:
260249
[],
261250
"orchestrator",
262251
undefined,
263-
{ omit: KEEPSTYLE_PROMPT_SECTION_OMIT },
252+
{ omit: ["toolChoice", "askVsProceed", "orchestration"] },
264253
);
265254
expect(keepstyle).toContain("Response style:");
266255
expect(keepstyle).not.toContain("Tool choice:");

src/agent/prompts.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,11 @@ export const GUIDELINE_SUB_BLOCK_IDS = [
120120

121121
export type GuidelineSubBlockId = (typeof GUIDELINE_SUB_BLOCK_IDS)[number];
122122

123-
/** Id-based guideline policy: which sub-blocks to drop. Unknown ids are ignored. */
123+
/** Id-based guideline policy: which sub-blocks to drop. */
124124
export interface GuidelineConfig {
125-
readonly omit?: readonly string[];
125+
readonly omit?: readonly GuidelineSubBlockId[];
126126
}
127127

128-
/**
129-
* Omit-set for the keepstyle guideline footprint: terse response style stays,
130-
* tool-choice / ask-vs-proceed / orchestration guidance drops. Scope and
131-
* conventions (build gate, verification evidence) always stay.
132-
*/
133-
export const KEEPSTYLE_PROMPT_SECTION_OMIT: readonly string[] = [
134-
"toolChoice",
135-
"askVsProceed",
136-
"orchestration",
137-
];
138-
139128
interface GuidelineBlockContext {
140129
readonly subAgent: boolean;
141130
readonly askDirector: boolean;
@@ -235,8 +224,8 @@ export function buildGuidelines(
235224
// Picks the collection-path copy: wait_agents vs mailbox mail.
236225
waitAgentsMounted?: boolean;
237226
// Id-based policy: drop the named sub-blocks (see GUIDELINE_SUB_BLOCKS).
238-
// Unknown ids are ignored; empty (default) keeps the full guidelines.
239-
omit?: readonly string[];
227+
// Empty (default) keeps the full guidelines.
228+
omit?: readonly GuidelineSubBlockId[];
240229
} = {},
241230
): string {
242231
const ctx: GuidelineBlockContext = {

src/config/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { COMMAND_NAME } from "../branding.js";
1212

1313
import { isDirectorId } from "../agent/directors/registry.js";
1414
import { DIRECTOR_IDS, type DirectorId } from "../agent/directors/types.js";
15+
import type { GuidelineSubBlockId } from "../agent/prompts.js";
1516
import {
1617
validateEffort,
1718
type ReasoningEffort,
@@ -564,7 +565,7 @@ export interface Config {
564565
systemPromptExtensions?: string[];
565566
// Guideline sub-block ids to drop from the chat system prompt (see
566567
// GUIDELINE_SUB_BLOCK_IDS in agent/prompts.ts). Omitted = full guidelines.
567-
promptSectionOmit?: string[];
568+
promptSectionOmit?: GuidelineSubBlockId[];
568569
// Per-call inactivity timeout in ms (default 120_000 in the harness). Tune
569570
// higher for reasoning models with long silent-thinking stretches.
570571
inactivityTimeoutMs?: number;

src/config/profiles.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import { homedir } from "node:os";
33
import { join } from "node:path";
44
import { type } from "arktype";
55
import { SETTINGS_DIR_NAME } from "../branding.js";
6+
import { GUIDELINE_SUB_BLOCK_IDS } from "../agent/prompts.js";
67

78
const ProfileSchema = type({
89
"profile?": "string",
910
"model?": "string",
1011
"systemPromptExtensions?": "string[]",
1112
// Guideline sub-block ids to drop from the chat system prompt (see
1213
// GUIDELINE_SUB_BLOCK_IDS in agent/prompts.ts). Omitted = full guidelines.
13-
"promptSectionOmit?": "string[]",
14+
// Unknown ids are rejected here so profile typos fail closed at load
15+
// instead of silently keeping the full guidelines.
16+
"promptSectionOmit?": type.enumerated(...GUIDELINE_SUB_BLOCK_IDS).array(),
1417
// Per-call inactivity timeout in milliseconds. If the provider yields no
1518
// inference event for this many ms, the call is aborted and the user sees
1619
// "Request timed out". Default in the inference harness is 120_000 (2 min).

src/profiles.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,27 @@ test("loadProfile parses systemPromptExtensions", async () => {
5757
});
5858
});
5959

60+
test("loadProfile parses promptSectionOmit", async () => {
61+
const dir = makeTmp();
62+
await mkdir(dir, { recursive: true });
63+
const path = join(dir, "profile.json");
64+
await writeJson(path, {
65+
promptSectionOmit: ["toolChoice", "orchestration"],
66+
});
67+
const result = await loadProfile(path);
68+
expect(result).toEqual({
69+
promptSectionOmit: ["toolChoice", "orchestration"],
70+
});
71+
});
72+
73+
test("loadProfile rejects unknown promptSectionOmit ids", async () => {
74+
const dir = makeTmp();
75+
await mkdir(dir, { recursive: true });
76+
const path = join(dir, "profile.json");
77+
await writeJson(path, { promptSectionOmit: ["no-such-block"] });
78+
await expect(loadProfile(path)).rejects.toThrow(/promptSectionOmit/);
79+
});
80+
6081
test("loadProfile rejects unknown keys", async () => {
6182
const dir = makeTmp();
6283
await mkdir(dir, { recursive: true });

src/session/runtime-assembly.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
import type { Compactor } from "@intx/types/runtime";
1515

1616
import { buildChatSystemPrompt } from "../agent/prompts.js";
17+
import type { GuidelineSubBlockId } from "../agent/prompts.js";
1718
import type { ToolAvailability } from "../agent/tool-search.js";
1819
import { gatherEnvironment } from "../agent/environment.js";
1920
import {
@@ -290,7 +291,7 @@ export interface SessionChatPromptArgs {
290291
toolAvailability: ToolAvailability;
291292
// Guideline sub-block ids to drop (see GUIDELINE_SUB_BLOCK_IDS).
292293
// Omitted = full guidelines.
293-
promptSectionOmit?: readonly string[];
294+
promptSectionOmit?: readonly GuidelineSubBlockId[];
294295
// Session-start snapshot from createAgentToolset. When provided, skip
295296
// rediscovery so the prompt listing and skill_search share one catalog.
296297
skills?: readonly SkillSummary[];

0 commit comments

Comments
 (0)