Skip to content

plan toolset: add pluggable Storage interface (parity with todo toolset) #3237

Description

@simonferquel-clanker

Summary

The plan builtin toolset hardcodes filesystem storage and exposes no seam for
embedders to supply their own backend. The todo toolset already solves this
with a pluggable Storage interface — plan should match it.

Current state

In pkg/tools/builtin/plan/plan.go:

  • CreateToolSet() returns a process-wide singleton (sync.OnceValue), so
    every agent in a process shares one plans folder with no way to isolate them.
  • New(dir string) only accepts a directory; the handlers call load/save
    directly against JSON files on disk.
  • There is no injection point, so storage cannot be swapped for tests or for
    alternative backends (e.g. a database or remote store), and there is no
    per-instance isolation.

By contrast, pkg/tools/builtin/todo/todo.go already defines a Storage
interface, a WithStorage(...) option, and a default in-memory implementation.

Proposal

Bring the plan toolset to parity with todo:

  1. Introduce a plan.Storage interface, e.g.:
    type Storage interface {
        Get(ctx context.Context, name string) (Plan, bool, error)
        Upsert(ctx context.Context, name, content, title, author string) (Plan, error)
        List(ctx context.Context) (plans []Summary, warnings []string, err error)
        Delete(ctx context.Context, name string) (deleted bool, err error)
    }
  2. Keep the existing filesystem implementation as the default (atomic
    temp+rename, name validation, unreadable-file warnings) — standalone
    behavior is unchanged.
  3. Add a plan.WithStorage(...) option and a per-instance constructor so
    embedders can inject a backend and get isolation instead of the
    process-wide singleton.
  4. Move the revision bump into Storage.Upsert so backends can make it atomic.
    The filesystem default keeps today's read-modify-write (last-writer-wins);
    other backends can do better.

The handlers (write_plan / read_plan / list_plans / delete_plan)
delegate to Storage rather than calling load/save directly.

Acceptance criteria

  • plan.Storage interface exists with a default filesystem implementation.
  • plan.WithStorage(...) option lets callers inject a custom backend.
  • A plan toolset can be constructed per-instance (not only as the global
    singleton); default CreateToolSet() behavior is preserved.
  • Revision bump is owned by Storage.Upsert.
  • Unit tests cover the default filesystem backend and a custom (e.g.
    in-memory) backend through the interface.
  • No behavior change for existing standalone/CLI usage.

Activity

  1. added
    area/toolsFor features/issues/fixes related to the usage of built-in and MCP tools
    on Jun 25, 2026
  2. self-assigned this
    on Jun 25, 2026
  3. added a commit that references this issue on Jun 25, 2026
    618fc60
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area/toolsFor features/issues/fixes related to the usage of built-in and MCP tools

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions