inkentry memory guide

How inkentry's project memory works: kinds, supersede chains, point-in-time queries, cross-project visibility, harvesting from git history, and git-notes storage.

inkentry memory is a per-project knowledge store. Use it to capture decisions, context, requirements, questions, and handoff notes that would otherwise live only in chat history or someone's head.

Code tells you what the system does. Memory tells you why it was built that way.


Storage

Memory entries are stored in a local SQLite database by default, and, with store_in_git_notes enabled (the default), also written through to refs/notes/inkentry on HEAD, so they travel with the repository. No external database or server is required.

Memory is scoped to a local project. Run inkentry init once per repository to create its .inkentry/ store; in a directory with no .inkentry/ project, inkentry memory and inkentry context fail closed with a no inkentry project here error rather than reading or writing a machine-global store. An explicit --db <path> overrides this.

  • Use --backend git-notes to make git-notes the primary backend instead of SQLite.
  • Point server_url at a shared inkentry-server to share memory across a team (see server setup).
  • Entries are searchable by full text at all times; semantic search (by meaning) is available when a server is running, and the local one is auto-started on demand.

The auto-started local inkentry-server (loopback) is used only for inference (embeddings and LLM for semantic search): it does not store memory. Your memory lives on a server only when you explicitly configure a team server_url.

git-notes write-through: when store_in_git_notes is true (the default), inkentry memory add also appends the entry to refs/notes/inkentry on HEAD. Failure to write the note is non-fatal: a warning is logged and the primary SQLite write is unaffected. Outside a git repo this is a graceful no-op.


Memory kinds

KindUse for
decisionArchitecture or design choices with rationale
contextBackground information that helps understand the codebase
requirementProduct or technical requirements
noteGeneral observations (default)
questionOpen questions that need an answer
answerAnswers to previously stored questions
handoffState transfer between work sessions or agents
intentActive work signal; surfaced by inkentry context with file-overlap warnings
antipatternThings to avoid; list with inkentry memory failures

These nine kinds are the whole set; an unrecognized --kind is rejected before any store is opened. inkentry memory failures is a shortcut for inkentry memory list --kind antipattern.


Adding entries

# Quick note with body inline
inkentry memory add --title "Chunker uses a token-window fallback" \
              --body "This applies to unsupported file types and oversized nodes." \
              --kind context \
              --tags chunker,indexer

# Open your $EDITOR for the body (omit --body)
inkentry memory add --title "Decision: use blake3 for file hashing" --kind decision

# Link to specific files
inkentry memory add --title "Auth middleware refactored" \
              --body "Moved session validation to src/auth/middleware.rs" \
              --files "src/auth/middleware.rs,src/auth/session.rs"

When --body is omitted, inkentry opens $VISUAL or $EDITOR (falling back to vi). Lines starting with # are stripped (comment convention).

From a URL

--from-url fetches content from a GitHub issue, Linear ticket, or any web page and stores it as a memory entry. The title is inferred from the page automatically.

# GitHub issue - uses `gh api` for clean structured content
inkentry memory add --from-url https://github.com/owner/repo/issues/42

# Override the inferred title
inkentry memory add --from-url https://github.com/owner/repo/issues/42 \
              --title "Auth: session token storage compliance issue" \
              --kind requirement

# Any URL - fetches page title and strips HTML
inkentry memory add --from-url https://linear.app/myteam/issue/ENG-1234/... \
              --kind context

For GitHub issues, inkentry calls gh api to get structured issue data (requires the GitHub CLI and gh auth login). For all other URLs it does an HTTP GET and extracts readable text.

For non-GitHub URLs, you can opt in to custom extraction: if a script exists at ~/.config/inkentry/scripts/web-to-md.ts, inkentry runs it under bun and uses its stdout instead of the built-in HTML-stripping fallback (useful for sites that need JS rendering). The script only runs if you have placed it at that exact, inkentry-owned path; set INKENTRY_SCRIPTS_DIR to look elsewhere. If bun or the script fails, inkentry silently falls back to the built-in extraction.


Supersede chains and point-in-time queries

Every memory entry has a valid_at timestamp (when it became true) and an optional invalid_at timestamp (when it stopped being true).

# Record when a decision became valid (ISO 8601)
inkentry memory add --title "Adopted monorepo layout" --kind decision \
              --valid-at 2026-01-15

# Record a replacement and the supersedes edge in one step: archives the old
# entry, sets its invalid_at, and links the new one
inkentry memory add --title "New auth approach" --kind decision --body "..." \
              --supersedes <old-id>

# Link two entries that already exist: archive <old-id>, edge it to <new-id>
inkentry memory supersede <old-id> <new-id>

# Mark two entries as related - creates a relates_to edge (no archiving)
inkentry memory add --title "Follow-up note" --kind note --body "..." \
              --relates-to <other-id>

Use a supersede edge whenever an earlier decision changes: it keeps a traceable chain of reasoning. Use --relates-to for non-superseding connections (a follow-up note, a related observation). A third edge kind, contradicts, is recorded automatically when the server detects a conflicting entry (see server setup); it surfaces in inkentry memory show and inkentry memory graph alongside the others.

valid_at / invalid_at make it possible to reconstruct what was known at any past date:

# Only entries that were valid at this date
inkentry memory list --as-of 2026-01-01
inkentry memory search "auth decisions" --as-of 2026-01-01

This is useful for post-mortems or understanding old decisions in the context they were made.


Searching, listing, and showing

# Semantic search - finds entries by meaning (hybrid is the default)
inkentry memory search "why did we choose sqlite"
inkentry memory search "authentication decisions" --limit 5

# Also surface 1-hop relates_to neighbours of each result
inkentry memory search "authentication decisions" --expand-graph

# Search mode: hybrid (default), semantic, text
inkentry memory search "auth" --mode semantic
inkentry memory search "auth" --mode text

# List recent entries (newest first)
inkentry memory list
inkentry memory list --kind decision
inkentry memory list --limit 50

# Include archived entries; filter by commit SHA (exact or prefix)
inkentry memory list --archived
inkentry memory list --source-ref abc1234

# Show a single entry - full body plus relationship edges
inkentry memory show 42
inkentry memory show 42 --format json

question and answer entries show titles only in list view to avoid context saturation. Use inkentry memory show <id> to read the full body.


Tracking topic evolution

inkentry memory timeline returns all entries related to a topic, sorted by the time they became valid, useful for understanding how a decision or understanding evolved.

inkentry memory timeline "authentication strategy"
inkentry memory timeline "database choice" --limit 30
inkentry memory timeline "auth" --format json

Relationship graph

# Show all edges for an entry (text)
inkentry memory graph 42

# Machine-readable
inkentry memory graph 42 --format json

memory graph and --expand-graph surface supersedes, relates_to, and contradicts edges with linked entry titles.


Cross-project visibility

When projects are linked with inkentry link, inkentry memory search, inkentry memory list, and inkentry context automatically surface relevant memory from linked projects alongside local results. This is how a settled decision recorded in one project remains visible to agents working in a sibling project.

Not all memory propagates. Only entries that match all three of the following criteria are surfaced from a linked project:

  • Kind: decision or requirement (never handoff, question, or note).
  • Tag: must carry the tag locked (for settled decisions) or cross-project (for cross-cutting items). Tags like auth or database alone are not enough.
  • Status: active only. Archived or superseded cross-project entries do not resurface after they are retracted in the source project.

Every result from a linked project is labelled with its origin: a [from: <project>] badge in text output, and source_project / source_project_path fields in JSON. Local results always appear first; cross-project results are appended and are not counted against --limit.

# Tag a decision as locked so linked projects can see it
inkentry memory add --kind decision \
  --title "SSE memory stream is server-owned" \
  --body "Clients must not assume a local SSE surface." \
  --tags v1,locked

# Skip the cross-project dep pass entirely
inkentry memory search "auth decisions" --local-only
inkentry memory list --kind decision --local-only
inkentry context --local-only

The dep pass reads each linked project's memory.db directly from disk (local SQLite only). It does not route through inkentry-server or any remote endpoint, so a linked project's memory is only reachable when its memory.db file is on the local filesystem.


Harvesting from git history

inkentry harvest reads your git log, sends commit messages to the LLM, and automatically extracts significant entries. It is a top-level command. Harvesting requires a reachable inkentry-server with a chat endpoint configured; there is no local-model path, and llm_model in ~/.config/inkentry/config.toml has no effect on it.

# Default: last 10 commits
inkentry harvest

# Custom range, or an entire branch
inkentry harvest --git-range HEAD~30..HEAD
inkentry harvest --git-range v1.0..HEAD
inkentry harvest --branch feature/auth

# Source: git log (default), Claude Code session history, or antipatterns
inkentry harvest --source git
inkentry harvest --source claude-code --confirm
inkentry harvest --source failures

Reading the Claude Code history file requires --confirm. Already-harvested commits are skipped (tracked via a git:<sha> tag). Routine commits ("fix typo", "wip", and so on) are ignored by the LLM.

inkentry memory harvest still works as a hidden, deprecated alias for one release; it prints a warning and runs inkentry harvest.

Automatic harvesting

Install the git hook and harvesting happens on every commit:

inkentry hooks install

This writes a post-commit hook that runs inkentry index and inkentry harvest after each commit (both --detach, so git is not blocked).


Syncing with a team server

Once a team server_url is configured, keep local and shared memory in step. The everyday path is inkentry sync; the one-way seeding primitives live under plumbing:

# Two-way: push local and pull remote (also available as `inkentry memory sync`)
inkentry sync

# One-way: seed the server from local entries
inkentry plumbing push

# One-way: pull new server entries into the local memory.db
inkentry plumbing pull

In the default local_first mode you rarely run inkentry sync by hand. Your writes commit to the local memory.db immediately and never block on the network; from an interactive terminal a background reconciler then drains what you recorded up to the server and pulls teammates' entries down, so the shared memory converges on its own. inkentry sync is the explicit escape hatch for when you want that reconcile to happen synchronously now rather than in the background, such as a CI job that needs entries pushed before it exits. Code never travels; only memory does. Archived entries are skipped by default; pass --include-archived to propagate tombstones. Which server (if any) reads and writes go to is governed by the mode config (offline / local_first / cloud_first); see the config reference. See server setup for configuring server_url and server_key.

Importing from a local server database

inkentry memory reconcile imports notes recorded by a running inkentry-server daemon into the project's local memory.db. Dedup is by content hash, and the source database is opened read-only.

# Import notes for the active project (default source: ~/.local/state/inkentry/server.db)
inkentry memory reconcile

# Preview what would be imported without writing anything
inkentry memory reconcile --dry-run

# Import notes for all projects found in the source database
inkentry memory reconcile --all-projects

# Override the source path
inkentry memory reconcile --source-db /var/run/inkentry/server.db

Entry identity and deduplication

Every entry has a content identity: a hash over exactly its kind, title, and body. Two people who independently record the same decision in two clones arrive at the same identity with no coordination, so inkentry memory list and inkentry context fold duplicate copies as they read, and a decision recorded twice shows once. Tags and linked files from the copies are merged onto the survivor, so nothing recorded on a folded copy is lost.

A memory.db that predates this, or that gathered entries from more than one machine, can hold rows that share an identity but differ in creation time, tags, or status. These are harmless and left in place, but you can collapse them explicitly:

inkentry memory dedupe --dry-run   # preview what would be collapsed
inkentry memory dedupe             # collapse duplicate groups

For each group, the earliest-created row survives; the others are removed after their tags and linked files are merged in, and the entry is archived if any copy was. It runs as a single transaction, so a failure part-way leaves memory.db exactly as it was. Deleting the losing rows is destructive, so back up memory.db (or your git-notes ref) first if you want to be able to undo it.

If entries are missing their local embeddings - after the embedder was down, or after a database migration - inkentry memory reindex backfills them (add --dry-run to preview, --force to recompute existing ones).

Once a store has no duplicates, a plain inkentry memory add for byte-identical kind/title/body no longer inserts a second row: it reuses the existing entry (merging in the new call's tags and linked files) and prints Already recorded as ... instead of Stored ....


Machine-readable output

All memory commands support --format json, and setting AGENT=true forces JSON mode globally:

AGENT=true inkentry memory list --kind question
AGENT=true inkentry memory search "database decisions"

inkentry plumbing read-memory emits memory entries as JSONL for scripting.


Tips

  • Store the "why", not just the "what" - the code already captures what was built.
  • Use question kind actively - when you hit a decision point you are unsure about, store it. Come back with inkentry memory list --kind question at the start of the next session.
  • Use handoff kind at the end of a long session to summarise the current state for your next session (or for another agent).
  • Tag entries - tags like auth, database, performance make inkentry memory list more scannable and improve search relevance.
  • Record a supersede edge when updating a decision - it archives the old entry, sets its invalidation time, and creates a traceable edge so you can always follow the chain of reasoning.
  • Use --relates-to for non-superseding connections.
  • Use --as-of for archaeology.

What's next

On this page