#416·ego-lite

docs(ego-browser): SKILL.md gates Task Space cleanup on the success path, so error and interrupted rounds leave spaces open

Author: lphehe-botCreated Sep 21, 2026Updated Sep 21, 2026

Scope

The runtime side of this is already tracked well: #270 (with 26h ps sampling), #363, #173 and the RFC in #405. This issue is deliberately narrowed to the skill text shipped in this reposkills/ego-browser/SKILL.md — which is the layer every agent reads first, and which currently contains an explicit escape hatch with no compensating cleanup step.

Environment

  • ego lite / ego-browser: 0.5.0.32, Chromium 152.0.7977.54, Node 24.18.1
  • macOS 26.6.2, Apple Silicon (arm64)
  • Skill: v2.0.0 (metadata date: 2026-09-09), shipped inside the app bundle at ~/.local/share/ego/ego-skills, symlinked to ~/.agents/skills/ego-browser
  • Agent host: Qoder; scripts run as one-shot ego-browser nodejs invocations

What the skill says today

Cleanup is gated on success, and stated once, ~440 lines into the document:

  • L443-445 — "When the task succeeds, close the TaskSpace by default with await task.finish({ keep: [] })."
  • L460-461 — "Use page.close() only while the task is still in progress. Do not call finish() when the task stops for user control or an error."
  • "Spaces, rounds, and pages" (L68-L81) only pushes the other way: one space per goal, resume it across rounds, "Task spaces, tabs, and Page labels persist", never create a new one to escape a stuck page.

The "don't finish on error" rule is correct on its own — it preserves the scene for diagnosis. The problem is what is absent: no positive instruction for that branch (who eventually reclaims the space, and how the user is told one is open), and no next-session rule to inventory what previous rounds abandoned. So a round that ends on a failed assertion, a wrapper timeout, a hand-off, or simply "the agent got its answer and stopped" leaves a Task Space plus its tabs and renderer open indefinitely by design. The 12-hour cleanup window mentioned by @liuwd8 on #270 (2026-08-15) is orders of magnitude beyond agent timescales, as #405 argues.

The same wording survives unchanged in v2.0.1-beta.1 (L491-492, L508-509), so this is not already fixed on dev.

Evidence (2026-09-21)

listTaskSpaces() at the start of a session, before any work was done:

json
[{"id":1,"name":"ops-page regression sweep","ownership":"agent","createdBy":"agent"},
 {"id":2,"name":"ops-page sweep fresh",     "ownership":"agent","createdBy":"agent"},
 {"id":8,"name":"inspect numeric relist UI","ownership":"agent","createdBy":"agent"},
 {"id":9,"name":"ads profit filter regression","ownership":"agent","createdBy":"agent"}]

Each held one openedBy: "agent" tab. Note what the names have in common: regression sweep, filter regression, inspect … UI — verification rounds, i.e. exactly the ones that most often stop on a failed assertion or on "result obtained, move to reporting". A client-side sweep reaped 1/2/8 with taskSpace(id)finish({ keep: [] }); user-owned and openedBy: "unknown" tabs were untouched, as documented.

Minor side observation while doing this: id 9 was present in one listTaskSpaces() call and gone from the next a minute later, and while it was still listed, taskSpace(9) failed with task space not found: 9. Listing and live set can apparently disagree briefly — separate from this issue, but worth knowing.

Proposed skill edits (docs only, no runtime change)

  1. Make reclamation a round rule, not a closing paragraph. In "Spaces, rounds, and pages", state that the script performing the last verification is the script that calls finish(); a round that ends without it is not finished. Agents that skim still hit it before they write any code.
  2. Give the non-success branch a positive action. After "Do not call finish() when the task stops for user control or an error", add what to do instead: call await task.handOff(), print task.spaceId, and say in the reply that the space is intentionally left open. Leftover-by-accident and left-on-purpose should be distinguishable to the user.
  3. Add a pre-flight inventory step. Before creating a space in a fresh session, call listTaskSpaces() and reap ownership === "agent" spaces belonging to no live round (taskSpace(id)takeOverTaskSpace(id)finish({ keep: [] })), noting that user-owned and delegated spaces are exempt.
  4. Ship that as a documented helper (a references/ snippet or an ego-browser cleanup flag) so agents do not hand-roll it — same direction as the manual-cleanup branches in #270 that could not be opened as PRs.

Workaround in use meanwhile

~15 lines, run before creating a space and at the end of a session:

javascript
const open = async (id) => {
  for (const fn of [taskSpace, takeOverTaskSpace, claimTaskSpace]) {
    try { return await fn(id); } catch {}
  }
};
for (const s of await listTaskSpaces()) {
  if (s.ownership !== "agent") continue;          // user tabs are protected by ego itself
  const t = await open(s.id);
  if (t) await t.finish({ keep: [] });            // closes the space when no protected tabs remain
}

Happy to test a docs PR against this session pattern, or to turn the snippet into a references/cleanup.md draft if that form is more useful.