Skip to content

[Feature] A working keyless local thread store (follow-up to #73) #179

Open

Description

@caspianchan31

What outcome should OpenMuse handle?

Self-hosting OpenMuse without CopilotKit Intelligence, with conversations kept in the deployment's own Postgres or PGlite: the request in #73.

I built this as a fork and released it as OpenMuse Standalone v0.1.0: https://github.com/caspianchan31/openmuse-standalone (MIT, based on main at 1ac68f3). I'm sharing it in case it helps consolidate #94 and #166, and I'm happy to open a PR or split pieces out, whichever the maintainers prefer.

Proposed behavior

  • Without CPK_INTELLIGENCE_API_KEY the API starts in local mode. With the key, behavior is unchanged from upstream, and upstream's config tests for the keyed path are kept.
  • The runtime runs in SSE mode on a persistent AgentRunner (run/connect/isRunning/stop) backed by the app's own store (chat_threads, chat_runs, chat_events). It does not implement LocalThreadEndpointRunner, whose synchronous methods would force an in-memory store.
  • /api/copilotkit/threads* (list, messages, rename, archive, restore, delete) and /api/main-thread are served by the Hono layer and scoped to the signed-in owner; /info.threadEndpoints is rewritten so the app uses them.
  • A send on a busy thread gets 409 agent_thread_locked before any SSE response, so the app holds the message as it does with AgentThreadLockedError.
  • Replies are journaled while running, and the terminal event waits for the durable save; a failed save surfaces as persistence_failed. Interrupted replies are recovered from the journal (PGlite at startup, Postgres once the lock is stale). Optional CHAT_RETENTION_DAYS.
  • Edits to upstream files are small and marked // Standalone:; new code lives in apps/server/src/standalone/.

On the points raised in the #166 review: new side chats persist through the agent-clone path with restart coverage; owner checks apply to every thread route and to run/connect/stop; failed and concurrent runs end cleanly and completion stays behind the durable write; there is no in-memory eviction, the database is the history.

How would we verify it works?

  • 583 tests (typecheck, biome, node:test); the runner and store suites also run against Postgres 17.
  • Keyless end-to-end in a browser: streaming, reload mid-reply resumes, Stop keeps the partial reply, a second tab's send is held via 409, side chats rename/archive/restore/delete, history survives an API restart and a kill -9.
  • Docs: docs/STANDALONE.md (architecture, upstream merge procedure) and docs/STANDALONE-THREADS.md (routes, schema, validation).

Known limits: no live cross-device thread metadata sync; live resume needs a single API instance.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions