Skip to content

skills: replace edit-services with an update-service front door#240

Open
gnguralnick wants to merge 9 commits into
mainfrom
gabriel/knowing-to-refresh
Open

skills: replace edit-services with an update-service front door#240
gnguralnick wants to merge 9 commits into
mainfrom
gabriel/knowing-to-refresh

Conversation

@gnguralnick

@gnguralnick gnguralnick commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What

Editing an existing service had no clear entry point in the skill set. The old edit-services skill only documented the supervisord [program:*] plumbing, so an agent that just edited a service's backend or frontend logic never had a reason to load it -- and nothing reliably prompted the two things that leave the user looking at stale state: reloading the process so the change takes effect, and refreshing the open web tab so they can see it.

This replaces edit-services with update-service, a front-door skill whose description triggers on editing/fixing/restyling/extending an existing service -- web service or background daemon -- and, on top of that, encodes how to change a service without ever risking the user's data.

The new skill

  • Live change loop: apply the change so it takes effect (restart for backend changes, since the runner has use_reloader=False; nothing for frontend-only; reread/update for definition changes), refresh the user's tab for web services (layout.py refresh <name>), then verify.
  • Scope-aware routing: a contained change goes straight through the live loop; a larger-scope change (redesign, new view, look-and-feel shift) routes back through the same interactive-delivery (mock-confirm) flow used to build the service, so heavy work never happens against an unconfirmed shape.
  • Turn-end handoff to the update-artifact / heal-artifact hardening core, mirroring how update-system-interface wraps that core for the workspace UI.

The supervisord program mechanics move into update-service/references/service-processes.md, cited only for definition-level changes. Cross-links in build-web-service (SKILL + cleanup.md), libs/bootstrap/README.md, and CLAUDE.md now point at update-service.

Protecting user data on service edits

A recurring, expensive failure mode when editing a service is not the code edit: it's verifying the change by writing test data into the live store and then "cleaning up" with a delete/reset whose predicate is too broad and takes real user records with it. The skill now encodes data isolation as the safeguard -- deliberately lighter than deferring every edit to an isolated worktree, which isolates the wrong thing (code) and doesn't even protect the data, since the running service still reads and writes the same runtime/<name>/.

  • The scaffold generates a redirectable data pointer. New services' runner.py now defines a DATA_DIR constant that defaults to runtime/<name>/ but honors a per-service <PACKAGE_UPPER>_DATA_DIR env var. Routing all persistent state through it lets an editing agent run a throwaway instance against a copy of the data with a single env var, never touching the live store. It's a per-service variable (not a shared SERVICE_DATA_DIR) so it can't leak into supervisord's inherited environment and silently repoint every service at once.
  • build-web-service documents the DATA_DIR convention where services are created, and why a hardcoded runtime/<name>/ at a call site silently bypasses the override and re-exposes the live data.
  • update-service gains a "Protect the user's data while you verify" section, ordered cheapest-first: read-only verification needs no ceremony (reading, including to render a preview, is safe -- only writes are dangerous); if verification must write, run against a copy outside runtime/ via the override and delete the copy; never delete from the live store to clean up (leave a stray test record rather than run a broad delete); snapshot before any genuine in-place change to the real store, strictly as a recovery net and never as a routine wipe-and-restore (overwriting a running store tears its state and loses any real writes that land during the test window); retrofit older hardcoded services when you touch them; and teardown leaves the data dir in place unless the user explicitly asks to remove it.

Scope

Skills plus one scaffold script -- no runtime application code changed. The scaffolder change was verified by generating services and confirming the output lints clean (ruff check / ruff format), imports correctly, and that DATA_DIR redirects to the env-var path while defaulting to runtime/<name>/. The skill validator passes on the new skill, and the code-guardian diff/verify and conversation gates ran clean on the front-door replacement.

