Skip to content

design(mcpx): resume upstream HTTP sessions after a cold restart — persist the resumable session state #1414

Open
1 of 2 issues completed

Description

@dezren39

Decision for Drew (one line): Approve persisting mcpx upstream HTTP session state (session IDs + protocol state, never resolved header secrets) to the owner-only state dir so a cold restart resumes sessions instead of re-initialising every HTTP upstream — yes/no; the graceful-shutdown DELETE question in Phase 3 rides on the same decision.

Design doc: files/designs/code-mcpx-cold-restart-session-resume.md in the keep-the-four-repos-healthy goal workspace. Register: central potential-projects list, same workspace (files/potential-projects.md).

Summary

A takeover preserves upstream HTTP sessions; a cold restart does not. pkgs/mcpx/TODO.md item 3: "The lease table is restored from sessions.json, but the upstream Mcp-Session-Id is kept only in memory and in the takeover handoff, so after a restart each HTTP upstream is initialised again." Roadmap: planning/roadmaps/mcpx.md → Next → "Resume upstream sessions after a cold restart" (status idea).

Costs per cold restart, per HTTP upstream: a fresh initialize handshake, silent loss of upstream per-session state, and one leaked upstream session (the dead daemon never sent DELETE; the new daemon doesn't know the ID — #260 fixed this leak for takeover only).

Proposal: persist the resumable half of each legacy streamable-HTTP upstream session in a new versioned state-dir file (same discipline as internal/daemon/sessions.go: atomic tmp+rename, defaults.PrivateMode, version envelope, ignore-on-mismatch), and let a cold-started daemon take the existing takeover restore path with the record standing in for the socket:

  • Record per pool instance: instance key, URL, config fingerprint (hash over session-defining config fields + header/auth names, never values), HTTPHandoff minus Headers (SessionID, Negotiated), the full mcpclient.Session protocol state, savedAt.
  • No resolved header values on disk — the codebase's rule is that handoff headers travel "over the takeover's unix socket only" (internal/mcpclient/http.go, HTTPHandoff comment). On restore, headers are re-derived from live config (httpOptions() + resolveAuth()), then the transport/client are seeded with the exact calls takeover makes: ht.Resume(...) and mcpclient.Resume(...) (internal/pool/handoff.go:238,326). No parallel resume implementation.
  • Save on legacy session establishment and on session-ID change (internal/mcpclient/http.go:156 is where IDs are adopted); save failure is logged, non-fatal — resume is an optimisation.
  • Scope: legacy-era streamable HTTP only. Modern era (2026-07-28) has no sessions; legacy SSE holds its session in the GET stream (TODO item 2 already accepts re-init there).

Grounding: internal/mcpclient/http.go (sessionID/negotiated in memory only), internal/mcpclient/handoff.go (SessionState(), Resume — the no-handshake client already exists), internal/pool/pool.go:704 (dial() seeds nothing today), internal/daemon/sessions.go (persistence precedent). Sibling of #1384 (stdio supervisor), which names cold-restart HTTP resume as an explicit non-goal — this design is that non-goal, designed separately. Alternatives (do nothing; fold into sessions.json; persist full handoff incl. headers; era-cache-only) and risks (stale-session resume, session IDs as bearer tokens on disk, write amplification, restore-path drift) are argued in the design doc.

Phased breakdown

  • Phase 1 — persist and resume. New state file + record type; fingerprint gate; restore via the takeover path. Defining test: session established → daemon SIGKILLed → restarted → first call succeeds, upstream saw no second initialize, request carried the original Mcp-Session-Id. Filed as a sub-issue of this issue.
  • Phase 2 — expiry fallback and record hygiene. First request on a resumed session answered 404 (or 401/403 after credential rotation) → drop record, fresh initialize, retry once, transparently. Server removed from config → record deleted + best-effort upstream DELETE, or logged left-to-expire (the mcpx: takeover orphans a removed server's upstream HTTP session — never DELETEd, left to expire upstream #260 degradation, made explicit). Coordinate with the roadmap's "Legacy client robustness" row (nix#200–Update module golang.org/x/net to v0.55.0 [SECURITY] #203) — this is only the resumed-session slice. Filed as a sub-issue of this issue.
  • Phase 3 — open questions, NOT filed. Graceful-shutdown semantics (should a restart-intent stop skip the DELETE that Close → closeSession sends today?); record TTL; dashboard resumed-vs-fresh visibility; whether takeover should also write the record. The shutdown question is part of Drew's decision above.

agent-note:v1 — filed by the repo design architect (dedicated code job, pass 2, 2026-10-27). Design doc + register entry as linked above. user/review carries the one-line direction decision at the top; Phases 1–2 are filed as sub-issues with queue/queued + task/triage packets enqueued to repo-health-code-pilot; Phase 3 stays as questions in the design doc.

Acceptance criteria

Activity

  1. added
    user/reviewNeeds a user-look: Muse reviews first and handles what it can
    priority/normalOrdinary priority (the default)
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    epicMeta-issue grouping multiple items (org Project Type=Epic)
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    kind/designA design proposal (design-architect): multi-step, phased, decision-bearing
    on Oct 27, 2026
  2. developing-today commented on Oct 27, 2026

    @developing-today
    Contributor
  3. added
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 27, 2026
  4. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Intake review

    Fleet monitor intake pass, 2026-10-27 (repo-fleet-monitor).

    Related: implementation phases already filed — #1415 (Phase 1: persist + resume legacy HTTP sessions) and #1416 (Phase 2: expiry fallback + record hygiene), both enriched earlier today and queued. Design doc: files/designs/code-mcpx-cold-restart-session-resume.md in the goal workspace.

    Notes: This is the decision parent, not a work item: it already carries priority/normal, effort/l, kind/design and user/review. The one-line decision in the body (persist session state — yes/no) is genuinely Drew's, so the phases stay queued behind it.

    Risks: Persisting anything beyond session IDs + protocol state would be a secrets bug — the acceptance criteria restate the never-secrets boundary.

    Suggested approach: Drew decides on this issue; on a yes, #1415 is the first claimable step.

  5. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — escalated to user (worker-first review attached)

  6. 23 remaining items

  7. added and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 3, 2026
  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: task 7711
    • Found via: monitor reconciliation
    • Task: Claim-eviction repair: the queue task for this issue is queued and unclaimed (its previous claim's lease expired without a complete/park/block), but the issue labels still said claimed. Labels reset to the truth: queue/queued + status/needs-triage. Checkpoint stage on the task: handed_off.
    • Expectation: a queue lane re-claims the task and resumes from the checkpoint stage named in the task progress.
  9. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  11. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: triaged (coverage pass; current-main revalidation)

    Verdict: still live; the cold-restart gap is confirmed at main 52fd50d17e750ae84385b5b41414e886eccee95e. pkgs/mcpx/TODO.md item 2 still says the upstream Mcp-Session-Id is only in memory/takeover handoff, so after cold restart each HTTP upstream is initialized again. No source changes or tests were needed for this triage.

    Decision and route: Drew’s YES to persist session IDs + protocol state (never resolved headers) is already recorded here (2026-10-28). The existing phases remain the correct route: #1415 (Phase 1) and #1416 (Phase 2); no duplicate tasks filed. Fresh checks: both issues remain OPEN; #1415 is status/blocked + queue/blocked, task 1483 is blocked, and PR search found no matching implementation PR. #1416 remains parked as task 1484 behind Phase 1.

    Blocker / ordering: #1415’s current blocker is stale: it still cites the #1414 decision as undecided although the YES is recorded; the 2026-10-31 reconciliation comment on #1415 already names this contradiction. The owner of task 1483 / fleet reconciliation should clear or replace that stale blocker; Phase 2 remains gated until Phase 1 lands. This triage does not take ownership of either implementation task.

    Found via: repo-health-code-pilot task 7711 (OpenChamber scheduled lane)

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

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/lLarge: multi-day or open-ended; expect staged check-insepicMeta-issue grouping multiple items (org Project Type=Epic)kind/designA design proposal (design-architect): multi-step, phased, decision-bearingmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions