Skip to content

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

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions