Skip to content

Arch violation: Adapter-level LaunchedSessionMeta files create a third layer of shadow state #112

@c-h-

Description

@c-h-

What state is duplicated

Each adapter persists its own LaunchedSessionMeta JSON files alongside the adapter's native storage:

  • Claude Code: ~/.claude/agentctl/sessions/<sessionId>.json
  • OpenCode: ~/.agentctl/opencode-sessions/<sessionId>.json
  • Codex: ~/.codex/agentctl/sessions/<sessionId>.json

These files contain: sessionId, pid, startTime, wrapperPid, cwd, model, prompt, launchedAt.

This creates three layers of state for a single session:

  1. Adapter native storage (ground truth)
  2. Adapter-level LaunchedSessionMeta files (shadow feat(phase-1): agent-ctl core + Claude Code adapter #1)
  3. Daemon state.json SessionRecord (shadow feat(openclaw): add OpenClaw gateway adapter #2, see Arch violation: Daemon state.json maintains full session registry that shadows adapter ground truth #110)

Where is the ground truth?

  • PID → process liveness: kill(pid, 0) and ps aux with start time cross-referencing
  • Session data: Adapter native files (JSONL, JSON session files)
  • CWD mapping: lsof on the actual process

How does it desync?

  1. Stale metadata accumulation: Currently ~/.agentctl/opencode-sessions/ has 20+ pending-*.json files for dead sessions. These files are only cleaned up during isSessionRunning() checks — if nobody queries those sessions, they persist forever.
  2. PID recycling false positives: Despite start-time cross-checking, the metadata can match a wrong process if the start time check fails (e.g., unparseable ps output, clock skew).
  3. Inconsistency with daemon state: The adapter meta may say a session is alive (PID exists) while daemon state.json says it's stopped (wrapper exited), or vice versa. reconcileAndEnrich() tries to merge these, but the merge logic is complex and error-prone.
  4. No TTL: Unlike adapter native files which are naturally scoped, these metadata files have no expiration mechanism.

User-visible symptom

  • Ghost sessions in agentctl list from stale metadata files
  • Inconsistent status between agentctl list (daemon) and agentctl status <id> (adapter direct query)
  • Slow discover() due to scanning metadata directories

Proposed fix

The LaunchedSessionMeta exists because adapters need PID information that isn't in their native storage (the adapter launched a detached process but the native session file doesn't record which PID is running it). The correct fix:

  1. Short-lived: Make metadata files self-cleaning with a hard TTL (e.g., 24h). If the process isn't alive after 24h, the metadata is definitely stale.
  2. Minimal: Only store the PID and process start time — drop cwd, model, prompt, launchedAt which duplicate adapter native data.
  3. Eventually: Explore whether adapters can determine PID association without agentctl's help (e.g., Claude Code's --continue flag includes sessionId in args, making ps-based matching reliable).

Related: #110

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions