Skip to content

Harness-adapter consolidation: one per-kind registry, then authoritative session tracking #116

Closed

Description

@Svampen

Groomed 2026-09-10. Kept as the adapter-consolidation issue the CLAUDE.md roadmap names (the duplicated Claude/opencode pairs in harness_events_*, harness_actions_*, skein-harness). Absorbed #240: step one is a single per-kind registry with a Rust counterpart, replacing the three places that encode which program a kind spawns (managedProgram(), managed_program(), cmdForKind()) and validating the kind string at the Tauri boundary. The session-tracking spikes below still hold. Tier 3.


Goal

Give Skein a first-class, authoritative model of the underlying "session" each harness is running — and track it over the life of the pane, not just at spawn. Today a harness's session is a value we guess once at spawn and assume never changes. It does change (the user runs /clear, or /exits and then <harness> --resume in the shell), and Skein silently keeps following the old one. We need to know the current session so we can keep Live Context (Activity / Plan / Diff) attached to the right thing — and offer to attach to a new or previous session — for any harness kind, not just Claude.

This must be harness-agnostic from day one. We support claude, opencode, and will add copilot and others. The abstraction has to fit all of them.

Why now

While building #80's Live Context, we added a Claude-specific heuristic (detect_newer_session) that watched the cwd's session directory for a newer .jsonl and offered a "re-attach" button (PR #115, now closed). It was a band-aid and it broke for the real workflow: two Claude harnesses in one room share a worktree → share a cwd → share one ~/.claude/projects/<cwd>/ directory, so each harness saw the other's growing session file as "a newer session" every turn. The fix attempts confirmed the deeper truth: Skein has no model of which session belongs to which pane. This issue is that missing foundation. The re-attach / auto-follow UX should be layered on top of it afterward.

Current state (recon)

Claude — file-on-disk, cwd-derived

  • Session id is pre-allocated at spawn (crypto.randomUUID() → claude --session-id <uuid>). App.tsx ~1999/2060, cmdForKind ~973.
  • The follower tails ~/.claude/projects/<encode_cwd(cwd)>/<sessionId>.jsonl (encode_cwd replaces /→-; session_jsonl_path), watching the parent dir non-recursively. harness_events_claude.rs.
  • A single project dir holds many session files (verified on disk: 7+ .jsonl in one dir). The session id lives inside every row, but the filename is the only per-session key — and the directory is shared by every pane in the cwd.
  • Gap: the follower is bound to one file path for life. /clear or claude --resume <id> in the same pane produces a different file the follower never looks at. Filename/mtime alone cannot say which pane owns which file.

