feat(prompts): composeInstructions honors overrides — the P2 segment reconciliation

The other half-wired seam: Platform's settings 'agent.prompts' switches
had no consumer in the agent layer, and their effectivePromptSegments
mirrored the PRE-sources-v1 loader — the shared/ fragments dir (the
learner-files doctrine) was invisible to it, so a naive delegation would
have silently dropped the segment from both agents' prompts.

- src/core/prompt-segments.ts (NEW, pure): the mirror-layout path
  grammar + segment types, defined exactly once; core stays zero-IO.
- src/agent/prompt-segments.ts (NEW, the CANONICAL discovery): base +
  shared/ + per-agent fragments, override resolution, allSegmentPaths.
  Composition is an agent concern; the agent layer reads its own tree
  (the architecture law pins agent and store apart — the first direct
  refactor attempt tripped the enforced import/no-restricted-paths
  zone, hence the three-layer split).
- src/store/prompt-overrides.ts: slimmed to the override-file CRUD
  behind the core port; shares the core grammar. Its doc no longer
  claims to be the canonical mirror.
- teacher-agent: composeInstructions(kind, switches?) composes through
  effectivePromptSegments — a segment whose switch is ON and whose
  override file exists composes the override text. Switches-less calls
  use the cached pure-source composition (byte-identical to the old
  loader, pinned by test); AgentBuildOptions.promptSwitches threads
  effective.agent.prompts per turn, composing fresh so override-file
  writes flip the very next turn.
- server barrel: re-exports the agent module's resolution for the
  settings routes (server→agent, the lawful direction).

Tests: byte-identical source pin, override swap-in (on/off/file-absent),
shared/ overridability in both agents, agent-construction threading,
updated segment-list pins with shared/ in composition order. This file
keeps owning PROMPTS_OVERRIDE_DIR (parallel-test discipline).

Gates: 671/671, typecheck, lint, build.
This commit is contained in:
amosblomqvist
2026-09-19 18:21:09 +03:00
parent 6c42393e42
commit 524bf6024a
8 changed files with 333 additions and 147 deletions
+1
View File
@@ -29,6 +29,7 @@ export * from "./layout.ts";
export * from "./plan-tree.ts";
export * from "./phases.ts";
export * from "./pending.ts";
export * from "./prompt-segments.ts";
export * from "./math-delims.ts";
export * from "./ui-message.ts";
export * from "./tool-parts.ts";
+47
View File
@@ -0,0 +1,47 @@
/**
* The pure prompt-segment grammar (P2 reconciliation, 2026-09-19):
* segment-path validation + the shared segment types, ZERO fs. Discovery
* (reading the agent's prompt tree) lives in the agent layer
* (src/agent/prompt-segments.ts — the canonical loader composeInstructions
* composes through); the override-file CRUD lives in the store adapter
* (src/store/prompt-overrides.ts). Both import THIS grammar so the
* mirror-layout rules are defined exactly once.
*
* Path grammar: "system-prompt.md" (the shared base), "shared/<fragment>"
* (sources v1: fragments composed by BOTH agents), "prober/<fragment>" /
* "teacher/<fragment>" — where <fragment> is numeric-prefixed + .md
* (e.g. 20-teaching.md). POSIX-relative, no traversal, no nesting.
*/
/** A prompt fragment: numeric order key + .md (e.g. 20-teaching.md). Nothing else loads. */
export const FRAGMENT_RE = /^\d+-[\w.-]+\.md$/;
/** Which agent's prompt directory to compose. */
export type PromptAgent = "prober" | "teacher";
/** One segment of one agent's composed prompt. */
export interface PromptSegmentInfo {
/** The override/store key — mirrors the source layout ("system-prompt.md",
* "shared/05-learner-files.md", "teacher/20-teaching.md"). Stable across
* agents: the base and shared/ fragments use the SAME path in both
* agents' lists. */
path: string;
/** The source text (what the segment runs without an override). */
sourceText: string;
/** The user's override text, or null when no file exists on disk. */
overrideText: string | null;
/** Effective state: the settings switch AND the file both present. */
enabled: boolean;
/** True for segments BOTH agents compose — the base and shared/ fragments. */
shared: boolean;
}
/** Validate a segment path against the mirror-layout rules (see module
* doc). Exported for the routes, which also check membership in the
* CURRENT source segment list. */
export function isValidSegmentPath(segmentPath: string): boolean {
if (segmentPath === "system-prompt.md") return true;
const m = /^(prober|teacher|shared)\/([\w.-]+\.md)$/.exec(segmentPath);
if (!m || m[2] === undefined) return false;
return FRAGMENT_RE.test(m[2]);
}