Skip to content

feat: session transformer & resume across Copilot, Claude Code, Codex, Gemini & gitagent#97

Open
Nivesh353 wants to merge 8 commits into
open-gitagent:mainfrom
Nivesh353:feat/session-portability
Open

feat: session transformer & resume across Copilot, Claude Code, Codex, Gemini & gitagent#97
Nivesh353 wants to merge 8 commits into
open-gitagent:mainfrom
Nivesh353:feat/session-portability

Conversation

@Nivesh353

Copy link
Copy Markdown

Summary

Makes a conversation portable across tools. You can transform a session from
one coding agent into another and continue it there — any → any across
Copilot ⇄ Claude Code ⇄ gitagent.

Same pattern OpenGAP already uses for agent definitions: one canonical format
in the middle, with per-tool read/write adapters.

Copilot ─┐ ┌─ Copilot
Claude ─┼─► canonical session ─┼─ Claude (any → any)
gitagent ─┘ (neutral format) └─ gitagent

How it works

  • Read a tool's native session → convert to a neutral canonical session
    (ordered messages + tool_call/tool_result + memory).
  • Write the canonical session into another tool's native format.
  • Resume = each tool's own native resume — we don't build a resume engine.

What's included

  • spec/schemas/session.schema.json — canonical session schema
  • src/sessions/canonical.ts — types + adapter registry
  • src/sessions/{copilot,claude,gitagent}.ts — per-tool read/write adapters
  • src/commands/session.ts — new opengap session command (list, export, import)
  • src/index.ts, docs.md — register + document

Per-tool specifics

Tool Read Write Resume
Copilot events.jsonl events.jsonl + session-store.db (node:sqlite) copilot --resume=<id>
Claude Code ~/.claude/projects/*.jsonl Anthropic transcript opengap run -a claude --resume=<uuid>
gitagent chat-history + MEMORY.md distilled MEMORY.md + committed chat/* branch voice UI / CLI (MEMORY.md)

Memory travels with the session (carried as leading context) so the target
recalls facts even when a transcript is thin.

Usage

opengap session list   --from copilot
opengap session import  --from copilot --session <id> --to gitagent --agent ./my-agent
# then resume with that tool's own resume command

Testing
54/54 unit tests pass (round-trip + schema validation).
Verified end-to-end: Copilot → gitagent (recalls in CLI + voice), Copilot resume in its CLI.
Hardened: node:sqlite guard, injection-safe (arg-array) git calls, MEMORY.md import dedup.

Nivesh353 added 2 commits July 7, 2026 23:33
Transform a conversation between tools via a neutral canonical format and
continue it in any of them (any → any) — same hub pattern as our
agent-definition adapters.

- spec/schemas/session.schema.json: canonical session format (messages + tool calls + memory)
- src/sessions/: canonical types + registry, and read/write adapters per tool
    copilot  → events.jsonl + session-store.db (node:sqlite)
    claude   → Anthropic transcript under ~/.claude/projects
    gitagent → distilled MEMORY.md + committed chat/* branch (voice UI)
- src/commands/session.ts: `opengap session list | export | import`
- src/index.ts, docs.md: register + document

Resume uses each tool's own native resume; memory is carried into the target.
Includes round-trip tests, node:sqlite guard, and injection-safe git calls.

@shreyas-lyzr shreyas-lyzr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three real issues worth fixing before merge, plus one minor observation. The session portability concept is solid and the adapter pattern is clean — mostly execution details.

Blocking: partial write in when the global store DB is missing
Blocking: silent branch-write on wrong branch in the gitagent git-native path
Non-blocking: collision in the Claude adapter
Minor: UUID validation/assignment ordering in

Inline comments below.

Comment thread src/sessions/copilot.ts
Comment thread src/sessions/gitagent.ts
Comment thread src/sessions/claude.ts
Comment thread src/sessions/claude.ts
…tch error, encodeCwd doc, uuid validate-first

@shreyas-lyzr shreyas-lyzr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four issues from the previous review are addressed:

  • Copilot partial write: requireSqlite() and the store-DB existence check are now both at the top of writeCopilot, before writeSessionState runs. An early failure leaves no orphaned state dir.
  • gitagent wrong-branch commit: the checkout fallback now throws explicitly instead of falling through silently. The finally block still restores the original branch correctly.
  • encodeCwd lossy collision: the doc comment now clearly explains the slash-to-dash encoding is Claude Code's own scheme, and calls out the alice-bob/alice/bob collision scenario.
  • sessionId validate-before-assign: the UUID guard runs before the const assignment, which reads cleanly now.

Good to merge.

Nivesh353 added 3 commits July 8, 2026 11:47
Codex read/write adapter: parse rollout JSONL → canonical (dropping
synthetic <environment_context>/<user_instructions> injections), and
write a native rollout + register in ~/.codex/state_<N>.sqlite threads
so `codex resume` lists it (Node 22.5+).

gitagent: list all chat/* branches (matches voice UI) and read a
branch's history via `git show` when it isn't checked out.

Plus codex wiring in session --from/--to help + schema, docs, and
codex.test.ts. 57/57 pass.
Read/write Gemini CLI sessions. Reader parses ~/.gemini/tmp/<slug>/
chats/*.jsonl (skipping $set patches + synthetic <session_context>/
editor-context turns), maps user/gemini/toolCalls/functionResponse →
canonical, and resolves cwd via ~/.gemini/projects.json.

Writer emits a --session-file JSONL (metadata + user/gemini turns,
memory as a leading turn); metadata carries sessionId + projectHash
(sha256 of cwd) so Gemini's loader accepts it. Resume:
`gemini --session-file <path>`.

Register gemini + wire into session --from/--to help and schema; add
gemini.test.ts and docs. Verified read + resume end-to-end. 59/59 pass.
Gemini logs an assistant turn's text twice — once as a plain record,
then again on the record carrying the tool call — so the reader emitted
the message twice. Track the last assistant text and drop the immediate
repeat (keeping its tool call). Add a dedupe test. 60/60 pass.

@shreyas-lyzr shreyas-lyzr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the last 3 commits (codex adapter, gemini adapter, and gemini dedupe fix). Overall the implementation is solid and well-tested. A few things worth addressing before this merges.

codex.ts — hardcoded model/context-window
The write path hardcodes model to 'gpt-5.5' and modelContextWindow to 258400. If Codex validates these fields against a known-model list during resume, the imported session will fail to replay. Worth either reading the model from an existing real session (parallel to detectCopilotVersion) or confirming Codex treats unknown values permissively.

gemini.ts — tool calls dropped on write
The write path drops tool_call / tool_result items with the comment "matching Gemini's own import filter". That is the right call if Gemini's --session-file truly ignores those records, but the comment itself says "best-effort, verify". If Gemini does accept functionCall/functionResponse in its JSONL, dropping them loses context the agent would benefit from on resume. Worth surfacing this explicitly in docs.md as a known limitation (not just an inline comment) so users aren't surprised.

gemini.ts — const dir = join(cwd)
join of a single argument is a no-op — the variable just aliases cwd. Either drop it or give it a name that communicates intent.

codex.test.ts — fixture shape comment
The third test works correctly (the fixture line is a properly-enveloped response_item), but a future reader seeing the helper function might not immediately see the outer envelope. A short comment on the fixture would prevent a future edit from breaking the test.

gemini.test.ts — ordering of reasoning + last message
The first test expects ['message', 'tool_call', 'tool_result', 'reasoning', 'message']. Reasoning and the final text both come from the same record (g4 has both thoughts and content). This is correct but non-obvious — a comment marking which fixture record produces each canonical item would help when the fixture is edited later.

Comment thread src/sessions/codex.ts
Comment thread src/sessions/gemini.ts
Comment thread src/sessions/gemini.ts
@Nivesh353 Nivesh353 changed the title feat: session transformer & resume across Copilot, Claude Code & gitagent feat: session transformer & resume across Copilot, Claude Code, Codex, Gemini & gitagent Jul 8, 2026
Nivesh353 added 2 commits July 8, 2026 18:54
The Gemini writer previously dropped all tool_call/tool_result items.
Gemini's --session-file import actually accepts them, so carry them:
tool_call → a `gemini` record with `toolCalls`, tool_result → a `user`
record with a `functionResponse`, paired by id so Gemini's history
reconstruction matches call↔response.

Sources that link tool calls by order rather than id (gitagent) have no
ids, so their tool steps are still dropped rather than emitted unpaired
(which Gemini could mis-render). Conversation text always resumes.

Verified end-to-end: codex→gemini resume renders the tool steps and the
agent continues in-context. Update docs limitation + add writer tests.
62/62 pass.
The Gemini writer previously dropped all tool_call/tool_result items.
Gemini's --session-file import actually accepts them, so carry them:
tool_call → a `gemini` record with `toolCalls`, tool_result → a `user`
record with a `functionResponse`, paired by id so Gemini's history
reconstruction matches call↔response.

Sources that link tool calls by order rather than id (gitagent) have no
ids, so their tool steps are still dropped rather than emitted unpaired
(which Gemini could mis-render). Conversation text always resumes.

Verified end-to-end: codex→gemini resume renders the tool steps and the
agent continues in-context. Update docs limitation + add writer tests.
62/62 pass.

@shreyas-lyzr shreyas-lyzr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three issues from the previous review are resolved.

  • Codex model/context-window: both detectModel() and detectContextWindow() now scrape real local session data before falling back to gpt-5.5/258400. The fallback values are never used if a real Codex installation is present.
  • Gemini join(cwd) no-op: removed. The write path uses cwd directly.
  • Tool-call silent drop: documented in docs.md as a 'Known limitation' section (line 32) with the id-pairing rationale, and backed by a dedicated test ('drops tool steps when ids are absent'). Users won't be surprised.

Gemini and Codex adapters look solid. Approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants