Skip to content

feat(import): rewrite-side legacy → rewrite first-boot import#314

Open
harshitsinghbhandari wants to merge 2 commits into
mainfrom
feat/ao-import-legacy
Open

feat(import): rewrite-side legacy → rewrite first-boot import#314
harshitsinghbhandari wants to merge 2 commits into
mainfrom
feat/ao-import-legacy

Conversation

@harshitsinghbhandari

Copy link
Copy Markdown
Collaborator

What & why

Implements the rewrite-side importer from the FINAL v2 plan: migration lives in the rewrite and runs as an opt-in import (not a legacy-side cutover). The rewrite reads the legacy flat-file store (~/.agent-orchestrator) read-only and writes its own SQLite DB via the native storage layer, so nothing can be stranded and nothing is irreversible: legacy files are never modified and a re-run skips existing rows.

Ports the already-built legacy-side TS reader (AgentWrapper #2144 / issue #2129) to Go; field mapping per ReverbCode #247.

What's included

  • internal/legacyimport — Go reader + pure field mappers:
    • lifecycle double-decode (lifecycle key, or statePayload+stateVersion:"2"), with stringified-nested-field coercion
    • role/orchestrator detection, sessionPrefix fallback (first 12 chars of project id)
    • 8→4 activity-state map, terminal/aider skip filters
    • per-harness resume-id selection, permission + harness enum remap (lossy notes surfaced)
    • Claude transcript slug + relocation to the rewrite's orchestrator worktree path {DataDir}/worktrees/{id}/orchestrator/{prefix}-orchestrator (verified against gitworktree.managedPath)
  • store.ImportSession — verbatim session insert (explicit id/num, ON CONFLICT(id) DO NOTHING) so the orchestrator lands at {prefix}-orchestrator, num 0; leaves the next store-generated worker at num 1 with no collision.
  • ao import — explicit, idempotent import. Flags: --from, --dry-run, --yes (non-interactive), --json. Refuses while a live daemon owns the run-file.
  • First-boot opt-inao start offers the import before launching the daemon when legacy data is present and the rewrite DB has no projects yet: `Found existing AO projects and sessions. Import them now? [Y/n]`. Declining or any failure is non-fatal; a non-interactive boot prints a hint to run ao import rather than auto-importing.

Scope (gist §6, locked)

All projects + per-project settings, and the single non-terminated orchestrator session per project (claude-code/codex/opencode; aider skipped with a note). Workers are not imported (they respawn fresh). The Claude transcript is relocated so a claude-code orchestrator resumes with context; codex/opencode resume by the global id carried in agent_session_id.

Decisions settled (per brief)

  • Writer path: the import opens the store directly and runs offline (daemon stopped), faithfully porting the #2129 reference and honoring the sole-writer invariant via a run-file guard, rather than introducing a REST surface the brief did not ask for. First-boot runs before the daemon launches; ao import refuses if a daemon is live.
  • codexModel / restoreFallbackReason: no rewrite column exists (a session has a single agent_session_id). Both are dropped and surfaced as import notes. agent_session_id carries codexThreadId for codex; the codex adapter resumes from the thread id alone, and restoreFallbackReason is forensic-only.
  • first_signal_at: backfilled from activity_last_at (mirrors migration 0010) so imported orchestrators don't flip to no_signal.

Tests / gate

Local gate green: cd backend && go build ./... && go test -race ./...1423 tests pass. New coverage: pure mappers (permission/harness remap, lifecycle double-decode, state map, resume-id, prefix fallback, timestamp fallbacks), transcript slug + relocation idempotency, end-to-end Run (import / idempotent re-run / dry-run / no-data), ImportSession verbatim+idempotent against a temp DB, and CLI ao import (no-data / json import / daemon-running refusal).

🤖 Generated with Claude Code

harshitsinghbhandari and others added 2 commits June 18, 2026 15:56
Port the legacy-side TS reader (AgentWrapper #2144/#2129) to Go and run the
migration inside the rewrite as an opt-in import, per the FINAL v2 plan. Reads
the legacy flat-file store (~/.agent-orchestrator) read-only and writes the
rewrite's own SQLite DB via the native storage layer; legacy files are never
touched, and a re-run skips existing rows, so a declined or failed import loses
nothing.

What's included:
- internal/legacyimport: Go reader + field mappers (issue #247). Lifecycle
  double-decode (lifecycle key or statePayload+stateVersion:"2"),
  role/orchestrator detection, sessionPrefix fallback (first 12 chars of id),
  8→4 activity-state map, per-harness resume-id selection, permission/harness
  remap, and the claude transcript slug + relocation to the rewrite's
  orchestrator worktree path ({DataDir}/worktrees/{id}/orchestrator/{prefix}-orchestrator).
- store.ImportSession: verbatim session insert (explicit id/num, ON CONFLICT
  DO NOTHING) so the orchestrator lands at id "{prefix}-orchestrator", num 0.
- `ao import`: explicit, idempotent import with --from/--dry-run/--yes/--json.
  Refuses while a live daemon owns the run-file (the daemon is sole writer; the
  import runs offline, matching the #2129 reference).
- First-boot opt-in: `ao start` offers the import before launching the daemon
  when legacy data is present and the rewrite DB has no projects yet. Declining
  or any failure is non-fatal; a non-interactive boot prints a hint instead of
  auto-importing.

Scope (gist §6): all projects + per-project settings, and the single
non-terminated orchestrator session per project (claude-code/codex/opencode;
aider skipped with a note). Workers are not imported (they respawn fresh).

Resume-id mapping (#247 §2.2): agent_session_id carries claudeSessionUuid /
codexThreadId / opencodeSessionId by harness. codexModel and
restoreFallbackReason have no rewrite column, so they are dropped and surfaced
as import notes — codex resumes from the thread id alone, the rest is forensic.

Gate: `go build ./... && go test -race ./...` green (1423 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- start.go: check fmt.Fprint* returns in the first-boot import path
- project.go: combine same-typed return params (gocritic paramTypeCombine)
- claude.go: use a pathExists helper so a missing transcript source is a normal
  skip, not an err-then-return-nil (nilerr)
- importer.go: fold best-effort transcript relocation into a switch so the
  non-fatal path no longer returns nil from an error branch (nilerr)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant