practice agent: problem + conclude tools, doctrine prompt, handler branch

- tools/practice.ts: problem (terminate pose; commitment hidden in args)
  and conclude (non-terminating; outcome/verdict/reveal/rung/missClass/
  retest), gated by activeProblemOf — pose-while-active and conclude-
  with-nothing-active both return invalidInputResult.
- prompts/practice/00-base.md (voice + accuracy + tool mechanics) and
  10-doctrine.md (the owner's doctrine verbatim; one approved edit —
  §I material arrival generalized). Practice composes its own directory
  ALONE: no teaching base, no shared fragments.
- createPracticeAgent (teacher-agent.ts); chat-handler branch — kind
  'practice' builds the practitioner from turn 1 and skips extraction.
- problemResult/concludeResult acks in tool-execute (conclude text
  single-sourced: CONCLUDE_ACK_TEXT).
This commit is contained in:
amosblomqvist
2026-10-04 21:47:23 +03:00
parent eb7454900c
commit 4bea0b46df
9 changed files with 592 additions and 13 deletions
+27 -4
View File
@@ -14,6 +14,7 @@ import {
type ProberToolContext,
type TeacherToolContext,
} from "./tools.ts";
import { practiceTools, type PracticeToolContext } from "./tools/practice.ts";
import { quizInputObject, quizInputProblems } from "../core/question-core.ts";
import { QUESTION_TOOLS, type EnabledTools } from "../core/pending.ts";
import { toLlmMessages } from "../core/native-messages.ts";
@@ -42,8 +43,9 @@ import { toLlmMessages } from "../core/native-messages.ts";
* without ever leaking into the model's prompt.
*/
/** Which agent's prompt directory to compose. */
export type AgentKind = "prober" | "teacher";
/** Which agent's prompt directory to compose. Practice (2026-10-04)
* composes ONLY its own directory — see prompt-segments.ts. */
export type AgentKind = "prober" | "teacher" | "practice";
// P2 (2026-09-19): segment discovery + override resolution live in ONE
// canonical place now — src/store/prompt-overrides.ts (listSourceSegments
@@ -82,6 +84,7 @@ export function composeInstructions(kind: AgentKind, switches?: Record<string, b
const composedInstructions: Record<AgentKind, string> = {
prober: composeInstructions("prober"),
teacher: composeInstructions("teacher"),
practice: composeInstructions("practice"),
};
// Money channel (wave 4): the chat-handler passes a per-turn collector;
@@ -159,7 +162,11 @@ function makeQuestionStopper() {
}
function baseAgentOptions(kind: AgentKind, opts: AgentBuildOptions, tools: AgentTool<any, any>[]) {
const engine = opts.engine ?? piEngineFor(kind);
// Practice never reaches the role fallback (the handler ALWAYS injects
// the session's conversation engine — practice rides the same
// conversation-spec resolution as teaching); the cast keeps AgentKind
// ("practice" is not a ModelRole) honest about that.
const engine = opts.engine ?? piEngineFor(kind as "prober" | "teacher");
const stopper = makeQuestionStopper();
const systemPrompt = opts.promptSwitches
? composeInstructions(kind, opts.promptSwitches)
@@ -269,7 +276,23 @@ export function bareQuestionTools(): Record<string, AgentTool<any>> {
};
}
/**
* The PRACTITIONER (practice sessions, owner design 2026-10-04): built
* from turn 1 of a kind:"practice" session — no prober→teacher split,
* no plan tools, no question tools. Its ENTIRE registry is
* problem + conclude (tools/practice.ts); the doctrine prompt
* (prompts/practice/) carries the method. The question-stop rule does
* not apply (problem rides terminate: true natively —
* QUESTION_TOOLS does not include it), so baseAgentOptions's stopper is
* a no-op here; it stays wired for uniformity.
*/
export function createPracticeAgent(ctx: PracticeToolContext, opts: AgentBuildOptions = {}) {
const tools = opts.tools ?? practiceTools(ctx);
return baseAgentOptions("practice", opts, Object.values(tools));
}
export type ProberAgent = ReturnType<typeof createProberAgent>;
export type TeacherAgent = ReturnType<typeof createTeacherAgent>;
export type TeachingAgent = ProberAgent | TeacherAgent;
export type PracticeAgent = ReturnType<typeof createPracticeAgent>;
export type TeachingAgent = ProberAgent | TeacherAgent | PracticeAgent;