Companion · Route Inventory deep-dive

REST API Analysis — Legacy → Go

A per-endpoint audit of the legacy Next.js API surface (25 routes under packages/web/src/app/api) mapped against the Go rewrite's existing inbound contracts. Each endpoint gets a verdict — build now, drop, defer, reshape, or different lane — so Phase 1b's "route shell" only registers what the new architecture actually needs.

Companion to: AO Backend Rewrite — Phase 1 Design Source: legacy composio/agent-orchestrator Target: backend/internal/ports
Verdict legend
build nowCore surface. Maps to an existing contract method or needs one added now.
dropDuplicate, alias, or a Next.js-only artifact with no place in the Go daemon.
deferNiche or one-time; safe to push past Phase 1, or keep CLI-only.
reshapeKeep the capability but as a read-model / daemon goroutine, not this shape of route.
different laneBelongs to the SCM poller / webhook ingress, not the session/project API.
Projects — the stated focus
Legacy paths link to source on ComposioHQ/agent-orchestrator @ main ↗. The New (v1) column is the proposed Go daemon route.
MethodLegacy pathNew (v1)PurposeVerdictNote
GET/api/projects/api/v1/projectsList registered projectsbuild nowNeeds new ProjectManager port
POST/api/projects/api/v1/projectsRegister project — git check, 409 on collisionbuild nowStructured 409 drives the add-project modal
GET/api/projects/:id/api/v1/projects/:idGet one (+ degraded-config payload)build now
DEL/api/projects/:id/api/v1/projects/:idUnregister + kill sessions + destroy workspaces + rm storagebuild nowComposes SessionManager.Kill + Workspace.Destroy
POST/api/projects/:id/api/v1/projects/:id/repairRepair wrapped/degraded configdeferOnly if the legacy YAML-wrapping is carried forward
POST/api/projects/reload— noneInvalidate cache + reload config from diskdropA Next.js in-process cache hack; a Go daemon reads config differently
Projects — API contract (request / response shapes)

GET  /api/v1/projects

build now
→ req (none) ← 200 { projects: [ { id, name, sessionPrefix, resolveError? } ] } ← 500 { error }

POST  /api/v1/projects

build now
→ req { path: string (required), projectId?: string, name?: string } ← 201 { ok: true, projectId: string } ← 400 { error } // invalid JSON · missing path · not a git repo ← 409 { error, existingProjectId, suggestedProjectId, suggestion: "choose-project-id" }

GET  /api/v1/projects/:id

build now
→ req (none) ← 200 { project: { id, name, path, repo, defaultBranch, agent?, runtime?, tracker?, scm?, reactions? } } ← 200 { error, projectId, degraded: true, project: { id, name, path, resolveError } } // degraded state ← 404 { error: "Unknown project: {id}" } ← 500 { error }

PATCH  /api/v1/projects/:id

verify
→ req { agent?, runtime?, tracker?, scm?, reactions? } // identity fields (projectId/path/repo/defaultBranch) frozen ← 200 { ok: true } ← 400 { error } // frozen identity field · invalid JSON · malformed local config ← 404 { error } // unknown project ← 409 { error } // degraded / missing registry path ← 500 { error }
⚠ verify: confirm the dashboard actually issues this PATCH before building it — it may be unused in practice.

DELETE  /api/v1/projects/:id

build now
→ req (none) ← 200 { ok: true, projectId, removedStorageDir: boolean } ← 400 { error } // invalid project id (path-traversal guard) ← 404 { error } // unknown project ← 500 { error }

POST  /api/v1/projects/:id/repair

defer
→ req (none) ← 200 { ok: true, repaired: true, projectId } ← 400 { error } // not degraded · not auto-repairable ← 404 { error } // unknown project ← 500 { error }

POST  /api/projects/reload

drop
→ req (none) ← 200 { reloaded: true, projectCount, degradedCount } ← 500 { error } // legacy only — no v1 equivalent; the Go daemon reloads config differently
Sessions — mostly covered by SessionManager
Legacy paths link to source on ComposioHQ/agent-orchestrator @ main ↗. New (v1) is the proposed Go daemon route; Maps to is the SessionManager method.
MethodLegacy pathNew (v1)Maps toVerdictNote
POST/api/spawn/api/v1/sessionsSpawnbuild now
GET/api/sessions/api/v1/sessionsListbuild now?project, ?active, ?orchestratorOnly, ?fresh filters
GET/api/sessions/:id/api/v1/sessions/:idGetbuild now
POST/api/sessions/:id/kill/api/v1/sessions/:id/killKillbuild now
POST/api/sessions/:id/restore/api/v1/sessions/:id/restoreRestorebuild now
POST/api/sessions/:id/send/api/v1/sessions/:id/sendSendbuild nowStrips control chars before runtime inject
POST/api/sessions/:id/message— noneSend (dup)dropIdentical to /send — both call sessionManager.send
PATCH/api/sessions/:id/api/v1/sessions/:id (PATCH)— (gap)build nowRename only (displayName). No inbound method — add Rename/SetMetadataPatchMetadata
POST/api/sessions/:id/remap/api/v1/sessions/:id/remapdeferOpenCode-specific dead-session recovery; keep only if the new agent needs it
GET/api/sessions/patches/api/v1/sessions/patchesreshapeLightweight diff read-model — belongs in a query layer, not SessionManager
POST/api/orchestrators/api/v1/orchestrators— (gap)build nowSpawn/relaunch orchestrator. Decide: a SessionKind on Spawn, or a dedicated method
SessionManager.Cleanup has no legacy HTTP route of its own — it's reached today through the project DELETE path. Decide whether to expose it directly.
Sessions — API contract (request / response shapes)

