ThreadKeeper is a local MCP server for durable productivity records and explicit,
versioned memory. An MCP client may launch the threadkeeper binary over stdio
or connect to an explicitly started, authenticated loopback Streamable HTTP
endpoint. The compatible core remains a stdio server. The loopback dashboard,
HTTP endpoint, optional reminder daemon, and V2C native-TLS remote service are
separate processes. No hosted account or authorization server is bundled.
You need:
- macOS, Linux, or Windows.
- Go 1.26.5 or newer when building from source.
- An MCP client that supports stdio servers.
- Optionally, an Ollama-compatible embedding endpoint for hybrid memory search and a separately configured model for review-only candidate extraction.
Build and verify from the repository root:
go build -trimpath -o threadkeeper ./cmd/threadkeeper
./threadkeeper doctor
go test ./...Use an absolute path to the resulting binary in client configuration. You may instead install it into your configured Go binary directory:
go install ./cmd/threadkeeperdoctor creates or opens the configured database, applies pending core and
enabled-module migrations, and checks the database, FTS5, time zone, and
optional embedding provider.
Without configuration, ThreadKeeper stores threadkeeper.db under the operating
system's user configuration directory, typically:
- macOS:
~/Library/Application Support/ThreadKeeper/threadkeeper.db - Linux:
$XDG_CONFIG_HOME/ThreadKeeper/threadkeeper.dbor~/.config/ThreadKeeper/threadkeeper.db - Windows:
%AppData%\ThreadKeeper\threadkeeper.db
Use the same explicit database path in every client that should share the same records:
./threadkeeper doctor --db /absolute/path/to/threadkeeper.dbDatabase, backup, export, and log files created by ThreadKeeper are restricted to the current user where the operating system supports Unix-style permissions.
Add a stdio server entry to the client's MCP configuration. Client products name the containing field differently, but the server entry is:
{
"mcpServers": {
"threadkeeper": {
"command": "/absolute/path/to/threadkeeper",
"args": [
"serve",
"--db",
"/absolute/path/to/threadkeeper.db",
"--time-zone",
"Asia/Kolkata"
]
}
}
}Use an IANA time zone such as UTC, Asia/Kolkata, or America/New_York.
Restart the client after changing its MCP configuration. The client should then
show a server named threadkeeper with 20 core tools, four resources, and two
read-only review prompts.
The client owns the process lifecycle. Running threadkeeper serve directly in
a terminal is useful only for protocol testing: it waits for newline-delimited
JSON-RPC messages on stdin and writes protocol responses to stdout.
With Node.js and npm available:
npx -y @modelcontextprotocol/inspector --cli \
/absolute/path/to/threadkeeper serve \
--db /absolute/path/to/threadkeeper.db \
--time-zone UTC \
--log-level error \
--method tools/listThe result should contain exactly 20 tools. To check resources:
npx -y @modelcontextprotocol/inspector --cli \
/absolute/path/to/threadkeeper serve \
--db /absolute/path/to/threadkeeper.db \
--time-zone UTC \
--log-level error \
--method resources/listInitialize a distinct local MCP credential once, then start the listener. Use a fixed port in client configuration; port zero is useful for temporary/manual sessions because it chooses an available port.
./threadkeeper http init \
--db /absolute/path/to/threadkeeper.db \
--http-token-file /absolute/private/path/threadkeeper-http.token
./threadkeeper http serve \
--db /absolute/path/to/threadkeeper.db \
--http-token-file /absolute/private/path/threadkeeper-http.token \
--http-address 127.0.0.1 \
--http-port 8787The endpoint is http://127.0.0.1:8787/mcp. Clients send the token as
Authorization: Bearer <token>. ThreadKeeper stores only its SHA-256 hash in
SQLite; the portable fallback file is created atomically with user-only
permissions and the token is never printed. A client may import that plaintext
token into its OS keychain and then restrict or remove its own fallback copy.
Verify the authenticated endpoint with the pinned official Inspector:
IFS= read -r THREADKEEPER_LOCAL_TOKEN < /absolute/private/path/threadkeeper-http.token
npx -y @modelcontextprotocol/inspector@0.22.0 --cli \
http://127.0.0.1:8787/mcp \
--transport http \
--method tools/list \
--header "Authorization: Bearer ${THREADKEEPER_LOCAL_TOKEN}"
unset THREADKEEPER_LOCAL_TOKENCredential lifecycle commands are explicit and survive process restarts:
./threadkeeper http status --db /absolute/path/to/threadkeeper.db --http-token-file /absolute/private/path/threadkeeper-http.token
./threadkeeper http init --db /absolute/path/to/threadkeeper.db --http-token-file /absolute/private/path/threadkeeper-http.token --rotate
./threadkeeper http revoke --db /absolute/path/to/threadkeeper.db --confirmationRotation revokes the previous bearer immediately. Revocation intentionally
leaves the now-invalid fallback file in place; use http init --rotate to
replace it safely. The listener accepts only explicit loopback IPs, rejects
forwarded and cross-origin requests, exposes no permissive CORS, and applies
body/header/rate/concurrency/connection/time limits. /health/live and
/health/ready reveal only bounded status. The dashboard does not accept this
credential and MCP HTTP does not accept dashboard launch/session credentials.
Remote mode is not an extension of localhost HTTP and never accepts its token. It requires a trusted external OAuth issuer, registered ThreadKeeper grant, native TLS, exact public resource URL, managed encrypted storage, and an operator go-live review. It maps issuer/subject to an internal owner and opens only that owner's physical database.
Follow remote deployment for provisioning and startup,
then keep the incident,
backup, and deletion runbooks
with the deployment. Offline clients use authenticated /sync/changes,
/sync/mutations, and /sync/snapshot; they never copy or merge a SQLite file.
After applying a full snapshot, resume /sync/changes after the cursor in the
snapshot response's X-ThreadKeeper-Change-Cursor header.
Normally, speak to the MCP-enabled agent in plain language. The agent chooses a tool, asks for missing destructive-action confirmation when necessary, and returns the structured result. You do not need to write JSON.
Good prompts are explicit about the desired durable action:
- “Capture this idea in my inbox: build a weekly review workflow.”
- “Create a high-priority task to draft the launch post by Friday at 5 PM.”
- “Remind me every week on Monday at 9 AM to review project alpha.”
- “Start a 45-minute focus session for project alpha.”
- “Save the current context for project alpha; the next action is to test sync.”
- “Record that we chose SQLite because the product is local-first.”
- “Remember that I prefer concise status summaries.”
- “Show my daily brief for tomorrow in Asia/Kolkata.”
- “Search all my ThreadKeeper data for database migration.”
Use “capture,” “create a task,” “remind me,” “save project context,” “record a decision,” and “remember this preference” deliberately: each stores a different kind of durable record.
| Need | Tool | Example request |
|---|---|---|
| Capture an unprocessed thought | inbox_capture |
“Capture this idea: add offline export.” |
| Create a concrete action | task_create |
“Create a task to test exports tomorrow.” |
| Review or filter tasks | task_list |
“List overdue pending tasks for project alpha.” |
| Finish a task | task_complete |
“Mark that export test task complete.” |
| Schedule a stored reminder | reminder_create |
“Remind me daily at 6 PM to write notes.” |
| Review reminders | reminder_list |
“List reminders due this week.” |
| Complete, cancel, or snooze | reminder_resolve |
“Snooze that reminder until 4 PM.” |
| Start focused work | focus_start |
“Start a 25-minute focus session for alpha.” |
| End focused work | focus_end |
“End the session as completed; tests pass.” |
| Save a resumable project snapshot | context_save |
“Save where we stopped on alpha.” |
| Resume a project | context_get |
“Get the latest context for alpha.” |
| Preserve a reasoned choice | decision_record |
“Record why we selected SQLite.” |
| Find prior choices | decision_search |
“Find decisions about persistence.” |
| Assemble today's structured agenda | daily_brief |
“Give me today's brief in Asia/Kolkata.” |
| Search every selected domain | global_search |
“Search tasks, contexts, and memories for FTS.” |
| Store explicit durable memory | memory_remember |
“Remember that I prefer dark mode.” |
| Retrieve relevant memory | memory_recall |
“What interface preferences have I told you?” |
| Correct memory without losing history | memory_correct |
“Correct that: I now prefer light mode.” |
| Permanently remove exact memory | memory_forget |
“Forget that preference; yes, permanently delete it.” |
| Explain provenance and validity | memory_explain |
“Explain where that preference came from.” |
The complete contracts, limits, and fields are declared to MCP clients at
runtime and stored under schemas/. See the tool catalogue
for the frozen V1 list.
- Ask the agent to capture an idea with
inbox_capture. - Later, ask it to create a task from the inbox item with
task_create. - Search the original idea with
global_searchwhen needed.
Inbox capture is intentionally lightweight. Use a task only when the item has a defined action, priority, or due time.
- Request
daily_brieffor a date and time zone. - Review overdue tasks, today's tasks, upcoming reminders, active focus, project contexts, inbox count, and relevant memories.
- Start work with
focus_startand end it withfocus_end. - Complete tasks and resolve reminders as work finishes.
The brief is structured data, not generated prose; the client may summarize it.
Before stopping:
- Save a context snapshot containing the current state, next action, blockers, and safe references.
- Record important choices separately as decisions.
In a later conversation, ask for the latest project context and related decisions. Context snapshots are immutable, so older snapshots remain available for historical lookup.
Use decision_record for a choice plus its reasoning and alternatives. Use
decision_search rather than memory when you need the authoritative decision
record. A memory can reference a decision, but it does not replace it.
Memory is explicit in V1:
- “Remember that I prefer morning meetings.” creates sourced memory.
memory_recallfinds it in a later session using keyword search or optional hybrid search.- A correction creates a new version and closes the old version's validity
interval; historical
asOfrecall can still return the former version. - Forgetting requires the exact memory ID and
confirmation: true. It removes canonical content, provenance, FTS data, embeddings, and feedback for that memory while retaining a content-free deletion audit.
Do not use memory for passwords, API keys, OTPs, private keys, session tokens, or other authentication material. ThreadKeeper rejects likely secrets, but its detection is heuristic rather than a password manager.
- Use
global_searchto find literal matches across operational records and memories, grouped by domain. - Use
decision_searchfor authoritative decisions. - Use
context_getfor the latest resumable state of one project. - Use
memory_recallfor relevant personal or project knowledge with temporal validity, confidence, importance, provenance, and explanations.
Clients may read these without selecting a tool:
| Resource | Contents |
|---|---|
threadkeeper://today |
Today's structured daily brief. |
threadkeeper://inbox |
Latest active inbox items. |
threadkeeper://memory/recent |
Recent active, non-sensitive memories. |
threadkeeper://system/status |
Version, schema, capabilities, metrics, index health, and backup status. |
threadkeeper://system/modules |
Enabled/installed V2A modules, migrations, contributed tools, and health; advertised only with a V2A profile. |
Resources are bounded and read-only. ThreadKeeper does not advertise resource subscriptions in V1.
Precedence is command-line flags, environment variables, user config.json,
then defaults.
| Setting | CLI flag | Environment | Config key | Default |
|---|---|---|---|---|
| Database | --db |
THREADKEEPER_DB |
databasePath |
OS user config directory |
| Time zone | --time-zone |
THREADKEEPER_TIME_ZONE |
timeZone |
UTC |
| Embedding endpoint | --embedding-endpoint |
THREADKEEPER_EMBED_ENDPOINT |
embeddingEndpoint |
disabled |
| Embedding model | --embedding-model |
THREADKEEPER_EMBED_MODEL |
embeddingModel |
disabled |
| Embedding dimensions | --embedding-dimensions |
THREADKEEPER_EMBED_DIMENSIONS |
embeddingDimensions |
disabled |
| Export allowlist | --export-dir |
THREADKEEPER_EXPORT_DIR |
exportDirectory |
database directory |
| Migration backups | --backup-retention |
THREADKEEPER_BACKUP_RETENTION |
backupRetention |
3 |
| Log level | --log-level |
THREADKEEPER_LOG_LEVEL |
logLevel |
info |
| Log file | --log-file |
THREADKEEPER_LOG_FILE |
logFile |
stderr |
| In-flight requests | --max-concurrency |
THREADKEEPER_MAX_CONCURRENCY |
maxConcurrency |
4 |
| Tool profiles | --profiles |
THREADKEEPER_PROFILES |
profiles |
core |
| Candidate endpoint | --candidate-endpoint |
THREADKEEPER_CANDIDATE_ENDPOINT |
candidateEndpoint |
disabled |
| Candidate model | --candidate-model |
THREADKEEPER_CANDIDATE_MODEL |
candidateModel |
disabled |
| Candidate batch | --candidate-batch-size |
THREADKEEPER_CANDIDATE_BATCH_SIZE |
candidateBatchSize |
64 |
| Scheduled candidates | --candidate-auto |
THREADKEEPER_CANDIDATE_AUTO |
candidateAuto |
false |
| Candidate interval | --candidate-interval-minutes |
THREADKEEPER_CANDIDATE_INTERVAL_MINUTES |
candidateIntervalMinutes |
60 |
| Daemon polling | --daemon-poll-seconds |
THREADKEEPER_DAEMON_POLL_SECONDS |
daemonPollSeconds |
30 |
| Missed-reminder grace | --daemon-grace-minutes |
THREADKEEPER_DAEMON_GRACE_MINUTES |
daemonGraceMinutes |
1440 |
| Inspector port | --dashboard-port |
THREADKEEPER_DASHBOARD_PORT |
dashboardPort |
available loopback port |
| MCP HTTP address | --http-address |
THREADKEEPER_HTTP_ADDRESS |
httpAddress |
127.0.0.1 |
| MCP HTTP port | --http-port |
THREADKEEPER_HTTP_PORT |
httpPort |
available loopback port |
| MCP token file | --http-token-file |
THREADKEEPER_HTTP_TOKEN_FILE |
httpTokenFile |
database path plus .http-token |
| MCP HTTP connections | --http-max-connections |
THREADKEEPER_HTTP_MAX_CONNECTIONS |
httpMaxConnections |
32 |
| MCP HTTP requests/minute | --http-rate-per-minute |
THREADKEEPER_HTTP_RATE_PER_MINUTE |
httpRatePerMinute |
120 |
| MCP HTTP body bytes | --http-body-bytes |
THREADKEEPER_HTTP_BODY_BYTES |
httpBodyBytes |
1048576 |
| MCP idle session | --http-session-minutes |
THREADKEEPER_HTTP_SESSION_MINUTES |
httpSessionMinutes |
30 |
Retrieval weights use --score-semantic, --score-keyword,
--score-recency, --score-importance, and --score-confidence; matching
environment variables are THREADKEEPER_SCORE_SEMANTIC,
THREADKEEPER_SCORE_KEYWORD, THREADKEEPER_SCORE_RECENCY,
THREADKEEPER_SCORE_IMPORTANCE, and THREADKEEPER_SCORE_CONFIDENCE.
The configuration file is ThreadKeeper/config.json inside the same OS user
configuration directory used for the default database. A complete example:
{
"databasePath": "/absolute/path/to/threadkeeper.db",
"timeZone": "Asia/Kolkata",
"embeddingEndpoint": "http://127.0.0.1:11434",
"embeddingModel": "nomic-embed-text",
"embeddingDimensions": 768,
"candidateEndpoint": "http://127.0.0.1:11434",
"candidateModel": "qwen3:8b",
"candidateBatchSize": 64,
"candidateAuto": false,
"candidateIntervalMinutes": 60,
"exportDirectory": "/absolute/path/to/threadkeeper-exports",
"backupRetention": 3,
"retrievalWeights": {
"semantic": 0.45,
"keyword": 0.25,
"recency": 0.10,
"importance": 0.10,
"confidence": 0.10
},
"logLevel": "info",
"logFile": "/absolute/path/to/threadkeeper.log",
"maxConcurrency": 4,
"profiles": ["core", "memory_intelligence", "review"],
"daemonPollSeconds": 30,
"daemonGraceMinutes": 1440,
"dashboardPort": 0,
"httpAddress": "127.0.0.1",
"httpPort": 8787,
"httpTokenFile": "/absolute/private/path/threadkeeper-http.token",
"httpMaxConnections": 32,
"httpRatePerMinute": 120,
"httpBodyBytes": 1048576,
"httpSessionMinutes": 30,
"pendingCandidateDays": 90,
"rejectedCandidateDays": 30,
"extractionInputDays": 30,
"activityEventDays": 180,
"retrievalDetailDays": 30
}Retrieval weights must be non-negative, total exactly 1 within floating-point tolerance, and leave at least one non-semantic signal available for keyword-only operation. Unknown config keys are rejected.
threadkeeper config-show prints the effective feature, provider, retention,
daemon, HTTP, and limit settings as JSON for support; threadkeeper remote config-show does the same for the remote profile. Database/export/log/token
paths, provider endpoints, identity values, and all secrets are omitted or
marked redacted.
ThreadKeeper works fully with SQLite FTS5 and no embedding service. To add hybrid retrieval, configure all three values for an Ollama-compatible endpoint:
{
"command": "/absolute/path/to/threadkeeper",
"args": [
"serve",
"--embedding-endpoint", "http://127.0.0.1:11434",
"--embedding-model", "nomic-embed-text",
"--embedding-dimensions", "768"
]
}The configured dimensions must match the model's actual vector size and are bounded from 1 to 65,536. Provider URLs cannot contain userinfo, query-string credentials, or fragments. The endpoint stays local unless you explicitly point it elsewhere. ThreadKeeper does not accept API keys in config. Rebuild derived vectors after changing the model:
./threadkeeper reindex --embeddings \
--embedding-endpoint http://127.0.0.1:11434 \
--embedding-model nomic-embed-text \
--embedding-dimensions 768Embedding vectors are derived data and are excluded from normal exports.
Enable the additive memory/review tools without changing the 20-tool core:
./threadkeeper serve --profiles core,memory_intelligence,reviewCandidate extraction is manual unless scheduling is explicitly enabled. It
reads only eligible activity and inbox items marked candidateEligible, sends
bounded safe summaries to the configured extractor, validates structured output,
and creates proposals—not memories:
./threadkeeper extract-candidates \
--candidate-endpoint http://127.0.0.1:11434 \
--candidate-model qwen3:8bUse memory_candidate_list and memory_candidate_review to inspect evidence
and approve, edit, reject, defer, or resolve conflicts. Merges require
memory_merge_preview followed by exact confirmation. The weekly_review tool
and daily_review/weekly_review MCP prompts are deterministic and read-only.
Copy-paste V1.1 prompts (replace values in <angle brackets>):
| Tool | Example prompt |
|---|---|
memory_candidate_list |
“Show my pending personal memory candidates, include their evidence, and limit the result to 10.” |
memory_candidate_review |
“Approve memory candidate <candidate-id> because the evidence confirms this is a stable preference.” |
memory_merge_preview |
“Preview merging memories <memory-id-a> and <memory-id-b> into ‘I prefer concise status summaries’; reason: they are duplicates. Do not execute the merge.” |
memory_merge |
“Execute merge preview <preview-token>. I confirm this exact merge.” |
memory_feedback |
“Mark memory <memory-id> from retrieval <retrieval-id> as incorrect; note that the preference is outdated.” |
weekly_review |
“Give me the weekly review ending 2026-07-18 in Asia/Kolkata, compare the previous week, include memory candidates, and omit bookmark backlog.” |
Launch the inspector on IPv4 and IPv6 loopback:
./threadkeeper dashboard --dashboard-port 0The command prints a one-time launch token to stderr. Paste it into the local unlock screen; it is exchanged for a short-lived HttpOnly session and is never placed in a URL. The inspector provides masked memory/provenance views, candidate review, retrieval score diagnostics, merge/retention previews, embedding generations, restore verification, jobs, audit, and system status.
Run reminders in the foreground or install the documented macOS user agent:
./threadkeeper daemon status
./threadkeeper daemon runSee reminder daemon on macOS. The daemon refuses missing, unmigrated, or checksum-changed databases and never applies a migration.
Retention is always previewed and exact-token confirmed:
./threadkeeper retention-preview
./threadkeeper retention-execute --preview-token <preview-uuid> --confirmationEnable only the domains you want. For example:
./threadkeeper doctor --profiles core,reading,habits,checklists
./threadkeeper serve --profiles core,reading,habits,checklistsAvailable profiles are reading, habits, checklists, shopping,
locations, and intelligence. The first doctor or serve with a profile
takes an online backup and applies only that module's seed-free migration.
Removing a profile hides its tools and jobs but preserves its tables and data.
Run the same profile set for daemon after migration; the daemon refuses to
apply migrations itself.
Copy-paste V2A prompts (replace values in <angle brackets>):
| Tool | Example prompt |
|---|---|
bookmark_save |
“Save https://example.com/sqlite-notes to my unread reading queue for project alpha with tag database; do not fetch metadata.” |
bookmark_search |
“Find unread bookmarks about SQLite tagged database in project alpha.” |
bookmark_update |
“Mark bookmark <bookmark-id> as read and note ‘Useful migration checklist’.” |
habit_create |
“Create a daily boolean habit named ‘Write project notes’ in Asia/Kolkata with target 1, starting 2026-07-18.” |
habit_log |
“Log habit <habit-id> as completed with value 1 for local date 2026-07-18.” |
habit_summary |
“Summarize habit <habit-id> from 2026-07-01 through 2026-07-31, including streaks but not individual logs.” |
checklist_create |
“Create a checklist named ‘Release ThreadKeeper’ with required items keyed run-tests, build-binary, and verify-backup, in that order.” |
checklist_start |
“Start checklist <checklist-id> with title ‘ThreadKeeper 2.0 release’ for project alpha.” |
checklist_update |
“Complete item run-tests in checklist run <run-id> with note ‘All tests passed’.” |
checklist_status |
“Show the current status and items for checklist run <run-id>.” |
shopping_update |
“Add 2 kilograms of rice to my Groceries list, category pantry.” |
shopping_list |
“Show active items on my Groceries list grouped by category.” |
item_location_update |
“Save item ‘Passport’ at location ‘Home / Office / Locked drawer’ with note ‘Blue folder’.” |
item_location_find |
“Where did I last report the passport? Include its location history.” |
productivity_insights |
“Show evidence-backed work-pattern and estimate-calibration insights for project alpha from 2026-06-01 through 2026-07-18, using at least 10 observations.” |
Bookmark saving performs no network request by default. fetchMetadata: true
queues a background fetch for threadkeeper daemon run. The worker pins a
validated public DNS answer, revalidates every redirect, rejects private,
loopback, link-local, carrier-grade NAT, and metadata targets, and accepts at
most 1 MiB of HTML within ten seconds. It stores selected inert metadata, never
page bodies or executable content.
Daily and weekly review include enabled habit, active-checklist, and bounded
shopping sections. Bookmark backlog appears only with
includeBookmarkBacklog: true; physical-item locations never appear in review.
The read-only productivity_insights tool requires at least ten observations
for work-pattern and estimate-calibration output.
./threadkeeper doctor --db /absolute/path/to/threadkeeper.dbThe output must be a new absolute file inside the allowed export directory:
./threadkeeper export \
--db /absolute/path/to/threadkeeper.db \
--format json \
--export-dir /absolute/path/to/exports \
--output /absolute/path/to/exports/threadkeeper.json
./threadkeeper export \
--db /absolute/path/to/threadkeeper.db \
--format ndjson \
--export-dir /absolute/path/to/exports \
--output /absolute/path/to/exports/threadkeeper.ndjsonExports include canonical operational records, active and superseded memories, provenance, tags, schema version, and separate checksummed streams for installed module records. They exclude fetched page bodies, module job leases, vectors, idempotency rows, provider payloads, and raw audit data.
./threadkeeper backup \
--db /absolute/path/to/threadkeeper.db \
--export-dir /absolute/path/to/backups \
--output /absolute/path/to/backups/threadkeeper-backup.dbBackup uses SQLite's online backup API. See backup and recovery before replacing a database. Every CLI backup is copied to an isolated temporary restore, opened against the exact migration set, and checked for SQLite integrity and foreign-key violations before success is reported.
./threadkeeper reindex --db /absolute/path/to/threadkeeper.db --ftsAdd --embeddings and the provider settings to rebuild vectors. Reindexing
changes only derived indexes, not canonical records.
./threadkeeper traces --db /absolute/path/to/threadkeeper.db --limit 25Traces contain request IDs, tool names, input hashes, entity IDs, status, error codes, durations, retrieval mode, and result counts. They do not contain raw tool input or memory content.
- Use an absolute binary path.
- Confirm the binary is executable.
- Run
doctorwith the same arguments used by the client. - Validate the client configuration as JSON and restart the client.
- Check stderr or the configured JSON log file.
The call failed its JSON Schema contract, often because of an unknown field,
invalid UUID, timestamp without an offset, invalid project slug, or value over a
documented limit. Let the client refresh tools/list and retry with the declared
schema.
The JSON shape was valid but a domain rule failed, such as blank text, reversed time bounds, or an invalid state transition.
Only one focus session may be active. End the returned session ID before starting another.
An idempotency key was reused for the same tool with different arguments. Reuse the key only for an exact retry, or send a new key for a new operation.
The memory resembles authentication material. Store it in a password manager, not ThreadKeeper.
Check that the endpoint is reachable, the model is installed, and the configured dimensions match. Remove embedding configuration to return to keyword-only retrieval.
Run threadkeeper daemon status against the same database. On macOS, verify the
notification permission, LaunchAgent state, missed-reminder grace, and daemon
stderr. The stdio server does not deliver OS notifications by itself.
Normal stdio servers exit when their client closes stdin. The MCP client should launch it again when needed.
- Canonical data remains in the configured local SQLite database.
- Outbound network access occurs only for explicitly configured embedding or candidate providers and opt-in bookmark metadata fetches. Local HTTP remains on the loopback interface.
- Logs and traces contain metadata and hashes, not raw inputs.
- Normal resources exclude sensitive memories.
- The database is not application-encrypted; use OS disk encryption and account security.
- V2B is single-user and local-only: no non-loopback HTTP, accounts, OAuth, cloud sync, or calendar/email integration. Both HTTP listeners bind loopback and use distinct credentials.
- Reminder recurrence supports none, daily, and weekly rules. New reminders may carry an IANA zone so local wall time survives daylight-saving changes; old fixed-offset reminders remain compatible.
Run the complete cross-session demo against a new database path:
bash scripts/demo.sh ./threadkeeper /tmp/threadkeeper-demo.dbIt captures an idea, creates and completes a task, runs focus, saves context, records a decision, stores and recalls memory in a new process, corrects and forgets memory, and prints content-free traces.
Release checks:
go test ./...
go vet ./...
go test -race ./...
go run ./cmd/evalFor architecture, protocol details, the memory model, security boundaries, and release evidence, continue with architecture, protocol, memory model, threat model, V1 acceptance, and V1.1 acceptance.