Skip to content

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

Open
2 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. 24 remaining items

  7. 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.
  8. 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
  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  10. 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)

  11. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Program-tending completion proposal (#1352): both phases of this design epic are closed — #1415 (closed 2026-11-04T15:26Z) and #1416 (closed 2026-11-05T01:49Z; states verified by direct reads this run). No open children remain. This supersedes the 2026-11-03 triage note, which predates both closures.

    Proposing closure of the epic; the close itself is the fleet's/Drew's call. Note for the next tending pass: ROADMAP.md's Now row names this epic under "mcpx robustness" — if the epic closes, that row is due its update then.

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