From 3b0b4b7faa292b111e69d17b3b9b343e4813f259 Mon Sep 17 00:00:00 2001 From: "Syring, Nikolas" Date: Sat, 25 Apr 2026 14:50:18 +0200 Subject: [PATCH] fix(widgets): require fresh source view before mutating existing widget 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 #6 (which covers the recovery path after a patchWidget error). This PR covers the case where the agent never attempts patchWidget at all. --- .../mod/_core/onscreen_agent/prompts/system-prompt.md | 6 ++++++ .../mod/_core/spaces/ext/skills/space-widgets/SKILL.md | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/app/L0/_all/mod/_core/onscreen_agent/prompts/system-prompt.md b/app/L0/_all/mod/_core/onscreen_agent/prompts/system-prompt.md index f0cd8f96..f137f341 100644 --- a/app/L0/_all/mod/_core/onscreen_agent/prompts/system-prompt.md +++ b/app/L0/_all/mod/_core/onscreen_agent/prompts/system-prompt.md @@ -101,6 +101,12 @@ traces - readWidget just succeeded on the same widget - _____user says do it, then do it, or execute - assistant patches that widget now from the fresh source +- behavior change requires fresh source + - the user asks to change, fix, repair, validate, adjust, or improve an existing widget by name or id + - no readWidget, patchWidget, renderWidget, or reloadWidget on that widget id has succeeded earlier in this conversation, or the source for that id is no longer fully visible in the current prompt + - assistant calls readWidget on that widget id now + - assistant does not patch, render, or upsert that widget in this same turn + - assistant does not reconstruct the widget from memory or from rendered HTML - create-one after empty catalog - widget discovery already returned [empty] - _____user says create one diff --git a/app/L0/_all/mod/_core/spaces/ext/skills/space-widgets/SKILL.md b/app/L0/_all/mod/_core/spaces/ext/skills/space-widgets/SKILL.md index 7e31e6ee..1b73de08 100644 --- a/app/L0/_all/mod/_core/spaces/ext/skills/space-widgets/SKILL.md +++ b/app/L0/_all/mod/_core/spaces/ext/skills/space-widgets/SKILL.md @@ -146,6 +146,14 @@ patch vs rewrite - If you build edits programmatically, parse the numbered renderer lines from readWidget() output or Current Widget `source↓`. Do not use widget.split("\n") array indexes as patch coordinates - If patchWidget() or renderWidget() says No files were written, the old widget file is still the source of truth. Fix and retry +read before mutate +- Before any patchWidget(), renderWidget(), or upsertWidget() on an existing widget id, the assistant must have a fresh source for that id +- A source is fresh when it came from a readWidget(), patchWidget(), renderWidget(), or reloadWidget() success on that same id earlier in the live conversation +- If the source for that id is no longer fully visible in the current prompt, or only the start and end of it are visible, call readWidget(id) first and patch on the next turn +- Never reconstruct a widget from memory, from the rendered HTML returned by seeWidget(), or from a guess at the original source. seeWidget() is for visual verification only and is never authoritative for source +- Treat a user complaint about widget behavior as a patch trigger, not a rewrite trigger. Read first, then patch the specific lines that change +- renderWidget() on an existing widget id replaces the entire renderer. Use it only for new widget ids, for explicit user requests to start over from scratch, or when the framework explicitly requires a full rewrite + example exact snippet patch _____javascript return await space.current.patchWidget("snake-game", {