fix(agents): pin post-discovered session ids on a verified completed; agy never takes another workspace's or session's conversation (Issue #40)
- BaseAgentAdapter: id_lifecycle (pre_assigned: claude, grok / post_discovered) and capture_active_session_id - exactly one verified candidate newer than the session that no other row records, else a reason (no-candidate, ambiguous, ...). - agy: verify_artifact (discover) rejects an id last_conversations.json maps to another workspace and always excludes ids other rows record (a cache hit no longer bypasses it). capture uses the cache only as a proposal: the conversation's presence lock or DB must be held by the row's pane process or a descendant (lsof/ps); anything unprovable is not pinned. - lib_py/agents/autopin.py: handle_completed, run by the side that verified the job's completed - the delegate-job wrapper (direct), create --onboard and the loop's wait_for_job (recovery, as a subprocess: G9). Local herdr jobs only; one (job, dispatch token, attempt) is handled once (job-scoped flock + marker in the job dir); writes go through atomic_dump_yaml; a pin records session_id_source=discovered / session_id_verified=true; a failure leaves the reason on the row and never changes the job's result. - reconcile.sh drift C for agy, hermes, opencode and codex calls the same capture (the agy *.db glob is gone); _pin_and_verify_resume uses pin_row. - Tests: tests/test_issue40_auto_pin.py; issue64 :407 now expects False; the static reconcile-source checks follow the move to the adapters; the two tier2 drift-C component tests create with --no-onboard (onboarding now pins). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ff8b381520
commit
feccfd5e03
19 files changed
+1128
-338
No files matched your search
@@ -169,6 +169,22 @@ def db_rows_naming(db: str, uuid: str, columns=(), id_tables=(), connect=None) -
|
||||
conn.close()
|
||||
return found
|
||||
|
||||
# Session-id lifecycles (Issue #40): the id is handed to the CLI before it
|
||||
# starts, or the CLI writes it to disk after its first turn.
|
||||
PRE_ASSIGNED = 'pre_assigned'
|
||||
POST_DISCOVERED = 'post_discovered'
|
||||
|
||||
|
||||
def capture_result(status: str, session_id: Optional[str] = None, reason: str = "",
|
||||
candidates: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
"""What capture_active_session_id found (Issue #40). `status` is
|
||||
'captured' (session_id set) or why nothing may be pinned: 'no-candidate',
|
||||
'ambiguous', 'sibling-claimed', 'rejected', 'unverifiable-owner',
|
||||
'owner-mismatch'."""
|
||||
return {"status": status, "session_id": session_id, "reason": reason,
|
||||
"candidates": list(candidates or [])}
|
||||
|
||||
|
||||
class SpawnSpec:
|
||||
def __init__(self, agent_name: str, session_name: str, command: str, env: Optional[Dict[str, str]] = None):
|
||||
self.agent_name = agent_name
|
||||
@@ -358,3 +374,28 @@ class BaseAgentAdapter:
|
||||
|
||||
def discover(self, ctx: DiscoveryContext) -> list:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def id_lifecycle(self) -> str:
|
||||
"""PRE_ASSIGNED: the id is given to the CLI at spawn (nothing to capture).
|
||||
POST_DISCOVERED: the CLI writes it after its first turn (Issue #40)."""
|
||||
return POST_DISCOVERED
|
||||
|
||||
def capture_active_session_id(self, ctx: DiscoveryContext) -> Dict[str, Any]:
|
||||
"""This session's id, decided only when it is unambiguous (Issue #40):
|
||||
exactly one discover() candidate that verify_session_uuid accepts and
|
||||
no other row records (ctx.row['_sibling_claimed_uuids']). Read-only."""
|
||||
claimed = set(ctx.row.get("_sibling_claimed_uuids") or [])
|
||||
found: List[str] = []
|
||||
for uuid in self.discover(ctx):
|
||||
if not uuid or uuid in claimed or uuid in found:
|
||||
continue
|
||||
if verify_session_uuid(ctx.cwd, self.name, uuid, row=ctx.row, home_dir=ctx.home_dir,
|
||||
claude_dir=ctx.claude_dir, mode="discover"):
|
||||
found.append(uuid)
|
||||
if not found:
|
||||
return capture_result("no-candidate", reason="no verified candidate newer than the session")
|
||||
if len(found) > 1:
|
||||
return capture_result("ambiguous", reason=f"{len(found)} candidates newer than the session",
|
||||
candidates=found)
|
||||
return capture_result("captured", session_id=found[0], candidates=found)
|
||||
Reference in new issue
Block a user