Skip to content

fix(widgets): require fresh source view before mutating an existing widget#30

Open
nsyring wants to merge 1 commit intoagent0ai:mainfrom
nsyring:fix/widget-read-before-mutate
Open

fix(widgets): require fresh source view before mutating an existing widget#30
nsyring wants to merge 1 commit intoagent0ai:mainfrom
nsyring:fix/widget-read-before-mutate

Conversation

@nsyring
Copy link
Copy Markdown

@nsyring nsyring commented Apr 26, 2026

fix(widgets): require a fresh source view before mutating an existing widget

Summary

Adds a "read before mutate" rule to the space-widgets skill and a matching staged-turn rule to the onscreen-agent system prompt. The agent must hold a fresh readWidget(id) / patchWidget(id) / renderWidget({id}) / reloadWidget(id) success from the live conversation before calling any of patchWidget, renderWidget, or upsertWidget on an existing widget id. If the only available source view is older than the most recent successful read-or-write on that id, or trimmed by the prompt-budget middle-replacement, the agent must call readWidget(id) first and patch on the next turn.

Why

app/L0/_all/mod/_core/spaces/AGENTS.md:196 already states the architectural intent:

"should strongly prefer readWidget(...) plus patchWidget(...) when the user wants to modify an existing widget [...] and should avoid direct file reads or full renderWidget(...) rewrites unless the user explicitly asks for a rewrite, the change is too broad for a clear patch, or JavaScript truly needs the raw widget file content"

That guidance lives in the developer-facing AGENTS.md but does not reach the prompt-facing skill or system prompt. The result is a recurring pathological pattern that I reproduced reliably during local widget development:

  • User asks for a small behavior change ("the search returns too many results, can you fix that?")
  • Agent has no fresh Current Widget source for that id (e.g. the prior conversation never read it, or the source has been mid-trimmed by the long-message placeholder mechanism in app/L0/_all/mod/_core/agent_prompt/prompt-items.js)
  • Agent skips readWidget, infers structure from rendered HTML or from a guess, and rewrites the entire renderer via upsertWidget / renderWidget
  • 500-600 lines of generated code replace a working widget for what should have been a 5-line patch

This is not a patchWidget failure-loop case (where the framework already explicitly says "use renderWidget"). The skill currently has rules covering that case but no rule covering "user asked for a behavior change without a fresh source view" — so the agent treats the implicit choice as open and frequently picks the destructive option.

What changed

app/L0/_all/mod/_core/spaces/ext/skills/space-widgets/SKILL.md (+7)

New read before mutate subsection placed directly after the existing patch vs rewrite subsection. It defines what counts as a fresh source view, requires readWidget(id) first when none is available, forbids reconstruction from memory or from seeWidget() rendered HTML, and explicitly states that a generic user complaint is not a full-rewrite signal on its own.

app/L0/_all/mod/_core/onscreen_agent/prompts/system-prompt.md (+6)

New behavior change requires fresh source staged turn rule, placed in the existing turn-rule list directly after fresh read then do it. It mirrors the SKILL.md guidance at the prompt-staging granularity that the system prompt uses elsewhere (visible defect repair, framework-corrected rewrite continues, etc.) so the rule actually fires per-turn.

No code changes, no new dependencies, no test updates needed. Mirrors the change footprint of #6 (also a SKILL.md-only contribution targeting widget-edit hygiene).

Relationship to other PRs

  • prevent agent widget editing loops #6 (prevent agent widget editing loops): Complementary. prevent agent widget editing loops #6 covers the recovery path after a patchWidget error ("call readWidget before retrying"). This PR covers the path before any mutation attempt on an existing widget, including the common case where the agent never tries patchWidget at all and goes straight to renderWidget / upsertWidget.

Test plan

This is a prompt and skill text change. There are no automated tests for the skill body in tests/ (none of the existing skill files are covered by unit tests, and the existing tests/spaces_*.mjs exercise widget-import resolution and prompt-context, not skill content), so verification is qualitative.

  • node --check passes on adjacent JS files (no JS touched, but verified working tree is otherwise clean)
  • Manual verification across two providers in npm run desktop:pack builds:
    • gpt-5.4 via OpenAI Codex provider — small behavior-change requests on existing widgets reliably trigger readWidget(id) first and a targeted patchWidget on the next turn, instead of the previous immediate renderWidget/upsertWidget full rewrite
    • Local qwen3-coder model — same provider-agnostic behavior. The skill rule and turn rule are LLM-agnostic; a smaller local model also follows them and produces only targeted edits

Out of scope (possible follow-ups)

  • Long-message placeholder byte-length stabilization in prompt-items.js to keep the source view byte-stable across turns, so the "fresh source view" guarantee survives long histories without re-reading. Tracked separately.
  • Marking the Current Widget transient section as trimAllowed: false so the freshly-published source survives transient-budget trimming. Tracked separately.
  • A runtime soft-warning in spaces/storage.js when renderWidget / upsertWidget overwrites an existing widget id without a recent readWidget on that id. Could promote the skill rule into structural enforcement; left for a follow-up so this PR stays a pure skill-and-prompt change.

🤖 Generated with Claude Code

The space-widgets skill and onscreen-agent system prompt had no rule
forcing readWidget before patchWidget, renderWidget, or upsertWidget on
an existing widget id. When the user asked for a small behavior change,
the agent would routinely skip readWidget and rewrite the entire
renderer from rendered HTML or from memory, producing 500+ lines of
regenerated code in place of a 5-line patch.

The architectural intent already lives in spaces/AGENTS.md but did not
reach the prompt-facing skill. This change adds:

- a "read before mutate" subsection in the space-widgets SKILL.md that
  defines what counts as a fresh source view and forbids reconstruction
  from memory or from seeWidget rendered HTML
- a matching "behavior change requires fresh source" staged turn rule
  in the onscreen-agent system prompt so the rule fires per turn

Complementary to PR agent0ai#6 (which covers the recovery path after a
patchWidget error). This PR covers the case where the agent never
attempts patchWidget at all.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant