virtual-context 0.2.9__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {virtual_context-0.2.9 → virtual_context-0.3.1}/PKG-INFO +175 -30
- virtual_context-0.3.1/README-draft.md +519 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/README.md +173 -28
- virtual_context-0.3.1/README.md.backup +1130 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/pyproject.toml +2 -2
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_cli_init.py +67 -11
- virtual_context-0.3.1/tests/test_engine_sync_turns.py +195 -0
- virtual_context-0.3.1/tests/test_session_state.py +148 -0
- virtual_context-0.3.1/tests/test_vcattach.py +121 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/__init__.py +1 -1
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/cli/main.py +21 -4
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/config.py +9 -1
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/composite_store.py +14 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/protocols.py +2 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/quote_search.py +11 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/tagging_pipeline.py +14 -4
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/engine.py +369 -60
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/formats.py +452 -65
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/handlers.py +105 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/message_filter.py +4 -4
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/registry.py +10 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/server.py +75 -18
- virtual_context-0.3.1/virtual_context/proxy/session_state.py +413 -0
- virtual_context-0.3.1/virtual_context/proxy/vcattach.py +97 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/storage/filesystem.py +30 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/storage/postgres.py +69 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/storage/sqlite.py +63 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/types.py +4 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/.gitignore +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/LICENSE +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/assets/dashboard.png +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/assets/hero.png +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/models.yaml +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/REGRESSION_MAP.md +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/conftest.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/docker-compose.test.yml +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/haiku/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/haiku/conftest.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/haiku/test_compaction.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/haiku/test_retrieval.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/haiku/test_tagging.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/ollama/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/ollama/conftest.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/ollama/test_compactor.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/ollama/test_pipeline.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/ollama/test_provider.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/ollama/test_tag_generator.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/proxy/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/proxy/test_dashboard_cors.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/proxy/test_metrics.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_assembler.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_backend_integration.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_budget_enforcement.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_compaction_commit_prune.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_compactor.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_compactor_concurrent.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_composite_store.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_config.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_context_bleed.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_conversation_identity.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_conversation_lifecycle.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_conversation_scoping.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_embedding_tag_generator.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_empty_turn_skip.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_engine_integration.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_engine_lookback.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_engine_state.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_fact_enrichment.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_fact_graph_integration.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_fact_link_checker.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_fact_link_query.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_fact_link_types.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_fact_links_sqlite.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_fact_redesign.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_fill_pass.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_find_quote.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_format_agnostic.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_headless.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_history_filter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_idf_retrieval.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_ingest_index_integrity.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_longmemeval_auth.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_mcp_server.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_media.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_message_filter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_metrics_persistence.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_model_catalog.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_model_limits.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_monitor.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_multi_instance.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_noop_fact_link_store.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_openrouter_provider.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_paging.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_passthrough_filter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_presets.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_prev_context_leak.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_provider_adapters.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_proxy.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_proxy_dashboard.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_proxy_formats.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_proxy_message_filter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_proxy_session.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_proxy_streaming.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_raw_content.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_recall_all.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_registry_lifecycle.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_request_captures_persistence.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_retriever.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_rrf_scoring.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_segmenter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_semantic_search.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_sender_identity.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_session_cache.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_session_date.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_storage_protocols.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_store_recovery.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_store_sqlite.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_stub_turn_handling.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_supersession.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_supersession_migration.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tag_canonicalizer.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tag_consolidator.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tag_generator.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tag_splitter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_telemetry.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_telemetry_integration.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tool_loop.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tool_output_interceptor.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tool_result_filter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tool_tags.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_tui.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_turn_grouping.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_turn_tag_index.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_unified_budget.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_upstream_trim.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/tests/test_verb_expansion.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual-context.yaml +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual-context.yaml.example +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/cli/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/conversation_identity.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/assembler.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/compaction_pipeline.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/compactor.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/conversation_store.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/embedding_provider.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/embedding_tag_generator.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/engine_utils.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/fact_query.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/fts_preprocessor.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/hint_builder.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/llm_utils.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/math_utils.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/model_catalog.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/monitor.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/paging_manager.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/provider_adapters.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/retrieval_assembler.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/retrieval_scoring.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/retriever.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/search_engine.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/segmenter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/semantic_search.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/store.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/tag_canonicalizer.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/tag_consolidator.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/tag_generator.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/tag_scoring.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/tag_splitter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/telemetry.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/temporal_resolver.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/tool_loop.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/tool_query.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/core/turn_tag_index.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/data/anthropic-tokenizer/tokenizer.json +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/ingest/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/ingest/curator.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/ingest/date_resolver.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/ingest/parsers.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/ingest/supersession.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/mcp/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/mcp/server.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/model_limits.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/openclaw/virtual-context.mjs +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/patterns.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/presets/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/presets/agentic.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/presets/base.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/presets/coding.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/providers/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/providers/anthropic.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/providers/base.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/providers/generic_openai.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/providers/ollama_native.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/_envelope.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/dashboard.html +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/dashboard.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/helpers.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/media.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/metrics.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/multi.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/session_cache.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/state.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/static/android-chrome-192x192.png +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/static/android-chrome-512x512.png +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/static/apple-touch-icon.png +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/static/favicon-16x16.png +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/static/favicon-32x32.png +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/static/favicon.ico +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/static/site.webmanifest +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/proxy/tool_output_interceptor.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/storage/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/storage/falkordb.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/storage/helpers.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/storage/neo4j.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/storage/noop_fact_link_store.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/token_counter.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/app.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/chat.tcss +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/chat_provider.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/headless.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/modals/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/modals/turn_inspector.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/state.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/widgets/__init__.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/widgets/budget_bar.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/widgets/chat_view.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/widgets/input_box.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/widgets/tag_panel.py +0 -0
- {virtual_context-0.2.9 → virtual_context-0.3.1}/virtual_context/tui/widgets/turn_list.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: virtual-context
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: OS-style virtual memory for LLM session context management
|
|
5
5
|
Project-URL: Homepage, https://virtual-context.com
|
|
6
6
|
Project-URL: Repository, https://github.com/virtual-context/virtual-context
|
|
@@ -16,7 +16,7 @@ Requires-Python: >=3.11
|
|
|
16
16
|
Requires-Dist: anthropic>=0.40
|
|
17
17
|
Requires-Dist: dateparser>=1.2
|
|
18
18
|
Requires-Dist: fastapi>=0.115
|
|
19
|
-
Requires-Dist: httpx>=0.27
|
|
19
|
+
Requires-Dist: httpx[http2]>=0.27
|
|
20
20
|
Requires-Dist: mcp>=1.0
|
|
21
21
|
Requires-Dist: openai>=1.50
|
|
22
22
|
Requires-Dist: pillow>=10.0
|
|
@@ -45,6 +45,13 @@ Provides-Extra: redis
|
|
|
45
45
|
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
46
46
|
Description-Content-Type: text/markdown
|
|
47
47
|
|
|
48
|
+
<!-- [](https://pypi.org/project/virtual-context/) -->
|
|
49
|
+
<!-- [](https://pypi.org/project/virtual-context/) -->
|
|
50
|
+
<!-- [](https://pypistats.org/packages/virtual-context) -->
|
|
51
|
+
<!-- [](https://github.com/yursilkidwai/virtual-context/blob/main/LICENSE) -->
|
|
52
|
+
[](https://discord.gg/YxDHKEZz)
|
|
53
|
+
[](https://x.com/virtualctx)
|
|
54
|
+
|
|
48
55
|
<p align="center">
|
|
49
56
|
<a href="assets/dashboard.png">
|
|
50
57
|
<img src="assets/dashboard.png" alt="virtual-context dashboard" width="800">
|
|
@@ -180,7 +187,7 @@ proxy:
|
|
|
180
187
|
**Daemon mode:** run as a background service:
|
|
181
188
|
|
|
182
189
|
```bash
|
|
183
|
-
# Creates config
|
|
190
|
+
# Creates ~/.virtualcontext/ with config + data, installs + starts daemon
|
|
184
191
|
virtual-context daemon install --upstream https://api.anthropic.com
|
|
185
192
|
|
|
186
193
|
# Or: guided interactive setup with daemon
|
|
@@ -282,6 +289,7 @@ Exposes virtual-context as an MCP server for integration with Claude Desktop, Cu
|
|
|
282
289
|
| Tool | `collapse_topic` | Collapse a topic back to summary or none |
|
|
283
290
|
| Tool | `find_quote` | Full-text search across all stored conversation text |
|
|
284
291
|
| Tool | `query_facts` | Structured fact lookup with subject/verb/object/status filters |
|
|
292
|
+
| Tool | `restore_tool` | Recover full content from compacted chain stubs or compressed media |
|
|
285
293
|
| Resource | `virtualcontext://domains` | List all tags |
|
|
286
294
|
| Resource | `virtualcontext://domains/{tag}` | Summaries for a specific tag |
|
|
287
295
|
| Prompt | `recall` | Suggest context retrieval for a topic |
|
|
@@ -293,11 +301,12 @@ Exposes virtual-context as an MCP server for integration with Claude Desktop, Cu
|
|
|
293
301
|
User message arrives
|
|
294
302
|
│
|
|
295
303
|
▼
|
|
296
|
-
|
|
297
|
-
│ ├─ Extract
|
|
298
|
-
│ ├─ Route to existing
|
|
299
|
-
│ ├─ No marker? →
|
|
300
|
-
│
|
|
304
|
+
Conversation routing (proxy mode)
|
|
305
|
+
│ ├─ Extract conversation ID from <!-- vc:conversation=UUID --> markers
|
|
306
|
+
│ ├─ Route to existing conversation or load persisted state from store + Redis
|
|
307
|
+
│ ├─ No marker? → derive stable ID from system prompt hash + format
|
|
308
|
+
│ ├─ Redis session cache: lossless restart, write-through history persistence
|
|
309
|
+
│ └─ Strip conversation markers before forwarding upstream
|
|
301
310
|
│
|
|
302
311
|
▼
|
|
303
312
|
Strip client envelope + extract metadata
|
|
@@ -307,13 +316,20 @@ Strip client envelope + extract metadata
|
|
|
307
316
|
│ └─ Metadata preserved on Message.metadata for downstream use
|
|
308
317
|
│
|
|
309
318
|
▼
|
|
319
|
+
Media compression (all paths — passthrough and active)
|
|
320
|
+
│ ├─ Detect base64 images across all 4 formats (Anthropic, OpenAI Chat, Responses, Gemini)
|
|
321
|
+
│ ├─ Compress to JPEG, store originals to disk for later recovery
|
|
322
|
+
│ ├─ Image token counting uses Anthropic formula: (width × height) / 750
|
|
323
|
+
│ └─ A 391KB screenshot → ~40KB compressed, saving ~88k tokens
|
|
324
|
+
│
|
|
325
|
+
▼
|
|
310
326
|
History ingestion (first request only)
|
|
311
327
|
│ ├─ Extract and tag all prior user+assistant pairs → bootstrap TurnTagIndex
|
|
312
328
|
│ ├─ Stub detection: media attachments/image placeholders get _stub tag (skip LLM tagger)
|
|
313
329
|
│ └─ Conversation-scoped: each conversation's index is independent
|
|
314
330
|
│
|
|
315
331
|
▼
|
|
316
|
-
Inbound tagging
|
|
332
|
+
Inbound tagging — identify what this message is about
|
|
317
333
|
│ ├─ Embedding tagger (recommended): cosine similarity against existing tag vocabulary
|
|
318
334
|
│ │ (closed-set, deterministic, can't hallucinate novel tags)
|
|
319
335
|
│ ├─ LLM / keyword tagger: alternative with vocabulary feedback
|
|
@@ -335,39 +351,66 @@ Assemble context within token budget
|
|
|
335
351
|
│ └─ Tag sections: retrieved summaries ordered by tag priority
|
|
336
352
|
│
|
|
337
353
|
▼
|
|
354
|
+
Chain collapse — compress tool-bearing history turns
|
|
355
|
+
│ ├─ Group raw messages into logical turns via group_into_turns()
|
|
356
|
+
│ ├─ Tool chains (assistant tool_use → user tool_result → assistant) → compact stubs
|
|
357
|
+
│ ├─ Stubs contain tool names, truncated previews, and restore refs
|
|
358
|
+
│ ├─ Handles all 4 formats: Anthropic, OpenAI Chat, OpenAI Responses, Gemini
|
|
359
|
+
│ ├─ Deep compaction: drops stubs entirely past configurable age threshold
|
|
360
|
+
│ └─ Non-tool turns outside protected window are dropped (summaries cover them)
|
|
361
|
+
│
|
|
362
|
+
▼
|
|
363
|
+
Store-backed recovery (when client truncates history)
|
|
364
|
+
│ ├─ Detect truncation: payload turns < 70% of stored turns
|
|
365
|
+
�� ├─ Recover chain snapshots from durable store (compact stubs with metadata)
|
|
366
|
+
│ ├─ Recover recent turns from stored turn_messages
|
|
367
|
+
│ └─ Sanitize restored turns: strip thinking blocks, replace media with placeholders
|
|
368
|
+
│
|
|
369
|
+
▼
|
|
338
370
|
Filter conversation history
|
|
339
371
|
│ ├─ Drop turns whose tags don't overlap with inbound tags
|
|
340
372
|
│ ├─ Preserve tool chains atomically (tool_use ↔ tool_result never separated)
|
|
373
|
+
│ ├─ Protected zone intrusion: stub tool results in protected turns 3+ when zone exceeds budget %
|
|
341
374
|
│ ├─ Protect recent turns (always kept regardless of tags)
|
|
342
375
|
│ └─ Temporal queries skip filtering entirely
|
|
343
376
|
│
|
|
344
377
|
▼
|
|
345
|
-
|
|
378
|
+
Budget enforcement — iterative payload reduction
|
|
379
|
+
│ ├─ Scan reducible items: conversation text, tool results, thinking blocks, images
|
|
380
|
+
│ ├─ Cut largest reducible item per iteration until under budget
|
|
381
|
+
│ ├─ Bloat fallback: if VC enrichment exceeds inbound size, fall back to pure passthrough
|
|
382
|
+
│ └─ Upstream trim: final trim to model's actual context window limit
|
|
383
|
+
│
|
|
384
|
+
▼
|
|
385
|
+
Fill pass — replenish context after compression
|
|
386
|
+
│ ├─ Phase 1a: overflow tag summaries (topics that didn't fit during assembly)
|
|
387
|
+
│ ├─ Phase 1b: breadth summaries (sample from remaining tags)
|
|
388
|
+
│ ├─ Phase 2: recent turns from store (newest first)
|
|
389
|
+
│ └─ Target: soft threshold between floor and budget ceiling
|
|
390
|
+
│
|
|
391
|
+
▼
|
|
392
|
+
Inject <virtual-context> block into last user message → forward to LLM
|
|
393
|
+
│ (injected into user messages, not system prompt, for Anthropic cache stability)
|
|
346
394
|
│
|
|
347
395
|
▼
|
|
348
396
|
LLM processes enriched context → produces response
|
|
349
397
|
│
|
|
350
398
|
▼
|
|
351
|
-
Inject
|
|
352
|
-
│ ├─ Streaming: emit final SSE delta with <!-- vc:
|
|
353
|
-
│
|
|
399
|
+
Inject conversation marker into response (proxy mode)
|
|
400
|
+
│ ├─ Streaming: emit final SSE delta with <!-- vc:conversation=UUID -->
|
|
401
|
+
│ ├─ Non-streaming: append marker to last text content block
|
|
402
|
+
│ └─ Skip injection when payload already contains a conversation marker
|
|
354
403
|
│
|
|
355
404
|
▼
|
|
356
|
-
Response tagging
|
|
405
|
+
Response tagging — LLM tags the full user+assistant pair (background thread)
|
|
357
406
|
│ ├─ Context lookback: feed N recent pairs as tagger context for short/ambiguous messages
|
|
358
407
|
│ ├─ Context bleed gate: embedding similarity blocks stale context on topic shifts
|
|
359
408
|
│ ├─ Retry on _general: if tagger returns only _general, retry with expanded context
|
|
360
409
|
│ ├─ Authoritative tags written to TurnTagIndex (vocabulary-building)
|
|
361
|
-
│ ├─ Fact signal extraction: lightweight subject/verb/object triples per turn
|
|
362
410
|
│ ├─ Related tags generated for cross-vocabulary retrieval
|
|
363
411
|
│ └─ Compactor generates related_tags at write time (vocabulary bridging)
|
|
364
412
|
│
|
|
365
413
|
▼
|
|
366
|
-
Fact curation (on inbound, before assembly)
|
|
367
|
-
│ └─ LLM scores retrieved facts for relevance to current query
|
|
368
|
-
│ Low-relevance facts dropped before assembly
|
|
369
|
-
│
|
|
370
|
-
▼
|
|
371
414
|
Check token thresholds (soft 70%, hard 85%)
|
|
372
415
|
│
|
|
373
416
|
▼ (if threshold exceeded)
|
|
@@ -377,14 +420,14 @@ Segment by tag → summarize each segment (concurrent, ThreadPoolExecutor)
|
|
|
377
420
|
│ ├─ Stub segments: media/attachment stubs get passthrough (no LLM), inherit neighbor's tags
|
|
378
421
|
│ ├─ XML-tagged prev_context: structural separation prevents context leak into summaries
|
|
379
422
|
│ ├─ Tags preserved: LLM can ADD refined/related tags but never REMOVE originals
|
|
380
|
-
│ ├─ Fact
|
|
423
|
+
│ ├─ Fact extraction: delete-and-replace per segment, code_mode filters investigatory noise
|
|
381
424
|
│ └─ Related tags written into stored segments for future cross-vocabulary retrieval
|
|
382
425
|
│
|
|
383
426
|
▼
|
|
384
427
|
Compute greedy set cover → build/update per-tag summaries (Layer 2)
|
|
385
428
|
│
|
|
386
429
|
▼
|
|
387
|
-
Persist engine state (TurnTagIndex + compaction watermark → store)
|
|
430
|
+
Persist engine state (TurnTagIndex + compaction watermark → store + Redis)
|
|
388
431
|
```
|
|
389
432
|
|
|
390
433
|
## Key Capabilities
|
|
@@ -451,7 +494,7 @@ For proxy/OpenClaw conversations, session dates come from envelope metadata time
|
|
|
451
494
|
|
|
452
495
|
### Context Awareness Hints
|
|
453
496
|
|
|
454
|
-
After compaction, the LLM loses visibility into what topics have been stored. virtual-context injects a lightweight `<context-topics>` block into the system prompt:
|
|
497
|
+
After compaction, the LLM loses visibility into what topics have been stored. virtual-context injects a lightweight `<context-topics>` block into the last user message (not the system prompt, so the system prompt remains stable and cacheable):
|
|
455
498
|
|
|
456
499
|
```xml
|
|
457
500
|
<context-topics>
|
|
@@ -469,9 +512,9 @@ This costs ~50-200 tokens and enables a natural drill-down loop: the user asks f
|
|
|
469
512
|
|
|
470
513
|
Summaries compress information but inevitably lose specific details. When the user says "I run 5K every morning" at turn 14, a summary might retain "runs regularly" but drop the exact distance and timing. Most memory systems extract facts in a single LLM pass and trust the output directly: raw text goes in, extracted facts come out, and those facts are stored as-is. virtual-context takes a fundamentally different approach with a two-phase pipeline where per-turn signals are treated as hints, not ground truth.
|
|
471
514
|
|
|
472
|
-
**
|
|
515
|
+
**Fact extraction (at compaction).** Facts are extracted from the full turn group when the compactor processes a segment, not per-turn during ingestion. This produces higher-quality facts because the LLM sees the complete conversation flow across multiple turns: what the user asked, how the assistant responded, what was clarified or corrected. The result is a structured `Fact` with full provenance: subject, verb, what (the core assertion), `fact_type` classification (`preference`, `biographical`, `decision`, `plan`, `opinion`, `routine`, `relationship`, `skill`, `medical`, `financial`, `general`), temporal status (active/completed/planned/abandoned/recurring), associated tags, session ID, and source turn numbers. Facts are stored in dedicated SQLite tables with indexes for efficient querying.
|
|
473
516
|
|
|
474
|
-
**
|
|
517
|
+
**Delete-and-replace on re-compaction.** When a segment is re-compacted (e.g., after new turns are added to an existing topic), all facts for that segment are atomically deleted and re-extracted from the full turn group. This prevents fact duplication across re-compaction cycles and ensures facts always reflect the latest understanding. A `code_mode` prompt modifier filters investigatory noise from coding conversations (e.g., "assistant examined file X") and focuses extraction on outcomes: what was built, fixed, changed, or decided.
|
|
475
518
|
|
|
476
519
|
**Why two phases matter.** A single-pass extractor processing "yes, let's go with PostgreSQL" in isolation has no idea what "yes" refers to. It might extract nothing, or hallucinate a fact. virtual-context's response tagger sees the surrounding turns ("Should we use PostgreSQL or MySQL for the user table?") and generates the correct signal. The consolidation pass then verifies it against the full segment before storing a permanent fact. Two chances to get it right, each with progressively more context.
|
|
477
520
|
|
|
@@ -494,6 +537,99 @@ vc_query_facts(fact_type="preference")
|
|
|
494
537
|
|
|
495
538
|
**Semantic fact search.** When structured filters return sparse results, a fallback embedding search matches the query intent against all stored facts' `what` fields by cosine similarity, surfacing relevant facts even when the subject/verb/object decomposition doesn't align.
|
|
496
539
|
|
|
540
|
+
### Chain Collapse and Tool Compression
|
|
541
|
+
|
|
542
|
+
Agent conversations are dominated by tool calls. A coding session with 50 tool rounds might have 900K tokens of tool output but only 60K of actual conversation. Raw tool output (file contents, search results, command output) is high-volume, low-reuse information that crushes the context window.
|
|
543
|
+
|
|
544
|
+
virtual-context collapses entire tool chains into compact stubs:
|
|
545
|
+
|
|
546
|
+
```
|
|
547
|
+
Before (3 messages, ~18K tokens):
|
|
548
|
+
assistant: [tool_use: Read file.py]
|
|
549
|
+
user: [tool_result: <full 500-line file contents>]
|
|
550
|
+
assistant: "The file has a bug on line 42..."
|
|
551
|
+
|
|
552
|
+
After (2 messages, ~200 tokens):
|
|
553
|
+
user: [compacted turn — tool activity: Read(file.py) — vc_restore_tool can recover full content]
|
|
554
|
+
assistant: "The file has a bug on line 42..."
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
Chain collapse handles all four provider formats (Anthropic `tool_use`/`tool_result`, OpenAI Chat `tool_calls`/`role:tool`, OpenAI Responses `function_call`/`function_call_output`, Gemini `functionCall`/`functionResponse`). Full raw tool output is stored durably with content-addressed refs and recoverable via `vc_restore_tool`.
|
|
558
|
+
|
|
559
|
+
**Deep compaction** drops stubs entirely past a configurable age threshold (`deep_compaction_ratio`). A stub from turn 5 in a 200-turn conversation adds no value; the segment summaries already cover that content.
|
|
560
|
+
|
|
561
|
+
### Media Compression
|
|
562
|
+
|
|
563
|
+
Base64 images in API payloads are enormous: a single screenshot is 300-500KB of base64, consuming ~100K tokens. Multimodal conversations with dozens of images can reach 10MB+.
|
|
564
|
+
|
|
565
|
+
virtual-context compresses images on first sight, before any pipeline processing:
|
|
566
|
+
|
|
567
|
+
- Detect base64 images across all 4 formats
|
|
568
|
+
- Compress to JPEG at configurable quality, store originals to disk
|
|
569
|
+
- Replace in-flight payload with compressed version
|
|
570
|
+
- A 391KB screenshot → ~40KB compressed, saving ~88K tokens per image
|
|
571
|
+
- Recovery via `vc_restore_tool` returns the original uncompressed content
|
|
572
|
+
|
|
573
|
+
Media compression runs on both passthrough and active paths, so even conversations that haven't triggered compaction benefit.
|
|
574
|
+
|
|
575
|
+
### Fill Pass
|
|
576
|
+
|
|
577
|
+
After chain collapse and budget enforcement compress the payload, the context window may have significant unused capacity. The fill pass replenishes it with high-value content from the store:
|
|
578
|
+
|
|
579
|
+
- **Phase 1a**: Overflow tag summaries — topics that didn't fit during initial assembly
|
|
580
|
+
- **Phase 1b**: Breadth summaries — sample from remaining tags for broader coverage
|
|
581
|
+
- **Phase 2**: Recent turns from store — newest first, filling toward a soft target threshold
|
|
582
|
+
|
|
583
|
+
The fill pass runs after all compression, so it never fights against budget enforcement. When clients truncate history (detected via `payload_turns < store_turns * 0.70`), the fill pass works with store-backed recovery to restore the most valuable context.
|
|
584
|
+
|
|
585
|
+
### Store-Backed Recovery
|
|
586
|
+
|
|
587
|
+
Clients (Claude Code, OpenClaw) sometimes truncate conversation history to manage their own context windows. When this happens, virtual-context detects the truncation and recovers from its durable store:
|
|
588
|
+
|
|
589
|
+
- **Chain snapshot recovery**: Restore compact tool chain stubs from stored `chain_snapshots`
|
|
590
|
+
- **Turn recovery**: Restore recent raw turns from stored `turn_messages`
|
|
591
|
+
- **Sanitization**: Strip thinking blocks, replace media with passive placeholders, remove orphaned tool scaffolding
|
|
592
|
+
|
|
593
|
+
Recovery is transparent to the client. The payload that reaches the LLM contains the recovered context as if it had never been truncated.
|
|
594
|
+
|
|
595
|
+
### Conversation Identity and VCATTACH
|
|
596
|
+
|
|
597
|
+
Every conversation gets a stable identity derived from the system prompt hash and conversation markers embedded in assistant responses. This identity routes requests to the right conversation's compacted segments, facts, and tags across restarts, deploys, and client changes.
|
|
598
|
+
|
|
599
|
+
When identity detaches — system prompt changes, client truncation loses the marker, a deploy produces a different hash — the user can reattach by typing:
|
|
600
|
+
|
|
601
|
+
```
|
|
602
|
+
VCATTACH <conversation_id>
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
The target can be a conversation label (`VCATTACH website`), a full UUID, or a UUID prefix. The proxy intercepts this before reaching the LLM, resolves the target, and returns a response with the correct conversation marker. From the next request onward, the client routes to the target conversation with its full compacted context.
|
|
606
|
+
|
|
607
|
+
**What VCATTACH enables:**
|
|
608
|
+
|
|
609
|
+
**Reattachment after detachment.** The most common case. A deploy or system prompt change creates an orphan conversation. The user types `VCATTACH <label>` and is back on their original conversation with all segments, facts, and tags intact.
|
|
610
|
+
|
|
611
|
+
**Cross-platform shared memory.** A user builds up deep context in Claude Code — architecture decisions, code patterns, debugging history — all compacted into segments and facts. They type `VCATTACH code-project` in a Telegram conversation with a different model and immediately get that full context. Both clients now share the same conversation identity: messages from either platform enrich the same compacted knowledge base. This isn't document sharing or chat mirroring — it's shared memory across platforms and models.
|
|
612
|
+
|
|
613
|
+
**Multi-agent collaboration.** Two agents (or two humans using different clients) can work on the same problem space simultaneously. Agent A researches an RFP in Claude Code, compacting findings into segments. Agent B drafts the proposal in a Telegram group, pulling from the same segments via retrieval. Each agent's contributions are compacted into the shared store. The next agent to query sees everything the other contributed — no manual handoff, no copy-paste, no shared documents. The virtual context IS the shared workspace.
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
**Client compaction recovery.** When clients manage their own context windows (Claude Code truncates old turns, OpenClaw resets sessions), the conversation marker can be lost. VCATTACH lets the user reconnect to the original conversation. The stored segments and facts survive independently of the client's history.
|
|
617
|
+
|
|
618
|
+
**Conversation merging.** A user accidentally creates two conversations about the same topic. They pick the one with richer context and `VCATTACH` the other to it. The old conversation is deleted; the target keeps all its compacted data.
|
|
619
|
+
|
|
620
|
+
**How it works:**
|
|
621
|
+
|
|
622
|
+
1. User types `VCATTACH <label_or_id>` as a normal message
|
|
623
|
+
2. Proxy detects the command (regex on the last user message only — history is inert)
|
|
624
|
+
3. Resolves target by label (case-insensitive) or UUID (exact or prefix match)
|
|
625
|
+
4. Registers an alias so old markers redirect permanently
|
|
626
|
+
5. Deletes the old conversation's engine state
|
|
627
|
+
6. Resets the target's compaction checkpoints (segments/facts preserved)
|
|
628
|
+
7. Returns a fake response with the target's conversation marker — no LLM call
|
|
629
|
+
8. Next request carries the new marker and routes to the target
|
|
630
|
+
|
|
631
|
+
The alias table is persistent — if a stale marker resurfaces from a cached client or different session, it follows the alias instead of creating a new orphan.
|
|
632
|
+
|
|
497
633
|
### Virtual Memory Paging
|
|
498
634
|
|
|
499
635
|
RAG retrieves content and appends it to the context window. It never frees space from what's already there. When a 100k document needs to enter a 120k window that already has 60k of conversation history, RAG has three options: truncate (lossy), error (useless), or chunk (every chunking approach either costs extra user turns, loses cross-chunk coherence, or both). Nobody touches the existing 60k. It sits there, potentially full of stale context from 30 turns ago that nobody needs anymore.
|
|
@@ -696,19 +832,23 @@ virtual-context chat --replay vc-session.json
|
|
|
696
832
|
|
|
697
833
|
### Proxy Deep Dive
|
|
698
834
|
|
|
699
|
-
**
|
|
835
|
+
**Conversation continuity.** The proxy injects an invisible `<!-- vc:conversation=UUID -->` marker into every assistant response. On subsequent requests, the proxy extracts the first marker in the conversation history, routes to the correct conversation, and strips markers before forwarding upstream. Stable conversation identity is derived from a format-specific hash of the system prompt and early messages, so the same client session always routes to the same conversation even across restarts. Valid UUID markers in the payload are accepted as-is without re-hashing.
|
|
836
|
+
|
|
837
|
+
**Redis session cache.** A write-through Redis cache persists conversation history and engine state across container restarts. On startup, conversations are restored from Redis with full history, eliminating cold-start re-ingestion. Degraded mode: if Redis is unavailable, the proxy falls back to store-only persistence with no interruption.
|
|
700
838
|
|
|
701
839
|
**Conversation-scoped retrieval.** All store retrieval methods are scoped by `conversation_id`. Multiple conversations sharing the same SQLite database are fully isolated; a new conversation never gets context from another conversation's segments.
|
|
702
840
|
|
|
703
|
-
**
|
|
841
|
+
**Pipeline suppression.** When a conversation has no compacted data, the pipeline is suppressed; requests pass through as-is. Once the first compaction runs, the pipeline activates automatically.
|
|
704
842
|
|
|
705
843
|
**History ingestion.** On the first request, the proxy extracts user+assistant pairs from the client's existing conversation history and tags each to bootstrap the TurnTagIndex. No cold-start period.
|
|
706
844
|
|
|
707
|
-
**
|
|
845
|
+
**Four-format support.** Auto-detects Anthropic, OpenAI Chat, OpenAI Responses, and Gemini request formats. Every pipeline stage (chain collapse, media compression, budget enforcement, context injection, stub generation, token counting) is format-aware through the `PayloadFormat` abstraction. A single proxy instance handles all formats on one port.
|
|
846
|
+
|
|
847
|
+
**Image-aware token counting.** Base64 images are counted using the Anthropic formula `(width × height) / 750` after scaling to 1568px max dimension, instead of tokenizing the raw base64 string. This prevents massive over-counting (a 10MB image payload is ~107K tokens, not 7M) and ensures budget enforcement and tier checks operate on accurate numbers.
|
|
708
848
|
|
|
709
849
|
**Streaming with zero added latency.** SSE streams are forwarded byte-for-byte. Text deltas are accumulated in the background for response tagging.
|
|
710
850
|
|
|
711
|
-
**Error-resilient.** If the engine fails, the request is forwarded to upstream unmodified. The proxy never blocks your LLM calls.
|
|
851
|
+
**Error-resilient.** If the engine fails, the request is forwarded to upstream unmodified. The proxy never blocks your LLM calls. If VC enrichment produces a larger payload than the original, the bloat fallback reverts to a pure passthrough with the original client body.
|
|
712
852
|
|
|
713
853
|
**Envelope stripping + metadata extraction.** Strips client metadata while extracting sender identity and timestamps from labeled JSON blocks. Group chat participants appear as "Sania" and "Yur" instead of generic "User". Original message timestamps give segments accurate chronological ordering.
|
|
714
854
|
|
|
@@ -766,8 +906,13 @@ Plugin for OpenClaw agents using lifecycle hooks for sync retrieval (`message.pr
|
|
|
766
906
|
| **ProxyServer** | `proxy/server.py` | HTTP proxy factory (`create_app`), delegates to state/registry/handlers |
|
|
767
907
|
| **ProxyState** | `proxy/state.py` | Session state machine: ingestion, tagging, compaction lifecycle |
|
|
768
908
|
| **SessionRegistry** | `proxy/registry.py` | Multi-session routing with fingerprint matching |
|
|
909
|
+
| **MessageFilter** | `proxy/message_filter.py` | Chain collapse, turn grouping, budget enforcement, fill pass, upstream trim |
|
|
910
|
+
| **TokenCounter** | `token_counter.py` | Image-aware token counting (Anthropic formula for images, tiktoken/estimate for text) |
|
|
911
|
+
| **MediaCompressor** | `proxy/media.py` | Base64 image compression, disk storage, intrusion detection |
|
|
912
|
+
| **ConversationIdentity** | `conversation_identity.py` | Stable per-format conversation ID hashing, marker extraction |
|
|
769
913
|
| **ProxyHandlers** | `proxy/handlers.py` | Streaming/non-streaming/passthrough HTTP request handlers |
|
|
770
914
|
| **MultiInstance** | `proxy/multi.py` | Multi-instance launcher: N uvicorn listeners, shared or per-port engine/store |
|
|
915
|
+
| **RedisSessionCache** | `proxy/redis_cache.py` | Write-through history cache for lossless restarts |
|
|
771
916
|
| **ProxyDashboard** | `proxy/dashboard.py` | Live SSE dashboard with request grid, turn inspector, session stats (auth-gated mutations) |
|
|
772
917
|
| **ProxyMetrics** | `proxy/metrics.py` | Thread-safe event collector with bounded deque + request capture ring buffer |
|
|
773
918
|
|
|
@@ -816,7 +961,7 @@ Both providers reuse a persistent `httpx.Client` across calls (connection poolin
|
|
|
816
961
|
|
|
817
962
|
**Tag preservation.** During compaction, the LLM can add refined tags but never remove original ones. A segment tagged `[ux, recipes, frontend]` stays tagged with all three even after summarization, ensuring cross-topic retrieval always works.
|
|
818
963
|
|
|
819
|
-
**Tool chain
|
|
964
|
+
**Tool chain collapse.** Historical tool chains (assistant `tool_use` → user `tool_result` → assistant response, and their OpenAI/Gemini equivalents) are collapsed into compact stubs containing tool names, truncated previews, and content-addressed restore refs. This is the single largest compression lever: a 937K-token payload with 52 tool chains collapses to ~65K. Full tool output is stored durably and recoverable via `vc_restore_tool`. Deep compaction drops stubs entirely past a configurable age threshold. The history filter preserves API-required message dependencies atomically; tool chains are never partially broken.
|
|
820
965
|
|
|
821
966
|
**The virtual memory analogy is literal, not metaphorical.** Every component in VC maps to a systems-level equivalent:
|
|
822
967
|
|