plans/2026-05-07-server-beta-independent-bullmq-observation-runtime.md
Status: implementation plan
Date: 2026-05-07
Release target: claude-mem 13 Server (beta)
Relationship to prior plans:
plans/2026-05-07-claude-mem-server-apache-bullmq-team-auth.md.plans/2026-05-07-claude-mem-13-server-beta-full-worker-parity.md where that plan allowed Server beta to wrap/copy WorkerService.Server beta must own its runtime end to end:
REST/MCP/hooks -> Server beta HTTP/API layer -> BullMQ observation jobs -> provider generation -> server storage/search
The worker remains the stable legacy runtime, but Server beta must not depend on WorkerService, worker HTTP routes, worker queue consumers, or worker process lifecycle to generate observations.
Server beta should use BullMQ/Valkey as its canonical queue and Postgres as its canonical observation store. SQLite remains the legacy worker/local compatibility store only. Redis/Valkey is runtime infrastructure for jobs, retries, concurrency, and observability, not the source of truth for observations.
Claude-mem's domain object is an observation. Server beta must preserve that wording in user-facing APIs, docs, jobs, storage names, tests, logs, and implementation plans.
Use "memory" only for legacy compatibility names that already exist in worker-era code or for external library/API concepts that cannot be renamed cleanly. New Server beta/Postgres concepts should be named around observations:
observations, not memory_itemsobservation_sources, not memory_sourcesObservationRepository, not MemoryItemsRepositoryGenerateObservationsForEventJob, not generic memory generation/v1/observations and observation-focused MCP tools as the canonical surfaceIf any compatibility endpoint still uses /v1/memories, it should be treated as an alias over observations, not the canonical Server beta model.
plans/2026-05-07-claude-mem-server-apache-bullmq-team-auth.mdplans/2026-05-07-claude-mem-13-server-beta-full-worker-parity.md/Users/alexnewman/Downloads/claude-mem-handoff-docs/claude-mem-server-plan.mdsrc/server/routes/v1/ServerV1Routes.tssrc/server/queue/BullMqObservationQueueEngine.tssrc/server/queue/ObservationQueueEngine.tssrc/services/worker-service.tssrc/services/worker/SessionManager.tssrc/services/worker/agents/ResponseProcessor.tssrc/services/worker/ClaudeProvider.tssrc/services/worker/GeminiProvider.tssrc/services/worker/OpenRouterProvider.tssrc/services/worker/http/shared.tssrc/storage/sqlite/agent-events.tssrc/storage/sqlite/memory-items.tssrc/core/schemas/agent-event.tssrc/core/schemas/memory-item.tsscripts/e2e-server-beta-docker.shdocker/e2e/server-beta-e2e.mjs/v1 server route stores supplied events and direct observation records under legacy "memory" route/repository names:
src/server/routes/v1/ServerV1Routes.ts registers POST /v1/events, POST /v1/events/batch, and POST /v1/memories.AgentEventsRepository.create(...) and MemoryItemsRepository.create(...).src/services/worker/SessionManager.ts consumes queued messages through getMessageIterator(...).src/services/worker-service.ts starts provider sessions through startSessionProcessor(...).src/services/worker/agents/ResponseProcessor.ts parses provider XML with parseAgentXml(...) and writes observations through sessionStore.storeObservations(...).Claude/Gemini/OpenRouter providers, session ingest routes, queue semantics, and hook routing as parity requirements, but it does not explicitly require /v1/events to generate observations.Worker processes jobs and moves successful jobs to completed or thrown jobs to failed.error listener.autorun: false.express.json() and use /api/auth/*splat for Express 5.src/services/server/Server.ts plus Better Auth docs.src/server/middleware/auth.ts and src/server/auth/api-key-service.ts.SessionStore as the server observation model.src/services/worker/ClaudeProvider.ts, GeminiProvider.ts, and OpenRouterProvider.ts, then move shared logic into src/server/generation or src/core/generation.src/sdk/parser.ts and current post-processing rules from src/services/worker/agents/ResponseProcessor.ts.Queue, Worker, and QueueEvents directly for Server beta generation queues.src/server/queue/redis-config.ts and existing Docker E2E setup.new WorkerService()./v1 a write-only event archive while claiming Server beta generates observations.express.json().src/services/worker-service.ts
Legacy worker runtime. Stable compatibility path. May import shared core pieces later.
src/server/runtime/ServerBetaService.ts
Independent server runtime. Owns HTTP server, BullMQ queues, provider generation workers,
server storage repositories, auth, health, and Docker deployment.
POST /v1/events
POST /v1/events/batch
Claude Code hook routed to Server beta
MCP observation_record_* tool
|
v
AgentEventsRepository transaction
|
v
ObservationGenerationJobRepository outbox row
|
v
BullMQ Queue.add(...)
|
v
BullMQ Worker processor
|
v
ProviderObservationGenerator
|
v
parseAgentXml / structured parser
|
v
ObservationRepository.create(...) + ObservationSourcesRepository.addSource(...)
|
v
QueueEvents/SSE/audit/search index update
pg and @types/pg to the Node/Bun TypeScript package manifest used by this repo;src/storage/postgres/config.ts for environment parsing, pool sizing, timeouts, and SSL settings;src/storage/postgres/pool.ts for the shared pg.Pool factory, health check, transactions, and graceful shutdown;src/storage/postgres/schema.ts for migration/bootstrap SQL and schema version constants;src/storage/postgres/index.ts for exports used by Server beta runtime wiring;CLAUDE_MEM_SERVER_DATABASE_URL;teams;projects;team_members;api_keys;audit_log;server_sessions;agent_events;observations;observation_sources;observation_generation_jobs;observation_generation_job_events.CREATE TABLE teams (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE projects (
id TEXT PRIMARY KEY,
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
name TEXT NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (id, team_id)
);
CREATE TABLE team_members (
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
user_id TEXT NOT NULL,
role TEXT NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (team_id, user_id)
);
CREATE TABLE api_keys (
id TEXT PRIMARY KEY,
key_hash TEXT NOT NULL UNIQUE,
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
actor_id TEXT NOT NULL,
scopes JSONB NOT NULL DEFAULT '[]'::jsonb,
revoked_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CHECK (project_id IS NULL OR team_id IS NOT NULL),
FOREIGN KEY (project_id, team_id) REFERENCES projects(id, team_id) ON DELETE CASCADE
);
CREATE TABLE audit_log (
id TEXT PRIMARY KEY,
team_id TEXT REFERENCES teams(id) ON DELETE SET NULL,
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
actor_id TEXT,
api_key_id TEXT REFERENCES api_keys(id) ON DELETE SET NULL,
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_id TEXT,
details JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CHECK (project_id IS NULL OR team_id IS NOT NULL),
FOREIGN KEY (project_id, team_id) REFERENCES projects(id, team_id) ON DELETE SET NULL
);
CREATE TABLE server_sessions (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
external_session_id TEXT,
content_session_id TEXT,
agent_id TEXT,
agent_type TEXT,
platform_source TEXT,
generation_status TEXT NOT NULL DEFAULT 'idle',
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
ended_at TIMESTAMPTZ,
last_generated_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (project_id, external_session_id),
FOREIGN KEY (project_id, team_id) REFERENCES projects(id, team_id) ON DELETE CASCADE
);
CREATE TABLE agent_events (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
server_session_id TEXT REFERENCES server_sessions(id) ON DELETE SET NULL,
source_adapter TEXT NOT NULL,
source_event_id TEXT,
idempotency_key TEXT NOT NULL,
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
occurred_at TIMESTAMPTZ NOT NULL,
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (idempotency_key),
UNIQUE (id, project_id, team_id),
FOREIGN KEY (project_id, team_id) REFERENCES projects(id, team_id) ON DELETE CASCADE
);
CREATE TABLE observation_generation_jobs (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
agent_event_id TEXT REFERENCES agent_events(id) ON DELETE CASCADE,
source_type TEXT NOT NULL CHECK (source_type IN ('agent_event', 'session_summary', 'observation_reindex')),
source_id TEXT NOT NULL,
server_session_id TEXT REFERENCES server_sessions(id) ON DELETE SET NULL,
job_type TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('queued', 'processing', 'completed', 'failed', 'cancelled')),
idempotency_key TEXT NOT NULL UNIQUE,
bullmq_job_id TEXT UNIQUE,
attempts INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
next_attempt_at TIMESTAMPTZ,
locked_at TIMESTAMPTZ,
locked_by TEXT,
completed_at TIMESTAMPTZ,
failed_at TIMESTAMPTZ,
cancelled_at TIMESTAMPTZ,
last_error JSONB,
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (team_id, project_id, source_type, source_id, job_type),
CHECK (
(source_type = 'agent_event' AND agent_event_id IS NOT NULL AND source_id = agent_event_id)
OR
(source_type = 'session_summary' AND agent_event_id IS NULL AND server_session_id IS NOT NULL AND source_id = server_session_id)
OR
(source_type = 'observation_reindex' AND agent_event_id IS NULL)
),
FOREIGN KEY (agent_event_id, project_id, team_id) REFERENCES agent_events(id, project_id, team_id) ON DELETE CASCADE,
FOREIGN KEY (project_id, team_id) REFERENCES projects(id, team_id) ON DELETE CASCADE
);
CREATE TABLE observations (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
server_session_id TEXT REFERENCES server_sessions(id) ON DELETE SET NULL,
kind TEXT NOT NULL DEFAULT 'observation',
content TEXT NOT NULL,
content_search TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
generation_key TEXT,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
embedding JSONB,
created_by_job_id TEXT REFERENCES observation_generation_jobs(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (team_id, project_id, generation_key),
FOREIGN KEY (project_id, team_id) REFERENCES projects(id, team_id) ON DELETE CASCADE
);
CREATE TABLE observation_sources (
id TEXT PRIMARY KEY,
observation_id TEXT NOT NULL REFERENCES observations(id) ON DELETE CASCADE,
agent_event_id TEXT REFERENCES agent_events(id) ON DELETE CASCADE,
generation_job_id TEXT REFERENCES observation_generation_jobs(id) ON DELETE SET NULL,
source_type TEXT NOT NULL CHECK (source_type IN ('agent_event', 'session_summary', 'observation_reindex', 'manual')),
source_id TEXT NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (observation_id, source_type, source_id),
UNIQUE (source_type, source_id, generation_job_id, observation_id),
CHECK (
(source_type = 'agent_event' AND agent_event_id IS NOT NULL AND source_id = agent_event_id)
OR
(source_type <> 'agent_event' AND agent_event_id IS NULL)
)
);
CREATE TABLE observation_generation_job_events (
id TEXT PRIMARY KEY,
generation_job_id TEXT NOT NULL REFERENCES observation_generation_jobs(id) ON DELETE CASCADE,
event_type TEXT NOT NULL CHECK (event_type IN ('queued', 'enqueued', 'processing', 'retry_scheduled', 'completed', 'failed', 'cancelled')),
status_after TEXT NOT NULL CHECK (status_after IN ('queued', 'processing', 'completed', 'failed', 'cancelled')),
attempt INTEGER NOT NULL DEFAULT 0,
details JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_agent_events_project_session ON agent_events(project_id, server_session_id, occurred_at);
CREATE INDEX idx_projects_team ON projects(team_id, id);
CREATE INDEX idx_agent_events_team_project ON agent_events(team_id, project_id, occurred_at);
CREATE INDEX idx_observations_project_session ON observations(project_id, server_session_id, created_at);
CREATE INDEX idx_observations_team_project ON observations(team_id, project_id, created_at);
CREATE INDEX idx_observations_content_search ON observations USING GIN (content_search);
CREATE INDEX idx_observation_sources_event ON observation_sources(agent_event_id);
CREATE INDEX idx_observation_sources_source ON observation_sources(source_type, source_id);
CREATE INDEX idx_observation_jobs_status_next_attempt ON observation_generation_jobs(status, next_attempt_at, created_at);
CREATE INDEX idx_observation_jobs_team_project ON observation_generation_jobs(team_id, project_id, status, created_at);
CREATE INDEX idx_observation_jobs_event ON observation_generation_jobs(agent_event_id);
CREATE INDEX idx_observation_jobs_source ON observation_generation_jobs(source_type, source_id);
CREATE INDEX idx_observation_job_events_job_created ON observation_generation_job_events(generation_job_id, created_at);
CREATE INDEX idx_audit_log_scope_created ON audit_log(project_id, team_id, created_at);
agent_events is the canonical Postgres table for raw ingested agent events and their project/session/team ownership;projects.team_id; Server beta has no unowned/default project mode in the Postgres canonical store;projects.team_id, require the caller's team/API-key scope to match it, and reject any request body or repository write where team_id disagrees with the project's owner;project_id and team_id must use FK-backed ownership validation through FOREIGN KEY (project_id, team_id) REFERENCES projects(id, team_id);observation_generation_jobs.source_type and observation_generation_jobs.source_id identify the durable source of work for event, summary, and reindex jobs without overloading event-only columns;source_type = 'agent_event', source_id = agent_event_id, and a non-null agent_event_id FK to the source agent_events row being processed;source_type = 'session_summary', source_id = server_session_id, and agent_event_id = NULL;source_type = 'observation_reindex', source_id set to the target observation ID or deterministic reindex scope ID, and agent_event_id = NULL;source_id ownership before job insert: session summary jobs must load the server_sessions row under the same project_id/team_id, and observation reindex jobs must load the target observation or documented reindex scope under the same project_id/team_id;observation_generation_job_events records durable lifecycle/outbox events for each observation generation job, including enqueue, processing, retry, completion, and failure state changes;observation_generation_job_events may reference agent_events through its job relationship, but it is not a replacement for agent_events and must not store raw event payloads as the canonical event record.observation_generation_jobs.status is constrained to queued, processing, completed, failed, or cancelled;queued -> processing -> completed, queued -> processing -> failed, queued -> cancelled, and retry transitions from stale/failed retryable work back to queued only when attempts < max_attempts;attempts increments only when a worker transitions a job to processing;next_attempt_at gates retry/reconciliation eligibility;locked_at and locked_by are set while a worker owns processing and are cleared or superseded on completion, failure, cancellation, or stale-lock recovery;completed_at, failed_at, and cancelled_at are terminal timestamps and exactly one may be non-null for terminal jobs;agent_events.source_event_id is optional adapter metadata only and must not be used as the sole idempotency authority;agent_events.idempotency_key is required and deterministic: when source_event_id is present, derive it from team_id, project_id, source_adapter, and source_event_id; when omitted, derive it from team_id, project_id, source_adapter, server_session_id, event_type, occurred_at, and a canonical JSON hash of payload;UNIQUE (idempotency_key) on agent_events suppresses duplicate ingestion for native event IDs, batch imports, and clients with omitted source event IDs;idempotency_key must be deterministic from team_id, project_id, source_type, source_id, and job_type, and UNIQUE (idempotency_key) suppresses duplicate outbox rows;UNIQUE (team_id, project_id, source_type, source_id, job_type) guarantees one source/job relationship per generation kind within the owning project/team scope across event, summary, and reindex jobs;bullmq_job_id must be deterministic and unique when present so reconciliation can safely re-add or replace terminal BullMQ jobs;observations.generation_key is nullable for direct/manual observations and required for provider/generated observations;generation_key must be deterministic as generation:v1:{generation_job_id}:{parsed_observation_index}:{canonical_content_fingerprint} where the content fingerprint is computed after parser normalization and before persistence;UNIQUE (team_id, project_id, generation_key) on observations is the primary retry idempotency guard within the owning project/team scope: retrying the same job and parsed observation must upsert/reload the existing observation instead of creating a new row;observations.created_by_job_id is a nullable foreign key to observation_generation_jobs(id); provider-generated observations must set it to the durable Postgres generation job that created the observation;observation_sources.generation_job_id is a nullable foreign key to observation_generation_jobs(id); generated observation source rows must set it when the observation came from a generation job;observation_sources.source_type and observation_sources.source_id mirror the job source model so generated observations can link to events, session summaries, reindex scopes, or manual/direct sources without ambiguous nullable uniqueness;UNIQUE (observation_id, source_type, source_id) guarantees a source cannot be linked to the same observation more than once;observation_sources: the same source_type, source_id, generation_job_id, and observation_id relationship must not be inserted twice;project_id and team_id and include them in the mutating SQL predicate before changing rows;ObservationRepository.search(...) must use the generated observations.content_search tsvector, the GIN index on content_search, and websearch_to_tsquery('english', query) for scoped full-text search;agent_events, for summary jobs it is server_sessions, and for reindex jobs it is the target observation or documented reindex scope. BullMQ payload data is advisory execution data, not authority.ProjectRepository;TeamRepository;ObservationRepository;ObservationSourcesRepository;ObservationGenerationJobRepository;ObservationGenerationJobEventsRepository for durable lifecycle/outbox events such as queued, enqueued, processing, retry scheduled, completed, failed, and cancelled;AgentEventsRepository backed by the Server beta Postgres connection.memory_items data can be migrated or viewed as observations;MemoryItemsRepository remains a current-code compatibility reference, not the Server beta repository contract.src/core/schemas/memory-item.ts only to preserve legacy import/alias behavior; new Server beta schemas should be named around observations.ProjectRepository.create(...) requires a valid team_id, lookup returns the owning team, and project-scoped repository writes reject mismatched team_id/project_id pairs;ObservationRepository.create(...) and lookup by project/session/team;ObservationRepository.search(...) uses the generated content_search column with the GIN-backed websearch_to_tsquery path and returns only rows for the requested project/team scope;ObservationSourcesRepository.addSource(...) idempotency;ObservationSourcesRepository.addSource(...) requires project/team scope and rejects wrong-scope observation/source/job relationships without inserting rows;AgentEventsRepository.create(...), batch insert/reload, lookup by project/session/team, deterministic idempotency_key generation when source_event_id is present, and deterministic idempotency_key fallback when source_event_id is omitted;agent_events rows and must not duplicate generation jobs;ObservationGenerationJobRepository create/status transition/reload and duplicate-job suppression for event, session summary, and reindex jobs using deterministic source_type, source_id, and idempotency_key;ObservationGenerationJobRepository.transitionStatus(...) requires project/team scope in both the conditional update and fallback reload and must not mutate rows when called with the wrong scope;observations.generation_key, including retrying the same job and parsed observation index/content without creating a duplicate observation;ObservationGenerationJobEventsRepository lifecycle append/list tests and outbox event linking through observation_generation_job_events;ObservationGenerationJobEventsRepository.append(...) requires project/team scope and appends only when the referenced job belongs to that project/team.rg -n "MemoryItemsRepository" src/server
memory_items or new repositories named MemoryItemsRepository.src/server/runtime/ServerBetaService.ts.src/server/runtime/create-server-beta-service.ts.src/server/runtime/types.ts for the service graph:
claude-mem server start|stop|restart|status to ServerBetaService, not WorkerService.WorkerService..server-beta.pid.server-beta.port.server-beta.runtime.json/v1/info.runtime = "server-beta" and /api/health.runtime = "server-beta" in Server beta.src/services/server/Server.ts.src/services/worker-service.ts; do not copy the full worker class.src/services/infrastructure/ProcessManager.ts.WorkerService dependency.rg -n "WorkerService|services/worker-service|worker/http" src/server src/npx-cli/commands/server.ts src/npx-cli/commands/worker.ts
WorkerService.npx claude-mem server status reports server-beta state independently of worker state.start|stop|status commands still work.src/server/jobs/types.ts:
ServerGenerationJobGenerateObservationsForEventJobGenerateObservationsForEventBatchJobGenerateSessionSummaryJobReindexObservationJobteam_id, project_id, source_type, source_id, and generation_job_id; event jobs additionally carry agent_event_id, summary jobs carry server_session_id, and reindex jobs carry the target observation ID or deterministic reindex scope ID.src/server/jobs/ServerJobQueue.ts wrapping BullMQ Queue, Worker, and QueueEvents.src/server/jobs/job-id.ts for deterministic, colon-free job IDs.src/server/jobs/outbox.ts using ObservationGenerationJobRepository:
observation_generation_jobs;source_type/source_id; lifecycle events live in observation_generation_job_events;queued, processing, completed, failed, cancelled;queued or stale processing;/v1/info, /api/health, and claude-mem server status.new Worker(queueName, async job => ...), attach worker.on('error', ...), and use worker events for completion/failure.concurrency, default conservative value 1 per provider/session lane, configurable later.src/server/queue/BullMqObservationQueueEngine.ts has tested deterministic job IDs and Redis health wiring; copy its safe ID and health patterns, not its worker-iterator compatibility shape.ObservationGenerationJobRepository;POST /v1/events and POST /v1/events/batch to:
?generate=false: store event only;?wait=true: if implemented in this phase, wait only for bounded queue acceptance or job status and return queued/accepted/job status. It must not claim observations were generated.GET /v1/jobs/:id for generation status.POST /v1/memories only as a compatibility alias for manual/direct observation insertion. It must not call the generator.src/server/routes/v1/ServerV1Routes.ts./v1/events/batch transaction.docker/e2e/server-beta-e2e.mjs.POST /v1/events returns event and generationJob.POST /v1/events?generate=false returns no generation job.POST /v1/events?wait=true, if implemented, returns queued/accepted/job status only; it does not return generated observation IDs or imply provider generation completed./api/sessions/observations./v1/events depend on Claude Code-specific hook payload shape.src/server/generation/ProviderObservationGenerator.ts.src/server/generation/providers/:
ClaudeObservationProviderGeminiObservationProviderOpenRouterObservationProvidersrc/server/generation/processGeneratedResponse.ts:
parseAgentXml(...);ObservationRepository;GET /v1/events/:id/observations to inspect generated observations for an event.observation_sources.sourceType = "agent_event" support if not already present, or add a server-specific source table mapping event IDs to observation IDs.AgentEvent records plus project/session metadata;<private> skip behavior.src/services/worker/agents/ResponseProcessor.ts.src/services/worker/ClaudeProvider.tssrc/services/worker/GeminiProvider.tssrc/services/worker/OpenRouterProvider.tssrc/core/schemas/memory-item.ts, but expose the Server beta create contract as an observation schema.src/services/worker/provider-errors.ts.POST /v1/events?wait=true returns generated observation IDs only after Phase 5 provider generation and persistence are wired and the job finishes within timeout.rg -n "services/worker/(ClaudeProvider|GeminiProvider|OpenRouterProvider|agents/ResponseProcessor)" src/server
WorkerRef, ActiveSession, or legacy worker session types into server generation.SessionStore tables from Server beta generation.server_sessions as the canonical Server beta session model.contentSessionId or generic external session ID;agentId;agentType;platformSource;generationStatus;lastGeneratedAtEpoch.ServerSessionRuntimeRepository helpers:
/v1/sessions/:id/end."summary".ActiveSession as the server runtime state object./v1/sessions/start or compatibility endpoint;/v1/events;/v1/sessions/:id/end.plugin/hooks/hooks.json.src/npx-cli/commands/install.ts./api/sessions/observations.observation_addobservation_record_eventobservation_searchobservation_contextobservation_generation_statusmemory_* MCP names may remain only as compatibility aliases over the observation tools.src/servers/mcp-server.ts.src/core/schemas/*.WorkerService into MCP server mode./api/sessions/observations -> convert legacy payload to AgentEvent -> enqueue Server beta generation job./api/sessions/summarize -> convert legacy payload to session-end/summary job.src/server/compat/*.src/services/worker/http/shared.ts.src/adapters/claude-code/mapper.ts.rg -n "services/worker/http/routes|WorkerService" src/server/compat src/server/runtime
CLAUDE_MEM_RUNTIME=server-betaCLAUDE_MEM_QUEUE_ENGINE=bullmqclaude-mem server worker startscripts/e2e-server-beta-docker.sh.docker/claude-mem/Dockerfile.plans/2026-05-06-redis-dependency-strategy.md.docker compose ps shows server + Postgres + Valkey./v1/events?wait=true creates generated observations.team_id;project_id;/v1/teams/:id/jobs/v1/projects/:id/jobssrc/storage/sqlite/teams.ts and src/storage/sqlite/auth.ts.src/server/routes/v1/ServerV1Routes.ts.claude-mem server jobs status.claude-mem server jobs retry <id>.claude-mem server jobs cancel <id>.claude-mem server jobs failed./v1/jobs list endpoint.completed, failed, progress, and error events.src/services/worker/http/routes/LogsRoutes.ts for log tailing style.server jobs failed.Phase 13 is not an implementation phase and does not need the implementation-phase template. It is the final release gate for proving the independently implemented Server beta runtime is complete, durable, and still compatible with the legacy worker runtime.
/v1/events generates observations;rg -n "new WorkerService|services/worker-service|services/worker/http/routes" src/server
rg -n "PendingMessageStore|SessionQueueProcessor" src/server
rg -n "CLAUDE_MEM_AUTH_MODE=local-dev|ALLOW_LOCAL_DEV_BYPASS" docker docs/server.md
rg -n "POST /v1/events|generationJob|wait=true" docs README.md
Expected:
Server beta is independent when all are true:
/v1/events can enqueue and generate observations.