docs(ego-browser): SKILL.md gates Task Space cleanup on the success path, so error and interrupted rounds leave spaces open
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 repo — skills/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, Chromium152.0.7977.54, Node24.18.1 - macOS
26.6.2, Apple Silicon (arm64) - Skill:
v2.0.0(metadatadate: 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 nodejsinvocations
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 callfinish()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:
[{"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)
- 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. - 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: callawait task.handOff(), printtask.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. - Add a pre-flight inventory step. Before creating a space in a fresh session, call
listTaskSpaces()and reapownership === "agent"spaces belonging to no live round (taskSpace(id)→takeOverTaskSpace(id)→finish({ keep: [] })), noting that user-owned and delegated spaces are exempt. - Ship that as a documented helper (a
references/snippet or anego-browsercleanup 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:
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.
Source: citrolabs/ego-lite