Use this file to discover all available pages before exploring further.
FlowState accumulates knowledge across pipeline runs in a local SQLite database called memory.db. Research findings, strategy assessments, interview decisions, and failure logs are all stored as searchable entries. Before each bridge call, the adapter queries the store and injects any relevant prior knowledge directly into the prompt — so the more times you run the pipeline on a project, the richer the context Claude receives.
The memory system automatically captures five categories of information, distinguished by a MemoryKind enum:
# flowstate/memory.pyclass MemoryKind(StrEnum): RESEARCH = "research" # findings from the Research step STRATEGY = "strategy" # assessments from the Strategy step DECISION = "decision" # interview answers and GSD planning outputs TOOL_RUN = "tool_run" # execution logs, including failures INSIGHT = "insight" # catch-all for other adapter outputs
research
Findings from every research topic. Each ## heading in research/report.md becomes a separate, independently searchable entry.
strategy
The pressure-test assessment from research/strategy.md, also split by headings for fine-grained retrieval.
decision
Interview answers stored at pipeline start, plus GSD planning outputs. Captures the why behind architectural choices.
tool_run
Failure logs from any blocked step. Stored so future runs can reference what went wrong and why.
insight
Catch-all kind for adapter outputs that don’t map to a specific tool. Available for custom integrations that extend the pipeline.
The orchestrator wires together a MemoryStore and an EventBus at the start of every pipeline run. Memory handlers listen for domain events and store artifacts automatically — no adapter has to call the store directly after a successful step.
Before the pipeline even begins its five steps, the orchestrator stores the interview answers as a decision memory. This means future strategy and research calls can retrieve the project’s core problem and architectural decisions as prior context:
Before each bridge call, the adapter calls memory.get_context(topic). If relevant memories exist, the result is a ## Prior Knowledge Markdown section that gets prepended to the prompt:
# flowstate/memory.pydef get_context(self, query: str, *, max_tokens: int = 2000) -> str: results = self.search(query, limit=10) if not results: return "" char_budget = max_tokens * 4 lines = ["## Prior Knowledge\n"] used = len(lines[0]) for sr in results: entry = sr.entry header = f"### {entry.summary} ({entry.kind.value})\n" body = entry.content.strip() block = header + body + "\n\n" if used + len(block) > char_budget: remaining = char_budget - used - len(header) - 10 if remaining > 100: lines.append(header + body[:remaining] + "...\n\n") break lines.append(block) used += len(block) return "".join(lines)
Results are ranked by BM25 relevance and truncated to a token budget (default 2,000 tokens, approximated as 4 characters per token) before injection. The section is empty if no relevant memories are found, so prompts are never padded with irrelevant context.
All memory operations go through MemoryStore. It is a context manager, but the orchestrator also calls memory.close() explicitly at the end of the pipeline.
from flowstate.memory import MemoryStore, MemoryEntry, MemoryKind# Open / create the store (creates memory.db in the project root)store = MemoryStore(root=Path("/my/project"))# or use as a context managerwith MemoryStore(root=Path("/my/project")) as store: ...
# Full-text BM25 search across all kindsresults: list[SearchResult] = store.search("kafka streams", limit=10)# Filter by kindresults = store.search("failure", kind=MemoryKind.TOOL_RUN, limit=5)for sr in results: print(sr.score, sr.entry.summary, sr.entry.content[:200])
# Fetch a single entry by its ID (returns None if not found)entry: MemoryEntry | None = store.get("abc123def456")# Fetch the most recent entries of a given kindentries: list[MemoryEntry] = store.get_by_kind(MemoryKind.RESEARCH, limit=20)
memory.db is a single SQLite file created in the project root the first time MemoryStore is instantiated. It is gitignored by default and fully portable — copy it to another machine and searches work immediately.
The full-text index uses SQLite’s FTS5 extension with the porter unicode61 tokenizer:
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5( summary, content, tags, content=memories, content_rowid=rowid, tokenize='porter unicode61');
Porter stemming means morphological variants match automatically: searching for "streaming" also matches entries that contain "streams" or "streamed". Search ranking uses BM25, SQLite’s built-in rank column on FTS5 queries.Triggers keep the FTS index in sync with the memories table automatically — inserts, updates, and deletes are all reflected immediately.
sqlite3 memory.db-- Show all memory kinds and countsSELECT kind, COUNT(*) as n FROM memories GROUP BY kind;-- Full-text searchSELECT summary, kind, created_atFROM memories_ftsJOIN memories ON memories.rowid = memories_fts.rowidWHERE memories_fts MATCH 'kafka streams'ORDER BY rankLIMIT 10;
The sqlite-vec Python package is listed as a dependency to enable future vector/embedding search. In v1, it is installed but dormant — the memories_fts FTS5 table handles all search. Vector search will be opt-in in a future release.
FlowState exposes memory operations as first-class CLI commands:
# Search stored knowledge with BM25 rankingflowstate memory search "kafka streams"# Show counts grouped by kindflowstate memory stats# Clear all memories (prompts for confirmation)flowstate memory clear
flowstate memory clear is irreversible. All stored research, strategy, and decision memories are deleted from memory.db. The file itself remains on disk.