opencode — server + port, DB-backed

  • No pre-alloc. Each spawn gets a fresh pick_free_port() and runs opencode --port <N> --hostname 127.0.0.1. The session id is captured after spawn from the SSE session.created event on that port (primary), with a sqlite poll of ~/.local/share/opencode/opencode.db (SELECT id FROM session WHERE directory=? AND time_archived IS NULL ORDER BY time_created DESC) as fallback. harness_events_opencode.rs, resume.rs, App.tsx captureOpencodeSessionId.
  • The per-pane port → SSE stream is authoritative while that process lives — opencode tells us busy/idle and which session was created. This is structurally better than Claude's file-sniffing.
  • Gap: if the user /exits and runs opencode again in the shell, the new process binds its own random port (not Skein's), so the adapter (pointing at the dead port) goes silent. Skein could rediscover via the opencode.db (new row for this directory), but that reintroduces the same shared-cwd ambiguity (which DB row belongs to which pane).

copilot / byoh

  • No session concept. cmdForKind returns a fixed argv; sessionId is never set. The abstraction must allow "this kind has no session" cleanly.

The session value itself

  • Harness.sessionId?: string — a bare, write-once string (setHarnessSessionId is first-writer-wins), persisted in the room blob, used on Skein restart to rewrite the spawn argv to resume form (--resume/--session/--continue) after validating the id still exists. types.ts, App.tsx, db.rs, resume.rs.
  • Gap: no lifecycle (pending / active / superseded / stale), no in-life rebinding, and cmdForKind/resumeCmd are hard-coded switches with no per-kind registry.

Process layer (the missing authoritative signal)

  • Skein spawns every PTY child itself; portable-pty's Child exposes process_id() — but Skein discards it (pty.rs: the Pty struct has no PID field). It has no tcgetpgrp, no process-tree inspection, no lsof. So today there is zero OS-level link from a pane to the process actually reading/writing its session.
  • This matters because process ownership is the only unambiguous answer to "which pane does this session belong to" when panes share a cwd: PID 1234 (pane A) ≠ PID 5678 (pane B) no matter how many files/rows share the folder.

What we're actually trying to achieve (scenarios)

For every harness kind that has a session concept, Skein should keep tracking the right session through:

  1. In-tool reset — user runs /clear (or the kind's equivalent) inside the running harness → a new underlying session begins in the same pane. Skein should notice and follow it (Live Context keeps working).
  2. Exit + resume in shell — user /exits the harness, gets the "Press Enter for shell" prompt, then types claude --resume <id> / opencode --session <id> / opencode in that shell. The new agent process is a child of Skein's shell PTY. Skein should detect the new (or resumed) session and re-bind to it — same pane, same harness, no need to recreate the harness.
  3. Multiple harnesses, one room/cwd — two claude (or two opencode) panes sharing a worktree must never be confused for each other. A session change in pane A must attribute to pane A only.
  4. Resume on Skein restart — unchanged in spirit (already works), but should fall out of the same model rather than being a separate code path.
  5. Attach to a previous session — because we now know the set of sessions for a cwd and which is current, we can offer "attach to an older session" deliberately (foundation for an in-Skein session switcher).

Once the model is authoritative, the UX on top can be automatic follow (preferred for the common case) and/or an explicit affordance — and crucially it won't misfire across sibling panes.

Proposed direction (not a locked design)

A per-harness SessionTracker abstraction, one implementation per kind, exposing a small harness-agnostic interface:

  • currentSession(): SessionRef | None — the session this pane is actually on right now (observed, may change).
  • an event/stream of session-changed transitions (old → new), with the cause when known (in-tool reset, restart-in-shell, resume).
  • the data needed to attach/resume (so the existing follower + Live Context re-point cleanly).

Backed by, per kind:

  • claude: the cwd's project dir as a session registry; disambiguated by process ownership.
  • opencode: the per-pane SSE/port as the live signal; the opencode.db (by directory) for rediscovery after a shell restart; disambiguated by process ownership where the DB is shared.
  • copilot/byoh: a no-op tracker (no session).

Harness.sessionId: string | undefined evolves into observed, lifecycle-aware state (current id + status), no longer write-once.

Enabling capability: process ownership

Capture each PTY child's PID (cheap — portable-pty already gives it to us) and, when a candidate session file/row appears, resolve which pane's process tree owns it. This is the authoritative disambiguator for shared-cwd. It requires platform-specific file/connection → PID lookup (macOS libproc/lsof, Linux /proc, Windows handle API) — we run macOS and Windows, so plan for both.

Open questions / spikes to resolve before locking the design

  1. Does Claude /clear create a new session file, or reuse the current one? This materially changes the design — if only full restarts create new files, Skein can largely own that path. Must verify empirically.
  2. Cross-platform file→PID (and socket→PID) cost. Spike macOS + Windows feasibility; decide if process-ownership is the foundation or a later enhancement with a cheaper interim (e.g. opencode leans on its SSE/DB; Claude uses a narrower heuristic until ownership lands).
  3. opencode shell-restart rediscovery. Can we reliably re-find a new opencode process's session (new random port) via the opencode.db directory row + ownership, and reconnect SSE? Is there a way to make opencode use a Skein-known port on user restart?
  4. Should Skein own the restart path? e.g. a "restart / new session" affordance Skein runs with a known id, sidestepping detection for the common case (typed /clear still needs detection).
  5. Resume-failure handling. If a resumed id is stale, fall back to --continue / fresh rather than erroring in the PTY.

Non-goals

  • Re-attach / auto-follow UX polish (separate, layered on top once the model is solid).
  • Changing the shared-worktree model (panes intentionally share a cwd — the model must cope with that, not remove it).
  • copilot/byoh session features (they have none).

Acceptance criteria (foundation)

  • A harness-agnostic SessionTracker interface with claude + opencode implementations and a no-op for sessionless kinds.
  • Skein knows each pane's current session and emits a session-changed signal, correctly attributed per-pane even with multiple harnesses in one cwd (covered by tests).
  • Scenarios 1–4 above are detectable (UX wiring can follow).
  • PTY child PID captured and available to the trackers (or a documented decision to defer ownership with a named interim).
  • Spikes 1–3 answered and written up before implementation locks.

Supersedes the closed PR #115 band-aid. Relates to #80 (Live Context consumes the session signal) and #50 (activity/phase tracking attaches per session).

Activity

  1. Svampen commented on Jun 23, 2026

    @Svampen
    ContributorAuthor

    Spike findings (round 1) — partly overturns the "process ownership via file→PID" direction

    Ran the three spikes (macOS empirical + Claude Code docs via the in-tool guide). One result invalidates an assumption in the issue body above, and the net picture is clearer and more tractable for the scenario we actually care about.

    Spike 1 — does Claude /clear create a new session file?

    Most likely YES (new session id → new .jsonl), but worth one direct confirmation.

    • Claude Code docs describe /clear as "Start a new conversation with empty context. The previous conversation stays available in /resume… To free up context while continuing the same conversation, use /compact instead." That wording — a new conversation, the previous one separately resumable/labelable, and /compact offered as the same-conversation alternative — indicates /clear forks a new session file. Matches the original bug report (after /clear, the follower lost tracking).
    • --resume <id> / --continue append to the existing <id>.jsonl (documented).
    • Action: a 30-second manual confirm (run /clear, watch the project dir for a new file) before locking design, but proceed on "/clear = new file".

    Spike 2 — can we authoritatively attribute a session to a pane? (the disambiguator)

    This is where the assumption broke.

    • ❌ Claude does NOT keep its .jsonl open. Tested against a live session's file (the transcript being written this very moment): lsof found no open handle, across 4 polls over 2s. Claude opens/appends/closes per write. → lsof <file> → owning PID does not work for Claude. The "process ownership via open file handle" enabling-capability proposed above is out for Claude.
    • ❌ No pid/ppid field in the JSONL rows; no pidfile / lock file convention (confirmed via docs). So there is no OS-level link from a Claude session file to its writer PID.
    • ✅ socket→PID works (lsof -nP -iTCP:<port> → PID). Sockets stay open, so this is reliable — relevant for opencode's port.
    • ✅ Process tree is readable (pgrep -P, ps -o ppid=), and crucially: Skein spawns the agent as a direct PTY child. Live ancestry of a running Claude pane:
      zsh(24214) ← zsh(26974) ← claude(26953) ← Skein.app(…) ← launchd
      
      i.e. portable-pty's child.process_id() is the agent PID — Skein just needs to keep it (today pty.rs discards it).
    • ✅ Both kinds carry session identity in their argv (straight out of ps):
      • claude --session-id <uuid> / claude --resume <uuid>
      • opencode --port <N> --hostname 127.0.0.1 --session <id> / --continue

    Spike 3 — opencode shell-restart rediscovery

    Feasible. A user-restarted opencode (new random port) is a descendant of the pane's PTY child; we can enumerate descendants, read its argv for --port/--session, and reconnect SSE to the new port (cross-checked via socket→PID). opencode is fully process-observable (argv carries port+session; socket→PID; SSE is authoritative while alive).


    Revised direction (grounded in the spike)

    The signals available differ sharply by kind, so the harness-agnostic SessionTracker must lean on process-tree + argv inspection (which works for both) rather than file→PID (which doesn't work for Claude):

    The scenario we actually set out to fix — /exit then <harness> --resume <id> in the shell — is authoritatively solvable for BOTH harnesses: the new agent is a descendant of the pane's known PTY child PID, and its argv contains the resumed session id. No file sniffing, no ambiguity.

    Coverage map:

    case claude opencode
    initial spawn id ✅ argv (--session-id) ✅ SSE session.created / argv / sqlite
    exit + --resume <id>/--session <id> in shell ✅ descendant + argv ✅ descendant + argv (+ socket→PID for new port)
    bare restart in shell (claude / opencode, no id) ⚠️ id not in argv → new file in cwd (ambiguous if multiple claude panes share cwd) ✅ socket→PID → new port → SSE
    in-tool /clear (new file, same process) ❌ no OS signal — argv stale, no open handle, no pid-in-file n/a (opencode reset shows on SSE)
    single harness per cwd ✅ always unambiguous ✅

    The one genuinely unsolvable-by-OS case is: multiple Claude panes sharing a cwd + in-tool /clear. There is no authoritative signal. Options for that residual:

    1. Skein owns the reset/new-session action (recommended) — a Skein-native "new session / clear" affordance that Skein executes with a known --session-id, so detection isn't needed. The user typing /clear manually inside Claude remains the only residual, handled best-effort.
    2. Keystroke-causality heuristic (attribute the new file to the pane the user last typed in) — the original instinct; works but a heuristic.
    3. Scope auto-follow to the unambiguous cases (single-claude-per-cwd, and all the ✅ rows) and degrade gracefully otherwise.

    Concrete capability changes implied

    • Keep the PTY child PID in pty.rs (it's already in hand from spawn_command) and expose it to the trackers.
    • Add a small process-introspection helper: enumerate descendants of a PID + read a PID's argv. macOS: libproc/proc_pidpath + sysctl(KERN_PROCARGS2) for argv (or shell out to ps).
    • opencode tracker: descendant-scan + argv + socket→PID to recover after shell restart and reconnect SSE.
    • claude tracker: argv at spawn/resume; for bare-restart//clear, rely on single-harness-per-cwd or a Skein-owned reset.

    ⚠️ Windows — UNVERIFIED (must spike on the Windows machine)

    All of the above is macOS-confirmed. Windows equivalents to validate (#108 dogfood):

    • process tree / descendants: CreateToolhelp32Snapshot or WMI Win32_Process.
    • a PID's argv: WMI Win32_Process.CommandLine (or NtQueryInformationProcess).
    • socket→PID: GetExtendedTcpTable.
    • whether a Windows claude/opencode keeps its session file open (may differ from macOS).

    Net: drop file→PID for Claude; build the tracker on process-tree + argv (works for both kinds and nails the exit+resume-in-shell scenario); treat multi-claude-same-cwd /clear as the one case to handle by Skein owning the action rather than detecting it.

  2. Svampen commented on Jun 23, 2026

    @Svampen
    ContributorAuthor

    Spike findings (round 2) — native harness integration should be the PRIMARY channel, not OS sniffing

    Followed the round-1 thread ("why is Skein reverse-engineering this when it owns the flow?") by verifying whether the harnesses can just tell Skein their session, via their own hook/plugin systems. Verified against Claude Code docs and the opencode docs/source. Result: this inverts the design — native integration becomes primary; OS/file sniffing demotes to a fallback.

    Why "Skein owns the PTY" isn't enough

    Owning the PTY gives Skein the transport (raw bytes in/out), not the meaning. A harness's session id is never printed to the terminal, so it isn't in the byte stream — /clear switches session invisibly. Scraping typed shell commands (claude --resume X) is possible from stdin but breaks on aliases/line-editing/history and can't see in-tool /clear. The session identity lives in the harness's private state; the clean way to get it is to have the harness emit it.

    Claude Code hooks — ✅ authoritative, solves the hard cases

    • SessionStart hook fires on startup, /clear, /resume, /compact, with a source field distinguishing them. SessionEnd fires on exit with a reason.
    • Payload includes session_id, cwd, transcript_path, hook_event_name (+ source, optional model).
    • Parent env vars propagate to the hook command. So Skein spawning a pane with SKEIN_HARNESS_ID=<id> (and e.g. SKEIN_HOOK_URL=http://127.0.0.1:<port>/hook) lets the hook report {harness_id, session_id, source, cwd} — unambiguous even when panes share a cwd, because the id is per-pane.
    • The env rides through /exit → shell → claude --resume X, so the exit+resume-in-shell scenario and bare restart are both solved authoritatively, as is /clear (the OS-unsolvable case from round 1).
    • Configurable per-project via .claude/settings.local.json (gitignored; no global edit). Skein can write it into each worktree.
    • Cross-platform → removes the Windows process-introspection spike entirely for Claude.
    • Caveats: SessionEnd can't block and has ~1.5s budget; a crash/SIGKILL won't fire it (re-sync on next SessionStart).

    opencode — ⚠️ upstream-limited; best-effort stays best-effort

    • opencode has plugins (.opencode/plugins/, an event hook) and the SSE /event stream we already use.
    • But opencode's server has no notion of a "current/active session" — session selection is TUI-client-only state that never reaches the server or event bus. session.created fires for new sessions; session.updated/idle/message.updated fire for the worked-on session; a silent switch emits nothing. Upstream: [FEATURE]: SessionStart hook for session lifecycle events anomalyco/opencode#5409 (request for session-start/select hook — open), #7451 (session-level SSE listener — closed not planned).
    • No per-instance env id (only boolean OPENCODE=1); the instance handle is the --port Skein already assigns.
    • A plugin can't natively know it belongs to our spawned instance vs another opencode in the same cwd without a correlating value passed in (e.g. per-instance OPENCODE_CONFIG_DIR).
    • Conclusion: the limitation is upstream, not fixable by Skein sniffing harder. Cleanest available = SSE on our assigned port (primary) + reconcile against GET /session ordered by time_updated for the spawn dir. If exact switch-detection matters, push the upstream issue rather than build a kludge.

    SSE reliability note (opencode)

    Recent opencode SSE bugs (stream closing right after server.connected; dropped publishes in 1.14.42+ — anomalyco/opencode#26697, #27966). Pin/test the version we ship against.


    Revised architecture (supersedes the round-1 "process ownership" direction)

    Invert the dependency: each harness reports its session to Skein through its native integration point; Skein supplies a stable per-pane identity + a local receiver.

    Harness-agnostic contract: "tell Skein {harness_id, session_id, event, cwd} when your session starts / changes / ends."

    • Skein provides: SKEIN_HARNESS_ID (+ callback address) injected on every spawn, including shell respawns; a tiny local receiver (HTTP on localhost, or a watched drop-file); normalization into the per-kind SessionTracker from the issue body.
    • Claude adapter (primary): SessionStart/SessionEnd hooks → POST to Skein. Authoritative; covers spawn, /clear, /resume, exit+resume-in-shell, bare restart, shared-cwd.
    • opencode adapter (primary): SSE on the assigned --port + session-list reconciliation; best-effort on silent switches (upstream-bounded). Optional plugin later if upstream adds a select event.
    • copilot / byoh: no-op (no session concept).
    • Fallback layer (demoted): the round-1 OS/file signals (process-tree + argv, file watch) kept only as a degraded mode for harnesses with no usable push channel, or as corroboration.

    Honest costs

    • Only as good as each harness's extension API (Claude: excellent; opencode: upstream-limited; future kinds: varies).
    • Install/version-coupling burden: Skein writes the hook config into each worktree and ships a small hook command; track each harness's hook-API version.
    • Need a sane fallback when a harness offers no push channel.

    Open items

    1. One direct confirm that /clear fires SessionStart source=clear in a live interactive TUI (docs say yes).
    2. Decide hook-install strategy: per-worktree .claude/settings.local.json (recommended) vs user-global, and how to merge with a user's existing hooks.
    3. Decide the receiver: localhost HTTP endpoint vs Skein-watched drop-file.
    4. opencode: per-instance plugin correlation (OPENCODE_CONFIG_DIR) only if/when we need more than SSE+port gives.
    5. Windows: hooks/SSE are cross-platform, so the only residual Windows work is for the fallback layer (skip unless we need it).

    Net: build the foundation on native harness integration (hooks/plugins) keyed by a Skein-injected per-pane env id; keep OS/file sniffing as fallback only. This is authoritative for Claude (incl. /clear), bounded-by-upstream for opencode, and cross-platform.

  3. Svampen commented on Jun 24, 2026

    @Svampen
    ContributorAuthor

    Spike round 3 — /clear confirmed empirically; Claude adapter design locked

    Ran the live test (a SessionStart/SessionEnd hook logging Claude's payload + an injected SKEIN_HARNESS_ID, in a normal terminal: SKEIN_HARNESS_ID=test-pane-A claude → turn → /clear → turn → exit).

    Result (verbatim, trimmed)

    SessionStart source=startup  session 7ac38652…  transcript …/7ac38652….jsonl   SKEIN_HARNESS_ID=test-pane-A
    SessionEnd   reason=clear    session 7ac38652…                                  SKEIN_HARNESS_ID=test-pane-A
    SessionStart source=clear    session 7bcd13cb…  transcript …/7bcd13cb….jsonl   SKEIN_HARNESS_ID=test-pane-A
    SessionEnd   reason=prompt_input_exit  session 7bcd13cb…                        SKEIN_HARNESS_ID=test-pane-A
    

    Two files on disk: 7ac38652….jsonl and 7bcd13cb….jsonl.

    Confirmed

    1. /clear creates a NEW session id + NEW .jsonl file (open item Allow the user to pick which shell Skein spawns #1 — resolved; the docs' "same file" reading was wrong).
    2. /clear emits a clean old→new transition: SessionEnd(reason=clear) for the old id, immediately followed by SessionStart(source=clear) for the new id. The adapter receives both ids — it can re-point the follower precisely with no file diffing.
    3. SKEIN_HARNESS_ID propagates to every hook event, including across /clear. This is the per-pane identity that disambiguates panes sharing a cwd — the case no OS/file approach could solve.
    4. SessionEnd.reason is meaningful (clear vs prompt_input_exit vs resume/logout) → Skein can distinguish "cleared, re-point & keep harness" from "user exited, await restart" for free.

    Decisions locked (round-2 open items resolved)

    • Install method: a single global Skein plugin that bundles the SessionStart/SessionEnd hooks, self-gated on $SKEIN_HARNESS_ID ([ -z "$SKEIN_HARNESS_ID" ] && exit 0 → clean no-op for non-Skein sessions). No per-worktree settings files, no merge with the user's own hooks. (Verified: plugin hooks are behaviourally identical to settings hooks — same payload, same env inheritance.) Build-time detail still open: the exact persistent local-install path (--plugin-dir vs managed plugin dir); hook behaviour is identical regardless, so this doesn't block design.
    • Transport: localhost HTTP. Skein passes SKEIN_HOOK_URL=http://127.0.0.1:<port>/session (+ a per-session token) at spawn; the hook curls the payload; Skein binds 127.0.0.1 only. No-op if SKEIN_HOOK_URL unset.
    • Identity: SKEIN_HARNESS_ID injected on every spawn (incl. shell respawns), so the chain /exit → shell → claude --resume X still reports the right pane.

    The Claude adapter is therefore: plugin (self-gated install) + localhost endpoint (transport) + SKEIN_HARNESS_ID (identity). Authoritative for spawn, /clear, /resume, exit+resume-in-shell, bare restart, and multi-pane shared-cwd. Cross-platform (no OS process introspection needed for Claude).

    Still open

    • opencode adapter: bounded by upstream (no server-side "current session"); plan stays SSE-on-assigned-port + GET /session reconciliation. A future opencode plugin could POST to the same Skein endpoint if upstream adds a select event.
    • Windows: hooks + localhost HTTP are cross-platform; only the fallback layer would need Windows-specific work, so it's deferrable.
    • Implementation plan (the SKEIN_HARNESS_ID/SKEIN_HOOK_URL env injection in spawn, the plugin payload, the Skein receiver, normalization into the SessionTracker) — to be drafted as the build task.
  4. Svampen commented on Jul 16, 2026

    @Svampen
    ContributorAuthor

    Design input from the grok-build recon (https://github.com/timeloop-vault/skein/blob/main/docs/grok-build-recon-2026-07-16.md, §1):

    • grok-build is ACP-first — its own TUI drives its own agent runtime over the Agent Client Protocol, even in-process, and sessions persist as the ACP update stream (updates.jsonl). Strong evidence for adopting ACP's vocabulary (SessionUpdate / ToolCallUpdate+Diff / PlanEntry / request_permission / turn events) as our normalized internal event model, with per-harness transports (JSONL tail, SSE, ACP stdio) at the edge. Caveat: their queueing/steering/review verbs are all x.ai/* extensions, not core ACP — a universal adapter won't erase per-harness divergence for advanced features.
    • Their tool_taxonomy.rs is the missing model for liveContext/payload.ts: one canonical envelope {name, kind (open-set), namespace, label, read_only, input} where label is the cross-harness join key and input projects only cross-harness fields (raw payload as fallback), plus a claude_alias.rs table mapping Claude tool names onto it — built because "two tables drifted apart".
    • Cautionary data point for the consolidation thesis: grok hand-duplicated its permission types between two crates "temporarily" and they silently diverged in the synced repo.
    • A grok harness (New harness kind: grok (xai-org/grok-build) #195) would be the live testbed for this model — its tailed events already arrive in ACP shape.
  5. Svampen commented on Sep 10, 2026

    @Svampen
    ContributorAuthor

    Backlog grooming 2026-09-10. Reworded as the harness-adapter consolidation issue (the CLAUDE.md roadmap item). Absorbed #240: a single per-kind registry with a Rust counterpart is the first concrete step; the duplicated Claude/opencode adapter pairs and authoritative session tracking follow on it.

  6. changed the title [-]Authoritative per-harness session tracking (harness-agnostic foundation)[/-] [+]Harness-adapter consolidation: one per-kind registry, then authoritative session tracking[/+] on Sep 10, 2026
  7. added 3 commits that reference this issue on Sep 21, 2026
  8. Svampen commented on Sep 22, 2026

    @Svampen
    ContributorAuthor

    Step four is on branch skein/skein-116-4 (1482669). This is the last step of #116 to ship here. The rest is split into new issues.

    • Claude fork: SessionStart source=fork (/branch, /fork, --fork-session) carries the fork's new id and its own transcript, so it is now followed like clear and resume.
    • opencode /new: session.created now carries parentID. A new root session is followed; a task-tool child session (always has parentID) is never captured or followed.
    • opencode /sessions picker: still publishes nothing upstream ([FEATURE]: SessionStart hook for session lifecycle events anomalyco/opencode#5409, closed stale; #18007 not merged). So the first user message in a different root session is the switch signal. Root vs child is decided from session.created/session.updated, else by asking the server (GET /session/<id>). A failed lookup follows nothing.
    • SessionEnd is deliberately not consumed. Every in-tool switch already arrives as a SessionStart with the new id. Exit is seen as PTY exit. And SessionEnd is reported not to fire on some /exits (SessionEnd hook doesn't fire on /exit command anthropics/claude-code#17885). It adds no signal Skein lacks.

    Split out:

    Once 1482669 lands, #116 can close.

  9. added a commit that references this issue on Sep 22, 2026
  10. added a commit that references this issue on Sep 22, 2026
  11. added a commit that references this issue on Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:harness-telemetryHarness phases, L2c adapters, transcripts, subagents

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions