Skip to content

mcpx: one durable task store across MCP and /v1 — Phase 1 of #1554 (#1555) - #1575

Merged
dezren39 merged 2 commits into
mainfrom
queue/1642-1555-one-store
Oct 28, 2026
Merged

dezren39 merged 2 commits into
mainfrom
queue/1642-1555-one-store

Conversation

@dezren39

@dezren39 dezren39 commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Phase 1 of #1554 for #1555, implemented per the triage verdict in queue task 1609 (this PR is queue task 1642).

What

One tasks.Store instance now serves both surfaces in the daemon:

  • mcpserver.Server gains an exported TaskStore field — the wiring style this codebase already uses (Notify, Passthrough, Timing are exported fields set post-construction in App.MCPServer(); there are no setters). When set, tasks() uses it and never lazily creates its own; when nil, today's lazy in-memory behaviour is byte-for-byte unchanged.
  • daemon.Server gains an exported TaskStore() accessor returning the existing sync.Once taskStore() instance (SQLite at <state>/tasks.db, in-memory fallback) — exactly one store per daemon; /v1 handlers keep using the unexported path.
  • Wiring: internal/cli/root.go, where the daemon server and lazyMCP are built in one process, sets a.daemonStore = srv.TaskStore(); App.MCPServer() injects it into the daemon-served MCP server alongside Notify/Passthrough/Timing.
  • Conformance gap ext-tasks-durable-before-response retired (internal/conformance/coverage_stateful.go) — its reason ("the task store is an in-memory map; a task does not survive the daemon") is no longer true for the daemon's MCP surface.

PollInterval — the one named source

protoTasks.pollInterval won, and neither surface's cadence changes. It was already the single named source for both: the daemon store reads it directly at construction (ops.go), and App.MCPServer() already fed the MCP side's Timing.TaskPoll from the same key. The shared store is configured once, at daemon construction, from protoTasks.pollInterval; Timing.resolved().TaskPoll remains only on the standalone lazy-creation path.

Standalone disposition (triage's open plumbing question)

Injection happens only on the daemon-served /mcp path. CmdServe's stdio shim, adapter.go and openapi.go constructions never have a.daemonStore set (root.go's daemon-run path is the only place it is assigned), so they keep a private in-memory store with unchanged behaviour — there is no second surface to share with in those processes.

Tests (new)

  • mcpserver/tasks_shared_store_test.go: the injected store is the store the MCP surface uses — an MCP-started task is Get-visible in the shared instance under the same id (the /v1 read path); an ownerless store-started (/v1) task is not listed by any MCP connection; an MCP-started terminal task survives store close/reopen from the same tasks.db, and a task working at close reloads as Failed ("daemon restarted before task completion").
  • daemon/taskstore_shared_test.go: TaskStore() returns the one instance.
  • Existing mcpserver task tests cover the standalone behaviour unchanged.

Verification is CI per fleet policy (cd pkgs/mcpx && go test ./... runs in the id workflow).

Refs #1555, #1554. Origin: dezren39/darwin-nix#209 TASK-27/TASK-20.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@dezren39
dezren39 requested a review from a team as a code owner October 28, 2026 03:08
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 32 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 513baef3-11cf-46f0-92bb-b4cb852eb204
Commits

Reviewing files that changed from the base of the PR and between 1da6d84 and 516df39.

Files selected for processing: 10
  • pkgs/mcpx/docs/spec/conformance-matrix.md
  • pkgs/mcpx/internal/cli/commands.go
  • pkgs/mcpx/internal/cli/root.go
  • pkgs/mcpx/internal/cli/serve.go
  • pkgs/mcpx/internal/conformance/coverage_stateful.go
  • pkgs/mcpx/internal/daemon/ops.go
  • pkgs/mcpx/internal/daemon/taskstore_shared_test.go
  • pkgs/mcpx/internal/mcpserver/server.go
  • pkgs/mcpx/internal/mcpserver/tasks.go
  • pkgs/mcpx/internal/mcpserver/tasks_shared_store_test.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-28T05:02:32.515740Z 516df39 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d659653966

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// MCPServer() (issue #1555). Set here, where the daemon server
// exists in this process; commands that never build one leave it
// nil and their MCP servers keep private stores.
a.daemonStore = srv.TaskStore()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Delay loading tasks until the previous daemon stops serving

