PRAANA Architecture
PRAANA is a single-process TypeScript CLI coding agent built around two memory systems:
- Adaptive Context — within-session working memory
- Cognitive Memory — learns from experience across sessions, scores what's confirmed, forgets what's noise
No daemon, no RPC server, no multi-process coordination.
Directory Structure
src/
main.ts — CLI entry point, TTY guard, slash commands, pi-tui launch
turn.ts — Per-turn orchestration (prompt → LLM → tools → banners)
session.ts — Session lifecycle (create/resume/end) & memory init
compile-classic.ts — Classic-mode prompt assembly (full verbatim history)
compiler.ts — Legacy budget-band compiler (unit tests only)
state-graph.ts — Tiered state management (active/soft/hard) & two-pass auto-hydrate (substring + BM25)
state-graph-checkpoint.ts — O(1) resume: persist/restore StateGraph snapshot (tiers, touchedTurn) after each turn
event-log.ts — Append-only JSONL event persistence with fsyncSync durability; in-memory parse cache (re-reads disk only on mtime/size change)
token-estimate.ts — Unicode-aware token heuristic (Latin/CJK/emoji/ZWJ); canonical shared estimator for all budget calculations
context-pressure.ts — Density-weighted effective-token accounting; raw-token safety net; resolves compaction config
cosine-similarity.ts — Cosine similarity for Float32Array embedding vectors; shared by context scoring and memory dedup
hash.ts — SHA-256 hashing utility; shared by session scope construction and skill scope keys
model-resolver.ts — /model parsing and provider+model resolution
provider-catalog.ts — live /models fetch + 6h cache for OpenAI-compatible providers
llm.ts — Provider registry and model building via pi-ai
config.ts — Multi-source JSON/TOML config loading & deep-merge
types.ts — Core shared TypeScript types
ui.ts — CLI output formatting, banners, and text colors
utils/
bm25.ts — Shared BM25 tokenization, scoring, and corpus statistics
context-engine/
index.ts — ContextEngine facade (store, ledger, extraction, checkpoint, telemetry, workflow patterns)
types.ts — Engine-level interfaces; WorkflowPattern
artifact-store.ts — Content-addressed blob store with distiller integration
turn-ledger.ts — Append-only typed turn records with BM25 search
distiller.ts — DistillerRegistry, sync/deferred dispatch, intensity selection
classify.ts — Fast regex-based content-type classification
summarize.ts — Generic head/tail summarisation; re-exports estimateTokens from token-estimate.ts
extraction.ts — Post-turn deterministic extraction (TurnDigest, activity, errors)
turn-digest.ts — TurnDigest extraction and state-graph diffing
activity-log.ts — ActivityEntry derivation and rolling buffer
error-tracker.ts — Open error tracking and test-pass/fail detection
checkpoint.ts — SessionCheckpoint reconciliation, rendering, replay from digests
state-snapshot.ts — StateGraph snapshot and diff for decision/constraint extraction
scoring.ts — Context-unit scoring (pin + recency + max(BM25, semantic) + hydrate boost) and budget selection
embedding-cache.ts — Per-turn embedding cache; precomputes vectors for all context units concurrently; invalidated when unit set changes between turns
engine-compiler.ts — Multi-resolution budget-band compiler; injects workflow context section when matching patterns exist
bm25.ts — Re-exports shared BM25 utilities for context-unit and turn-search scoring
density.ts — SectionDensityKind → weight table; densityWeight() used by engine-compiler for weighted pressure
turn-recorder.ts — Per-turn event recording for the turn ledger
event-lineage.ts — Trace artifacts back to producing turns, decisions, files
telemetry.ts — Per-session telemetry: pressure events, retrieval rates, distiller savings
workflow-tracker.ts — Workflow pattern tracking: extract tool sequences + artifact types, persist to workflow_patterns table, render compact Workflow Context section
db.ts — SQLite schema for all engine tables: artifacts, turns, checkpoint, scorecard, workflow_patterns
distillers/
index.ts — Default distiller registry factory
git-diff.ts — Diff hunk compaction with context reduction
npm-test.ts — Test output: collect failures, summarise passes
tsc-errors.ts — Build error deduplication
rg-results.ts — Search result dedup and top-k
generic.ts — Log template mining + generic head/tail
tools/
index.ts — Tool registry (all tool definitions combined)
tool-def.ts — Type helper for defining tools
system.ts — shell, read_file, write_file, edit_file, read_and_summarize, batch_write, batch_edit
search-code.ts — search_code: ripgrep-backed structured code search (rg --json → file:line:column matches with context)
knowledge.ts — recall, remember, retrieve_artifact, context_summary, search_turn_events, event_lineage
memory.ts — Adaptive Context state-graph tools (tasks, decisions, constraints, notes)
memory/
index.ts — Memory store exports
store.ts — High-level MemoryStore API (remember, recall, session start/end); isSessionGood export
db.ts — SQLite schema: entries, entry_scopes, sessions, entries_vec, skill_stats, skill_cooccurrence
embeddings.ts — Ollama embedder adapter (optional, opt-in)
types.ts — Memory-specific types
summarizer.ts — LLM-based extraction logic (conversation transcript → learnings JSON)
openai-summarizer.ts — Chat completion fetch adapter for summarization
skills/
index.ts — SkillRuntime: discovery, load tracking, telemetry (engine mode); buildSkillMetadataCatalog with usefulness-ranked ordering
skill-stats-store.ts — Cross-session skill effectiveness: loadUsefulness (dual-scope), flush (boost/decay), co-occurrence recording
types.ts — SkillRecord (with scope), LoadedSkill (with used flag), SkillEffect, telemetry types
Runtime Architecture (Turn Flow)
User input
→ main.ts interactive TTY entrypoint
→ runTurn(session, input) in turn.ts
1) append user_message event
2) select mode:
Engine: context_engine.enabled=true AND ContextEngine initialized (default)
Classic: context engine disabled, or enabled but init failed
3) [engine only] auto-hydrate matching peripheral state (two-pass: substring keyword + BM25 relevance)
4) [engine only] capture StateGraph snapshot (for post-turn diff)
5) [engine only] ingest tool results through distillers; classic passes through
6) compile prompt:
Engine: compileEngineWithMetrics (budget bands + scored units + checkpoint)
Classic: compileClassicWithMetrics (system frame + skill catalog + full history)
7) piStream() with tools (tool set varies by mode — see Tools)
8) log tool_call / tool_result + context_action events
9) append agent_message event
10) [engine only] flush deferred distillations, append turn record to ledger
11) [engine only] extract TurnDigest (deterministic, no LLM)
12) [engine only] reconcile SessionCheckpoint from digest
13) increment turn count
14) [engine only] apply tier demotion rules + cleanupStaleSkills()
15) persist state graph checkpoint (session.persistStateGraphCheckpoint())
16) print per-turn memory/prompt banner
Session Boundary Flow
SESSION START:
Session.create()
→ init EventLog + StateGraph
→ init MemoryStore (if enabled)
→ memory.sessionStart(context)
→ cache digest markdown
SESSION RESUME:
Session.resume(sessionId)
→ load state_graph_checkpoint.json if present → O(1) StateGraph restore (tiers + touchedTurn preserved)
→ replay only context_action events that occurred after the checkpoint's last_event_id
→ restore model override from the last model_override system_note
→ re-init MemoryStore + regenerate digest
SESSION END:
Session.end(reason, transcriptEvents)
→ memory.sessionEnd(reason, events)
→ if summarizer enabled: extractLearnings from transcript events
→ write learnings into Cognitive Memory
→ close event log
State Model (Adaptive Context)
Defined in src/types.ts:
export type StateObjectKind = "task" | "decision" | "constraint" | "note";
export type StateTier = "active" | "soft" | "hard";
export interface StateObject {
id: string; // ULID
kind: StateObjectKind;
tier: StateTier;
payload: StatePayload; // TaskPayload | DecisionPayload | ConstraintPayload | NotePayload
created: number; // unix ms
updated: number; // unix ms
lastTouched: number; // unix ms
}
Tier Behavior in Prompts
| Tier | Prompt Representation | When |
|---|---|---|
| Active | Full payload | Current working set |
| Soft | Summarized one-liner | Recently idle |
| Hard | Minimal anchor (ID) | Older context, available via hydrate |
Tier Demotion
Managed dynamically after each turn in src/turn.ts:
active→softafteridle_soft_after_turns(default: 20 turns since last touched)active/soft→hardafteridle_hard_after_turns(default: 50 turns since last touched)
Auto-Hydration
Before a turn prompt is compiled, stateGraph.autoHydrate(userInput) runs two passes against peripheral state objects:
- Substring keyword match — extracts keywords (alphanumeric strings $\ge 3$ characters, filtering common English stop words) and matches them against the searchable text representation of peripheral objects. Fast and catches exact references.
- BM25 relevance — scores remaining peripheral objects with
bm25Relevance(query, text). Objects scoring $\ge 0.15$ (theBM25_HYDRATE_THRESHOLD) are promoted even when no substring keywords matched. This catches fuzzy overlap that substring matching misses — for example, when a user mentions "S3 upload" and a peripheral object about "AWS bucket" gets promoted.
Returns AutoHydrateResult[] with { id, text, method } (method is "substring" or "bm25"). The text field is passed through to context-unit scoring for hydrate_boost calculation (see Scoring below). Promotions are logged as context_action events with reason: "auto_hydrate" and hydrate_method: "substring" | "bm25".
Event Log
Location: ~/.praana/sessions/<session_id>/events.jsonl
In-memory cache: After the first read, EventLog maintains a parsed event index in memory. On every subsequent readAll, readLast, search, or replayContextActions call it checks mtime and size via statSync; if unchanged it returns the cached array directly — no JSONL re-parsing. append() updates the cache inline before writing to disk, so the cache never drifts. getLastEvent() is O(1) direct cache access.
Each line is a single JSON line representing an Event:
user_messageagent_messagetool_calltool_resultcontext_actionsystem_note
Writing uses writeSync followed by fsyncSync to guarantee durable persistence on every event. Rebuilding state on session resume replays context_action events.
Agents can search the full log in-session via the search_session_log tool (keyword AND/OR search). This is distinct from recall, which queries cross-session Cognitive Memory in SQLite.
Compiler
PRAANA has two runtime compile paths. Selection happens in turn.ts:
const useEngineCompiler = contextEngineEnabled && !!session.contextEngine;
const classicMode = !useEngineCompiler;
- Engine mode —
context_engine.enabled = trueandContextEngineinitialized successfully at session start. - Classic mode — engine disabled, or enabled but initialization failed (falls back to full classic behaviour).
src/compiler.ts (compileWithMetrics) is retained for unit tests only; it is not used at runtime.
Engine mode (context_engine.enabled = true, engine initialized)
src/context-engine/engine-compiler.ts constructs a multi-resolution prompt from budget bands:
- Band 1 (always): System frame, AGENTS.md, skills — existing allocation.
- Band 2 (always): Active user request + last 2 full turns verbatim — ~3000 tokens.
- Band 3 (always): SessionCheckpoint (pinned) — ~2000 tokens. Reconciled deterministically from
TurnDigestafter each turn. Sections: active request, session narrative, plan history, constraints, decisions (with retained rationale), files, findings, errors, activity. See Session Checkpoint below. - Band 4 (scored): Fact cards, artifact cards, activity entries — ~3000 tokens.
- Band 5 (scored): Older turn digests — ~2000 tokens.
- Band 6 (remaining): Memory digest, low-priority state — remainder.
Context units are scored with a 5-term model: pin_boost × W_pin + recency(age) × W_recency + max(BM25 × W_relevance, semantic_similarity × W_semantic) + hydrate_boost × W_hydrate. BM25 handles exact keyword overlap; embedding cosine similarity handles semantic paraphrases (e.g. "auth middleware" vs "authentication handler"). The effective relevance term is max(bm25, semantic) so either signal can win. The hydrate boost is the maximum bm25Relevance(ht, unitContent) across all auto-hydrated object texts — it gives a lift to context units (e.g. older turn digests) that discuss the same objects the user just implicitly referenced. Default weights: w_pin=1.0, w_recency=0.5, w_relevance=0.3, w_semantic=0.3, w_hydrate_boost=0.2. Embeddings are precomputed per compile pass and cached in a session-scoped LRU cache; when the embedder is unavailable or fails, scoring falls back to BM25-only.
Pressure monitoring uses density-weighted effective tokens: contextPressure = weightedTokens / usableBudget. Each section kind has a hardcoded weight in src/context-engine/density.ts (decisions/constraints/plans = 1.0; open errors = 0.8; narrative/file/peripheral = 0.6; verbatim turns = 0.9; turn digests/artifacts = 0.4; findings/activity/fixed errors = 0.25). Low-density filler counts less toward pressure so a prompt full of error traces does not trigger compaction as aggressively as one full of architectural decisions. >0.70 triggers compaction (drop older digests, tighten checkpoint findings/activity budgets). >0.85 triggers emergency (keep checkpoint decisions/constraints/plans/open errors only — omit findings, activity, fixed errors — plus last 2 verbatim turns and current artifact cards). Raw-token safety net: when estimated raw prompt tokens exceed the usable budget or raw fill exceeds emergency_at, emergency mode is forced even if weighted pressure is lower. Section token caps (findings 300, scored bands 3000/2000) bound worst-case raw size; /stats shows both raw and weighted fill.
Score logging: in debug mode, scores.jsonl records every context unit's score breakdown per turn. Inspect via /why <unit-id>.
Session Checkpoint
src/context-engine/checkpoint.ts maintains a structured within-session summary that survives when turns scroll out of the verbatim window (turns 7+). It is reconciled from TurnDigest data — never by the LLM — to prevent drift.
| Section | Behaviour |
|---|---|
| Active request | Latest user intent (overwritten each turn) |
| Session narrative | Rolling prose from meaningful turns (decisions, constraints, errors, file writes, intent changes). Max 20 entries, 400-token render budget |
| Plan | Current plan + superseded one-liners with completion hints. Extracted from assistant step lists via pattern matching. 400-token budget |
| Constraints | Append-only, never dropped. Includes implicit constraints auto-extracted from user messages |
| Decisions | Summary + rationale; compact after 10 idle turns but rationale is never dropped. 800-token budget |
| Files in play | Evicted after 10 idle turns |
| Findings | Rolling artifact refs, oldest evicted at 30 entries. 300-token budget |
| Errors | Open errors full detail; fixed errors one-liners |
| Activity | Rolling 15 entries from activity log |
Implicit knowledge capture has two layers: (1) the system frame nudges the agent to call add_constraint / add_note for preferences and corrections — this is the primary mechanism because the LLM is the language-understanding component, not regex; (2) extractImplicitConstraints() in turn-digest.ts is a minimal safety net that only captures the syntactically unambiguous "not X, Y" correction pattern. Patterns like "let's use", "we use", "I prefer", "how about" are NOT regex-matched because natural language is too varied — those are the LLM's responsibility via the nudge.
Classic mode (compile-classic.ts)
When classic mode is active, src/compile-classic.ts builds a flat prompt with no token-budget truncation and no Adaptive Context sections:
- System frame — cwd, session ID, tool descriptions, AGENTS.md project context, skills guidance (use
load_skillwhen relevant). - Skills catalog — name + description only;
load_skill(skill_id)loads full body on demand. No pre-injected bodies, no residency tiers. - Cognitive Memory — digest (if enabled).
- Conversation history — full verbatim event log (
readAll()), excludingcontext_actionandsystem_note. Tool results are not truncated. - Current input — latest user message.
Runtime differences from engine mode:
| Engine mode | Classic mode | |
|---|---|---|
| Compiler | compileEngineWithMetrics |
compileClassicWithMetrics |
| History | Budget bands + checkpoint + scored digests | Full verbatim log |
| StateGraph tools | Available | Hidden from tool list |
| Engine tools | Available | Hidden |
| SkillRuntime | Load tracker + eviction (engine only) | Not present (plain agent) |
| Auto-hydrate / tier demotion | Yes | Skipped |
| TurnDigest / checkpoint | Yes | Skipped |
/why scores |
Available (debug) | N/A |
Classic mode matches how agents like Claude Code and pi operate — a growing transcript with no working-memory tiering. Set measurement_mode = true to write engine telemetry while running classic mode (development/debug only).
Legacy compiler (compiler.ts)
The original 5-section budget-band allocator (compileWithMetrics) remains in the tree for regression tests only. It applied per-section token budgets, truncated recent turns, and included Adaptive Context active/peripheral sections. Runtime no longer uses this path.
Tools
Defined in src/tools/ using Zod schemas and normalized via zod-to-json-schema. The registered tool set depends on compile mode (describeTools({ contextEngineEnabled, classicMode })).
Shared tools (classic and engine mode)
search_session_log(query, kinds?, limit?)— keyword search over the current session event log
System Tools (src/tools/system.ts)
shell(command, timeout?)— executes a bash command with timeout (default: 30s); optional sandbox allowlist via[shell]read_file(path, offset?, limit?)— reads a file with optional pagination limitsread_and_summarize(path)— structured file overview (size, line count, head/tail preview)write_file(path, content)— writes or overwrites a file (creates parent directories)edit_file(path, oldText, newText)— replaces text based on exact, unique matching (optional diff preview)batch_write(files[])/batch_edit(edits[])— multi-file create/replace in one turn
Code Search (src/tools/search-code.ts)
search_code(pattern, path?, globs?, max_results?, ...)— ripgrep-backed structured search (rg --json→ file:line:column matches)
Cognitive Memory Tools (src/tools/knowledge.ts)
recall(query, mode?, kinds?)— searches Cognitive Memory and logs amemory_recallsystem noteremember(content, kind?, certainty?, scope?)— writes facts, decisions, preferences, patterns, mistakes, or constraints directly to Cognitive Memoryforget_memory(id)— tombstones a Cognitive Memory entry (retracted = 1). The entry is excluded from future recall and digest, but the row is retained for audit.
Adaptive Context Tools (src/tools/memory.ts — engine mode only)
create_task(title, description?)— creates a task in working memorycomplete_task(id)— marks a task as done, auto-demoting it to thesofttierretract_task(id)— tombstones a state object (any kind)add_constraint(text)— records a constraint rule/limitation in working memory and mirrors it to Cognitive Memory when enabled (not incognito)decide(summary, rationale)— records an architectural/design decision in working memory and mirrors it to Cognitive Memory as adecisionentryadd_note(text)— records a general note in working memory and mirrors it to Cognitive Memory as afactentrysoft_unload(id)/hard_unload(id)/hydrate(id)— tier managementlist_state()— lists all state objects with their IDs, kinds, tiers, and summaries
Context Engine Tools (src/tools/knowledge.ts — engine mode only)
retrieve_artifact(id, options?)— retrieves raw artifact content with optional grep/line/jsonPath selectorscontext_summary()— current checkpoint + open errors + recent activity + session statssearch_turn_events(query)— BM25 search over turn ledgerevent_lineage(artifactId)— trace artifact → producing turn → decisions → related artifacts
Cognitive Memory
Key Components
MemoryStore(src/memory/store.ts): Coordinates high-level Cognitive Memory operations, session starts, and session ends.- SQLite Database (
src/memory/db.ts): Maintains durable sqlite/vec0 tables under~/.praana/memory.db. - Embedder (
src/memory/embeddings.ts+src/memory/embedder-factory.ts+src/memory/transformers-embedder.ts): supportsauto,transformers,transformers-nomic, andollama.autouses Transformers.js (shipped as a dependency); model weights cache in~/.praana/models/. - Summarizer Adapter (
src/memory/openai-summarizer.ts): Adapts chat completions to OpenAI-compatible endpoints (OpenAI or OpenRouter). - Extraction Logic (
src/memory/summarizer.ts): At session end, sends the full transcript to an LLM and extracts up to 5 structured learnings across six kinds:fact,preference,decision,pattern,mistake,constraint. Each learning includes a certainty level (high/medium/low) that maps to an initial confidence score.
Memory Kinds
| Kind | What it stores |
|---|---|
fact |
Verifiable project knowledge |
preference |
Working style preferences |
decision |
Architectural choices with rationale |
pattern |
Recurring approaches that work |
mistake |
A failure and the lesson extracted from it |
constraint |
A rule that must always hold |
Scope Isolation (Multi-Context Safety)
To prevent cross-project context leaks, all memory queries and writes are isolated using scopes:
- Default scopes are constructed at session start:
["user:<hashed_username>", "agent:praana", "context:<hashed_cwd_path>"]. - The SQLite query layer enforces AND-scoping: recalled entries must match all scopes in a single query.
In project sessions, MemoryStore runs two queries and merges by entry id:
- Full project scopes (
user+agent+context) — project-local facts and decisions. - Global-only scopes (
user+agent) — entries without acontext:scope (preferences, cross-project patterns).
Global-only queries exclude entries that carry context:, so project facts stay local. Ranking is unified across the merged set; semantic conflicts between global and project entries are not auto-resolved.
Ranking and Decay
The memory retrieval system fuses multiple signals into a unified search score:
- Vector distance (from
entries_vecusingsqlite-vec) maps candidate entries. - Confidence: Base confidence is derived from extraction certainty (
high= 0.8,medium= 0.5,low= 0.3) and decays at 5% per day: $\text{conf} \times 0.95^{\text{days}}$. - Recency: Candidates receive a boost up to $+0.2$ based on how recently they were last accessed.
- Pinned Flag: Pinned memories receive a $+0.3$ score boost, ensuring they are always highly prioritized or visible in digests.
- Tool-outcome reinforcement (#45): any entry surfaced in a session (via the session-start digest or
recall()) is reinforced at session end — validity up; usefulness boosted only when acted on and the placeholder session-success signal is positive, neutral when acted on without that signal, and decayed when ignored. An entry surfaced across ≥2 distinct sessions with validity ≥0.7 is promoted from Layer 1 to Layer 2 (deep memory).
Schema Migrations
The SQLite schema evolves through additive ALTER TABLE migrations applied at every openMemoryDb() call. ensureLayerColumns() inspects PRAGMA table_info(entries) and runs each ALTER TABLE ... ADD COLUMN only if the column is missing — making the migrations idempotent and safe for existing installs. New columns always carry a DEFAULT so existing rows are populated automatically. The retracted column (added with the RETRACT opcode) defaults to 0, so all pre-existing entries remain visible until explicitly tombstoned.
Skill Effectiveness Feedback Loop
Source: src/skills/skill-stats-store.ts, src/memory/db.ts
At session start, SkillStatsStore.loadUsefulness() performs a dual-scope read from memory.db's skill_stats(skill_id, scope, usefulness, load_count, used_count, last_loaded_at) table — global skills first, project overrides on collision — and returns a Map<string, number> used to sort the skill catalog by descending usefulness. At session end (engine mode only), flush() updates each skill's score:
- Boost (α = 0.15): skill was loaded and
markResidentSkillsUsed()fired after a non-load_skilltool ran. - Decay (β = 0.05): skill was loaded but never used.
- No change: skill was never loaded this session.
Co-occurrence pairs (skills resident together during a used turn) are recorded to skill_cooccurrence(scope, skill_a, skill_b, count) for a future ranking consumer (#161). The scope field (context:<hash(gitRoot)> for project skills, "" for global) prevents cross-project bleed — a project's custom skill score does not pollute scores in other repos.
Workflow Pattern Tracking
Source: src/context-engine/workflow-tracker.ts, src/context-engine/db.ts
After each session ends, persistSessionPattern() extracts:
- Tool sequence — globally-deduplicated first-occurrence-ordered list of tools called this session.
- Artifact types — frequency-sorted list of artifact kind labels (e.g.
git_diff,tsc_errors,shell_output).
Both are stored in workflow_patterns(task_type, tool_sequence_json, artifact_types_json, hit_count, last_seen_at) keyed by hash(taskType + toolSequence + artifactTypes). Rows older than 30 days are pruned via pruneExpiredPatterns() inside runShutdownMaintenance().
At compile time, renderWorkflowContext() queries patterns matching the current taskType, selects the top-N by hit_count, and renders a compact Workflow Context section injected into the prompt just before the session checkpoint. This gives the engine a prior over which tools and artifact types are likely to appear — without adding it to the scored pool. Patterns for a different task type are never included, so a coding session's workflow history does not bleed into a debugging session.
Telemetry Scorecard
Source: src/context-engine/db.ts (scorecard table), src/context-engine/telemetry.ts, ScorecardTracker in session/turn
One row per session in the context-engine SQLite DB. No text content is stored — only numeric signals:
| Signal group | What's tracked |
|---|---|
| Context | retrieve_artifact calls, repeat file-reads, search_turn_events calls, pressure events, compaction triggers |
| Memory | recall call count, recall-used %, project-scoped validity/usefulness deltas at session start and end |
| Skills | unique skill loads, total load events, reloads, underloads (loaded but never used), token cost |
| Resume | read-path digests and skill catalog ids for dedup across resumed sessions |
Active when: context_engine.enabled = true (always) or measurement_mode = true (classic/debug — scorecard-only DB, no engine tables). Counters persist across resume via persistProgress() called after every turn.
Query: /scorecard in-session shows the current session row. For cross-session A/B analysis, query the scorecard table in the context-engine DB directly (see #17).
Configuration
Config files are deep-merged from lower to higher precedence (later overrides earlier):
- Global JSON:
~/.praana/praana.config.json - Global TOML:
~/.praana/config.toml - Local JSON:
./praana.config.json - Local TOML:
./praana.config.toml
[llm]
provider = "openrouter" # openrouter | openai | deepseek | groq | xai | fireworks | together | ollama | opencode | anthropic | google | mistral | amazon-bedrock
model = "deepseek/deepseek-v4-pro" # any model supported by the chosen provider
[memory]
enabled = true
summarizer = "openrouter" # openrouter | disabled
db_path = "~/.praana/memory.db"
[compiler]
token_budget = 100000
recent_turns = 10
recent_turns_token_budget = 30000
[tiers]
idle_soft_after_turns = 20
idle_hard_after_turns = 50
[context_engine]
enabled = true # false = classic mode (full verbatim transcript)
measurement_mode = false # write telemetry in classic mode (debug)
artifact_inline_threshold = 400
artifact_ttl_turns = 50
checkpoint_enabled = true # narrative, plan history, constraints, decisions w/ rationale
[context_engine.distiller]
default_intensity = "full" # lite | full
[context_engine.scoring]
w_pin = 1.0
w_recency = 0.5
w_relevance = 0.3
w_semantic = 0.3 # embedding similarity; combined with BM25 via max()
w_hydrate_boost = 0.2 # boost for context units overlapping auto-hydrated objects
[context_engine.pressure]
compact_at = 0.70
emergency_at = 0.85
[session]
log_dir = "~/.praana/sessions"
Env Vars
PRAANA_MODEL— overrides default modelPRAANA_SUMMARIZER_MODEL— overrides summarizer model (defaults togoogle/gemini-2.5-flashon OpenRouter)PRAANA_CONTEXT_ENGINE— overridescontext_engine.enabledPRAANA_MEASUREMENT_MODE— overridescontext_engine.measurement_mode- Provider-specific keys:
OPENROUTER_API_KEY,OPENAI_API_KEY,OPENCODE_API_KEY,DEEPSEEK_API_KEY, etc.
Mid-session model switching
/model is handled by model-resolver.ts and validated before the switch toast appears.
Syntax: /model [provider] <model-id> — provider is optional and space-separated only (not provider/model as a single token unless it is the model id on the current provider).
Resolution order:
- pi-ai static catalog (
getModel(provider, id)) - Live provider catalog from
GET {baseUrl}/modelsviaprovider-catalog.ts(6-hour disk cache at~/.praana/provider-catalog-cache.json) - Reject with an error toast if still unknown
Live catalog providers (OpenAI-compatible /models): OpenRouter, OpenCode, OpenAI, DeepSeek, Groq, xAI, Fireworks, Together, Ollama. Anthropic, Google, Mistral, and Bedrock rely on pi-ai only.
Persistence: successful switches write model_override and optionally provider_override system notes to the event log. session.ts restores the latest overrides on resume. Routing prefixes like openrouter/ or opencode/ are stripped before API calls.
UI and Slash Commands
PRAANA's current CLI requires an interactive TTY and launches the pi-tui terminal shell (src/ui/tui/). The TUI uses native scrollback, transcript replay, markdown rendering, identity/glance bars, slash-command/file autocomplete, toast overlays, buffered shell output, and full thinking-text rendering when /thinking on is enabled.
Slash commands are handled by src/slash-commands.ts:
/exit— ends session, triggers summarizer, saves and quits/clear,/new— clears working-memory state (StateGraph + engine checkpoint)/state— lists all state objects and tiers, or prints an empty-state guidance message/stats— prints session metadata plus working-memory and Cognitive Memory stats/digest— prints the current Cognitive Memory markdown digest/events— lists the last 20 events in the event log/recall <query>— performs manual vector recall query/model [provider] <id>— switch model and optionally provider (persisted to log; validated via pi-ai + live catalog)/sessions— lists last 15 historical sessions for easy resuming/incognito <on|off>— toggles Cognitive Memory persistence/debug— toggles detailed tool block tracing and compiles turns to files underprompts/(andscores.jsonlin engine mode)/thinking <on|off>— toggles visibility of LLM reasoning stream/why <id>— explains why a context unit was included or excluded from the last compiled prompt (engine mode only)/help— prints slash commands documentation
CLI flags: praana --incognito, praana --debug, praana --config <path>, plus subcommands such as praana resume <session_id>, praana init, and praana memory dedupe. See src/app-banner.ts and src/cli-args.ts for the full list.