practice: the between-problems pause — conclude ends the turn, the learner's go-ahead paces the stream (round 16)

Owner ruling (2026-10-06): every concluded problem opens a pause. The
agent may speak its closing words about the learner's last thinking
after the conclude result, then ENDS ITS TURN — a system-prompt law
deliberately, per owner correction, NOT tool-level terminate. The next
problem is never posed until the learner explicitly moves on; the pause
is the session's teaching space (repairs, clarifications, follow-ups —
still surgical, never re-teaching).

The move-on signal is a first-class input: { type: "next" } appends a
practice-next marker (content = single-sourced NEXT_PROBLEM_TEXT — the
model's unambiguous go-ahead: pose per the selection rules or wrap if
the arc is complete). Honest 400s: practice sessions only, nothing
active (mid-problem input rides the dock), riding after any pending
dismissal synthesis exactly like text.

The fast path (owner approved: composer-band chip + Enter-on-empty):
- Composer.enterAction gains "next" — Enter on an EMPTY composer in
  the pause fires the signal; typing anything makes Enter a send again;
  busy (steer/stop) outranks the pause.
- The Next chip hovers at the composer band's top-right (pick-pill
  chrome register) whenever the pause is live and a problem has ever
  been posed; the placeholder becomes the hint ("Ask about this
  problem — or press Enter for the next one…", exact);
- the transcript renders the signal as a compact centered NEXT PROBLEM
  line (mono-caps chrome); session-core appends it optimistically.

CONCLUDE_ACK_TEXT rewritten to carry the law; doctrine §VIII gains the
pause law (owner-sanctioned edit #3); 00-base's conclude/dismissal
bullets rewritten + the between-problems section added.
This commit is contained in:
amosblomqvist
2026-10-06 03:47:05 +03:00
parent a2db80b72d
commit 573d89ae66
12 changed files with 246 additions and 36 deletions
+4 -2
View File
@@ -13,8 +13,10 @@ Two tools, and everything else is conversation. The mechanics only — the metho
Only one problem may be active at a time — the tool rejects a pose while one is live. Conclude before posing the next.
**`conclude`** — ends the active problem: records the outcome and verdict. The reveal is the modelSolution you committed at pose time — the learner sees it verbatim; there is no reveal field to fill. The turn does NOT end at conclude; the same reply reveals and poses the next problem (or wraps the session in text). The tool rejects a conclude with nothing active.
**`conclude`** — ends the active problem: records the outcome and verdict. The reveal is the modelSolution you committed at pose time — the learner sees it verbatim; there is no reveal field to fill. After the conclude's result you may speak your closing words about the learner's work — the read of their last thinking, what the verdict means, one earned credit — and then you END YOUR TURN: no further tool calls in that reply, never another problem. The next problem is posed only on the learner's explicit go-ahead (below). The tool rejects a conclude with nothing active.
**Dismissals.** The learner may discard a problem without concluding it. You will see the synthesis arrive as a conclude call with outcome "dismissed" — treat it as full surrender of that problem (the reveal was declined; mark it for a fresh-surface retest if it mattered) and pose the next demand per the selection rules. Do not comment on the dismissal beyond what the stream needs.
**Between problems (the pause).** Every concluded problem opens a pause, and the pause is where this session's teaching lives: the learner can ask about the problem just finished — what went wrong, why a move was right, what the hint meant. Answer fully but surgically: repairs, clarifications, follow-up questions — the teaching session's craft at practice scale, never re-teaching. Each answer ends its turn; never pose a problem at the end of one. The next problem is posed ONLY when the learner explicitly moves on — a *Next problem* message (their one-click signal), or their own words asking for the next one. On that signal, pose per the selection rules — or, if the arc is complete, wrap the session in text. A clarification question is never the go-ahead; when in doubt, answer and wait.
**Dismissals.** The learner may discard a problem without concluding it. You will see the synthesis arrive as a conclude call with outcome "dismissed" — treat it as full surrender of that problem (the reveal was declined; mark it for a fresh-surface retest if it mattered). The dismissal concludes the problem like any other: the pause law applies — no new problem until the learner's explicit go-ahead. Do not comment on the dismissal beyond what the stream needs.
**The rarely-needed set.** Beyond the two tools sit the learner's files (read, ls, find, grep), the research and pdf jobs, and the map readers (list_sessions, list_propositions). Reach for them only when the situation genuinely requires: the session opened without picked propositions and targets must be chosen — look at the map; the learner references a file — read it; a load-bearing fact is uncertain — research it. These are exceptions, not habits — the session's own material and its proposition list come first.
+6 -5
View File
@@ -1,7 +1,8 @@
<!-- Copied from the owner's doctrine (2026-10-04 20.37) with one approved edit:
§I material-arrival generalized (verbal descriptions, outside problems).
Tool mechanics live in 00-base.md; theory in
docs/vision-practice-sessions.md's source note. -->
<!-- Copied from the owner's doctrine (2026-10-04 20.37) with approved edits:
§I material-arrival generalized (verbal descriptions, outside problems);
§VIII pause law (owner round 16, 2026-10-06: concluding ends the turn;
the learner's explicit go-ahead paces the stream). Tool mechanics live
in 00-base.md; theory in docs/vision-practice-sessions.md's source note. -->
# The practice session
@@ -116,7 +117,7 @@ Classify every miss: cue miss (nothing came), discrimination error (wrong candid
## VIII. Progression
A claimed answer is not a signal to progress. You own progression, not the answer count. A problem may take many claims; staying is the default while yield remains.
A claimed answer is not a signal to progress. You own progression, not the answer count. A problem may take many claims; staying is the default while yield remains. Concluding ends your turn; the next problem is posed only when the learner explicitly moves on — the pause between problems is theirs to use (questions, repairs, clarifications) and theirs to end.
Move on when the problem is solved correctly; when the learner surrenders or cannot proceed; or when the marginal yield of staying is exhausted — flailing, repeated misses, frustration. Moving on spends a reveal: the committed solution becomes visible to the learner when the next problem is assigned. Do not spend reveals for nothing, and do not withhold them past the point of exhaustion.
+13 -2
View File
@@ -193,9 +193,20 @@ export const THINKING_LABEL = "Your thinking";
/** The conclude tool's ack — shared by the executed call (agent layer) and
* the dismissal-synthesized pair (input-events), so a concluded problem
* reads identically to the model however it concluded. */
* reads identically to the model however it concluded. Owner round 16:
* the turn ENDS after concluding (a system-prompt law, no tool-level
* terminate — the model may speak its closing words first, then must
* stop calling tools); the next problem waits for the learner's
* explicit go-ahead. */
export const CONCLUDE_ACK_TEXT =
"Problem concluded — the composer is free; pose the next problem or wrap up.";
"Problem concluded. Say your closing words about the learner's work if useful, then END YOUR TURN — no further tool calls. The next problem is NEVER posed here: it waits for the learner's explicit go-ahead (a *Next problem* message, or their own words asking for the next one). Between problems, answer their questions about the concluded one.";
/** The learner's explicit move-on signal (owner round 16, 2026-10-06):
* the model-facing text of the practice-next marker — single-sourced
* like THINKING_LABEL. The button/Enter-on-empty path appends the
* marker; the model reads this and poses (or wraps) per the doctrine. */
export const NEXT_PROBLEM_TEXT =
"[NEXT PROBLEM] — the learner's explicit go-ahead: pose the next problem now per the selection rules, or wrap the session in text if the arc is complete.";
/** The compaction marker's model-facing header (owner round 14,
* 2026-10-05). The marker's content is this header + the summary — the
+22 -1
View File
@@ -32,6 +32,7 @@ import {
concludedByDismissalPair,
danglingQuestionOf,
newAnswerResult,
newNextMessage,
newProblemDismissedMarker,
newThinkingMessage,
newUserMessage,
@@ -48,7 +49,12 @@ export type ChatInput =
* thinking on the ACTIVE problem — one lane (owner 2026-10-05):
* whether it claims an answer is read from the text. One
* problem may carry many submissions; each is its own turn. */
| { type: "thinking"; problemCallId: string; text: string };
| { type: "thinking"; problemCallId: string; text: string }
/** The practice move-on signal (owner round 16): the learner's
* explicit go-ahead during the between-problems pause — the Next
* chip or Enter on an empty composer. Practice sessions only, and
* only with nothing active (mid-problem input rides the dock). */
| { type: "next" };
/** A wake-pipeline steer delivery — same turn composition, server-origin.
* Wave C (2026-09-26): the idle wake carries EVERY queued marker as its
@@ -172,6 +178,8 @@ export function parseChatInput(input: unknown): ChatInput {
throw new ChatInputError("thinking input requires non-empty text");
}
return { type: "thinking", problemCallId: i.problemCallId, text: i.text };
case "next":
return { type: "next" };
default:
throw new ChatInputError(`unknown input type: ${String(i.type)}`);
}
@@ -206,6 +214,19 @@ export function inputAppends(record: { messages: LhMessage[] }, input: ChatInput
}
return [...dismissalConclusion(record), newThinkingMessage(input.problemCallId, input.text)];
}
case "next": {
// Honest 400s, the thinking gate's discipline: the signal is a
// between-problems affordance — practice sessions only, nothing
// active (mid-problem input rides the dock), and the client gates
// make violations unreachable in practice anyway.
if ((record as { kind?: string }).kind !== "practice") {
throw new ChatInputError("next input is a practice-session affordance");
}
if (activeProblemOf(record.messages)) {
throw new ChatInputError("next input requires no active problem");
}
return [...dismissalConclusion(record), newNextMessage()];
}
}
}
+20
View File
@@ -39,6 +39,7 @@ import { QUESTION_TOOLS } from "./pending.ts";
import {
CONCLUDE_ACK_TEXT,
HANDOFF_TEXT,
NEXT_PROBLEM_TEXT,
THINKING_LABEL,
} from "./contracts/chat.ts";
import { compactAnswerForModel, type QuestionAnswerData } from "./question-core.ts";
@@ -71,6 +72,7 @@ export interface LhMarkerMessage {
| "subagent-abort"
| "plan-block"
| "practice-thinking"
| "practice-next"
| "problem-dismissed"
| "compaction";
/** The model-facing text ("" for plan blocks — UI-only). */
@@ -248,6 +250,24 @@ export function newThinkingMessage(problemCallId: string, text: string): LhMarke
};
}
/** The learner's explicit move-on signal (owner round 16): appended by
* the next input (the composer band's chip, or Enter on an empty
* composer during the between-problems pause). The content is the
* single-sourced NEXT_PROBLEM_TEXT — the model's unambiguous go-ahead;
* the transcript renders its own compact line off the marker (never
* the content). */
export function newNextMessage(): LhMarkerMessage {
return {
id: newMessageId(),
role: "custom",
customType: "practice-next",
content: NEXT_PROBLEM_TEXT,
display: true,
details: {},
timestamp: Date.now(),
};
}
/** The problem-dismissal marker (ask-style discard, owner 2026-10-04):
* UI-ONLY — toLlmMessages filters it. The model's face arrives later, at
* the user's next input, as the synthesized conclude pair
+12
View File
@@ -401,6 +401,18 @@ function projectMarker(marker: LhMarkerMessage, out: LhViewMessage[]): void {
});
return;
}
case "practice-next": {
// The learner's explicit move-on signal (owner round 16): a compact
// centered system line — the mono-caps chrome register, not prose.
// The model reads the marker's content; the transcript never shows
// it (the label is chrome, single-sourced in the renderer).
out.push({
id: marker.id,
role: "user",
parts: [{ type: "data-practice-next", id: `pn_${marker.id}`, data: null }],
});
return;
}
case "problem-dismissed":
// UI-only (the ask-style dismissal): the synthesized conclude
// pair — which rides the NEXT input — carries the record's face.