Virtual Context can be configured through environment variables, configuration files, or programmatic options when using the Python API. This page documents all available configuration parameters for storage backends, embedding models, compaction models, retrieval tuning, and proxy behavior.
The most common configuration choices are the storage backend (SQLite for single-instance development, PostgreSQL for production multi-tenant setups), the embedding model used for semantic retrieval (defaults to text-embedding-3-small), and the compaction model that generates summaries and extracts structured facts during the hierarchical compression pipeline.
For a broader understanding of how these configuration options affect the system pipeline, see the architecture documentation. For benchmark results showing how different configurations affect recall quality and token efficiency, see the benchmark results.
Configuration
Storage, models and tuning parameters.
Minimal Config
Configuration lives in a YAML file, discovered automatically or named with -c. The dataclass defaults in the engine are the source of truth; this page documents the keys most deployments touch.
version: "0.2"
context_window: 120000
tag_generator:
type: "llm"
provider: "anthropic"
model: "claude-haiku-4-5-20251001"
summarization:
provider: "anthropic"
model: "claude-haiku-4-5-20251001"
storage:
backend: "sqlite"Defaults That Affect You
These are on out of the box. Each one changes behaviour in a deployment that has configured nothing, so they are worth reading before the reference below.
| Key | Default | What it does when you leave it alone |
|---|---|---|
compaction.code_mode | true | Coding-oriented summarization prompts, investigatory actions excluded from facts, code references emitted. Applies to non-coding deployments too. |
compaction.pre_compaction_filtering | aggressive | Drops tag-irrelevant turns from the payload before the first compaction, using the standard protected window. |
compaction.skip_active_tags | true | Does not re-retrieve tags whose content is already in the raw window. |
compaction.session_gap_minutes | 30 | Silently splits segments at conversational time gaps, so widely spaced messages summarize separately even on the same topic. |
tag_generator.temporal_heuristic_enabled | true | Marks a query temporal from date patterns even when the tagger did not, routing it to time-scoped retrieval. |
search.tool_guard_enabled | true | Stops search-tool bursts past the threshold for the rest of the window. |
history_widening_threshold | 0.10 | When a client widens its history window backward, clears ingest state and re-ingests the full history. |
pre_compaction_filtering takes off, conservative or aggressive: off passes every turn through, conservative drops with a doubled protected window, and the default drops with the standard one. After the first compaction the standard window always applies regardless.
Top-Level
| Key | Description |
|---|---|
version | Config schema version |
context_window | Total token budget for the context window |
token_counter | Counting mode: anthropic, tiktoken or estimate |
storage_root | Root directory for data files |
Tag Generator
tag_generator:
type: "llm" # "llm" or "keyword"
provider: "anthropic"
model: "claude-haiku-4-5-20251001"
max_tags: 10
min_tags: 5llm tags completed turns with the configured model and gives the best quality. keyword uses a configured vocabulary: fastest, lowest quality, useful for testing or very cost-sensitive setups.
tag_generator:
prompt_mode: "detailed" # or "compact" for cheaper models
temporal_heuristic_enabled: true # on by default
context_lookback_pairs: 5 # recent turn pairs fed as tagger context
context_bleed_threshold: 0.1 # embedding gate on that context
disable_thinking: false # prepend /no_think (local-model workaround)
keyword_fallback:
tag_keywords: {} # tag -> keywords, for type: "keyword"
tag_patterns: {} # tag -> regex patterns
tag_splitting:
enabled: false # auto-split overly broad tags
frequency_threshold: 15keyword_fallback is how you shape deterministic tagging: with type: "keyword", tags come only from that vocabulary. disable_thinking is a model workaround for local models that emit thinking blocks and break JSON tagging; leave it off unless you hit that.
Inbound tagging is configured separately under retrieval.inbound_tagger_type. The default embedding assigns tags by vector similarity against the existing vocabulary using a local model, with no model call on the request path. The LLM tagger always tags the completed turn on the background path, so every turn ends up LLM-tagged regardless.
Compaction
compaction:
soft_threshold: 0.70
hard_threshold: 0.85
protected_recent_turns: 6
min_summary_tokens: 200
max_summary_tokens: 2000
defer_payload_mutation: false # preserve the prompt-cache prefix while warm
flush_ttl_seconds: 300 # cache considered cold after this idle time
fill_pass_enabled: false # rewrite payload toward a target fill each request
store_recovery_threshold: 0.70 # recover from store below this fraction of stored turnsThresholds are fractions of the context window: at 0.70 with a 120K window, compaction starts around 84K tokens in use. Protected turns are never compacted, keeping recent conversation at full fidelity. Setting that too high wastes budget; too low loses recent context.
defer_payload_mutation and the flush gate are covered under engine internals. fill_pass_enabled conflicts with deferral because it rewrites the payload every request, and the proxy warns when both are on.
Segmentation and merge tuning
compaction:
summary_ratio: 0.15 # target summary size as a fraction of source
max_segment_tokens: 2000
max_segment_turns: 20 # 0 = unlimited
tag_overlap_threshold: 0.5 # min overlap to keep turns in one segment
tool_result_segment_threshold: 50000 # bytes; larger results get their own segment
merge_lookback: 5 # segments checked for a merge candidate
merge_overlap_threshold: 0.35
max_concurrent_summaries: 4
session_gap_minutes: 30 # on by default
code_mode: true # on by defaultSummarization
summarization:
provider: "anthropic"
model: "claude-haiku-4-5-20251001"
temperature: 0.3 # lower is more faithfulThe summarization model is independent of your upstream. A small fast model is usually the right choice here even when the upstream is a frontier model.
Storage
storage:
backend: "sqlite" # sqlite | filesystem | postgres | neo4j | falkordb
sqlite:
path: ".virtualcontext/store.db"
postgres:
dsn: "postgresql://user:pass@host:5432/vc"SQLite is the default and needs no setup. PostgreSQL, which requires the postgres extra, is the backend for multi-worker deployments. Neo4j and FalkorDB add graph-based fact traversal. The filesystem backend stores segments as Markdown with YAML frontmatter and does not host the full feature set.
Retrieval
retrieval:
inbound_tagger_type: "embedding"
embedding_model: "all-MiniLM-L6-v2"
active_tag_lookback: 4
anchorless_lookback: 6
strategy_config:
default:
max_results: 10
max_budget_fraction: 0.25
include_related: trueOnly the default strategy entry is read; per-strategy overrides beyond it have no effect. active_tag_lookback excludes tags from the last N turns because their content is already in raw history; higher values reduce redundancy but risk missing older content under the same tags. max_budget_fraction caps injected context as a fraction of the window.
Scoring
retrieval:
scoring:
idf_weight: 0.50
bm25_weight: 0.30
embedding_weight: 0.20
rrf_k: 60
embedding_context_turns: 0 # 0 = bare query only
embedding_context_guard: true
embedding_reserved_seats: 0
dampening:
hub_enabled: true # penalize tags above the p90 segment count
gravity_enabled: true # halve embedding scores with no BM25 support
resolution_enabled: true # boost fact-bearing tagsembedding_context_turns blends the last N turns into the query vector, so an under-specified question inherits the elided topic from recent context. embedding_context_guard then scores each tag by the better of its bare-query and blended similarity, so irrelevant recent context can never demote a relevant tag. embedding_reserved_seats forces the top embedding-only candidates into the fused results, for queries where the embedding signal is the only one that finds the right topic.
Assembly
assembly:
tag_context_max_tokens: 30000
recent_turns_always_included: 3
context_hint_enabled: true
context_hint_max_tokens: 2000
protected_window_db_source: "off" # "merge" for unified multi-channel conversations
actor_card_enabled: false # per-person memory cards; ships dark
speaker_roster_enabled: false # participant roster; ships darkThe speaker gates, the actor-card keys and how the attribution subsystem fits together are documented on the attribution page.
Search and Tool Guard
search:
tool_guard_enabled: true # stop runaway search-tool bursts
tool_guard_window_seconds: 120
tool_guard_threshold: 10 # calls per window before the guard trips
find_quote_default_results: 5
find_quote_max_results: 20
query_facts_default_limit: 50
speaker_selection_enabled: falsesearch:
search_facts_max_results: 10
fts_snippet_chars: 500 # snippet length from full-text matches
tool_output_snippet_chars: 100
postgres_max_words: 100 # word cap on Postgres full-text queries
max_index_bytes: 524288 # cap on indexed tool-output bytesThe tool guard is on by default. If the model issues more than tool_guard_threshold search calls inside tool_guard_window_seconds, further search calls stop for the rest of the window. If search tools appear to stop working mid-conversation, check this first. Raise the threshold or disable the guard if your workload legitimately searches in bursts.
Core Context
assembly.core_files pins file content into every assembled context, which makes it a capability rather than a tuning knob: a project brief, a style guide or a schema can be made permanently present without relying on retrieval to surface it.
assembly:
core_files:
- path: "PROJECT.md"
priority: 8
core_context_max_tokens: 18000
facts_max_tokens: 20000Each entry takes a path and an optional priority, defaulting to 5, with higher rendering first. Files are read from disk, concatenated with per-file headers and budgeted by core_context_max_tokens. Missing files are skipped silently, so a typo in a path degrades to no pinned content rather than an error.
Paging
paging:
enabled: false
auto_promote: true
auto_evict: true
max_tool_loops: 10When enabled, models on the configured autonomous-model list receive the paging tools and drive topic expansion themselves.
Tool Output
tool_output:
enabled: false # intercept and truncate oversized tool results
default_truncate_threshold: 8192
default_head_ratio: 0.6
default_tail_ratio: 0.4Facts
facts:
graph_links: true
link_types: [supersedes, caused_by, part_of, contradicts, same_as, related_to]Supersession and curation are separate optional passes over the fact store, both disabled by default, each taking its own provider and model.
Providers
providers:
openrouter:
base_url: "https://openrouter.ai/api/v1"
api_key_env: "OPENROUTER_API_KEY"
summarization:
provider: "openrouter"
model: "some/model-slug"The block declares named endpoints that the tagger and summarizer refer to by label, and onboard and init generate it for you. When a providers block is present, summarization.provider must name one of its entries or validation fails. api_key_env names the environment variable holding the key, which is how non-Anthropic keys are wired in.
Proxy
The listen address comes from the CLI, not YAML: --host defaults to 127.0.0.1 and --port to 5757. The block below configures logging, limits, the session cache and multi-instance mode.
proxy:
request_log_dir: null
llm_calls_log: null
upstream_context_limit: null
passthrough_trim_ratio: 0.40
redis_url: null # or the REDIS_URL environment variable
redis_history_cap: 200
instances:
- port: 5757
upstream: "https://api.anthropic.com"
label: "anthropic"
config: "./virtual-context-proxy-anthropic.yaml"Each instance may carry its own config file with isolated storage, tagger and summarizer settings. Instances without a config field share the master engine.
Tag Rules
tag_rules:
- match: "legal-*"
priority: 1
summary_prompt: "Summarize precisely, preserving dates, names, and docket numbers."match is a glob pattern over tag names and priority breaks ties when several rules match, lower winning. Tag rules override the summarization prompt for matching segments; they do not force segments into the assembled context.
Presets and Validation
Two presets ship with the package. coding uses aggressive compaction, code-specific keyword fallbacks, tool output interception and the fact graph, tuned for tool-heavy development sessions. agentic is balanced for autonomous agent conversations across any domain, also with the fact graph enabled.
virtual-context presets list
virtual-context presets show coding # print the preset’s full config as YAML
virtual-context init coding # bootstrap a config file from it
virtual-context config validatepresets show prints exactly what a preset sets, so the summaries above never need to be taken on trust.
Validation reports missing required fields, invalid types and cross-field violations such as a soft threshold at or above the hard threshold.
Environment Variables
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY | Key for the Anthropic provider, read directly |
OPENAI_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY | Provider keys, honored through a providers.<label>.api_key_env entry |
REDIS_URL | Redis session cache URL; overrides proxy.redis_url |
VC_DASHBOARD_TOKEN | Require a token on dashboard endpoints; unauthenticated when unset |
DATABASE_URL | Postgres DSN fallback for the CLI, consulted only when neither a storage flag nor -c was given |
VC_DATA_DIR | Data directory for deployments whose store has no local database path. Media originals saved by image compression land beneath it per conversation, and are cleaned up when a conversation is deleted |
VIRTUAL_CONTEXT_CONFIG | Config path override, read by the MCP server only |