From 9ad1a35adcb5d501304793cc2776d06c87026d6e Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Wed, 8 Jul 2026 14:58:19 +1000 Subject: [PATCH] goal-editing policy: proposal-first with user approval; G-038 console-anchor finding + acceptance clause Policy (GOALS.md maintenance rules, mirrored in root AGENTS.md and goals/AGENTS.md): - agents apply goal-entry adds/edits only on user request, proposal-first: show the proposed wording and get explicit approval before writing (replaces "only the user adds or edits goals" - the user was applying agent-drafted edits by hand anyway) - suggesting candidate goals is welcome and encouraged when grounded in the work at hand; a suggestion is a flagged proposal, never a file edit; bulk goal surveys stay out of bounds - detail-file updates from user-directed work on a goal proceed without pre-approval, reported in the completion summary; index stays canonical - unchanged: achieved-flip on met acceptance (reported), no proposed->active flips G-038 (user-approved): - Acceptance gains a post-restart terminal-query clause: a code-interp query (e.g. 'help console') must succeed instead of being refused by the stale settled can_respond=0 anchor - detail file records the proven mechanism for the post-restart console failure (piped phase settles can_respond=0; punkshell.tcl opens CONIN$ as a new channel leaving the repl thread's stdin as the exhausted pipe; the anchored default console keeps the dead pair; forced resettle honestly re-settles 0 via layer-1 eof), the rebind requirements (re-point {in out} AND unsettle), and the hidden-console repro/regression harness Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- AGENTS.md | 2 +- GOALS.md | 11 +++-- goals/AGENTS.md | 3 +- goals/G-038-piped-session-continuity.md | 61 ++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 82585739..1c7fa8ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -200,4 +200,4 @@ The project version is fully independent of module versions. A module bump (even - Source-tree build, testing, linting, and file-resolution workflow lives in `src/AGENTS.md`. - Tcl module authoring conventions live in `src/modules/AGENTS.md` and closer module child docs. - If AGENTS.md conflicts with CLAUDE.md, AGENTS.md wins. -- Technical project goals live in root `GOALS.md` (index) with optional detail prose in `goals/G--.md`. Only the user adds or edits goals. Agents auto-flip a goal to `achieved ` when its stated acceptance criterion is met as part of the DOX closeout pass, and must flag `proposed`→`active` transitions in their completion report for user confirmation. See `GOALS.md` for the full workflow, including how to author a new goal when asked. +- Technical project goals live in root `GOALS.md` (index) with optional detail prose in `goals/G--.md`. Goals are user-owned: agents add or edit goal entries only at the user's request, proposal-first (show the proposed wording, get explicit approval before writing - see the `GOALS.md` maintenance rules); never on their own initiative. Suggesting candidate goals when work surfaces something goal-worthy is welcome and encouraged - as a flagged proposal, not a file edit. Detail-file updates from user-directed work need only be reported in the completion summary. Agents auto-flip a goal to `achieved ` when its stated acceptance criterion is met as part of the DOX closeout pass, and must flag `proposed`→`active` transitions in their completion report for user confirmation. See `GOALS.md` for the full workflow, including how to author a new goal when asked. diff --git a/GOALS.md b/GOALS.md index 01ba391d..9f39fecb 100644 --- a/GOALS.md +++ b/GOALS.md @@ -24,9 +24,12 @@ Acceptance: - `superseded by G-` — replaced; do not delete the old entry ### Maintenance rules -- Only the user adds or edits goals. Agents must not invent goals. -- An agent whose work satisfies a goal's `Acceptance:` must flip that goal to `achieved ` as part of its DOX closeout pass, and report the flip in its completion summary. +- Goals are user-owned. Agents add or edit goal entries only at the user's request or with the user's explicit approval - never on their own initiative, and never in bulk from an agent's own survey of what "should" be goals. +- Suggesting is always allowed and encouraged when grounded in the work at hand: a discovered gap, a recurring manual step, a deferred design decision, or a natural follow-on that fits what is currently being worked on. Flag it as a candidate goal in conversation or the completion report, optionally with a drafted block ready for approval. A suggestion is not an edit - nothing is written to this file or `goals/` until the user approves per the proposal-first rule. +- Proposal-first: before writing a new goal entry or changing an existing entry's contract (title, status tag, Scope, Goal, Acceptance), show the user the proposed wording - the full block for a new goal, the changed clause(s) for an edit - and get explicit approval. If the user already supplied or approved the exact wording this session, apply it and report what was written. +- Exception (sanctioned autonomous edit): an agent whose work satisfies a goal's `Acceptance:` must flip that goal to `achieved ` as part of its DOX closeout pass, and report the flip in its completion summary. - Agents must not flip `proposed` → `active`. They flag it in their completion report for the user to confirm. +- Detail files under `goals/` may be updated without pre-approval when recording findings, decisions, or verification artifacts from work the user directed on that goal or its subject matter; report such updates in the completion summary. The index entry is canonical - a detail edit must never contradict it. - The `Goal:` line must stay self-contained enough that an agent who skips the detail file still does no harm. Detail files are enrichment, not load-bearing for safety. - When the achieved section grows past ~30 entries, the oldest are moved to `GOALS-archive.md` and their detail files to `goals/archive/`. - If a goal cannot be safely summarized in one line, that is a signal it is really two goals — split it. @@ -42,7 +45,7 @@ When the user asks to "write a goal for X" or "help me draft a goal for Y", do t - **Acceptance:** What is the measurable, verifiable pass/fail criterion an agent can check against? If the user cannot state one, propose 2-3 candidate criteria and ask them to pick or refine. - **Status:** Default to `proposed` unless the user says it is already in progress (`active`). - **Detail file?** Only if the goal has non-obvious rationale, a multi-phase plan, alternatives worth recording, or needs more than ~3 lines of prose to state properly. If yes, propose a slug and offer to draft the detail file too. If no, omit the `Detail:` line. -3. Draft the goal block in this file's format and show it to the user for review and edit. Do not commit it as `active` unless the user confirms. +3. Draft the goal block in this file's format and show it to the user for review, applying it only after approval (per the maintenance rules). Do not set it `active` unless the user confirms. 4. If a detail file is warranted and the user approves, create `goals/G--.md` using the structure in `goals/AGENTS.md`. 5. Do not delete or rewrite existing goals to make room for a new one. Append with the next free ID. @@ -274,7 +277,7 @@ Acceptance: with a newer package version placed under src/vendorlib_tcl9/ shell` leaves ::jjj inspectable at the prompt, replacing today's silent fresh-session swap (least-surprise violation; motivating transcripts in the detail file) and obsoleting the standalone piped-error-record mechanism this goal described before its 2026-07-08 rework. -Acceptance: after `'set ::jjj blah;error xxxx' | shell` reaches the interactive prompt: `set ::jjj` returns blah; the xxxx message, errorInfo traceback and errorCode are inspectable (via preserved ::errorInfo if nothing in the handover overwrites it, else a documented record - the chosen mechanism noted in the detail file); a proc defined and a package required during the piped phase remain available; a cwd change from the piped phase persists; a one-line notice at the restart says the session continued from piped input (mentioning the error when the last piped command errored, quiet otherwise); interactive exit/quit teardown afterwards is clean (the G-036 regression harness still passes); PUNK_PIPE_EOF policy semantics and the script subcommand are unchanged; verified on both Tcl generations. +Acceptance: after `'set ::jjj blah;error xxxx' | shell` reaches the interactive prompt: `set ::jjj` returns blah; the xxxx message, errorInfo traceback and errorCode are inspectable (via preserved ::errorInfo if nothing in the handover overwrites it, else a documented record - the chosen mechanism noted in the detail file); a proc defined and a package required during the piped phase remain available; a cwd change from the piped phase persists; a one-line notice at the restart says the session continued from piped input (mentioning the error when the last piped command errored, quiet otherwise); after the restart a terminal query from the code interp succeeds (e.g. `help console` runs its cursor-position test instead of being refused by the stale settled can_respond=0 anchor); interactive exit/quit teardown afterwards is clean (the G-036 regression harness still passes); PUNK_PIPE_EOF policy semantics and the script subcommand are unchanged; verified on both Tcl generations. ### G-039 [proposed] Investigate the orphaned-shell one-core spin on a dead console Scope: src/modules/punk/repl-999999.0a1.0.tm (console reader/event loop and EOF/error paths), src/modules/punk/console-999999.0a1.0.tm; investigation-first diff --git a/goals/AGENTS.md b/goals/AGENTS.md index e60f4c67..02ada7f4 100644 --- a/goals/AGENTS.md +++ b/goals/AGENTS.md @@ -7,7 +7,8 @@ Holds optional detail prose for goals in the root `GOALS.md` index that need mor ## Ownership - Files here are owned by the goal authoring workflow described in root `GOALS.md`. -- Only the user adds or edits goals. Agents may draft detail files for user review but must not invent goals. +- Goals are user-owned: agents add or edit goals only at the user's request or with explicit approval, proposal-first for index-entry changes (see the root `GOALS.md` maintenance rules). Agents must not write goals on their own initiative - suggesting candidate goals for user approval is welcome and encouraged. +- Detail files may be updated without pre-approval when recording findings, decisions, or verification artifacts from user-directed work on the goal or its subject matter; report such updates in the completion summary. - The root `GOALS.md` index entry is the source of truth. If index and detail disagree, the index wins; fix the detail. ## Local Contracts diff --git a/goals/G-038-piped-session-continuity.md b/goals/G-038-piped-session-continuity.md index f97d2fab..5cc02697 100644 --- a/goals/G-038-piped-session-continuity.md +++ b/goals/G-038-piped-session-continuity.md @@ -2,7 +2,7 @@ Status: proposed Scope: src/lib/app-punkshell/punkshell.tcl (eof-restart handover), src/modules/punk/repl-999999.0a1.0.tm (eof-restart done-mode that skips codethread teardown), src/modules/punk/repl/codethread-999999.0a1.0.tm (as touched) -Acceptance: after `'set ::jjj blah;error xxxx' | shell` reaches the interactive prompt: `set ::jjj` returns blah; the xxxx message, errorInfo traceback and errorCode are inspectable (via preserved ::errorInfo if nothing in the handover overwrites it, else a documented record - the chosen mechanism noted in the detail file); a proc defined and a package required during the piped phase remain available; a cwd change from the piped phase persists; a one-line notice at the restart says the session continued from piped input (mentioning the error when the last piped command errored, quiet otherwise); interactive exit/quit teardown afterwards is clean (the G-036 regression harness still passes); PUNK_PIPE_EOF policy semantics and the script subcommand are unchanged; verified on both Tcl generations. +Acceptance: after `'set ::jjj blah;error xxxx' | shell` reaches the interactive prompt: `set ::jjj` returns blah; the xxxx message, errorInfo traceback and errorCode are inspectable (via preserved ::errorInfo if nothing in the handover overwrites it, else a documented record - the chosen mechanism noted in the detail file); a proc defined and a package required during the piped phase remain available; a cwd change from the piped phase persists; a one-line notice at the restart says the session continued from piped input (mentioning the error when the last piped command errored, quiet otherwise); after the restart a terminal query from the code interp succeeds (e.g. `help console` runs its cursor-position test instead of being refused by the stale settled can_respond=0 anchor); interactive exit/quit teardown afterwards is clean (the G-036 regression harness still passes); PUNK_PIPE_EOF policy semantics and the script subcommand are unchanged; verified on both Tcl generations. ## Context @@ -21,6 +21,25 @@ did in the code interp - variables, procs, namespaces, package requires, cwd, er is silently discarded. The user's mental model is "my session continues interactively", i.e. the same expectation a tclsh user has typing those commands then continuing. +A third symptom class (user report 2026-07-08, mechanism proven same day - see Notes for +the harness): after the restart, `help console` and EVERY terminal query (get_cursor_pos, +dec_get_mode, test_char_width, ...) fail with "console object for 'stdin' 'stdout' is +settled as unable to respond (can_respond=0) - refusing to emit query". Chain: + +1. During the piped phase the anchored `default` opunk console ({stdin stdout}) settles + can_respond=0 - correct at the time, stdin is a pipe (punk::console 0.7.1 settling). +2. At EOF, punkshell.tcl (~l.501-517) opens CONIN$ as a NEW channel (name `file...`) and + SetStdHandle's the process stdin handle, but never closes or replaces the repl thread's + Tcl channel `stdin` - it remains the exhausted pipe (chan eof = 1), and the anchored + default console instance still references {stdin stdout}. +3. The fresh codethread's code interp DOES get a real console stdin (new thread std + channels initialise from the process handles), but its queries route (G-007) to the + console-owning repl thread, where the stale anchor refuses them. +4. Recovery is impossible without re-anchoring: `settle_can_respond -force default` in the + owner thread honestly re-settles 0 via layer 1 (input at eof) because the object's + input IS a dead channel. The stale anchor, not just the stale verdict, is the defect - + exactly the gap flagged by the review comment at repl-999999.0a1.0.tm ~l.3168. + Precedent in the codebase: the repl-internal restart path (`repl::reopen_stdin`) is context-preserving by design - it reuses the live codethread and restarts the repl on the reopened console channel. Its problem is structural, not conceptual, and this goal should @@ -66,6 +85,16 @@ The caller-driven restart already identified during G-036 as the clean fix direc - renewed: input channel + repl reader, tcl_interactive, prompt state; the anchored default console object must be rebound to the new channels (known stale-after-reopen comment at repl-999999 ~line 3168; adjacent to G-001/G-011 console-object work). + This is now a proven user-visible defect, not just hygiene (see Context: post-restart + `help console`/terminal-query failures). The rebind needs BOTH halves: re-point the + anchored default instance's {in out} to the renewed input channel AND unsettle + can_respond (set -1, or settle_can_respond -force after the rebind) so the first + interactive query re-probes against the real console. Design choice to record when + implementing: either close the dead stdin before opening CONIN$ so the new channel + lands in the stdin std-channel slot (spec {stdin stdout} becomes correct by itself, + smallest console-side change, but mind readers/stacks still referencing stdin during + the handover), or keep the current open-then-SetStdHandle order and add an explicit + console re-anchor step in the handover. - persists: everything in the codethread/code interp, including `after` timers and fileevents registered by the piped script (a feature, but note it as a behaviour change - a piped script's scheduled work now keeps running into the interactive @@ -121,3 +150,33 @@ Risks / interactions: G-002 (non-nested subshell architecture - same family of repl lifecycle decoupling); G-031 (componentized boot); G-001/G-011 (console object rebinding on reopen); G-015 (script subcommand unaffected - it never drops to interactive). + +### Post-restart console-query harness (2026-07-08) + +Hidden-console repro/regression check for the stale-console-anchor symptom, reusable when +implementing the rebind (pattern per the G-036 batch harness). Pipe a file containing +`puts test` plus a `repl eval` that schedules a post-restart probe (repl-interp timers +survive the restart; codethread timers do not), run under a hidden console so the CONIN$ +restart occurs, and have the probe write its findings to a file: + +``` +puts test +repl eval {after 6000 { + thread::send -async $::repl::codethread {interp eval code { + set f [open $::env(TEMP)/helpconsole_diag.txt w] + puts $f [expr {[catch {punk::console::get_cursor_pos} r] ? "ERROR: $r" : "OK: $r"}] + close $f + }} + after 15000 {::punk::repl::exit 0} +}} +``` + +Launch: `Start-Process cmd.exe -WindowStyle Hidden -ArgumentList '/c',' src shell < > 2>&1'`. +Pre-fix signature: ERROR "...settled as unable to respond (can_respond=0) - refusing to +emit query". Post-fix: the probe should emit a query (in a real terminal it succeeds; +under a hidden conhost a query timeout is acceptable - the assertion is that the refusal +is gone and repl-thread `chan eof stdin`-style staleness no longer applies to the console +object's channels). State observed pre-fix, for reference: repl thread post-restart has +`stdin` eof=1 (the exhausted pipe) alongside the new `file...` CONIN$ channel; the code +interp's stdin is a live console channel (utf-16, -inputmode present); anchored instances +known to the repl thread: `default` (anchored to the dead pair).