Model or dataset
thecodacus/understory avatar
thecodacus/understory

Understory: plain-markdown memory for AI agents, wired into a living graph

understory — memory that grows. Self-wiring, plain-markdown memory for AI agents (MCP + local models + a living graph).

323 stars74 forksTypeScriptApache-2.0

At a glance

What is it?
Understory is an Apache-2.0 MCP server and web UI that files what your agents learn as markdown concept files in the Open Knowledge Format, cross-links them into a graph, and enforces bundle conformance in code rather than prompts. Any OpenAI-compatible or Anthropic-compatible endpoint can drive it, including a local llama.cpp server.
Who is it for?
Understory fits agent builders who want memory as portable, git-diffable markdown and are comfortable pointing it at their own LLM endpoint, including a local llama-server with the jinja flag for tool calling. It does not fit deployments that cannot spend an inference call per memory operation, since every tool call drives an internal agent, and it does not fit teams needing a battle-tested release line, because no releases are published yet.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

Memory that stays in your git repository

Agent memory usually ends up in a store you cannot read, diff or leave. Understory, by thecodacus, takes the opposite bet: it calls itself memory that grows, a self-wiring, plain-markdown memory layer beneath your agents. Every fact an agent learns is filed as a markdown concept, cross-linked into a living knowledge graph, and kept healthy by the agent itself. The README's pitch is that the result stays searchable, diffable and entirely yours, and that it runs well on local models.

The storage format is not invented here. Bundles follow the Open Knowledge Format (OKF) v0.1 spec: plain markdown files with YAML frontmatter, readable by humans, diffable in git, and portable across tools. That choice matters more than any feature list, because it means your agent's memory is a directory of text files you can back up, review in a pull request, or walk away with.

Three ways in, one agent

The system exposes one internal LLM agent through three doors. The first is an MCP server with five tools, memory_query, memory_add, memory_update, memory_status and memory_maintain, served over stdio or streamable HTTP, and each call drives that internal agent with the OKF spec in its system prompt. The second is a web UI: you browse the bundle as a tree with a concept viewer, an update log and a conformance badge, see the memory as an Obsidian-style force-directed graph that you can drag, pan and zoom, with nodes colored by type, sized by connections and orphans ringed in red, and chat with the same agent to test it while its tool calls render inline.

The third door is the one that rewards trust-building. Every agent run, whether a query, a mutation or a chat, records its traversal of searches, reads and writes as a compact notation under a .traces directory in the bundle. The graph view lists recent runs, and selecting one replays the path as numbered directed hops over the graph, with visited concepts ringed and search hits dotted. For a memory you are supposed to rely on, being able to watch exactly how an answer was assembled is the difference between a database and a black box.

Conformance is enforced in code, not prompts

The design rule the README states in bold terms is that conformance lives in code, not prompts. A deterministic bundle layer validates frontmatter, requiring the type field, regenerates index.md files, appends entries to log.md newest-first according to section 7 of the spec, and sandboxes every path to the bundle root.

The division of labour is the point. The LLM decides what to change; the code guarantees the result is a conformant bundle. An agent can hallucinate a file name or forget a frontmatter field, and the layer beneath it still refuses to produce a malformed bundle. For infrastructure meant to accumulate value over months of agent sessions, that is the load-bearing decision in the whole project.

Quick start with Docker

No clone is needed because the image is public. The README's compose file points at ghcr.io/thecodacus/understory:latest, exposes port 3800, mounts a volume at /bundle, and passes the provider configuration through environment variables, with BUNDLE_ROOT set to /bundle and an extra_hosts entry so the container can reach a llama.cpp server on the host. The first nine lines:

code
services:
  understory:
    image: ghcr.io/thecodacus/understory:latest
    ports:
      - "3800:3800"
    # Lets the container reach a llama.cpp server running on the host via
    # http://host.docker.internal:8080/v1 (see "Local llama.cpp" below).
    extra_hosts:
      - "host.docker.internal:host-gateway"

Then bring the stack up:

bash
docker compose up -d

The web UI appears at http://localhost:3800, and the MCP endpoint waits at http://localhost:3800/mcp over streamable HTTP. Registering it with an MCP client is one documented command:

bash
claude mcp add --transport http ustory http://localhost:3800/mcp

Your agent now has the five memory tools and, the README notes, receives a seed overview of the memory at every session start. The suggested first move is to teach it something with memory_add, the example being that deployments happen on Fridays and never Mondays, then open the graph and watch the concept wire itself in. Portainer users can load the repository's docker-compose.portainer.yml as a stack.

