practice — discretized parameter axes: named levels, never a number (owner-approved 2026-09-29)

Contracts: LadderRung 0-4, CueDistance 0-2, Presentation theory|
application; PracticeParams carries the three named levels. Doc
comments hold the three definition tables verbatim (rung names +
definitions from the ladder doctrine; cue levels; presentation
levels).

Core: LADDER_RUNGS/CUE_LEVELS/PRESENTATIONS tables in src/core/
practice.ts (definitions verbatim) — ONE source spoken by the agents'
prompts, the dialog's stepped controls, and the exam header; core
barrel re-exports practice.ts (pure domain, browser-safe).

Agents: Part I's ladder intro now 'five rungs, numbered; the request
arrives as a rung' with bullets numbered 0-4 (definitions untouched);
Part III's parameters block replaced with the named-level doctrine
(rung = exact class, no continuum; cue counts lesson examples AND
previous problems' situations; presentation dresses, never changes);
partitioner's RESPECT THE PARAMETERS replaced (low rungs = many small
assignments, high = larger; far cue makes confusables-together MORE
important; presentation does not shape the partition). renderParams
outputs the exact named-level block. Anchor/0..1 language swept dead.

Service + store: strict union validation — floats between levels,
out-of-range, wrong strings, legacy dial shapes all 400 at the API
and fail isProblemSet (legacy sets skipped from lists; no migration —
data/practice is disposable scratch, owner wipes before testing).

UI: dialog = 5-detent ladder slider + 3-detent cue slider (each shows
the level NAME + one-line definition) + Theory|Application segmented
toggle; defaults rung 2 / cue 1 / theory; Create posts the new shape.
Exam header gains the levels line ('Direct exercise · Adjacent
context · Theory').

Tests: renderParams exact-block pin; strict-validation pins (new
tests/practice-service.test.ts — bad unions 400, legacy shape 400);
store pins (legacy floats + 2.5 rung fail); render pins (detents +
names + definitions, defaults, detent-swap, toggle active segment,
levels line); every params fixture moved off floats. 1138/1138, tsc
0, lint 0, build 0.

Docs: architecture.md — discretization paragraph (compact tables,
named-level invocation, defaults, one-table source), UI mentions;
README axes sentence.
This commit is contained in:
amosblomqvist
2026-09-29 20:06:35 +03:00
parent a53127a764
commit e92679254d
15 changed files with 319 additions and 106 deletions
+20 -12
View File
@@ -36,7 +36,7 @@ import type { PiEngine } from "../models.ts";
import { runSubmitAgent } from "./submit-agent.ts";
import { callUsage } from "./extraction.ts";
import type { CallUsage, PracticeParams, ReasoningChain } from "../../core/contracts.ts";
import { chainCoverage } from "../../core/practice.ts";
import { chainCoverage, CUE_LEVELS, LADDER_RUNGS, PRESENTATIONS } from "../../core/practice.ts";
// ─── doctrines ───────────────────────────────────────────────────────────────
@@ -109,13 +109,13 @@ Three operations modify a problem at any position on the axes.
### The ladder
Canonical points on the main axis, bottom to top:
The main axis, bottom to top — five rungs, numbered; the request arrives as a rung:
- **Worked example** — the solution is shown; nothing is demanded. An installation object; nothing yet to recall.
- **Completion** — the path is shown with a gap; the learner supplies one step. What to recall is nearly announced.
- **Direct exercise** — one step, no hint, but the situation makes the applicable proposition unmistakable. Trains execution, not choosing.
- **Composition** — several steps, chained; the path is narrow but must be held and run.
- **Path-finding** — several steps, the path concealed, distractors present, the statement suggesting wrong methods. The competition shape.
- **0 Worked example** — the solution is shown; nothing is demanded. An installation object; nothing yet to recall.
- **1 Completion** — the path is shown with a gap; the learner supplies one step. What to recall is nearly announced.
- **2 Direct exercise** — one step, no hint, but the situation makes the applicable proposition unmistakable. Trains execution, not choosing.
- **3 Composition** — several steps, chained; the path is narrow but must be held and run.
- **4 Path-finding** — several steps, the path concealed, distractors present, the statement suggesting wrong methods. The competition shape.
The ladder is the main axis only. Cue distance and the three operations apply at every point.
@@ -183,7 +183,11 @@ You are one generator in a two-stage pipeline: a partitioner has already divided
INPUTS (in the user message):
- YOUR TARGET: the chains this problem is built to strengthen — the partitioner's assignment.
- THE FULL SET: the proposition legend and chain list of the ENTIRE selection, not just the target. A coherent problem often cannot be built from the target alone — you may draw on other propositions of the full set as supporting material when the problem needs it. DOCTRINE: minimal use of outside propositions, maximal focus on the target — the target is what the learner must recall; outside propositions are material the solution may pass through. The never-announce rule covers every proposition, target or not.
- THE PARAMETERS: search-to-solution ratio, cue distance, presentation, each 0..1 (0 = flashcard end / lesson-close / pure theory; 1 = competition end / far transfer / real-world application). Apply Part II step 2: degrade honestly if the target cannot support them — the degradation shows in the problem's own shape, never in meta-commentary.
- THE PARAMETERS: three settings, each a named level — never a number to interpolate.
THE LADDER RUNG (the main axis): 0 Worked example · 1 Completion · 2 Direct exercise · 3 Composition · 4 Path-finding (Part I's ladder). The invocation names your rung; build exactly that CLASS of problem. There is no continuum between rungs — a rung-3 problem is composition, not "composition with a little path-finding."
THE CUE DISTANCE: 0 Lesson context · 1 Adjacent context · 2 Far context — how far the situation sits from the cues the learner has already met: the lesson's own examples AND every previous problem's situation. Far context is the edge of the recognizable band — the proposition must still genuinely apply, and the learner must be able to find it by looking.
THE PRESENTATION: Theory (the subject's own terms — symbols, formal objects, native vocabulary) or Application (a concrete worldly affair in which the proposition's structure appears in the wild). It dresses the situation; it never changes the rung or the cue distance.
Apply Part II step 2: degrade honestly if the target cannot support them — the degradation shows in the problem's own shape, never in meta-commentary.
- PREVIOUS PROBLEMS: problems the learner has already answered, with the chains they exercised. Do not rebuild one; keep your situation distant from theirs — cue distance counts previous problems' situations (Part I, "Cue distance").
OUTPUT — your entire deliverable is ONE submit_problem call:
@@ -201,7 +205,7 @@ PARTITION DOCTRINE:
- SMALLEST SOUND SET: prefer fewer, well-loaded problems. But never force unrelated material together: a problem's chains must share a real target relationship (confusable with each other, feeding the same conclusion, forming a path, or clustered around one idea). If the only thing two chains share is the graph, they belong in different problems.
- GROUP CONFUSABLES TOGETHER: propositions that apply to almost the same situations must land in the same problem — discrimination (choosing between near-identical situations) is a problem-engineering operation, and it is only possible when both confusables are in the room.
- READ THE MIX OFF THE SHAPE: many singletons and few multi-premise chains suggest recall + discrimination problems; long premise chains suggest composition and path-finding. Your partition is where that judgment lives.
- RESPECT THE PARAMETERS: the request carries the three dials (search-to-solution ratio, cue distance, presentation). Higher ratios support — and favor — larger assignments (more material to conceal a path in); at the bottom of the axis, one-step problems dominate and single chains stand alone.
- RESPECT THE PARAMETERS: the request names the ladder rung, the cue distance, and the presentation. Low rungs (0–2) mean many small assignments — single chains standing alone; high rungs (3–4) mean larger assignments — composition needs material to chain, path-finding needs distractor material to conceal the path among. Far cue distance makes grouping confusables together MORE important: the discrimination must travel into the far situation. Presentation does not shape your partition.
- DO NOT DUPLICATE: previous problems the learner has already seen are listed with their chains. If an existing problem already covers a group of chains, re-partition so the new set differs in what it groups and how.
OUTPUT — your entire deliverable is ONE submit_partition call: "partitions", a list of { chains: [ids] }, one entry per problem, together containing every input chain exactly once. No empty entries; no unknown ids; no problem without a real target relationship.`;
@@ -277,10 +281,14 @@ export function renderChains(chains: ReasoningChain[], propositions: Proposition
}
export function renderParams(params: PracticeParams): string {
const rung = LADDER_RUNGS[params.ladderRung];
const cue = CUE_LEVELS[params.cueDistance];
const pres = PRESENTATIONS[params.presentation];
return [
`search-to-solution ratio: ${params.searchSolutionRatio.toFixed(1)} (0 = announced recall, 1 = competition problem)`,
`cue distance: ${params.cueDistance.toFixed(1)} (0 = lesson-close, 1 = far transfer)`,
`presentation: ${params.presentation.toFixed(1)} (0 = pure theory, 1 = real-world application)`,
"THE PARAMETERS",
`Ladder rung ${params.ladderRung} — ${rung.name}: ${rung.definition}`,
`Cue distance ${params.cueDistance} — ${cue.name}: ${cue.definition}`,
`Presentation: ${pres.name} — ${pres.definition}`,
].join("\n");
}
+52 -12
View File
@@ -49,17 +49,57 @@ export interface ReasoningChain {
conclusion: string;
}
/** The three dials every problem set is configured with (owner, 2026-09-29)
* — all 0..1, interpreted by the agents against the framework's axes. */
/** The main axis, discretized (owner, 2026-09-29 — named levels, never
* a number to interpolate; definitions verbatim from the ladder
* doctrine): */
export type LadderRung = 0 | 1 | 2 | 3 | 4;
/** Cue distance from every situation the learner has already met — the
* lesson's own examples AND every previous problem's situation
* (definitions owner-approved 2026-09-29): */
export type CueDistance = 0 | 1 | 2;
/** How the situation is dressed — never changes the rung or the cue
* distance: */
export type Presentation = "theory" | "application";
/** The three settings every problem set is configured with (owner,
* 2026-09-29) — each a named level, invoked as that level exactly:
*
* THE LADDER (the main axis, bottom to top):
* 0 Worked example — the solution is shown; nothing is demanded. An
* installation object; nothing yet to recall.
* 1 Completion — the path is shown with a gap; the learner supplies
* one step. What to recall is nearly announced.
* 2 Direct exercise — one step, no hint, but the situation makes the
* applicable proposition unmistakable. Trains execution, not choosing.
* 3 Composition — several steps, chained; the path is narrow but must
* be held and run.
* 4 Path-finding — several steps, the path concealed, distractors
* present, the statement suggesting wrong methods. The competition
* shape.
*
* THE CUE DISTANCE:
* 0 Lesson context — the textbook/toy-example shape in the teaching's
* own framing.
* 1 Adjacent context — a genuinely new situation built from the
* teaching's own world — same domain, different setup/packaging.
* 2 Far context — as far from the typical cue as recognition allows,
* the edge of the recognizable band; the proposition must still
* genuinely apply, and the learner must be able to find it by looking.
*
* THE PRESENTATION:
* theory — the subject's own terms — symbols, formal objects, native
* vocabulary.
* application — a concrete worldly affair — a case, episode, system,
* or story — in which the proposition's structure appears in the wild. */
export interface PracticeParams {
/** Search-to-solution ratio — the main axis: flashcard recall (0) to
* competition problem (1). */
searchSolutionRatio: number;
/** Cue distance from every situation already used (0 = near the
* lesson, 1 = far transfer — still inside the recognizable band). */
cueDistance: number;
/** Presentation axis: pure theory (0) to real-world application (1). */
presentation: number;
/** Which class of problem to build — exactly that rung. */
ladderRung: LadderRung;
/** How far the situation sits from the cues already met. */
cueDistance: CueDistance;
/** How the situation is dressed. */
presentation: Presentation;
}
/** Where a set stands in the two-stage run. The set is PERSISTED at
@@ -119,8 +159,8 @@ export interface Problem {
export interface ProblemSet {
/** "ps_" + ts36 + rand — store-assigned. */
id: string;
/** Display label — auto-generated (the session's title, or
* date-based). */
/** Display label — the picked cluster's label, or the dated
* default. */
title: string;
createdAt: string;
params: PracticeParams;
+1
View File
@@ -31,6 +31,7 @@ export * from "./phases.ts";
export * from "./pending.ts";
export * from "./prompt-segments.ts";
export * from "./math-delims.ts";
export * from "./practice.ts";
// Wave B (owner-approved 2026-09-26): the native record (LhMessage), the
// and the input-events contract — all
// pure logic, browser-safe.
+24 -1
View File
@@ -11,7 +11,30 @@
* the union of the problems' chains must equal the set's chains; the
* partitioner's submit is validated against it.
*/
import type { GraphEdge, GraphNode, Problem, ReasoningChain } from "./contracts.ts";
import type { CueDistance, GraphEdge, GraphNode, LadderRung, Presentation, Problem, ReasoningChain } from "./contracts.ts";
/** The named levels (owner-approved 2026-09-29) — definitions verbatim
* from the ladder doctrine / the contract's tables. The UI's stepped
* controls, the exam header's levels line, and the agents' prompt
* rendering all speak these names — never a bare number. */
export const LADDER_RUNGS: Record<LadderRung, { name: string; definition: string }> = {
0: { name: "Worked example", definition: "the solution is shown; nothing is demanded. An installation object; nothing yet to recall." },
1: { name: "Completion", definition: "the path is shown with a gap; the learner supplies one step. What to recall is nearly announced." },
2: { name: "Direct exercise", definition: "one step, no hint, but the situation makes the applicable proposition unmistakable. Trains execution, not choosing." },
3: { name: "Composition", definition: "several steps, chained; the path is narrow but must be held and run." },
4: { name: "Path-finding", definition: "several steps, the path concealed, distractors present, the statement suggesting wrong methods. The competition shape." },
};
export const CUE_LEVELS: Record<CueDistance, { name: string; definition: string }> = {
0: { name: "Lesson context", definition: "the textbook/toy-example shape in the teaching's own framing." },
1: { name: "Adjacent context", definition: "a genuinely new situation built from the teaching's own world — same domain, different setup/packaging." },
2: { name: "Far context", definition: "as far from the typical cue as recognition allows, the edge of the recognizable band — the proposition must still genuinely apply, and the learner must be able to find it by looking." },
};
export const PRESENTATIONS: Record<Presentation, { name: string; definition: string }> = {
theory: { name: "Theory", definition: "the subject's own terms — symbols, formal objects, native vocabulary." },
application: { name: "Application", definition: "a concrete worldly affair — a case, episode, system, or story — in which the proposition's structure appears in the wild." },
};
/** The selection's follows-edges as chains: every edge into a node joins
* that node's (one) chain; a node with no in- AND no out-follows-edge in
+7 -4
View File
@@ -41,10 +41,13 @@ export function getPracticeSet(id: string): ProblemSet | null {
function parseParams(raw: unknown): PracticeParams | null {
if (typeof raw !== "object" || raw === null) return null;
const p = raw as Record<string, unknown>;
const dial = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1;
if (!dial(p.searchSolutionRatio) || !dial(p.cueDistance) || !dial(p.presentation)) return null;
// Named levels ONLY (owner, 2026-09-29): exact unions, nothing between —
// a rung is a rung; there is no float to interpolate.
if (p.ladderRung !== 0 && p.ladderRung !== 1 && p.ladderRung !== 2 && p.ladderRung !== 3 && p.ladderRung !== 4) return null;
if (p.cueDistance !== 0 && p.cueDistance !== 1 && p.cueDistance !== 2) return null;
if (p.presentation !== "theory" && p.presentation !== "application") return null;
return {
searchSolutionRatio: p.searchSolutionRatio,
ladderRung: p.ladderRung,
cueDistance: p.cueDistance,
presentation: p.presentation,
};
@@ -88,7 +91,7 @@ export async function createPracticeSetResponse(body: unknown): Promise<Response
const params = parseParams(b.params);
if (!params) {
return Response.json(
{ error: "params must carry searchSolutionRatio, cueDistance, presentation as numbers in [0,1]" },
{ error: "params must carry ladderRung (0-4), cueDistance (0-2), presentation (theory|application) as exact levels" },
{ status: 400 },
);
}
+10 -6
View File
@@ -35,13 +35,17 @@ const isStringArray = (v: unknown): v is string[] =>
const isParams = (v: unknown): boolean => {
if (typeof v !== "object" || v === null) return false;
const p = v as Record<string, unknown>;
// Named levels ONLY (owner, 2026-09-29): legacy float-params sets fail
// here and are skipped from lists — no migration (owner wipes
// data/practice before testing).
return (
typeof p.searchSolutionRatio === "number" &&
Number.isFinite(p.searchSolutionRatio) &&
typeof p.cueDistance === "number" &&
Number.isFinite(p.cueDistance) &&
typeof p.presentation === "number" &&
Number.isFinite(p.presentation)
(p.ladderRung === 0 ||
p.ladderRung === 1 ||
p.ladderRung === 2 ||
p.ladderRung === 3 ||
p.ladderRung === 4) &&
(p.cueDistance === 0 || p.cueDistance === 1 || p.cueDistance === 2) &&
(p.presentation === "theory" || p.presentation === "application")
);
};