fix(issue-10,13): check every agy artifact for holders and purge orphaned leftovers

Round-3 review of f5a6575 (planner-reviewer-codex-01, R1/R2).

R1 - the adapter's in-use check handed only the conversation DB to lsof, so a
holder that keeps the presence lock (or a sidecar) open while naming the uuid
on no command line and in no session variable was invisible, and the held
lock was unlinked from under it. AgyAgentAdapter.conversation_in_use now
passes every path of the conversation (DB, -wal/-shm/-journal, brain,
annotation, presence lock).

R2 - both resolver stages required the DB, so once an interrupted or pre-#10
purge had removed it, the WAL/annotation/lock left behind were "no artifact"
(exit 5) forever. BaseAgentAdapter.has_artifacts (default: a uuid-bearing
artifact path that exists) replaces the resolver's path fallback, and agy's
override accepts any of its pieces.

Tests: a flock holder that names the uuid nowhere is refused and nothing is
removed; orphaned leftovers resolve to agy and are purged. VERSIONS.md
v4.4.3 items 1-2 and orc-onboard SKILL.md updated.

Refs #10 #13

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Fable 5.1 committed 2026-09-13 10:30:02 +09:00
1 parent f5a6575bf2
commit fae70c13d9
6 files changed
+90 -9

No files matched your search

+5 -5
View File
@@ -13,8 +13,9 @@ def _resolve_purge_target(agent_hint: str, uuid: str, workspace: str):
An explicit agent is trusted as long as its artifact exists. `auto` tries
each adapter's own verification first (revalidate mode: an orchestrator
conversation is not in any workspace cache), then a plain path check for
adapters whose artifact path embeds the uuid.
conversation is not in any workspace cache), then the adapter's own
`has_artifacts` - which, for agy, also accepts orphaned sidecars, the
annotation or the presence lock left behind after the DB is gone.
"""
from lib_py.agents.base import DiscoveryContext
names = [agent_hint] if agent_hint and agent_hint != 'auto' else list(PURGE_PROBE_ORDER)
@@ -32,11 +33,10 @@ def _resolve_purge_target(agent_hint: str, uuid: str, workspace: str):
pass
for name, adapter, ctx in probes:
try:
path = adapter.artifact_path(uuid, ctx)
if adapter.has_artifacts(uuid, ctx):
return name, adapter, ctx
except Exception:
continue
if uuid in path and os.path.exists(path):
return name, adapter, ctx
return None
+22 -1
View File
@@ -1,6 +1,6 @@
import os, sys, json, sqlite3, shutil
from typing import Optional, Any
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, conversation_in_use
class AgyAgentAdapter(BaseAgentAdapter):
@property
@@ -74,6 +74,27 @@ class AgyAgentAdapter(BaseAgentAdapter):
def _cli_root(self, ctx: DiscoveryContext) -> str:
return f"{ctx.home_dir}/.gemini/antigravity-cli"
def _artifact_paths(self, uuid: str, ctx: DiscoveryContext) -> list:
"""Every on-disk piece of one conversation, DB first: the SQLite
sidecars, the brain dir, the title annotation and the presence lock."""
db = self.artifact_path(uuid, ctx)
root = self._cli_root(ctx)
return ([db] + [f"{db}{suffix}" for suffix in ("-wal", "-shm", "-journal")]
+ [f"{root}/brain/{uuid}", f"{root}/annotations/{uuid}.pbtxt",
f"{root}/presence/{uuid}.lock"])
def has_artifacts(self, uuid: str, ctx: DiscoveryContext) -> bool:
# Any piece counts, not only the DB: a purge that was interrupted (or
# the pre-#10 purge) can leave a WAL, annotation or lock behind with
# the DB already gone, and those orphans must still be reachable.
return any(os.path.exists(p) for p in self._artifact_paths(uuid, ctx))
def conversation_in_use(self, uuid: str, ctx: DiscoveryContext) -> bool:
# The holder may keep the presence lock (or a sidecar) open rather
# than the DB, and may name the uuid nowhere; the lsof leg of the
# check must see every path of the conversation.
return conversation_in_use(uuid, self._artifact_paths(uuid, ctx))
def purge_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list:
# Issue #10: a purge must leave nothing resumable behind. Besides the
# conversation DB and the brain dir, agy keeps SQLite sidecars (-wal /
+8
View File
@@ -191,6 +191,14 @@ class BaseAgentAdapter:
on-disk tree override this; the default has nothing to scan."""
return []
def has_artifacts(self, uuid: str, ctx: DiscoveryContext) -> bool:
"""Anything of this conversation is on disk (a purge target exists
even when `verify_artifact` would not vouch for it). The default only
trusts an artifact path that embeds the uuid - a shared store (one DB
for every conversation) exists for any uuid and proves nothing."""
path = self.artifact_path(uuid, ctx)
return uuid in path and os.path.exists(path)
def conversation_in_use(self, uuid: str, ctx: DiscoveryContext) -> bool:
"""True while a live process still references the conversation."""
return conversation_in_use(uuid, [self.artifact_path(uuid, ctx)])
@@ -45,7 +45,7 @@ Registers an orchestrator session UUID into the `orchestrator_uuids` list of `.m
# residual: none (agent: agy)
```
The purge runs before the registry update, so a refused purge (conversation in use, exit `5`) changes nothing. If no adapter finds an artifact for the UUID the registration is still removed with a warning. For agy the adapter also removes the SQLite sidecars (`-wal`/`-shm`/`-journal`), the title annotation and the presence lock (Issue #10) and reports anything under `~/.gemini/antigravity-cli` that still carries the UUID as `WARN residual:`.
The purge runs before the registry update, so a refused purge (conversation in use, exit `5`) changes nothing. If no adapter finds an artifact for the UUID the registration is still removed with a warning. For agy the adapter also removes the SQLite sidecars (`-wal`/`-shm`/`-journal`), the title annotation and the presence lock (Issue #10) and reports anything under `~/.gemini/antigravity-cli` that still carries the UUID as `WARN residual:`. Orphaned leftovers — a WAL, annotation or lock whose DB is already gone — still resolve to agy and are purged, and the in-use check asks `lsof` about every one of those paths, so a holder that keeps only the presence lock open (naming the UUID nowhere) is refused too.
## Auto-Detection Hierarchy (Rev.2)