Providers: cloud APIs or a local llama-server

The provider system accepts any OpenAI-compatible or Anthropic-compatible API. You set LLM_API_BASE_URL, LLM_API_KEY and LLM_MODEL and leave LLM_PROVIDER unset; the format switch LLM_API_FORMAT takes openai or anthropic. The README gives one-line examples for DeepSeek, OpenAI, Anthropic and Groq, and a local setup that needs only the base URL:

bash
LLM_API_BASE_URL=http://host.docker.internal:8080/v1 LLM_MODEL=

One Docker detail trips people up, and the README calls it out: inside a container, localhost is the container itself, so a llama-server on the host is reached at host.docker.internal, which the compose file maps via extra_hosts; running from source on the same box, plain localhost works. An optional fallback chain uses the matching LLM_FALLBACK variables, so a local model can defer to a hosted one, and the old per-provider environment variables still work but are deprecated.

For llama.cpp specifically, the server must run with the jinja flag to enable OpenAI-style tool calling:

bash
llama-server -m model.gguf --jinja --host 0.0.0.0 --port 8080

No model id is needed in understory's configuration because it discovers the model for llama-server-like endpoints, and behind llama-swap the discovery prefers the currently loaded model, so a query does not trigger a multi-minute model swap; a specific model can be pinned with LLM_MODEL.

From source: three packages and a thin seam

The repository is a pnpm monorepo with three packages, and the split mirrors the design rule. packages/core holds the OKF bundle layer, which involves zero LLM calls, plus the agent loop built on the Vercel AI SDK with search, read, list, write, patch and delete tools, and the provider registry. packages/server is an Express app exposing MCP over streamable HTTP at /mcp, a stdio binary, a REST browse API, streaming chat, and the built web UI. packages/web is a Vite, React and TypeScript frontend with Tailwind for the bundle browser and the agent chat.

Running from source follows the README's block:

bash
pnpm install
pnpm build
cp .env.example .env   # add your API key

BUNDLE_ROOT=./sample-bundle \
LLM_API_BASE_URL=https://api.deepseek.com/v1 \
LLM_API_KEY=sk-... \
LLM_API_FORMAT=openai \
LLM_MODEL=deepseek-chat \
node packages/server/dist/index.js

The server then serves the web UI, the REST API and the MCP endpoint on port 3800. A sample-bundle directory ships in the repository, which gives you something conformant to point BUNDLE_ROOT at before you have memories of your own.

What to weigh before adopting

The costs are as explicit as the benefits. Every memory operation drives an internal LLM agent, so each add, update or query consumes tokens against whatever provider you configured; the README's answer is that the system runs well on local models, and the fallback chain lets a local model carry routine traffic with a hosted one behind it. The bundle format is pinned to version 0.1 of the OKF spec, an early revision, so expect the format to evolve. And the repository has published no GitHub releases so far, with the last push on 2026-09-13, which makes pinning the image digest rather than the latest tag the careful choice.

None of these are hidden; they are the visible edges of a design that puts everything in plain files and makes the agent do the filing. If your agents need memory you can read, diff and take with you, the trade is a good one. If you need memory that never costs an inference call, this is not that project.

Editorial conclusion

Understory fits agent builders who want memory as portable, git-diffable markdown and are comfortable pointing it at their own LLM endpoint, including a local llama-server with the jinja flag for tool calling. It does not fit deployments that cannot spend an inference call per memory operation, since every tool call drives an internal agent, and it does not fit teams needing a battle-tested release line, because no releases are published yet. Before adopting, run docker compose up with the sample bundle, try one memory_add and replay its trace in the graph view, and pin your image digest. The last push was on 2026-09-13 under the Apache-2.0 licence.

Frequently asked questions

Does Understory need a cloud LLM?

No. The provider layer accepts any OpenAI-compatible or Anthropic-compatible endpoint, including a llama.cpp server on your own hardware, and an optional fallback provider can sit behind a local model.

Where does Understory store memories?

As plain markdown files with YAML frontmatter inside a bundle directory mounted at /bundle in Docker. The files follow the Open Knowledge Format, so they stay readable, diffable in git and portable across tools.

How do my agents talk to Understory?

Through five MCP tools, memory_query, memory_add, memory_update, memory_status and memory_maintain, exposed over stdio or streamable HTTP at the /mcp endpoint.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. thecodacus/understory on GitHub
Community notes

Community notes