practice: ONE send lane — the register is read from the text

Owner round 2 (2026-10-05), Slice H: the Nudge/Commit two-lane buttons
are gone. One Send; Enter sends, Shift+Enter is a newline. Whether a
submission claims an answer is apparent from the thinking itself — the
agent reads it (hint ladder for traces, moves+verdict for claims).

- The thinking input loses the commit field end-to-end: ChatInput,
  parseChatInput, newThinkingMessage (details and the model-facing label
  both collapse to the single 'Your thinking'), the optimistic marker.
- Legacy records' commit flags are deliberately unread.
- Doctrine: the two labeled channels become one read instruction; 'answer
  commits' section retitled 'Claimed answers' — a claim is not a
  progress signal.
- ThinkingCard: one label, collapsed arrival (the unified row).
- The dock placeholder no longer explains lanes that no longer exist.
This commit is contained in:
amosblomqvist
2026-10-05 03:46:32 +03:00
parent f460a90be9
commit 0bd3236af0
13 changed files with 101 additions and 130 deletions
+3 -3
View File
@@ -23,10 +23,10 @@ He has to be able to trust you completely; one confidently-delivered hallucinati
Two tools, and everything else is conversation. The mechanics only — the method (when to pose, what to pose, how to respond) is the doctrine's.
**`problem`** — poses one problem. The statement field is what the learner sees (markdown and LaTeX render); the modelSolution is your private commitment, hidden from the learner until you conclude. The call ends the turn. The learner then works in a thinking field; their submissions arrive back as labeled messages:
**`problem`** — poses one problem. The statement field is what the learner sees (markdown and LaTeX render); the modelSolution is your private commitment, hidden from the learner until you conclude. The call ends the turn. The learner then works in a thinking field; each submission arrives back as a *Your thinking* message. Whether it claims an answer is IN THE TEXT, not declared anywhere — read it:
- *Thinking trace (working, not committed):* they are mid-work and asking for a nudge. This is a hint request — the hint ladder applies, one rung per request. A trace may span several of these.
- *Committed answer:* they claim an answer. Extract the moves, deliver the verdict. Remember: a commit is not a progress signal.
- Working trace (mid-work, stuck, or asking for a nudge): the hint ladder applies, one rung per request. A trace may span several submissions.
- A claimed answer: extract the moves, deliver the verdict. A claim is not a progress signal.
Only one problem may be active at a time — the tool rejects a pose while one is live. Conclude before posing the next.
+4 -4
View File
@@ -81,7 +81,7 @@ The statement never announces its target. If the situation makes the applicable
## VI. Hints
The policy covers every hint request and every intermediate submission that is not an answer commit — including a bare "I'm stuck" with an empty trace, where there is nothing to diagnose and you therefore start at the top of the ladder.
The policy covers every submission that does not claim an answer — including a bare "I'm stuck" with an empty trace, where there is nothing to diagnose and you therefore start at the top of the ladder.
The economics first: revealed information cannot be unrevealed, and the learner can always think longer and ask again. So help is held to the minimum — regardless of how much the learner is struggling.
@@ -104,7 +104,7 @@ Three structural rules:
The rung a problem needed is a difficulty readout — only R0–R1 means the problem was about right; R3 and above means it was over the edge. Let this feed the next demand's difficulty.
## VII. Answer commits
## VII. Claimed answers
The thinking trace carries junk by design. Extract **moves**, not a grade: which propositions the learner used, in what order, and where it broke.
@@ -116,7 +116,7 @@ Classify every miss: cue miss (nothing came), discrimination error (wrong candid
## VIII. Progression
An answer commit is not a signal to progress. You own progression, not the commit count. A problem may take many commits; 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.
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.
@@ -136,5 +136,5 @@ If the learner brings a problem from outside — an exam problem, something they
- Never reveal what the learner can still generate — one rung per request, verification before correction.
- Never re-ask in the same surface.
- Never teach beyond a surgical line.
- Never mistake a commit for a progress signal.
- Never mistake a claimed answer for a progress signal.
- Never soften a verdict or inflate one.
+4 -2
View File
@@ -186,8 +186,10 @@ export const HANDOFF_TEXT =
/** The model-facing register of a thinking submission (newThinkingMessage
* prefixes the learner's text with this label). Hints and commits ride
* one channel — the label is the ONLY difference the model sees. */
export const THINKING_HINT_LABEL = "Thinking trace (working, not committed)";
export const THINKING_COMMIT_LABEL = "Committed answer";
/** The single submission label (owner round, 2026-10-05): one send
* lane — whether a submission claims an answer is read from the text,
* not declared by a button. */
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
+5 -8
View File
@@ -45,10 +45,10 @@ export type ChatInput =
| { type: "answer"; toolCallId: string; answer: QuestionAnswerData }
| { type: "continue" }
/** The practice dock's two buttons (owner, 2026-10-04): the learner's
* thinking on the ACTIVE problem — commit=false is the hint request
* (working, not committed), commit=true the answer commit. One
* 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; commit: boolean; text: string };
| { type: "thinking"; problemCallId: string; text: string };
/** A wake-pipeline steer delivery — same turn composition, server-origin.
* Wave C (2026-09-26): the idle wake carries EVERY queued marker as its
@@ -168,13 +168,10 @@ export function parseChatInput(input: unknown): ChatInput {
if (typeof i.problemCallId !== "string" || i.problemCallId.length === 0) {
throw new ChatInputError("thinking input requires problemCallId");
}
if (typeof i.commit !== "boolean") {
throw new ChatInputError("thinking input requires commit (boolean)");
}
if (typeof i.text !== "string" || i.text.trim().length === 0) {
throw new ChatInputError("thinking input requires non-empty text");
}
return { type: "thinking", problemCallId: i.problemCallId, commit: i.commit, text: i.text };
return { type: "thinking", problemCallId: i.problemCallId, text: i.text };
default:
throw new ChatInputError(`unknown input type: ${String(i.type)}`);
}
@@ -207,7 +204,7 @@ export function inputAppends(record: { messages: LhMessage[] }, input: ChatInput
if (!active || active.toolCallId !== input.problemCallId) {
throw new ChatInputError("thinking input targets no active problem");
}
return [...dismissalConclusion(record), newThinkingMessage(input.problemCallId, input.commit, input.text)];
return [...dismissalConclusion(record), newThinkingMessage(input.problemCallId, input.text)];
}
}
}
+9 -10
View File
@@ -39,8 +39,7 @@ import { QUESTION_TOOLS } from "./pending.ts";
import {
CONCLUDE_ACK_TEXT,
HANDOFF_TEXT,
THINKING_COMMIT_LABEL,
THINKING_HINT_LABEL,
THINKING_LABEL,
} from "./contracts/chat.ts";
import { compactAnswerForModel, type QuestionAnswerData } from "./question-core.ts";
@@ -224,20 +223,20 @@ export function newGraphContextMessage(picks: readonly GraphContextPick[], chain
};
}
/** The learner's thinking submission (the practice dock's two buttons):
* a user-register message the model reads WITH its label (hint vs
* commit — the register is the whole difference), while `details` keeps
* the raw payload for the transcript card. One problem may carry many
/** The learner's thinking submission (ONE send lane, owner round
* 2026-10-05): the register is no longer declared by a button — the
* model READS the text to tell working traces from claimed answers. The
* content label is the single "Your thinking"; `details` keeps the raw
* payload for the transcript card. One problem may carry many
* submissions. */
export function newThinkingMessage(problemCallId: string, commit: boolean, text: string): LhMarkerMessage {
const label = commit ? THINKING_COMMIT_LABEL : THINKING_HINT_LABEL;
export function newThinkingMessage(problemCallId: string, text: string): LhMarkerMessage {
return {
id: newMessageId(),
role: "custom",
customType: "practice-thinking",
content: `${label}:\n${text}`,
content: `${THINKING_LABEL}:\n${text}`,
display: true,
details: { problemCallId, commit, text },
details: { problemCallId, text },
timestamp: Date.now(),
};
}