During takeover, this opens the task database before srv.TakeOver() transfers ownership and drains the predecessor's HTTP requests. tasks.Open() snapshots the rows and rewrites nonterminal tasks as failed, so a task completed by the predecessor between this initialization and handover remains incorrectly failed in the successor; tasks created during that interval are missing from its store entirely. An ordinary duplicate daemon startup also modifies the database before Listen() rejects it. Defer loading the store until the predecessor has stopped updating it.

Useful? React with 👍 / 👎.

if s.TaskStore != nil {
// Injected: the daemon's one store, already configured
// (PollInterval included) by whoever built it.
s.taskStore = s.TaskStore

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve connection ownership when restoring durable MCP tasks

After a daemon restart, the injected store restores MCP tasks but the server's taskOwners map is empty. visible() treats a missing owner as permission to access the task by ID, so a legacy task previously restricted to its originating connection becomes readable through tasks/get and tasks/result by any other connection that knows its ID. This bypasses the connection isolation explicitly enforced in requirements_test.go's another-connection-cannot-list-or-read-it test. Persist the ownership restriction alongside durable tasks, or otherwise deny foreign connections access to restored connection-owned tasks.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f29b78f6d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +482 to +483
if a.daemonStore != nil {
srv.TaskStore = a.daemonStore

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the injected task store when adding extra tools

On the daemon's MCP path, this assignment is discarded before the server is returned: App.MCPServer() appends a.v1OpTools() and then calls srv.WithExtras(extras), whose new Server literal in internal/mcpserver/server.go does not copy TaskStore. Consequently, the daemon-served MCP server still creates a private in-memory store, so MCP tasks remain invisible through /v1 and disappear on restart. Preserve TaskStore in WithExtras, or perform the injection after that decoration; the new tests inject directly into an undecorated server and therefore miss this production path.

Useful? React with 👍 / 👎.

…1555)

Phase 1 of #1554 (triage: queue task 1609). mcpserver.Server gains an exported TaskStore field, set in App.MCPServer() alongside Notify/Passthrough/Timing when the App runs the daemon (root.go wires daemon.Server.TaskStore(), a new exported accessor for the existing sync.Once store). When injected, tasks() uses that store and never creates its own; standalone servers (adapter, openapi, stdio shim) keep the lazy in-memory behaviour. PollInterval: the shared store keeps the daemon construction value from protoTasks.pollInterval -- the same setting Timing.TaskPoll reads -- so neither surface cadence changes. Retires conformance gap ext-tasks-durable-before-response. Tests: injected store is the MCP surface's store (cross-surface both directions, ownerless /v1 tasks unlisted by MCP), MCP tasks survive store reopen with working-task reaping, daemon accessor identity.
…s/list in the shared-store test, conformance coverage for the retired gap

treefmt-check found gofmt drift in cli/commands.go (import order) and mcpserver/server.go (field alignment). The shared-store test listed via the 2026-07-28 extension, which removed tasks/list; it now lists through the legacy handler with a legacy-started task on a second server sharing the store (legacy list requires an owner match; modern-started tasks are ownerless by design). Retiring the ext-tasks-durable-before-response gap left its matrix cell uncovered; coverage now names TestInjectedStoreKeepsMCPTasksAcrossReopen@2026-07-28 and the matrix doc is regenerated. mcpserver + conformance + cli + daemon packages pass on the laptop.
@dezren39
dezren39 force-pushed the queue/1642-1555-one-store branch from d05af42 to 516df39 Compare October 28, 2026 05:02
@dezren39
dezren39 merged commit 1658679 into main Oct 28, 2026
17 checks passed
@dezren39
dezren39 deleted the queue/1642-1555-one-store branch October 28, 2026 08:03
@dezren39
dezren39 restored the queue/1642-1555-one-store branch November 1, 2026 07:50
@dezren39
dezren39 deleted the queue/1642-1555-one-store branch November 1, 2026 16:44
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