POST  /api/v1/sessions

build now
→ req { projectId: string (required), issueId?: string, prompt?: string (≤ 4096) } ← 201 { session } ← 400 { error } // missing projectId · prompt too long ← 404 { error } // unknown project ← 500 { error }

GET  /api/v1/sessions

build now
→ req ?project= &?active= &?orchestratorOnly= &?fresh= ← 200 { sessions: [ … ] }

GET  /api/v1/sessions/:id

build now
→ req (none) ← 200 { session } // dashboard session read-model ← 404 { error }

POST  /api/v1/sessions/:id/kill

build now
→ req (none) ← 200 { ok: true, sessionId } ← 404 { error }

POST  /api/v1/sessions/:id/restore

build now
→ req (none) ← 200 { ok: true, sessionId, session } ← 404 { error } // unknown session ← 409 { error } // not restorable in current state ← 422 { error } // restore preconditions unmet

POST  /api/v1/sessions/:id/send

build now
→ req { message: string (required, ≤ MAX) } ← 200 { ok: true, sessionId, message } ← 400 { error } // empty / too long ← 404 { error }

PATCH  /api/v1/sessions/:id

build now
→ req { displayName: string } // rename only ← 200 { session } ← 400 { error } ← 404 { error } // gap: needs a Rename / SetMetadata inbound method → LifecycleStore.PatchMetadata // (must never write the derived display status)

POST  /api/v1/sessions/:id/remap

defer
→ req { opencodeSessionId: string } ← 200 { ok: true } ← 404 { error } // unknown session ← 422 { error } // remap not applicable // OpenCode-specific dead-session recovery; only if the new agent needs it

GET  /api/v1/sessions/patches

reshape
→ req (none) ← 200 { sessions: [ …patch read-model… ] } // reshape into a query / read-model layer, not SessionManager

POST  /api/v1/orchestrators

build now
→ req { projectId: string (required), clean?: boolean } ← 201 { orchestrator: { id, projectId, projectName } } ← 400 { error } // missing projectId ← 404 { error } // unknown project // clean=true → relaunchOrchestrator; else spawnOrchestrator
SCM / issues / PRs — a different lane
Legacy paths link to source on ComposioHQ/agent-orchestrator @ main ↗. These belong to the SCM poller / webhook ingress, not the session/project API — so most have no /api/v1 session-route equivalent.
MethodLegacy pathNew (v1)PurposeVerdictNote
POST/api/prs/:id/merge— SCM laneMerge a PRdifferent laneSCM action
POST/api/prs/:id/resolve-comments— SCM laneResolve review comments on a PRdifferent laneSCM action
SCM — API contract (request / response shapes — for the SCM lane to own)

POST  /api/prs/:id/merge

SCM lane
→ req (none) ← 200 { ok: true, prNumber, method: "squash" } ← 404 { error } // unknown PR ← 409 { error } // not mergeable ← 422 { error } // merge preconditions unmet

POST  /api/prs/:id/resolve-comments

SCM lane
→ req { commentIds?: string[] } // omit to resolve all ← 200 { ok: true, resolved: number } ← 404 { error } // unknown PR ← 422 { error } // nothing to resolve
Platform / infra
Legacy paths link to source on ComposioHQ/agent-orchestrator @ main ↗. The New (v1) column shows the proposed Go route — several are CLI/desktop concerns with no daemon REST equivalent.
MethodLegacy pathNew (v1)PurposeVerdictNote
GET/api/observability/api/v1/observabilityObservability / metricsreshapeRead-model / metrics endpoint
GET/api/version— noneAO version + update channeldropCLI / desktop self-update concern
POST/api/update— noneKick off ao updatedropNot the daemon's REST surface
GET/api/filesystem/browse/api/v1/filesystem/browseDirectory picker for add-projectdeferKeep one browse route
GET/api/browse-directory— none307 redirect → filesystem/browsedropRedundant alias
GET/api/runtime/terminal/mux (P4)Terminal runtime inforeshapeTied to the tmux/PTY (WebSocket) lane — Phase 4
Platform — API contract (request / response shapes)

GET  /api/v1/observability

reshape
→ req (none) ← 200 { …metrics / health summary… }

GET  /api/version

drop
→ req (none) ← 200 { current, latest, channel, …, checkedAt } // CLI / desktop self-update concern — not the daemon's REST surface

POST  /api/update

drop
→ req (none) ← 202 { ok: true, message } // update kicked off ← 409 { ok: false, message } // refuses while active sessions exist

GET  /api/v1/filesystem/browse

defer
→ req ?path=~ // ~ expands to home ← 200 { entries: [ … ] } ← 400 { error } // invalid path ← 404 { error } // not found

GET  /api/browse-directory

drop
← 307 → /api/filesystem/browse // redundant alias

GET  /api/runtime/terminal

reshape → P4
→ req (none) ← 200 { terminalPort, directTerminalPort, proxyWsPath } // folds into the tmux/PTY WebSocket lane (/mux) in Phase 4
Recommendation — at a glance

Build now

core
All /projects CRUD (new ProjectManager port) · session spawn / list / get / kill / restore / send · plus two contract additions: session rename and orchestrator spawn.

Drop

cut
/sessions/:id/message (dup) · /projects/reload (cache hack) · /version + /update (CLI) · /browse-directory (alias).

Defer

later
Project repair · session remap · setup-labels · filesystem browse.

Different lane

SCM
issues · backlog · verify · PR merge · webhooks · observability — SCM-poller & read-model concerns. Webhooks/pollers feed ApplySCMObservation; they aren't pulled through REST handlers.