virtual-context 0.3.8__tar.gz → 0.4__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.3.8 → virtual_context-0.4}/CLAUDE.md +3 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/PKG-INFO +1 -1
- {virtual_context-0.3.8 → virtual_context-0.4}/pyproject.toml +1 -1
- {virtual_context-0.3.8 → virtual_context-0.4}/specs/cache-aware-compaction.md +93 -93
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/haiku/test_compaction.py +3 -3
- virtual_context-0.4/tests/test_active_compaction_op_invariants.py +172 -0
- virtual_context-0.4/tests/test_canonical_row_epoch_guards.py +224 -0
- virtual_context-0.4/tests/test_canonical_row_epoch_guards_postgres.py +200 -0
- virtual_context-0.4/tests/test_canonical_turn_grouping.py +206 -0
- virtual_context-0.4/tests/test_canonical_turns_schema.py +32 -0
- virtual_context-0.4/tests/test_canonical_turns_schema_postgres.py +47 -0
- virtual_context-0.4/tests/test_claim_compaction_lease_signature.py +147 -0
- virtual_context-0.4/tests/test_claim_compaction_lease_signature_postgres.py +114 -0
- virtual_context-0.4/tests/test_cleanup_abandoned_compaction.py +329 -0
- virtual_context-0.4/tests/test_cleanup_abandoned_compaction_postgres.py +381 -0
- virtual_context-0.4/tests/test_compacting_phase_takeover.py +834 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_compaction_commit_prune.py +3 -23
- virtual_context-0.4/tests/test_compaction_heartbeat_sidecar.py +139 -0
- virtual_context-0.4/tests/test_compaction_lease_lost_abort.py +54 -0
- virtual_context-0.4/tests/test_compaction_lifecycle.py +170 -0
- virtual_context-0.4/tests/test_compaction_lifecycle_contract.py +69 -0
- virtual_context-0.4/tests/test_compaction_lifecycle_wiring.py +167 -0
- virtual_context-0.4/tests/test_compaction_operation.py +80 -0
- virtual_context-0.4/tests/test_compaction_operation_crud.py +396 -0
- virtual_context-0.4/tests/test_compaction_operation_crud_postgres.py +400 -0
- virtual_context-0.4/tests/test_compaction_operation_id_uuid.py +94 -0
- virtual_context-0.4/tests/test_compaction_operation_postgres.py +50 -0
- virtual_context-0.4/tests/test_compaction_ownership_guards.py +742 -0
- virtual_context-0.4/tests/test_compaction_progress_event.py +84 -0
- virtual_context-0.4/tests/test_compaction_resume_e2e_postgres.py +342 -0
- virtual_context-0.4/tests/test_compaction_resume_schema_migration.py +99 -0
- virtual_context-0.4/tests/test_compaction_resume_schema_migration_postgres.py +173 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_composite_store.py +12 -2
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_conversation_coverage_report.py +44 -2
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_conversation_lifecycle.py +7 -5
- virtual_context-0.4/tests/test_conversation_schema.py +19 -0
- virtual_context-0.4/tests/test_conversation_schema_postgres.py +35 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_deferred_payload_compaction.py +174 -77
- virtual_context-0.4/tests/test_delete_resurrect_epoch_races.py +179 -0
- virtual_context-0.4/tests/test_engine_event_bus.py +65 -0
- virtual_context-0.4/tests/test_engine_lifecycle_epoch.py +146 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_engine_state.py +210 -40
- virtual_context-0.4/tests/test_engine_sync_turns.py +844 -0
- virtual_context-0.4/tests/test_filesystem_store_compaction_compat.py +172 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_find_quote.py +26 -26
- virtual_context-0.4/tests/test_find_stale_lease_helpers.py +192 -0
- virtual_context-0.4/tests/test_handle_prepare_payload.py +707 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_history_filter.py +1 -1
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_ingest_index_integrity.py +8 -8
- virtual_context-0.4/tests/test_ingest_reconciler_append_fastpath.py +390 -0
- virtual_context-0.4/tests/test_ingest_reconciler_epoch.py +142 -0
- virtual_context-0.4/tests/test_ingestion_compaction_full_cycle.py +227 -0
- virtual_context-0.4/tests/test_ingestion_db_sweep_catchup.py +222 -0
- virtual_context-0.4/tests/test_ingestion_episode.py +74 -0
- virtual_context-0.4/tests/test_ingestion_episode_crud.py +198 -0
- virtual_context-0.4/tests/test_ingestion_episode_crud_postgres.py +301 -0
- virtual_context-0.4/tests/test_ingestion_episode_postgres.py +50 -0
- virtual_context-0.4/tests/test_ingestion_finalize_self_heal.py +291 -0
- virtual_context-0.4/tests/test_ingestion_progress_event.py +75 -0
- virtual_context-0.4/tests/test_ingestion_regression_resume.py +471 -0
- virtual_context-0.4/tests/test_legacy_ingestion_lease_ownership.py +1095 -0
- virtual_context-0.4/tests/test_lifecycle_epoch.py +47 -0
- virtual_context-0.4/tests/test_lifecycle_epoch_store.py +221 -0
- virtual_context-0.4/tests/test_lifecycle_epoch_store_postgres.py +290 -0
- virtual_context-0.4/tests/test_mark_canonical_turns_compacted_all_halves.py +194 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_monitor.py +1 -1
- virtual_context-0.4/tests/test_multi_worker_sliding_window.py +247 -0
- virtual_context-0.4/tests/test_neo4j_falkordb_compaction_compat.py +81 -0
- virtual_context-0.4/tests/test_orphan_compacting_repair.py +102 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_paging.py +10 -10
- virtual_context-0.4/tests/test_phase_transition_events.py +112 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_postgres_store.py +75 -0
- virtual_context-0.4/tests/test_progress_event_bus.py +91 -0
- virtual_context-0.4/tests/test_progress_events.py +74 -0
- virtual_context-0.4/tests/test_progress_snapshot.py +202 -0
- virtual_context-0.4/tests/test_progress_snapshot_postgres.py +334 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_proxy.py +175 -5
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_proxy_dashboard.py +6 -6
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_proxy_message_filter.py +6 -6
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_proxy_session.py +277 -8
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_proxy_streaming.py +1 -1
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_raw_content.py +14 -17
- virtual_context-0.4/tests/test_refresh_compaction_heartbeat.py +85 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_request_captures_persistence.py +110 -18
- virtual_context-0.4/tests/test_request_metadata.py +187 -0
- virtual_context-0.4/tests/test_request_metadata_postgres.py +196 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_semantic_search.py +7 -7
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_sender_identity.py +2 -2
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_session_date.py +109 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_session_state.py +31 -3
- virtual_context-0.4/tests/test_sqlite_schema_add_column_race.py +99 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_store_recovery.py +9 -8
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tag_splitter.py +4 -4
- virtual_context-0.4/tests/test_tagger_loop.py +231 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tool_loop.py +1 -1
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tool_result_filter.py +7 -8
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tool_tags.py +4 -4
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_turn_tag_index.py +2 -2
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_vcattach.py +20 -9
- virtual_context-0.4/tests_by_section.sh +248 -0
- virtual_context-0.4/tests_timing_report.md +72 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/__init__.py +1 -1
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/cli/main.py +2 -3
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/assembler.py +6 -1
- virtual_context-0.4/virtual_context/core/canonical_turns.py +96 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/compaction_pipeline.py +253 -124
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/compactor.py +18 -2
- virtual_context-0.4/virtual_context/core/composite_store.py +1096 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/conversation_store.py +10 -6
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/coverage_report.py +12 -2
- virtual_context-0.4/virtual_context/core/event_bus.py +43 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/hint_builder.py +10 -11
- virtual_context-0.4/virtual_context/core/ingest_reconciler.py +844 -0
- virtual_context-0.4/virtual_context/core/lifecycle_epoch.py +25 -0
- virtual_context-0.4/virtual_context/core/progress_events.py +37 -0
- virtual_context-0.4/virtual_context/core/progress_snapshot.py +40 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/protocols.py +109 -24
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/provider_adapters.py +12 -4
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/quote_search.py +12 -29
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/retrieval_assembler.py +9 -6
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/search_engine.py +3 -3
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/segmenter.py +1 -1
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/semantic_search.py +26 -36
- virtual_context-0.4/virtual_context/core/store.py +1112 -0
- virtual_context-0.4/virtual_context/core/tagging_pipeline.py +1514 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/tool_query.py +1 -1
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/turn_tag_index.py +38 -6
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/engine.py +524 -299
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/dashboard.html +7 -7
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/dashboard.py +8 -8
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/formats.py +237 -115
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/handlers.py +37 -19
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/helpers.py +17 -6
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/message_filter.py +193 -27
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/metrics.py +29 -8
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/server.py +221 -61
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/session_state.py +65 -21
- virtual_context-0.4/virtual_context/proxy/state.py +3854 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/storage/falkordb.py +18 -2
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/storage/filesystem.py +37 -129
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/storage/neo4j.py +18 -2
- virtual_context-0.4/virtual_context/storage/postgres.py +5302 -0
- virtual_context-0.4/virtual_context/storage/sqlite.py +5987 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/types.py +105 -29
- virtual_context-0.3.8/tests/test_engine_sync_turns.py +0 -195
- virtual_context-0.3.8/tests/test_full_text_backfill.py +0 -478
- virtual_context-0.3.8/virtual_context/core/composite_store.py +0 -546
- virtual_context-0.3.8/virtual_context/core/full_text_backfill.py +0 -691
- virtual_context-0.3.8/virtual_context/core/store.py +0 -627
- virtual_context-0.3.8/virtual_context/core/tagging_pipeline.py +0 -907
- virtual_context-0.3.8/virtual_context/proxy/state.py +0 -1967
- virtual_context-0.3.8/virtual_context/storage/postgres.py +0 -2900
- virtual_context-0.3.8/virtual_context/storage/sqlite.py +0 -3676
- {virtual_context-0.3.8 → virtual_context-0.4}/.github/workflows/python-publish.yml +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/.gitignore +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/LICENSE +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/README.md +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/assets/dashboard.png +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/assets/hero.png +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/models.yaml +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/scripts/install.ps1 +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/scripts/install.sh +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/REGRESSION_MAP.md +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/conftest.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/docker-compose.test.yml +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/haiku/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/haiku/conftest.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/haiku/test_retrieval.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/haiku/test_tagging.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/ollama/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/ollama/conftest.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/ollama/test_compactor.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/ollama/test_pipeline.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/ollama/test_provider.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/ollama/test_tag_generator.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/proxy/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/proxy/test_dashboard_cors.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/proxy/test_metrics.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_assembler.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_backend_integration.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_beam_judge.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_budget_enforcement.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_cli_init.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_compactor.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_compactor_concurrent.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_config.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_context_bleed.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_conversation_identity.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_conversation_scoping.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_embedding_tag_generator.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_empty_turn_skip.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_engine_integration.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_engine_lookback.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_fact_enrichment.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_fact_graph_integration.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_fact_link_checker.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_fact_link_query.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_fact_link_types.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_fact_links_sqlite.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_fact_redesign.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_fill_pass.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_format_agnostic.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_headless.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_idf_retrieval.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_longmemeval_auth.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_mcp_server.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_media.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_message_filter.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_metrics_persistence.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_model_catalog.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_model_limits.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_multi_instance.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_noop_fact_link_store.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_openrouter_provider.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_passthrough_filter.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_presets.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_prev_context_leak.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_provider_adapters.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_proxy_formats.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_recall_all.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_registry_lifecycle.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_retriever.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_rrf_scoring.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_segmenter.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_session_cache.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_storage_protocols.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_store_sqlite.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_stub_turn_handling.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_supersession.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_supersession_migration.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tag_canonicalizer.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tag_consolidator.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tag_generator.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_telemetry.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_telemetry_integration.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_temporal_resolver.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tool_output_interceptor.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_tui.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_turn_grouping.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_unified_budget.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_upstream_trim.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/tests/test_verb_expansion.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual-context.yaml +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual-context.yaml.example +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/cli/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/config.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/conversation_identity.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/embedding_provider.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/embedding_tag_generator.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/engine_utils.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/fact_query.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/fts_preprocessor.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/llm_utils.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/math_utils.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/model_catalog.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/monitor.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/paging_manager.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/retrieval_scoring.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/retriever.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/tag_canonicalizer.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/tag_consolidator.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/tag_generator.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/tag_scoring.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/tag_splitter.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/telemetry.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/temporal_resolver.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/core/tool_loop.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/data/anthropic-tokenizer/tokenizer.json +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/data/models.yaml +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/ingest/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/ingest/curator.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/ingest/date_resolver.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/ingest/parsers.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/ingest/supersession.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/mcp/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/mcp/server.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/model_limits.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/openclaw/virtual-context.mjs +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/patterns.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/presets/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/presets/agentic.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/presets/base.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/presets/coding.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/providers/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/providers/anthropic.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/providers/base.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/providers/generic_openai.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/providers/ollama_native.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/_envelope.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/media.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/multi.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/registry.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/session_cache.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/static/android-chrome-192x192.png +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/static/android-chrome-512x512.png +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/static/apple-touch-icon.png +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/static/favicon-16x16.png +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/static/favicon-32x32.png +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/static/favicon.ico +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/static/site.webmanifest +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/tool_output_interceptor.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/proxy/vcattach.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/storage/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/storage/helpers.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/storage/noop_fact_link_store.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/token_counter.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/app.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/chat.tcss +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/chat_provider.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/headless.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/modals/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/modals/turn_inspector.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/state.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/widgets/__init__.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/widgets/budget_bar.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/widgets/chat_view.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/widgets/input_box.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/widgets/tag_panel.py +0 -0
- {virtual_context-0.3.8 → virtual_context-0.4}/virtual_context/tui/widgets/turn_list.py +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Virtual Context
|
|
2
2
|
|
|
3
|
+
## WORKFLOW: ONE ISSUE AT A TIME
|
|
4
|
+
Work through benchmark issues one at a time. Do not move on from an issue without an explicit directive from the user. Never suggest moving on to the next issue. The user will decide when to move on.
|
|
5
|
+
|
|
3
6
|
## Environment
|
|
4
7
|
|
|
5
8
|
Always run tests with `.venv/bin/pytest`. Always use `.venv/bin/python` for any Python commands. The project venv must be used for all operations.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: virtual-context
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
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
|
|
@@ -18,8 +18,8 @@ Split every historical-payload mutation into two phases:
|
|
|
18
18
|
|
|
19
19
|
| Phase | When | Where | What it does |
|
|
20
20
|
|-------|------|-------|-------------|
|
|
21
|
-
| **Prepare** | Soft threshold (background) | Compaction pipeline | Summarizes text, archives tool outputs, archives media references, advances `
|
|
22
|
-
| **Flush** | Hard threshold or cache-cold (request-time) | Server request path | Drops raw turns, stubs tool outputs, collapses chains, stubs media, switches to post-flush mode, advances `
|
|
21
|
+
| **Prepare** | Soft threshold (background) | Compaction pipeline | Summarizes text, archives tool outputs, archives media references, advances `compacted_prefix_messages` |
|
|
22
|
+
| **Flush** | Hard threshold or cache-cold (request-time) | Server request path | Drops raw turns, stubs tool outputs, collapses chains, stubs media, switches to post-flush mode, advances `flushed_prefix_messages` |
|
|
23
23
|
|
|
24
24
|
**Core invariant**: While the cache is warm and the payload fits within the hard threshold, the outbound request body is byte-identical to the previous request for all historical content. No operation may rewrite historical messages until the flush gate fires.
|
|
25
25
|
|
|
@@ -30,30 +30,30 @@ Split every historical-payload mutation into two phases:
|
|
|
30
30
|
Two watermarks track compaction progress independently:
|
|
31
31
|
|
|
32
32
|
```
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
compacted_prefix_messages — "archives exist up to here" (set by background compactor)
|
|
34
|
+
flushed_prefix_messages — "raw content removed up to here" (set by flush gate)
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
**Invariant**: `0 <=
|
|
37
|
+
**Invariant**: `0 <= flushed_prefix_messages <= compacted_prefix_messages` always.
|
|
38
38
|
|
|
39
39
|
### Lifecycle
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
Turn 0 Turn 20 Turn 40 Turn 80
|
|
43
43
|
| | | |
|
|
44
|
-
|<--
|
|
44
|
+
|<-- flushed_prefix_messages=0 | | (no mutations applied)
|
|
45
45
|
| | | |
|
|
46
|
-
|<-------
|
|
46
|
+
|<------- compacted_prefix_messages=40 | (summaries + archives ready)
|
|
47
47
|
| | | |
|
|
48
48
|
| | | |
|
|
49
49
|
[--- flush fires at hard threshold ---]
|
|
50
50
|
| | | |
|
|
51
|
-
|<-------
|
|
51
|
+
|<------- flushed_prefix_messages=40 | (raw content removed)
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
### State transitions
|
|
55
55
|
|
|
56
|
-
| Event | `
|
|
56
|
+
| Event | `compacted_prefix_messages` | `flushed_prefix_messages` | Payload |
|
|
57
57
|
|-------|--------------------|--------------------|---------|
|
|
58
58
|
| Initial | 0 | 0 | Raw history |
|
|
59
59
|
| Prepare runs at soft threshold | N | 0 | Unchanged (cache-stable) |
|
|
@@ -61,15 +61,15 @@ flushed_through — "raw content removed up to here" (set by flush gate)
|
|
|
61
61
|
| Flush fires (hard threshold or cache-cold) | M | M | Shrunk: summaries replace raw content |
|
|
62
62
|
| Prepare runs again | P > M | M | Unchanged until next flush |
|
|
63
63
|
|
|
64
|
-
### What `
|
|
64
|
+
### What `compacted_prefix_messages` means now
|
|
65
65
|
|
|
66
|
-
`
|
|
66
|
+
`compacted_prefix_messages` represents the message index up to which ALL archival artifacts exist:
|
|
67
67
|
- Text summaries/segments are stored for these turns
|
|
68
68
|
- Tool outputs in this range have been archived to the store (or will be archived idempotently at flush time by the existing helpers)
|
|
69
69
|
- Media positions in this range are known
|
|
70
70
|
- It is safe to flush any content up to this boundary
|
|
71
71
|
|
|
72
|
-
`
|
|
72
|
+
`flushed_prefix_messages` represents the message index up to which raw content has actually been removed from the outbound payload and replaced with compacted representations.
|
|
73
73
|
|
|
74
74
|
---
|
|
75
75
|
|
|
@@ -95,7 +95,7 @@ class MonitorConfig:
|
|
|
95
95
|
When `defer_payload_mutation` is **False** (default):
|
|
96
96
|
- All behavior is identical to today
|
|
97
97
|
- `soft_threshold` / `hard_threshold` keep their existing defaults (0.70 / 0.85)
|
|
98
|
-
- `
|
|
98
|
+
- `flushed_prefix_messages` tracks `compacted_prefix_messages` automatically (set to `compacted_prefix_messages` on every request)
|
|
99
99
|
- All existing code paths run unchanged
|
|
100
100
|
- `flush_ttl_seconds` is ignored
|
|
101
101
|
|
|
@@ -122,15 +122,15 @@ Operators who enable deferred mode without adjusting thresholds get: compaction
|
|
|
122
122
|
@dataclass
|
|
123
123
|
class EngineState:
|
|
124
124
|
# Existing fields unchanged
|
|
125
|
-
|
|
125
|
+
compacted_prefix_messages: int = 0 # semantics broadened: "all archives ready up to here"
|
|
126
126
|
# ...
|
|
127
127
|
|
|
128
128
|
# NEW
|
|
129
|
-
|
|
129
|
+
flushed_prefix_messages: int = 0 # "raw content removed from payload up to here"
|
|
130
130
|
last_request_time: float = 0.0 # time.time() of last COMPLETED upstream call
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
**Invariant enforcement**: On every update to `
|
|
133
|
+
**Invariant enforcement**: On every update to `flushed_prefix_messages`, assert `flushed_prefix_messages <= compacted_prefix_messages`. If violated, log error and clamp.
|
|
134
134
|
|
|
135
135
|
---
|
|
136
136
|
|
|
@@ -144,8 +144,8 @@ Runs at the soft threshold via the existing compaction pipeline. The compactor a
|
|
|
144
144
|
|----------|-----|---------|
|
|
145
145
|
| Text summaries / segments | Existing compaction pipeline (`compaction_pipeline.py`) | Tag summaries in store |
|
|
146
146
|
| Tool-output archives | Existing helpers archive tool content to store as a side effect of stubbing. For prepare-only mode, the compaction pipeline should pre-archive tool outputs in the compacted range so flush-time archival is a no-op. **Fallback**: the flush-time helpers (`stub_tool_outputs_by_position`, `collapse_turn_chains`) archive idempotently before replacing, so flush still works correctly even if pre-archival was skipped. |
|
|
147
|
-
| Media position metadata | Lightweight: the compacted range boundary (`
|
|
148
|
-
| Flushable range metadata | `
|
|
147
|
+
| Media position metadata | Lightweight: the compacted range boundary (`compacted_prefix_messages`) is sufficient. `stub_media_by_position()` uses the protected window and turn groups to identify stale media at flush time. No separate media index needed. |
|
|
148
|
+
| Flushable range metadata | `compacted_prefix_messages` itself — everything below this boundary has archives and can be safely flushed |
|
|
149
149
|
|
|
150
150
|
### What the prepare phase must NOT do
|
|
151
151
|
|
|
@@ -157,7 +157,7 @@ Runs at the soft threshold via the existing compaction pipeline. The compactor a
|
|
|
157
157
|
- Must not inject paging tools or context hints that reference compacted state
|
|
158
158
|
- Must not modify the outbound request body in any way
|
|
159
159
|
|
|
160
|
-
The prepare phase advances `
|
|
160
|
+
The prepare phase advances `compacted_prefix_messages` only. All payload mutations are reserved for the flush gate.
|
|
161
161
|
|
|
162
162
|
---
|
|
163
163
|
|
|
@@ -170,12 +170,12 @@ The flush gate runs on every inbound request and decides whether to apply payloa
|
|
|
170
170
|
```python
|
|
171
171
|
if not mon.defer_payload_mutation:
|
|
172
172
|
# Legacy mode: flush immediately on every request (today's behavior)
|
|
173
|
-
should_flush = (
|
|
173
|
+
should_flush = (compacted_prefix_messages > flushed_prefix_messages)
|
|
174
174
|
use_two_pass = False
|
|
175
175
|
|
|
176
176
|
elif cache_age >= mon.flush_ttl_seconds:
|
|
177
177
|
# Cache is cold — flush in pass 1 directly (fast path)
|
|
178
|
-
should_flush = (
|
|
178
|
+
should_flush = (compacted_prefix_messages > flushed_prefix_messages)
|
|
179
179
|
use_two_pass = False
|
|
180
180
|
|
|
181
181
|
else:
|
|
@@ -189,8 +189,8 @@ else:
|
|
|
189
189
|
When `should_flush` is True, ALL of the following run as a single batch. The order matters:
|
|
190
190
|
|
|
191
191
|
```
|
|
192
|
-
1. Set
|
|
193
|
-
2. drop_compacted_turns(body, drop_boundary=
|
|
192
|
+
1. Set flushed_prefix_messages = compacted_prefix_messages (tentative — see §6.3)
|
|
193
|
+
2. drop_compacted_turns(body, drop_boundary=compacted_prefix_messages)
|
|
194
194
|
3. collapse_turn_chains(body) [stage 2 — tool chain collapse]
|
|
195
195
|
4. stub_tool_outputs_by_position(body) [protected-zone intrusion]
|
|
196
196
|
5. stub_media_by_position(body)
|
|
@@ -205,8 +205,8 @@ When the cache is warm and `use_two_pass` is True:
|
|
|
205
205
|
|
|
206
206
|
```
|
|
207
207
|
Pass 1: Build the full outbound payload WITHOUT any historical mutations.
|
|
208
|
-
-
|
|
209
|
-
- All payload-affecting code paths use
|
|
208
|
+
- flushed_prefix_messages stays at its current value
|
|
209
|
+
- All payload-affecting code paths use flushed_prefix_messages (still 0 or previous flush)
|
|
210
210
|
- Assembler runs in pre-flush mode (tag-based retrieval, no hints)
|
|
211
211
|
- No tool/media stubbing, no turn dropping, no chain collapse
|
|
212
212
|
- Context injection, paging, and assembly run normally
|
|
@@ -215,23 +215,23 @@ Pass 1: Build the full outbound payload WITHOUT any historical mutations.
|
|
|
215
215
|
Size check: outbound_tokens >= context_window * hard_threshold?
|
|
216
216
|
|
|
217
217
|
Pass 2 (only if size check triggers):
|
|
218
|
-
- Set tentative_flush =
|
|
219
|
-
- Re-run retrieval assembler with
|
|
218
|
+
- Set tentative_flush = compacted_prefix_messages
|
|
219
|
+
- Re-run retrieval assembler with flushed_prefix_messages = tentative_flush
|
|
220
220
|
(switches to post-flush mode: all summaries, no tag filter)
|
|
221
221
|
- Apply all flush operations (steps 1–8 above) on body
|
|
222
222
|
- Re-inject context with post-flush assembled content
|
|
223
223
|
- Re-inject paging tools
|
|
224
224
|
- Re-measure outbound_tokens
|
|
225
|
-
- Commit:
|
|
225
|
+
- Commit: flushed_prefix_messages = tentative_flush (only after dispatch)
|
|
226
226
|
```
|
|
227
227
|
|
|
228
228
|
### Tentative flush and commit
|
|
229
229
|
|
|
230
|
-
Pass 2 uses a local `tentative_flush` variable. `
|
|
230
|
+
Pass 2 uses a local `tentative_flush` variable. `flushed_prefix_messages` on EngineState is only updated after the flushed payload is chosen and the upstream request is dispatched. If reassembly or dispatch fails, `flushed_prefix_messages` stays at its old value and the next request retries. This prevents orphaned state where `flushed_prefix_messages` advances but no flushed payload was ever served.
|
|
231
231
|
|
|
232
232
|
### Cold-cache fast path
|
|
233
233
|
|
|
234
|
-
When `cache_age >= flush_ttl_seconds`, there's no prefix to protect. All flush operations run in a single pass with no reassembly. The assembler is told `
|
|
234
|
+
When `cache_age >= flush_ttl_seconds`, there's no prefix to protect. All flush operations run in a single pass with no reassembly. The assembler is told `flushed_prefix_messages = compacted_prefix_messages` from the start.
|
|
235
235
|
|
|
236
236
|
---
|
|
237
237
|
|
|
@@ -241,7 +241,7 @@ When `cache_age >= flush_ttl_seconds`, there's no prefix to protect. All flush o
|
|
|
241
241
|
|
|
242
242
|
This requires gating **every** operation that rewrites historical messages:
|
|
243
243
|
|
|
244
|
-
### Gated operations (ALL require `
|
|
244
|
+
### Gated operations (ALL require `flushed_prefix_messages > previous_flushed_through`)
|
|
245
245
|
|
|
246
246
|
| Operation | Location | What it rewrites |
|
|
247
247
|
|-----------|----------|-----------------|
|
|
@@ -255,11 +255,11 @@ This requires gating **every** operation that rewrites historical messages:
|
|
|
255
255
|
| VC tool injection | `tool_query.py:111` | Adds VC tools to tool schema |
|
|
256
256
|
| `_pre_compaction` flag | `server.py:723` | Selects which stubbing path runs |
|
|
257
257
|
|
|
258
|
-
**Stage-1 tool stubbing (server.py:780)**: In the current system, this runs on every request regardless of `
|
|
258
|
+
**Stage-1 tool stubbing (server.py:780)**: In the current system, this runs on every request regardless of `compacted_prefix_messages`. When a tool output ages out of the protected window between requests, it gets stubbed, changing a historical message. In deferred mode, this is explicitly gated: stage-1 tool stubbing is **skipped entirely** while the cache is warm. Tool outputs stay raw until flush. This trades payload size for prefix stability — the hard threshold catches the growth.
|
|
259
259
|
|
|
260
260
|
**Media stubbing (server.py:796)**: Same treatment. `stub_media_by_position()` is skipped while cache is warm. Images stay inline until flush. Conversations with many large images will hit the hard threshold sooner, triggering an earlier flush.
|
|
261
261
|
|
|
262
|
-
**`drop_topic_only_stubs()` (server.py:808)**: This removes dead-weight stubs (stubs without restore refs). It only operates on stubs, not raw content. In deferred mode with `
|
|
262
|
+
**`drop_topic_only_stubs()` (server.py:808)**: This removes dead-weight stubs (stubs without restore refs). It only operates on stubs, not raw content. In deferred mode with `flushed_prefix_messages == 0`, there are no stubs to drop, so it's a no-op. No gating needed.
|
|
263
263
|
|
|
264
264
|
**`merge_consecutive_conversational()` (server.py:813)**: This merges adjacent same-role messages to fix alternation violations. It can change message boundaries. In deferred mode, this should only run on new (unflushed) content, or be skipped if the merge would touch historical messages. **Implementation note**: if the merge only ever touches the last few messages (which is the common case), this is safe. If it can reach into historical messages, gate it.
|
|
265
265
|
|
|
@@ -286,35 +286,35 @@ This requires gating **every** operation that rewrites historical messages:
|
|
|
286
286
|
|
|
287
287
|
---
|
|
288
288
|
|
|
289
|
-
## 9. Complete `
|
|
289
|
+
## 9. Complete `compacted_prefix_messages` Audit
|
|
290
290
|
|
|
291
|
-
Every code path that reads `
|
|
291
|
+
Every code path that reads `compacted_prefix_messages` is classified:
|
|
292
292
|
|
|
293
|
-
### Must use `
|
|
293
|
+
### Must use `flushed_prefix_messages` (payload-affecting paths)
|
|
294
294
|
|
|
295
295
|
| Location | Current code | Why |
|
|
296
296
|
|----------|-------------|-----|
|
|
297
|
-
| `retrieval_assembler.py:112` | `if
|
|
298
|
-
| `retrieval_assembler.py:141` | `post_compaction=(
|
|
299
|
-
| `retrieval_assembler.py:199` | `post_compaction=(
|
|
300
|
-
| `retrieval_assembler.py:336` | `watermark =
|
|
301
|
-
| `retrieval_assembler.py:419` | `if
|
|
302
|
-
| `server.py:685` | `if
|
|
303
|
-
| `server.py:723` | `_pre_compaction =
|
|
304
|
-
| `server.py:744` | `_ct = int(
|
|
305
|
-
| `server.py:831` | `compacted_count = int(
|
|
306
|
-
| `tool_query.py:111` | `
|
|
297
|
+
| `retrieval_assembler.py:112` | `if compacted_prefix_messages > 0: active_tags = []` | Suppresses tag retrieval; only after raw turns are gone |
|
|
298
|
+
| `retrieval_assembler.py:141` | `post_compaction=(compacted_prefix_messages > 0)` | Enables summary-floor injection in retriever |
|
|
299
|
+
| `retrieval_assembler.py:199` | `post_compaction=(compacted_prefix_messages > 0)` | Same (retry path) |
|
|
300
|
+
| `retrieval_assembler.py:336` | `watermark = compacted_prefix_messages` | Turn filtering in `_filter_irrelevant_turns()` |
|
|
301
|
+
| `retrieval_assembler.py:419` | `if compacted_prefix_messages == 0: return ""` | Gates `_build_context_hint()` emission |
|
|
302
|
+
| `server.py:685` | `if compacted_prefix_messages > 0: drop_compacted_turns(...)` | Gated by flush (§6) |
|
|
303
|
+
| `server.py:723` | `_pre_compaction = compacted_prefix_messages == 0` | Selects stage-1 vs stage-2 stubbing path |
|
|
304
|
+
| `server.py:744` | `_ct = int(compacted_prefix_messages)` | Post-compaction chain collapse |
|
|
305
|
+
| `server.py:831` | `compacted_count = int(compacted_prefix_messages)` | Paging tool injection |
|
|
306
|
+
| `tool_query.py:111` | `compacted_prefix_messages > 0` | VC tool injection |
|
|
307
307
|
|
|
308
308
|
### Must be parameterized (`history_offset`)
|
|
309
309
|
|
|
310
310
|
| Location | Current code | Fix |
|
|
311
311
|
|----------|-------------|-----|
|
|
312
|
-
| `types.py:317` | `history_offset()` returns `
|
|
313
|
-
| `retrieval_assembler.py:119,274,359` | Callers of `history_offset()` | Pass `watermark=
|
|
314
|
-
| `compaction_pipeline.py:128,179` | Callers of `history_offset()` | Pass no watermark (default = `
|
|
315
|
-
| `tagging_pipeline.py:337` | Caller of `history_offset()` | Pass no watermark (default = `
|
|
312
|
+
| `types.py:317` | `history_offset()` returns `compacted_prefix_messages` | Add `watermark` parameter (see §9.1) |
|
|
313
|
+
| `retrieval_assembler.py:119,274,359` | Callers of `history_offset()` | Pass `watermark=flushed_prefix_messages` |
|
|
314
|
+
| `compaction_pipeline.py:128,179` | Callers of `history_offset()` | Pass no watermark (default = `compacted_prefix_messages`) |
|
|
315
|
+
| `tagging_pipeline.py:337` | Caller of `history_offset()` | Pass no watermark (default = `compacted_prefix_messages`) |
|
|
316
316
|
|
|
317
|
-
### Keep `
|
|
317
|
+
### Keep `compacted_prefix_messages` (compaction/storage paths)
|
|
318
318
|
|
|
319
319
|
| Location | Why it stays |
|
|
320
320
|
|----------|-------------|
|
|
@@ -324,46 +324,46 @@ Every code path that reads `compacted_through` is classified:
|
|
|
324
324
|
| `engine.py:892,979,981,1036,1089` | Snapshot save/diagnostic |
|
|
325
325
|
| `engine.py:1235,1236,1301,1304,1329,1334` | Checkpoint comparison |
|
|
326
326
|
|
|
327
|
-
### Informational (add `
|
|
327
|
+
### Informational (add `flushed_prefix_messages` alongside)
|
|
328
328
|
|
|
329
329
|
| Location | Change |
|
|
330
330
|
|----------|--------|
|
|
331
331
|
| `dashboard.py:272,522,739,1098` | Display both watermarks |
|
|
332
332
|
| `helpers.py:510` | Log both watermarks |
|
|
333
333
|
| `state.py:473,574,796` | Log both watermarks |
|
|
334
|
-
| `dashboard.html:615,789,1142,1144,1178,1399,1826` | Show `
|
|
334
|
+
| `dashboard.html:615,789,1142,1144,1178,1399,1826` | Show `flushed_prefix_messages` indicator |
|
|
335
335
|
|
|
336
336
|
### 9.1 `history_offset()` Parameterization
|
|
337
337
|
|
|
338
|
-
`history_offset()` (`types.py:317`) is shared by compaction/tagging (need `
|
|
338
|
+
`history_offset()` (`types.py:317`) is shared by compaction/tagging (need `compacted_prefix_messages`) and payload-assembly (need `flushed_prefix_messages`). A blanket swap breaks compaction.
|
|
339
339
|
|
|
340
340
|
**Fix**: Add an optional `watermark` parameter:
|
|
341
341
|
|
|
342
342
|
```python
|
|
343
343
|
def history_offset(self, history_len: int, *, total_turns_indexed: int | None = None,
|
|
344
344
|
watermark: int | None = None) -> int:
|
|
345
|
-
wm = watermark if watermark is not None else self.
|
|
345
|
+
wm = watermark if watermark is not None else self.compacted_prefix_messages
|
|
346
346
|
if wm < history_len:
|
|
347
347
|
return wm
|
|
348
348
|
# ... rest of sliding-window logic uses wm
|
|
349
349
|
```
|
|
350
350
|
|
|
351
|
-
- Compaction/tagging callers: no `watermark` arg (defaults to `
|
|
352
|
-
- Retrieval assembler callers: `watermark=self._engine_state.
|
|
351
|
+
- Compaction/tagging callers: no `watermark` arg (defaults to `compacted_prefix_messages`)
|
|
352
|
+
- Retrieval assembler callers: `watermark=self._engine_state.flushed_prefix_messages`
|
|
353
353
|
|
|
354
354
|
---
|
|
355
355
|
|
|
356
356
|
## 10. `drop_compacted_turns()` Boundary Fix
|
|
357
357
|
|
|
358
|
-
**Current behavior** (`message_filter.py:681–753`): Uses `
|
|
358
|
+
**Current behavior** (`message_filter.py:681–753`): Uses `compacted_prefix_messages` as a gate only (`> 0` → enabled). Once enabled, drops ALL non-tool turns outside the protected window via `range(protected_start)`, regardless of whether those turns have summaries.
|
|
359
359
|
|
|
360
|
-
**Problem**: When flush fires after multiple deferred prepare rounds, `
|
|
360
|
+
**Problem**: When flush fires after multiple deferred prepare rounds, `compacted_prefix_messages` might be at turn 50 but 50 more turns have accumulated. Protected window covers the last 6. The function drops turns 0–94, but only 0–49 have summaries.
|
|
361
361
|
|
|
362
362
|
**Fix**: Add `drop_boundary` parameter. Only drop turns below the boundary:
|
|
363
363
|
|
|
364
364
|
```python
|
|
365
365
|
def drop_compacted_turns(
|
|
366
|
-
body, turn_tag_index,
|
|
366
|
+
body, turn_tag_index, compacted_prefix_messages,
|
|
367
367
|
*, fmt=None, protected_recent_turns=6,
|
|
368
368
|
drop_boundary: int | None = None, # NEW
|
|
369
369
|
) -> tuple[dict, int]:
|
|
@@ -374,35 +374,35 @@ def drop_compacted_turns(
|
|
|
374
374
|
# ... existing drop logic
|
|
375
375
|
```
|
|
376
376
|
|
|
377
|
-
Flush gate calls: `drop_compacted_turns(body, ..., drop_boundary=
|
|
377
|
+
Flush gate calls: `drop_compacted_turns(body, ..., drop_boundary=compacted_prefix_messages)`
|
|
378
378
|
|
|
379
379
|
---
|
|
380
380
|
|
|
381
381
|
## 11. Complete Persistence Surface
|
|
382
382
|
|
|
383
|
-
Both `
|
|
383
|
+
Both `flushed_prefix_messages` and `last_request_time` must be persisted across ALL serialization paths.
|
|
384
384
|
|
|
385
385
|
### Write-side (SAVE)
|
|
386
386
|
|
|
387
387
|
| Location | What to add |
|
|
388
388
|
|----------|------------|
|
|
389
|
-
| `engine.py:891` (`extract_session_state()`) | `
|
|
389
|
+
| `engine.py:891` (`extract_session_state()`) | `flushed_prefix_messages=self._engine_state.flushed_prefix_messages, last_request_time=self._engine_state.last_request_time` |
|
|
390
390
|
| `engine.py:979` (`EngineStateSnapshot(...)` constructor) | Same two fields |
|
|
391
391
|
|
|
392
392
|
### Dataclasses
|
|
393
393
|
|
|
394
394
|
| Location | What to add |
|
|
395
395
|
|----------|------------|
|
|
396
|
-
| `types.py` EngineState | `
|
|
397
|
-
| `types.py` EngineStateSnapshot | `
|
|
398
|
-
| `session_state.py` SessionState | `
|
|
396
|
+
| `types.py` EngineState | `flushed_prefix_messages: int = 0`, `last_request_time: float = 0.0` |
|
|
397
|
+
| `types.py` EngineStateSnapshot | `flushed_prefix_messages: int = 0`, `last_request_time: float = 0.0` |
|
|
398
|
+
| `session_state.py` SessionState | `flushed_prefix_messages: int = 0`, `last_request_time: float = 0.0` |
|
|
399
399
|
|
|
400
400
|
### Serialization (SessionState)
|
|
401
401
|
|
|
402
402
|
| Location | What to add |
|
|
403
403
|
|----------|------------|
|
|
404
404
|
| `session_state.py:39–58` (`to_json()`) | Include both fields in dict |
|
|
405
|
-
| `session_state.py:60–80` (`from_json()`) | Read with `.get("
|
|
405
|
+
| `session_state.py:60–80` (`from_json()`) | Read with `.get("flushed_prefix_messages", 0)` etc. |
|
|
406
406
|
|
|
407
407
|
### Conversion (SessionState ↔ EngineStateSnapshot)
|
|
408
408
|
|
|
@@ -415,9 +415,9 @@ Both `flushed_through` and `last_request_time` must be persisted across ALL seri
|
|
|
415
415
|
|
|
416
416
|
| Location | What to add |
|
|
417
417
|
|----------|------------|
|
|
418
|
-
| `engine.py:554` | `self._engine_state.
|
|
419
|
-
| `engine.py:637` | `self._engine_state.
|
|
420
|
-
| `engine.py:773` | `self._engine_state.
|
|
418
|
+
| `engine.py:554` | `self._engine_state.flushed_prefix_messages = saved.flushed_prefix_messages` |
|
|
419
|
+
| `engine.py:637` | `self._engine_state.flushed_prefix_messages = es.get("flushed_prefix_messages", 0)` |
|
|
420
|
+
| `engine.py:773` | `self._engine_state.flushed_prefix_messages = state.flushed_prefix_messages` |
|
|
421
421
|
| Same three lines | Parallel for `last_request_time` |
|
|
422
422
|
|
|
423
423
|
### Storage backends
|
|
@@ -426,7 +426,7 @@ Both `flushed_through` and `last_request_time` must be persisted across ALL seri
|
|
|
426
426
|
|----------|--------|
|
|
427
427
|
| `filesystem.py:677` (save) | Add both fields to dict |
|
|
428
428
|
| `filesystem.py:737` (load) | Read with defaults |
|
|
429
|
-
| `sqlite.py:75` (schema) | `ALTER TABLE engine_state ADD COLUMN
|
|
429
|
+
| `sqlite.py:75` (schema) | `ALTER TABLE engine_state ADD COLUMN flushed_prefix_messages INTEGER NOT NULL DEFAULT 0; ADD COLUMN last_request_time REAL NOT NULL DEFAULT 0.0;` |
|
|
430
430
|
| `sqlite.py:1582` (insert) | Include new columns |
|
|
431
431
|
| `sqlite.py:1608,1628,1664` (load) | Read new columns |
|
|
432
432
|
| `postgres.py:85` (schema) | Same DDL |
|
|
@@ -436,7 +436,7 @@ Both `flushed_through` and `last_request_time` must be persisted across ALL seri
|
|
|
436
436
|
### Backward compatibility on deserialize
|
|
437
437
|
|
|
438
438
|
All new fields default to 0/0.0. Old state without these fields deserializes correctly. On first request after upgrade:
|
|
439
|
-
- `
|
|
439
|
+
- `flushed_prefix_messages = 0` + `compacted_prefix_messages > 0` → pre-flush mode
|
|
440
440
|
- `last_request_time = 0.0` → `cache_age` huge → cold-cache fast path → immediate flush
|
|
441
441
|
|
|
442
442
|
First request flushes pending compaction, then subsequent requests benefit from deferral.
|
|
@@ -448,13 +448,13 @@ First request flushes pending compaction, then subsequent requests benefit from
|
|
|
448
448
|
### Defer event
|
|
449
449
|
|
|
450
450
|
```
|
|
451
|
-
DEFER-PAYLOAD: cache_age=%.1fs outbound_tokens=%d threshold=%d
|
|
451
|
+
DEFER-PAYLOAD: cache_age=%.1fs outbound_tokens=%d threshold=%d compacted_prefix_messages=%d flushed_prefix_messages=%d — preserving prefix
|
|
452
452
|
```
|
|
453
453
|
|
|
454
454
|
### Flush event
|
|
455
455
|
|
|
456
456
|
```
|
|
457
|
-
FLUSH-PAYLOAD: reason=%s cache_age=%.1fs outbound_tokens=%d
|
|
457
|
+
FLUSH-PAYLOAD: reason=%s cache_age=%.1fs outbound_tokens=%d compacted_prefix_messages=%d flushed_prefix_messages=%d→%d turns_dropped=%d tools_stubbed=%d media_stubbed=%d
|
|
458
458
|
```
|
|
459
459
|
|
|
460
460
|
Where `reason` is one of: `cold_cache`, `size_threshold`, `legacy_immediate`.
|
|
@@ -492,13 +492,13 @@ This helper at `server.py:813` merges adjacent same-role messages. If a merge re
|
|
|
492
492
|
Pass 1 measures full payload. Exceeds threshold. Pass 2 flushes and reassembles. Subsequent requests benefit from deferral.
|
|
493
493
|
|
|
494
494
|
### Multiple prepare rounds pending
|
|
495
|
-
Prepare runs several times advancing `
|
|
495
|
+
Prepare runs several times advancing `compacted_prefix_messages` from N to M to P. `flushed_prefix_messages` stays at 0. Retrieval assembler stays in pre-flush mode (no duplicate content). On flush, `flushed_prefix_messages` jumps to P. `drop_compacted_turns(drop_boundary=P)` drops only summarized turns.
|
|
496
496
|
|
|
497
497
|
### Slow conversations (>5 min between turns)
|
|
498
498
|
Every request finds `cache_age >= flush_ttl_seconds`. Cold-cache fast path. Behaves like today. No deferral benefit (correct: cache already expired).
|
|
499
499
|
|
|
500
|
-
### `
|
|
501
|
-
Assert invariant on every update. If violated, log error and clamp: `
|
|
500
|
+
### `flushed_prefix_messages > compacted_prefix_messages` guard
|
|
501
|
+
Assert invariant on every update. If violated, log error and clamp: `flushed_prefix_messages = min(flushed_prefix_messages, compacted_prefix_messages)`.
|
|
502
502
|
|
|
503
503
|
### Cross-worker handoff
|
|
504
504
|
Both watermarks persisted in Redis checkpoint and store backends. New worker restores correct state. If checkpoint is lost, defaults to 0/0.0 → cold-cache flush on first request.
|
|
@@ -507,7 +507,7 @@ Both watermarks persisted in Redis checkpoint and store backends. New worker res
|
|
|
507
507
|
Warm-cache-near-limit path pays for two assembly cycles. Fires once per flush cycle (when payload first hits hard threshold). For 200k window with soft=0.30 and hard=0.97, the payload grows from ~60k to ~194k over many turns before this triggers. Expected: rare, bounded cost.
|
|
508
508
|
|
|
509
509
|
### `defer_payload_mutation=False` with new fields
|
|
510
|
-
`
|
|
510
|
+
`flushed_prefix_messages` is set to `compacted_prefix_messages` on every request (auto-tracking). All existing code paths run unchanged. The new fields exist but are no-ops. Zero behavioral change.
|
|
511
511
|
|
|
512
512
|
---
|
|
513
513
|
|
|
@@ -517,21 +517,21 @@ Warm-cache-near-limit path pays for two assembly cycles. Fires once per flush cy
|
|
|
517
517
|
|
|
518
518
|
| Test | Verifies |
|
|
519
519
|
|------|----------|
|
|
520
|
-
| `test_defer_off_identical_to_today` | `defer_payload_mutation=False` → exact same behavior, `
|
|
521
|
-
| `test_prepare_does_not_mutate_body` | Prepare phase advances `
|
|
520
|
+
| `test_defer_off_identical_to_today` | `defer_payload_mutation=False` → exact same behavior, `flushed_prefix_messages` auto-tracks `compacted_prefix_messages` |
|
|
521
|
+
| `test_prepare_does_not_mutate_body` | Prepare phase advances `compacted_prefix_messages` but body is unchanged |
|
|
522
522
|
| `test_warm_cache_skips_all_mutations` | Cache warm + below threshold → no turn drops, no tool stubs, no media stubs, no chain collapse |
|
|
523
|
-
| `test_flush_at_hard_threshold` | Warm cache + outbound >= threshold → all mutations applied, `
|
|
523
|
+
| `test_flush_at_hard_threshold` | Warm cache + outbound >= threshold → all mutations applied, `flushed_prefix_messages` advanced |
|
|
524
524
|
| `test_flush_when_cache_cold` | `cache_age >= TTL` → cold-cache fast path, all mutations applied |
|
|
525
525
|
| `test_stage1_tool_stubbing_gated` | Warm cache → stage-1 `stub_tool_outputs_by_position()` is skipped (tool outputs stay raw) |
|
|
526
526
|
| `test_media_stubbing_gated` | Warm cache → `stub_media_by_position()` is skipped (images stay inline) |
|
|
527
527
|
| `test_chain_collapse_gated` | Warm cache → `collapse_turn_chains()` is skipped |
|
|
528
|
-
| `test_assembler_uses_flushed_through` | `
|
|
529
|
-
| `test_assembler_post_flush` | `
|
|
528
|
+
| `test_assembler_uses_flushed_through` | `compacted_prefix_messages=10, flushed_prefix_messages=0` → pre-flush mode (tag retrieval, no hints) |
|
|
529
|
+
| `test_assembler_post_flush` | `compacted_prefix_messages=10, flushed_prefix_messages=10` → post-flush mode (all summaries, hints) |
|
|
530
530
|
| `test_drop_respects_boundary` | `drop_boundary=50` with 100 turns → only turns below boundary dropped |
|
|
531
|
-
| `test_history_offset_parameterized` | Assembler callers pass `
|
|
531
|
+
| `test_history_offset_parameterized` | Assembler callers pass `flushed_prefix_messages`; compaction callers use default `compacted_prefix_messages` |
|
|
532
532
|
| `test_timestamp_set_after_dispatch` | `last_request_time` updated after upstream call, not before gate |
|
|
533
|
-
| `test_tentative_flush_not_committed_on_failure` | If pass-2 dispatch fails, `
|
|
534
|
-
| `test_invariant_enforcement` | Setting `
|
|
533
|
+
| `test_tentative_flush_not_committed_on_failure` | If pass-2 dispatch fails, `flushed_prefix_messages` stays at old value |
|
|
534
|
+
| `test_invariant_enforcement` | Setting `flushed_prefix_messages > compacted_prefix_messages` → error log + clamp |
|
|
535
535
|
| `test_persistence_roundtrip` | Save/restore round-trips both watermarks through all paths: SessionState, EngineStateSnapshot, filesystem, sqlite, postgres |
|
|
536
536
|
| `test_two_pass_reassembles_context` | Pass 2 re-runs assembler in post-flush mode with correct summaries |
|
|
537
537
|
|
|
@@ -539,10 +539,10 @@ Warm-cache-near-limit path pays for two assembly cycles. Fires once per flush cy
|
|
|
539
539
|
|
|
540
540
|
1. Tenant with `defer_payload_mutation=true, soft_threshold=0.30, hard_threshold=0.97`
|
|
541
541
|
2. Send requests until prepare fires at soft threshold
|
|
542
|
-
3. Verify: `
|
|
542
|
+
3. Verify: `compacted_prefix_messages` advanced, `flushed_prefix_messages=0`, payload byte-identical to previous request (excluding new turns)
|
|
543
543
|
4. Verify: no tool stubs, no media stubs, no chain collapse, no context hints
|
|
544
544
|
5. Continue sending until fully-assembled payload hits 97%
|
|
545
|
-
6. Verify: pass 2 fires. All mutations applied atomically. `
|
|
545
|
+
6. Verify: pass 2 fires. All mutations applied atomically. `flushed_prefix_messages = compacted_prefix_messages`. Payload shrinks.
|
|
546
546
|
7. Verify: metrics report from pass 2 (not pass 1)
|
|
547
547
|
|
|
548
548
|
### Production validation
|
|
@@ -559,15 +559,15 @@ Warm-cache-near-limit path pays for two assembly cycles. Fires once per flush cy
|
|
|
559
559
|
|
|
560
560
|
| File | Change |
|
|
561
561
|
|------|--------|
|
|
562
|
-
| `virtual_context/types.py` | Add `defer_payload_mutation`, `flush_ttl_seconds` to MonitorConfig. Add `
|
|
562
|
+
| `virtual_context/types.py` | Add `defer_payload_mutation`, `flush_ttl_seconds` to MonitorConfig. Add `flushed_prefix_messages`, `last_request_time` to EngineState and EngineStateSnapshot. Add `watermark` param to `history_offset()`. |
|
|
563
563
|
| `virtual_context/proxy/session_state.py` | Add fields to SessionState, `to_json()`, `from_json()`, `_state_to_snapshot()`, `_snapshot_to_state()` |
|
|
564
564
|
| `virtual_context/engine.py` | Write-side: map new fields in `extract_session_state()` (line 891) and `EngineStateSnapshot()` constructor (line 979). Load-side: restore at lines 554, 637, 773. |
|
|
565
|
-
| `virtual_context/core/retrieval_assembler.py` | Lines 112, 141, 199, 336, 419: switch to `
|
|
566
|
-
| `virtual_context/core/tool_query.py` | Line 111: switch to `
|
|
565
|
+
| `virtual_context/core/retrieval_assembler.py` | Lines 112, 141, 199, 336, 419: switch to `flushed_prefix_messages`. Lines 119, 274, 359: pass `watermark=flushed_prefix_messages` to `history_offset()`. |
|
|
566
|
+
| `virtual_context/core/tool_query.py` | Line 111: switch to `flushed_prefix_messages` |
|
|
567
567
|
| `virtual_context/proxy/server.py` | Flush gate with two-pass logic. Gate ALL mutation operations (drop, stub, collapse, media, stage-1). Set `last_request_time` after dispatch. Tentative flush commit. Logging. Legacy auto-tracking when `defer_payload_mutation=False`. |
|
|
568
568
|
| `virtual_context/proxy/message_filter.py` | Add `drop_boundary` param to `drop_compacted_turns()` |
|
|
569
569
|
| `virtual_context/storage/filesystem.py` | Serialize/deserialize new fields |
|
|
570
570
|
| `virtual_context/storage/sqlite.py` | Schema DDL, insert, load paths |
|
|
571
571
|
| `virtual_context/storage/postgres.py` | Schema DDL, upsert, load paths |
|
|
572
|
-
| `virtual_context/proxy/dashboard.py` | Expose `
|
|
573
|
-
| `virtual_context/proxy/helpers.py` | Log `
|
|
572
|
+
| `virtual_context/proxy/dashboard.py` | Expose `flushed_prefix_messages` alongside `compacted_prefix_messages` |
|
|
573
|
+
| `virtual_context/proxy/helpers.py` | Log `flushed_prefix_messages` |
|
|
@@ -94,8 +94,8 @@ class TestCompactionCycle:
|
|
|
94
94
|
history = _simulate_turns(engine, pairs)
|
|
95
95
|
|
|
96
96
|
# Compaction should have fired
|
|
97
|
-
assert engine._engine_state.
|
|
98
|
-
f"Expected compaction, but _compacted_through={engine._engine_state.
|
|
97
|
+
assert engine._engine_state.compacted_prefix_messages > 0, (
|
|
98
|
+
f"Expected compaction, but _compacted_through={engine._engine_state.compacted_prefix_messages}"
|
|
99
99
|
)
|
|
100
100
|
|
|
101
101
|
# Post-compaction retrieval should still work
|
|
@@ -204,7 +204,7 @@ class TestCompactionCycle:
|
|
|
204
204
|
history = _simulate_turns(engine, pairs)
|
|
205
205
|
|
|
206
206
|
# Should have compacted multiple times
|
|
207
|
-
assert engine._engine_state.
|
|
207
|
+
assert engine._engine_state.compacted_prefix_messages > 0
|
|
208
208
|
|
|
209
209
|
# Engine should still respond without errors
|
|
210
210
|
result = engine.on_message_inbound("What topics did we cover?", history)
|