Notes

  • The front-door replacement improves the odds the right process is invoked, but triggering is still model-judgment; an agent that never considers any skill can still slip through. A deterministic PreToolUse hook (detect an edit to a registered service's lib, inject a reminder) remains available as a backstop if stale-tab / skipped-hardening keeps recurring.
  • The data-safety guidance is advisory, not enforced -- nothing mechanically blocks a destructive op against a runtime/* store. A PreToolUse hook that blocks or confirms such ops would make it a hard backstop if the guidance alone proves insufficient. Existing services are retrofitted lazily (when next edited) rather than in a big-bang migration; until then they fall back to read-only verification plus the snapshot net.

Gabriel Guralnick added 4 commits July 1, 2026 15:30
Changing an existing service had no clear entry point. edit-services only
covered the supervisord [program:*] plumbing and was usually skipped when an
agent just edited service code, so nothing reliably prompted refreshing the
user's tab or the turn-end hardening.

update-service is a front door that triggers on editing/fixing/restyling/
extending an existing service -- web service or background daemon. It owns the
live change loop (apply the change so it takes effect, refresh the user's tab
for web services, verify) and the turn-end handoff to update-artifact /
heal-artifact. Larger-scope changes route back through the same
interactive-delivery (mock-confirm) flow used to build the service, so heavy
work never happens against an unconfirmed shape.

The supervisord program mechanics move into
update-service/references/service-processes.md. Cross-links in build-web-service,
libs/bootstrap/README.md, and CLAUDE.md now point at update-service.
telegram-bot runs in a tmux window in this deployment, not as a supervisord
[program:*], so it can't be `supervisorctl restart`ed -- a misleading example
for a skill whose daemon path is built on supervisorctl. Swap it for
runtime-backup and cloudflared, which are genuine supervisord programs.
The recurring failure mode when editing a service is verifying a change by
writing test data into the live store and then deleting it as cleanup,
with a too-broad delete taking real records with it.

- Scaffold now generates a DATA_DIR constant that defaults to
  runtime/<name>/ but honors a per-service <PACKAGE_UPPER>_DATA_DIR env
  var, so a service's data location can be redirected without code edits.
- build-web-service documents routing all persistent state through
  DATA_DIR and why it keeps later edits safe.
- update-service adds a 'protect the user's data' section: verify
  read-only or against a disposable copy pointed at by the override,
  never delete from the live store to clean up, snapshot before any
  in-place change, and leave the data dir in place on teardown.
nothing to refresh; the change takes effect when the process restarts.

**This skill is the front door for editing a service's code.** You do not
need to have loaded it to start typing an edit -- but the moment you are

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if an agent is reading this text it has already loaded the skill - only the description is seen without loading it

web tab keeps showing the old page until it's refreshed.

If you are doing something other than editing an existing service, you are
in the wrong skill:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're probably hammering this point too much - don't need it both here and in the description. in general we can probably make this whole skill a bit more concise

Gabriel Guralnick added 5 commits July 9, 2026 11:38
Extract the "boot a throwaway instance of a service on a spare port"
motion -- previously hand-rolled in the update-service data-isolation
prose and baked into the system-interface preview -- into one shared,
unopinionated script, .agents/shared/scripts/serve_isolated_instance.py
(up/down). It takes the launch command, cwd, and env overrides as
parameters, picks a free port, injects it, waits for health, and either
returns the loopback URL (for the agent's own data-safe testing) or,
given the preview flags, registers it and wraps it in the labeled
"preview" frame as a tab. The frame wrapper (preview_wrapper_server.py)
moves into shared alongside it.

update-system-interface's preview/unpreview become thin adapters that
delegate boot/teardown to the shared script, passing the
system-interface specifics (neuter layout persistence, probe
/api/agents, register inner app + wrapper). The merge and
reveal/auto-rollback machinery -- the part whose failure strands the
user with no UI -- stays owned by reveal_system_interface.py and was
intentionally not generalized.

Scaffolded Flask services now read their listen port from a
<PKG>_PORT env override (mirroring <PKG>_DATA_DIR), so a throwaway can
bind a spare port beside the live one. update-service documents the
shared-script invocation for data-safe testing and the optional
user-facing preview, and tells agents to retrofit both overrides onto
older services.
Two chats editing the same service produced four collision shapes: live
edits interleaving in the shared working tree, duplicate harden workers
(name/branch/runtime dir all derive from the service name), a hardened
branch merging over a moved base, and merges landing mid-edit.

Serialize the foreground with an advisory per-service editing lease (a
cross-agent tk ticket taken by update-service before touching code,
released each turn end; another agent's lease is surfaced to the user,
never silently broken). Make harden passes single-flight per artifact
with coalescing, specified in the new shared harden-contention.md
reference: a lead finding a live pass leaves a note instead of
dispatching a sibling; before merging, the lead waits out the foreground
lease and runs a merge-base freshness check over the artifact's
footprint; stale or conflicted passes are never hand-resolved but
discarded and superseded by one pass covering everything since the last
hardened merge. The foreground always wins: a live edit never waits on a
pass, it just makes the pass stale.
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