Skip to content

Refinement edges: scripted marker is one-shot; Refine after a failed turn dead-ends #43

Description

@ryandmonk

Found during merged-main verification of #40 (c046ef8). Recording only.

1. The scripted refinement marker is one-shot. apps/agent/src/project.ts:484 guards the transform with !textNode.text.endsWith(" (refined)"), so once the seeded surface already carries the marker the adapter returns it unchanged. Executed against the real agent: turns 2, 3 and 4 of a refinement thread were byte-identical (592 B, same sha), each still reporting outcome: passed, rendering a canvas, and offering Accept. The slice's own stated invariant is that a scripted refinement returns a deterministic change when the seed is present — that holds for refinement #1 and silently stops thereafter. Real models are unaffected (verified live with gemma4:e4b). Untested because both the DOM spec and the agent test perform exactly one refinement.

2. Refine is offered on failed turns and dead-ends. build.ts:112 records progress.surface from the last attempt regardless of outcome, so canRefine (build-view.tsx:142) and the seed selection (state.tsx:517) both accept a failed turn. Refining one seeds the invalid surface and, in scripted mode, the single-entry refinement script exhausts: the turn renders the raw string ScriptedAdapter: script exhausted after 1 generation(s) with no surface. The natural reaction to a failure — "fix the broken bit" — is the one path that breaks.

3. An intent with no worked example silently borrows another intent's. apps/agent/src/project.ts:517-518 falls back with matching.at(-1) ?? examples.at(-1), crossing intents. The run reports passed and renders a screen built for a different intent; Accept then refuses it forever (correctly — the surface doesn't lint for the selected intent). Note the asymmetry with the adjacent lines, which do fail honestly when no example exists at all.

Directions (not decided): make the marker idempotent-but-progressive or vary it per turn; gate canRefine on outcome === "passed"; make the cross-intent fallback an honest refusal like its neighbour.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions