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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user