dulus 3.13.4__tar.gz → 3.13.5__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.
- {dulus-3.13.4/dulus.egg-info → dulus-3.13.5}/PKG-INFO +1 -1
- {dulus-3.13.4 → dulus-3.13.5}/agent.py +13 -0
- {dulus-3.13.4 → dulus-3.13.5}/compaction.py +30 -0
- {dulus-3.13.4 → dulus-3.13.5}/context.py +22 -2
- {dulus-3.13.4 → dulus-3.13.5}/docs/CHANGELOG.md +12 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/news.md +2 -0
- {dulus-3.13.4 → dulus-3.13.5/dulus.egg-info}/PKG-INFO +1 -1
- {dulus-3.13.4 → dulus-3.13.5}/dulus.egg-info/SOURCES.txt +1 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus.py +29 -12
- {dulus-3.13.4 → dulus-3.13.5}/lookback.py +36 -3
- {dulus-3.13.4 → dulus-3.13.5}/memory/__init__.py +12 -1
- {dulus-3.13.4 → dulus-3.13.5}/memory/palace.py +153 -0
- {dulus-3.13.4 → dulus-3.13.5}/pyproject.toml +1 -1
- dulus-3.13.5/tests/test_gold_memory_preload.py +151 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_lookback_compact.py +53 -0
- {dulus-3.13.4 → dulus-3.13.5}/webchat_server.py +60 -0
- {dulus-3.13.4 → dulus-3.13.5}/LICENSE +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/MANIFEST.in +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/README.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/algolia_search.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/analytics.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/agents_bridge.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/compressor.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/context.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/githook.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/marketplace.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/mempalace_bridge.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/paths.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/personas.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/plugins.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/server.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/backend/tasks.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/banners/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/banners/banner_rotator.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/banners/banners_dulus_ai.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/batch_api.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/checkpoint/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/checkpoint/hooks.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/checkpoint/store.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/checkpoint/types.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/__main__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/ansi.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/assets/ascii/dulus_logo.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/assets/ascii/eagle.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/assets/ascii/terminal_art.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/banners.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/demo.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/effects.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/effort_slider.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/progress.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/py.typed +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/spinners.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cli_animations/status.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/clipboard_utils.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/cloudsave.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/common.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/config.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/active_persona.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/marketplace.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/personas.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/plugins/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/plugins/composio/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/plugins/composio/plugin.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/data/tasks.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/display_blocks.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/API.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/BRAND_GUIDE.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/CONTRIBUTING.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/DEPLOYMENT.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/FAQ.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/GETTING_STARTED.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_AR.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_EN.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_ES.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_FR.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_JA.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_KO.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_PT.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_RU.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/README_ZH.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/SECURITY.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/api.html +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/architecture.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/azure-speech-template.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/dashboard/index.html +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/divider.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/dulus-bird.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/generate.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/index.html +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/nvidia-models.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/particle-playground.html +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/personas/index.html +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/poetry-banner.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/preview.html +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/python-console.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/auto-adapter.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/dulus-os.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/hero.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/lookback_two_windows.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/pyc_heap.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/pyc_receipts.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/pyc_trap.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/roundtable.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/task-board.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/readme/webchat.png +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-agents.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-brainstorm.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-bridges.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-features.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-freetier.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-memory.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-models.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-perms.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-plugins.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-quickstart.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/sec-ssj.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/spinners.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/split-pane.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/terminal-boot.svg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/docs/termux-edge.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus.egg-info/requires.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus.egg-info/top_level.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_account.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_bar_client.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_gui.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_mcp/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_mcp/client.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_mcp/commands.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_mcp/config.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_mcp/hub.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_mcp/tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_mcp/types.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_quick_menu.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/add_dir_manager.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/afk_mode.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/approval_runtime.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/background_tasks.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/clipboard_utils.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/diff_visualizer.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/export_import.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/hook_engine.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/notification_manager.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/session_fork.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/shell_mode.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/todo_visualizer.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/tree_ls.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/wire_events.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/dulus_tools/yolo_mode.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/file_filter.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/fs_compass.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/governance.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/agent_bridge.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/chat_widget.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/main_window.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/personas.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/sandbox_server.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/session_utils.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/settings_dialog.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/sidebar.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/tasks_view.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/themes.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/gui/tool_panel.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/input.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/license_manager.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/audit.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/consolidator.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/context.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/mempalace_bridge.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/offload.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/scan.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/adaptation_guides.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/askuserquestion_after_numbered_list.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/background_jobs_no_extra_timers.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/cleverness_over_resources.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/corporate_tls_breaks_curl_cffi.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/hardened_rules.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/investigator_workflow_heuristic.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/native_code_over_sketchy_plugins.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/no_personal_info_in_code.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/searches_include_modified_date.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/tmux_send_keys_power_patterns.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/token_optimization_technique.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/tool_chaining_capability.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/insights/webbridge_evaluate_first_pattern.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/seeds/short_memory.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/sessions.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/store.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/types.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/memory/vector_search.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/model_params.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/multi_agent/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/multi_agent/subagent.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/multi_agent/tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/paste_placeholders.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/personality_config.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/plugin/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/plugin/autoadapter.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/plugin/loader.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/plugin/recommend.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/plugin/store.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/plugin/types.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/profiles.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/providers.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/pyconsole.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/README.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/dist/index.html +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox/info.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/sandbox_bootstrap.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/setup.cfg +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/builtin.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/LICENSE-obsidian-skills +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/README.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/defuddle/SKILL.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/json-canvas/SKILL.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/obsidian-bases/SKILL.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/obsidian-cli/SKILL.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/README.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/SKILL.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/legal/drop.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/methods.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/references/state-machine.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/badbool.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/brokers.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/cdp.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/config.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/crypto.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/dossier.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/emailer.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/ledger.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/legal.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/paths.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/pdd.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/registry.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/report.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/scan.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/storage.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/tiers.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/scripts/vectors.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/clawhub.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/executor.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/loader.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skill/tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/skills.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/soul.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/spinner.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/steer_input.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/subagent.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/task/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/task/store.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/task/tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/task/types.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_afk_yolo.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_approval_runtime.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_background_task_tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_background_tasks.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_chatgpt_oauth.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_checkpoint.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_cli_animations_integration.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_clipboard_utils.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_compaction.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_composio_tool_generator.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_diff_view.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_diff_visualization.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_display_blocks.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_dulus_account.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_export_import.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_grok_usage.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_hook_engine.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_injection_fix.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_json_protocol.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_license.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_lookback_cache.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_mcp.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_mcp_algolia.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_memory.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_memory_home_resolution.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_notification_manager.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_plugin.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_profile_isolation.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_profile_plugin_scope.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_provider_errors.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_session_fork.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_shell_detection.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_shell_mode.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_skills.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_skillssh_algolia.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_steer_input.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_subagent.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_task.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_telegram_buffer.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_think_tool.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_todo_tool.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_todo_visualization.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_tool_registry.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_tree_ls.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_voice.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_webbridge_tabs.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tests/test_wire_events.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tmux_tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/token_budget.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tool_registry.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tools_background.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tools_think.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/tools_todo.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/ui/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/ui/input.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/ui/render.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/updater.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/voice/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/voice/audio_utils.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/voice/keyterms.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/voice/recorder.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/voice/stt.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/voice/tts.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/voice/wake_word.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/webbridge/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/webbridge/core.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/webbridge/tools.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/webchat.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/webchat_ui/__init__.py +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/webchat_ui/app.js +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/webchat_ui/index.html +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/webchat_ui/style.css +0 -0
- {dulus-3.13.4 → dulus-3.13.5}/welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.5
|
|
4
4
|
Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
|
|
5
5
|
Author-email: KevRojo <dev@dulus.ai>
|
|
6
6
|
Maintainer-email: KevRojo <dev@dulus.ai>
|
|
@@ -206,6 +206,19 @@ def run(
|
|
|
206
206
|
except Exception:
|
|
207
207
|
_api_source, _lb_meta = state.messages, {"enabled": False, "truncated": False}
|
|
208
208
|
|
|
209
|
+
# Strip GUI/REPL baseline display blobs (soul/gold/welcome). The model
|
|
210
|
+
# already has them via system prompt; leaving them as role:assistant
|
|
211
|
+
# made web-history consolidate drop everything before the last
|
|
212
|
+
# assistant (= the gold blob itself) on first turn.
|
|
213
|
+
try:
|
|
214
|
+
from memory import is_baseline_display_message
|
|
215
|
+
_api_source = [
|
|
216
|
+
m for m in _api_source
|
|
217
|
+
if not is_baseline_display_message(m)
|
|
218
|
+
]
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
209
222
|
# Sanitize message contents before sending to API (surrogate safety)
|
|
210
223
|
_safe_messages = []
|
|
211
224
|
for m in _api_source:
|
|
@@ -678,6 +678,13 @@ def save_loopback_archive(messages: list, state=None, config: dict | None = None
|
|
|
678
678
|
Writes ~/.dulus/loopback_archives/archive_<session>.json and binds
|
|
679
679
|
config["_loopback_archive_path"] + config["_loopback_archive"] (in-memory
|
|
680
680
|
copy) for the Loopback tool. Returns the path or None on failure.
|
|
681
|
+
|
|
682
|
+
CRITICAL — second-/Nth-compact guard:
|
|
683
|
+
A later ``/compact`` runs against an *already slimmed* ``state.messages``.
|
|
684
|
+
Naively rewriting the archive would overwrite the original full history
|
|
685
|
+
with the post-compact crumbs. We NEVER shrink a durable archive: if an
|
|
686
|
+
in-memory or on-disk copy is longer than ``messages``, keep the longer one
|
|
687
|
+
and only refresh the binding.
|
|
681
688
|
"""
|
|
682
689
|
if not messages:
|
|
683
690
|
return None
|
|
@@ -685,6 +692,29 @@ def save_loopback_archive(messages: list, state=None, config: dict | None = None
|
|
|
685
692
|
LOOPBACK_ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
|
|
686
693
|
sid = _session_key(state, config)
|
|
687
694
|
path = LOOPBACK_ARCHIVE_DIR / f"archive_{sid}.json"
|
|
695
|
+
|
|
696
|
+
# Resolve the longest known full archive for this session.
|
|
697
|
+
prev: list = []
|
|
698
|
+
if isinstance(config, dict):
|
|
699
|
+
live = config.get("_loopback_archive")
|
|
700
|
+
if isinstance(live, list) and live:
|
|
701
|
+
prev = live
|
|
702
|
+
if not prev and path.exists():
|
|
703
|
+
try:
|
|
704
|
+
disk = json.loads(path.read_text(encoding="utf-8"))
|
|
705
|
+
disk_msgs = disk.get("messages") if isinstance(disk, dict) else None
|
|
706
|
+
if isinstance(disk_msgs, list) and disk_msgs:
|
|
707
|
+
prev = disk_msgs
|
|
708
|
+
except Exception:
|
|
709
|
+
prev = []
|
|
710
|
+
|
|
711
|
+
if prev and len(prev) > len(messages):
|
|
712
|
+
# Refuse to shrink. Keep the fuller archive as the durable source.
|
|
713
|
+
if isinstance(config, dict):
|
|
714
|
+
config["_loopback_archive"] = prev
|
|
715
|
+
config["_loopback_archive_path"] = str(path)
|
|
716
|
+
return path
|
|
717
|
+
|
|
688
718
|
payload = {
|
|
689
719
|
"session_id": sid,
|
|
690
720
|
"timestamp": time.strftime("%Y%m%d_%H%M%S"),
|
|
@@ -442,12 +442,28 @@ def _resolve_reply_language(config: dict | None) -> str:
|
|
|
442
442
|
return raw
|
|
443
443
|
|
|
444
444
|
|
|
445
|
+
|
|
446
|
+
def _append_baseline_fragments(prompt: str) -> str:
|
|
447
|
+
"""Attach soul + gold system fragments (always-on baseline)."""
|
|
448
|
+
try:
|
|
449
|
+
from memory import soul_system_fragment, gold_system_fragment
|
|
450
|
+
_soul = soul_system_fragment()
|
|
451
|
+
if _soul:
|
|
452
|
+
prompt += "\n\n" + _soul
|
|
453
|
+
_gold = gold_system_fragment()
|
|
454
|
+
if _gold:
|
|
455
|
+
prompt += "\n\n" + _gold
|
|
456
|
+
except Exception:
|
|
457
|
+
pass
|
|
458
|
+
return prompt
|
|
459
|
+
|
|
460
|
+
|
|
445
461
|
def build_system_prompt(config: dict | None = None) -> str:
|
|
446
462
|
import platform
|
|
447
463
|
model_lower = (config.get("model", "") if config else "").lower()
|
|
448
464
|
is_deepseek_r1 = "deepseek-r1" in model_lower or "deepseek-reasoner" in model_lower
|
|
449
465
|
if is_deepseek_r1 and config and config.get("deep_override", False):
|
|
450
|
-
return _build_ollama_system_prompt(config)
|
|
466
|
+
return _append_baseline_fragments(_build_ollama_system_prompt(config))
|
|
451
467
|
|
|
452
468
|
auto_show = "ON" if (not config or config.get("auto_show", True)) else "OFF"
|
|
453
469
|
lite = bool(config and config.get("lite_mode"))
|
|
@@ -470,8 +486,12 @@ def build_system_prompt(config: dict | None = None) -> str:
|
|
|
470
486
|
dulus_md="" if lite else get_dulus_md(),
|
|
471
487
|
)
|
|
472
488
|
|
|
489
|
+
# Soul + Gold baseline ALWAYS (even in lite) — model source of truth.
|
|
490
|
+
# Display copies in chat are GUI-only and get stripped before the API call.
|
|
491
|
+
prompt = _append_baseline_fragments(prompt)
|
|
492
|
+
|
|
473
493
|
if lite:
|
|
474
|
-
# Bail early — minimal prompt only.
|
|
494
|
+
# Bail early — minimal prompt only (baseline already attached).
|
|
475
495
|
return prompt
|
|
476
496
|
|
|
477
497
|
try:
|
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.13.5] - 2026-08-31
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Gold / short_memory baseline**: curated gold memories (incl. `short_memory`) and soul now ride in `build_system_prompt` every turn — always-on, **not** gated by `/mem_palace`
|
|
14
|
+
- **First-turn amnesia**: agent + lookback strip GUI/REPL gold/soul/welcome *display* blobs before the provider call, so web-history consolidate no longer treats gold as “last assistant reply” and drops real context
|
|
15
|
+
- **MemPalace dual-path noise**: per-turn inject (CLI + WebChat) skips `short_memory` / soul — already in the system prompt
|
|
16
|
+
- **Second `/compact` archive wipe**: `save_loopback_archive` refuses to shrink; Loopback keeps the full pre-compact history across N compacts
|
|
17
|
+
- **WebChat gold preload**: session seeds gold *display* copies for the transcript; model source of truth is still the system prompt (public GUI stays as-is — this is server/engine, not an Interant GUI port)
|
|
18
|
+
|
|
19
|
+
### Tests
|
|
20
|
+
- `test_gold_memory_preload` + `test_lookback_compact` (11 passed)
|
|
21
|
+
|
|
10
22
|
## [3.13.4] - 2026-08-31
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## 🔥🔥🔥 News (Pacific Time)
|
|
4
4
|
|
|
5
|
+
- August 31, 2026 (**v3.13.5**): **🏆 short_memory stopped vanishing.** Gold memories + soul now live in the **system prompt** every turn (always-on — `/mem_palace off` no longer starves them). WebChat seeds gold for the transcript; the agent/lookback strip those display blobs before the provider call so first-turn consolidate can’t eat your context. Second `/compact` no longer wipes the Loopback archive. Engine + WebChat fix — not a desktop-GUI rewrite. `pip install -U dulus`. 🦅
|
|
6
|
+
|
|
5
7
|
- August 31, 2026 (**v3.13.4**): **Context windows are honest again.** `token_budget` ships so compaction knows Claude is 200k, Gemini is 1M, DeepSeek is 64k — not a flat 128k for everyone. Quality CI: **925 passed**. `pip install -U dulus`. 🦅
|
|
6
8
|
|
|
7
9
|
- August 31, 2026 (**v3.13.3**): **`/compact` got serious.** Full archive → disk for Loopback, live context → ~0 with a dense hint card, and **lookback is forced ON** for that session even if it was off — so quality does not fall off a cliff after compact. `pip install -U dulus`. 🦅
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.5
|
|
4
4
|
Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
|
|
5
5
|
Author-email: KevRojo <dev@dulus.ai>
|
|
6
6
|
Maintainer-email: KevRojo <dev@dulus.ai>
|
|
@@ -332,6 +332,7 @@ tests/test_diff_visualization.py
|
|
|
332
332
|
tests/test_display_blocks.py
|
|
333
333
|
tests/test_dulus_account.py
|
|
334
334
|
tests/test_export_import.py
|
|
335
|
+
tests/test_gold_memory_preload.py
|
|
335
336
|
tests/test_grok_usage.py
|
|
336
337
|
tests/test_hook_engine.py
|
|
337
338
|
tests/test_injection_fix.py
|
|
@@ -395,7 +395,7 @@ try:
|
|
|
395
395
|
from importlib.metadata import version as _pkg_version
|
|
396
396
|
VERSION = _pkg_version("dulus")
|
|
397
397
|
except Exception:
|
|
398
|
-
VERSION = "3.13.
|
|
398
|
+
VERSION = "3.13.5" # dev fallback — keep in sync with pyproject.toml
|
|
399
399
|
|
|
400
400
|
# ── Machine-readable protocol output (`--output json`) ─────────────────────
|
|
401
401
|
# Dulus is increasingly run as a child process by another agent runtime (an
|
|
@@ -3570,6 +3570,8 @@ def cmd_mem_palace(args: str, _state, config) -> bool:
|
|
|
3570
3570
|
config["mem_palace"] = not config.get("mem_palace", True)
|
|
3571
3571
|
state_str = "ON" if config["mem_palace"] else "OFF"
|
|
3572
3572
|
ok(f"MemPalace auto-injection: {state_str}")
|
|
3573
|
+
if not config["mem_palace"]:
|
|
3574
|
+
info("Gold memories (incl. short_memory) still load at session start.")
|
|
3573
3575
|
save_config(config)
|
|
3574
3576
|
return True
|
|
3575
3577
|
|
|
@@ -12227,18 +12229,21 @@ def repl(config: dict, initial_prompt: str | None = None):
|
|
|
12227
12229
|
except Exception as e:
|
|
12228
12230
|
startup_status_msgs.append(clr(f" ⚠ Schema inject skip: {e}", "yellow"))
|
|
12229
12231
|
|
|
12230
|
-
# ── Gold Memories Auto-Load
|
|
12231
|
-
#
|
|
12232
|
-
#
|
|
12232
|
+
# ── Gold Memories Auto-Load (GUI/REPL display copies) ─────────────────────
|
|
12233
|
+
# Model source of truth is build_system_prompt → gold_system_fragment().
|
|
12234
|
+
# These assistant-role copies are for the transcript/GUI only and get
|
|
12235
|
+
# stripped before the provider call (agent.py + lookback). Never gated
|
|
12236
|
+
# by /mem_palace (that toggle is only for per-turn semantic search).
|
|
12233
12237
|
try:
|
|
12234
|
-
from memory import
|
|
12235
|
-
|
|
12236
|
-
for
|
|
12237
|
-
state.messages.append(
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12238
|
+
from memory import gold_context_messages, gold_system_fragment
|
|
12239
|
+
_gold_in_system = bool(gold_system_fragment())
|
|
12240
|
+
for _msg in gold_context_messages():
|
|
12241
|
+
state.messages.append(_msg)
|
|
12242
|
+
_gold_name = _msg["content"].split("]", 1)[0].removeprefix("[Golden Memory Loaded: ")
|
|
12243
|
+
_where = "system+GUI" if _gold_in_system else "GUI only"
|
|
12244
|
+
startup_status_msgs.append(
|
|
12245
|
+
clr(f" 🏆 Gold memory loaded ({_where}): {_gold_name}", "yellow", "bold")
|
|
12246
|
+
)
|
|
12242
12247
|
except Exception:
|
|
12243
12248
|
pass
|
|
12244
12249
|
|
|
@@ -12735,6 +12740,18 @@ def repl(config: dict, initial_prompt: str | None = None):
|
|
|
12735
12740
|
for _h in _raw_hits:
|
|
12736
12741
|
_mp_log(f" hit: score={float(_h.get('keyword_score', 0.0)):.3f} {_h.get('name','?')}", "dim")
|
|
12737
12742
|
_kept = [h for h in _raw_hits if float(h.get("keyword_score", 0.0)) >= _MIN_SCORE]
|
|
12743
|
+
# Skip short_memory/soul — already in system-prompt baseline.
|
|
12744
|
+
try:
|
|
12745
|
+
from memory import is_baseline_memory_name
|
|
12746
|
+
_kept = [
|
|
12747
|
+
h for h in _kept
|
|
12748
|
+
if not is_baseline_memory_name(h.get("name"))
|
|
12749
|
+
and not is_baseline_memory_name(
|
|
12750
|
+
(h.get("description") or "").split()[:1][0] if h.get("description") else None
|
|
12751
|
+
)
|
|
12752
|
+
]
|
|
12753
|
+
except Exception:
|
|
12754
|
+
pass
|
|
12738
12755
|
# ── Dedup: skip memories already injected earlier in this session.
|
|
12739
12756
|
# Key by content hash (not name) because mempalace often returns
|
|
12740
12757
|
# generic names like "palace" for every hit in a wing — name-based
|
|
@@ -155,6 +155,25 @@ def find_lookback_start(messages: list, turns: int) -> int:
|
|
|
155
155
|
return start
|
|
156
156
|
|
|
157
157
|
|
|
158
|
+
|
|
159
|
+
def _strip_baseline_display(messages: list) -> list:
|
|
160
|
+
"""Drop soul/gold/welcome GUI display blobs from an API window.
|
|
161
|
+
|
|
162
|
+
Those ride in the system prompt now (see memory.gold_system_fragment /
|
|
163
|
+
soul_system_fragment). Keeping them as role:assistant in the API payload
|
|
164
|
+
made web-history consolidate treat gold as "last assistant reply".
|
|
165
|
+
"""
|
|
166
|
+
if not messages:
|
|
167
|
+
return messages
|
|
168
|
+
try:
|
|
169
|
+
from memory import is_baseline_display_message
|
|
170
|
+
except Exception:
|
|
171
|
+
return messages
|
|
172
|
+
out = [m for m in messages if not is_baseline_display_message(m)]
|
|
173
|
+
# Preserve identity when nothing was stripped (callers/tests rely on it).
|
|
174
|
+
return messages if len(out) == len(messages) else out
|
|
175
|
+
|
|
176
|
+
|
|
158
177
|
def apply_lookback_window(
|
|
159
178
|
messages: list,
|
|
160
179
|
config: dict | None = None,
|
|
@@ -166,6 +185,9 @@ def apply_lookback_window(
|
|
|
166
185
|
|
|
167
186
|
Full ``messages`` is never mutated. When disabled or small enough, the
|
|
168
187
|
original list is returned (same object) with meta.truncated=False.
|
|
188
|
+
|
|
189
|
+
Baseline display blobs (soul/gold/welcome) are always stripped from the
|
|
190
|
+
returned API window — they live in the system prompt, not chat turns.
|
|
169
191
|
"""
|
|
170
192
|
msgs = messages or []
|
|
171
193
|
on = lookback_enabled(config) if enabled is None else bool(enabled)
|
|
@@ -186,9 +208,20 @@ def apply_lookback_window(
|
|
|
186
208
|
"start_index": 0,
|
|
187
209
|
"hidden_messages": 0,
|
|
188
210
|
}
|
|
211
|
+
def _api_window(window: list, meta: dict[str, Any]) -> tuple[list, dict[str, Any]]:
|
|
212
|
+
"""Strip baseline display blobs and refresh window counts."""
|
|
213
|
+
cleaned = _strip_baseline_display(window)
|
|
214
|
+
if cleaned is window:
|
|
215
|
+
return window, meta
|
|
216
|
+
meta = dict(meta)
|
|
217
|
+
meta["window_messages"] = len(cleaned)
|
|
218
|
+
meta["window_user_turns"] = count_user_turns(cleaned)
|
|
219
|
+
meta["baseline_stripped"] = len(window) - len(cleaned)
|
|
220
|
+
return cleaned, meta
|
|
221
|
+
|
|
189
222
|
if not on or total == 0:
|
|
190
223
|
_clear_anchor(config)
|
|
191
|
-
return msgs, meta
|
|
224
|
+
return _api_window(msgs, meta)
|
|
192
225
|
|
|
193
226
|
# Hysteresis: reuse the previous start (anchor) while its window still
|
|
194
227
|
# holds n..n+slack user turns. The API prefix then stays append-only
|
|
@@ -211,7 +244,7 @@ def apply_lookback_window(
|
|
|
211
244
|
|
|
212
245
|
if start <= 0:
|
|
213
246
|
_clear_anchor(config)
|
|
214
|
-
return msgs, meta
|
|
247
|
+
return _api_window(msgs, meta)
|
|
215
248
|
|
|
216
249
|
# Cache-aware gate: only truncate when the head we'd hide is meaningfully
|
|
217
250
|
# bigger than the window we'd keep. Otherwise dropping the front busts the
|
|
@@ -242,7 +275,7 @@ def apply_lookback_window(
|
|
|
242
275
|
"window_user_turns": count_user_turns(window),
|
|
243
276
|
"hidden_messages": start,
|
|
244
277
|
})
|
|
245
|
-
return window, meta
|
|
278
|
+
return _api_window(window, meta)
|
|
246
279
|
|
|
247
280
|
|
|
248
281
|
def lookback_system_note(meta: dict[str, Any]) -> str:
|
|
@@ -57,7 +57,18 @@ from .types import ( # noqa: F401
|
|
|
57
57
|
WHAT_NOT_TO_SAVE,
|
|
58
58
|
)
|
|
59
59
|
from .consolidator import consolidate_session, mine_files, snapshot_memory_files, new_memory_files # noqa: F401
|
|
60
|
-
from .palace import
|
|
60
|
+
from .palace import ( # noqa: F401
|
|
61
|
+
ensure_memory_palace,
|
|
62
|
+
ensure_short_memory,
|
|
63
|
+
gold_context_messages,
|
|
64
|
+
gold_system_fragment,
|
|
65
|
+
soul_system_fragment,
|
|
66
|
+
is_baseline_display_message,
|
|
67
|
+
is_baseline_memory_name,
|
|
68
|
+
GOLD_MARKER,
|
|
69
|
+
SOUL_MARKER,
|
|
70
|
+
BASELINE_DISPLAY_MARKERS,
|
|
71
|
+
)
|
|
61
72
|
from .mempalace_bridge import ( # noqa: F401
|
|
62
73
|
schedule_mempalace_mine,
|
|
63
74
|
wait_pending_mines,
|
|
@@ -13,6 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
|
|
14
14
|
from .store import (
|
|
15
15
|
MemoryEntry,
|
|
16
|
+
USER_MEMORY_DIR,
|
|
16
17
|
get_memory_dir,
|
|
17
18
|
has_stacked_frontmatter,
|
|
18
19
|
parse_frontmatter,
|
|
@@ -267,6 +268,158 @@ def seed_insight_memories() -> bool:
|
|
|
267
268
|
return changed
|
|
268
269
|
|
|
269
270
|
|
|
271
|
+
|
|
272
|
+
# ── Baseline display markers (GUI/REPL transcript only) ─────────────────────
|
|
273
|
+
# These role:assistant blobs are for the human to see in the chat UI. The model
|
|
274
|
+
# source of truth is gold_system_fragment() / soul_system_fragment() in the
|
|
275
|
+
# system prompt. Agent + lookback strip these markers from the API payload.
|
|
276
|
+
GOLD_MARKER = "[Golden Memory Loaded:"
|
|
277
|
+
SOUL_MARKER = "[Identity Essence Loaded:"
|
|
278
|
+
SOUL_RELOAD_MARKER = "[Identity Essence Reloaded:"
|
|
279
|
+
WELCOME_MARKER = "<!-- dulus:welcome -->"
|
|
280
|
+
BASELINE_DISPLAY_MARKERS = (
|
|
281
|
+
GOLD_MARKER,
|
|
282
|
+
SOUL_MARKER,
|
|
283
|
+
SOUL_RELOAD_MARKER,
|
|
284
|
+
WELCOME_MARKER,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def is_baseline_display_message(msg: dict | None) -> bool:
|
|
289
|
+
"""True if *msg* is a GUI/REPL display blob for soul/gold/welcome."""
|
|
290
|
+
if not isinstance(msg, dict):
|
|
291
|
+
return False
|
|
292
|
+
content = msg.get("content") or ""
|
|
293
|
+
if not isinstance(content, str):
|
|
294
|
+
return False
|
|
295
|
+
s = content.lstrip()
|
|
296
|
+
return any(s.startswith(m) for m in BASELINE_DISPLAY_MARKERS)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def is_baseline_memory_name(name: str | None) -> bool:
|
|
300
|
+
"""True for memories that already ride in the system-prompt baseline.
|
|
301
|
+
|
|
302
|
+
MemPalace per-turn inject must skip these — re-injecting a truncated
|
|
303
|
+
``short_memory`` on top of the full system copy is pure noise.
|
|
304
|
+
"""
|
|
305
|
+
if not name:
|
|
306
|
+
return False
|
|
307
|
+
key = str(name).strip().lower().replace("-", "_").replace(" ", "_")
|
|
308
|
+
key = key.rsplit("/", 1)[-1].rsplit("\\", 1)[-1].rsplit(".", 1)[0]
|
|
309
|
+
if key in {"short_memory", "shortmemory", "soul"}:
|
|
310
|
+
return True
|
|
311
|
+
if key.startswith("golden_memory") or "short_memory" in key:
|
|
312
|
+
return True
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _clamp_body(body: str, max_chars: int) -> str:
|
|
317
|
+
body = (body or "").strip()
|
|
318
|
+
if not body or len(body) <= max_chars:
|
|
319
|
+
return body
|
|
320
|
+
head = max_chars * 3 // 4
|
|
321
|
+
return (
|
|
322
|
+
body[:head]
|
|
323
|
+
+ "\n\n[... gold memory clipped — file is oversized; "
|
|
324
|
+
"re-curate it with MemorySave ...]\n\n"
|
|
325
|
+
+ body[-(max_chars - head):]
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _iter_gold_entries(max_chars: int = 8000):
|
|
330
|
+
"""Yield (name, body) for every gold memory, short_memory first."""
|
|
331
|
+
try:
|
|
332
|
+
ensure_short_memory(force_gold=True)
|
|
333
|
+
except Exception:
|
|
334
|
+
pass
|
|
335
|
+
try:
|
|
336
|
+
from .store import load_index
|
|
337
|
+
entries = [e for e in load_index("all") if getattr(e, "gold", False)]
|
|
338
|
+
except Exception:
|
|
339
|
+
return
|
|
340
|
+
def _sort_key(e):
|
|
341
|
+
name = (getattr(e, "name", "") or "").lower()
|
|
342
|
+
return (0 if name == "short_memory" else 1, name)
|
|
343
|
+
|
|
344
|
+
for entry in sorted(entries, key=_sort_key):
|
|
345
|
+
body = _clamp_body(getattr(entry, "content", "") or "", max_chars)
|
|
346
|
+
if not body:
|
|
347
|
+
continue
|
|
348
|
+
yield (getattr(entry, "name", "gold") or "gold", body)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def gold_system_fragment(max_chars: int = 8000, max_total: int = 12000) -> str:
|
|
352
|
+
"""Gold memories as a stable system-prompt block (model source of truth).
|
|
353
|
+
|
|
354
|
+
This is how the model actually *receives* short_memory and other gold
|
|
355
|
+
entries — not as ``role:assistant`` chat turns (those break web-history
|
|
356
|
+
consolidate, Anthropic role alternation, and lookback).
|
|
357
|
+
|
|
358
|
+
Deliberately NOT gated by ``mem_palace``. Called every turn from
|
|
359
|
+
``build_system_prompt`` so edits to short_memory.md show up immediately.
|
|
360
|
+
"""
|
|
361
|
+
parts: list[str] = []
|
|
362
|
+
total = 0
|
|
363
|
+
for name, body in _iter_gold_entries(max_chars=max_chars):
|
|
364
|
+
block = f"[Golden Memory Loaded: {name}]\n\n{body}"
|
|
365
|
+
if total and total + len(block) > max_total:
|
|
366
|
+
break
|
|
367
|
+
parts.append(block)
|
|
368
|
+
total += len(block) + 2
|
|
369
|
+
if not parts:
|
|
370
|
+
return ""
|
|
371
|
+
return (
|
|
372
|
+
"# ── Golden Memory (always-on baseline — NOT gated by /mem_palace) ──\n"
|
|
373
|
+
"These are curated essentials. Treat short_memory as the live scratchpad "
|
|
374
|
+
"of where we left off; do not ask the user to re-explain it.\n\n"
|
|
375
|
+
+ "\n\n".join(parts)
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def soul_system_fragment(max_chars: int = 8000) -> str:
|
|
380
|
+
"""Soul identity as a system-prompt block (same rationale as gold)."""
|
|
381
|
+
try:
|
|
382
|
+
path = USER_MEMORY_DIR / "soul.md"
|
|
383
|
+
if not path.exists():
|
|
384
|
+
return ""
|
|
385
|
+
raw = path.read_text(encoding="utf-8", errors="replace")
|
|
386
|
+
body = raw
|
|
387
|
+
if raw.lstrip().startswith("---"):
|
|
388
|
+
try:
|
|
389
|
+
from .store import parse_frontmatter
|
|
390
|
+
_, body = parse_frontmatter(raw)
|
|
391
|
+
except Exception:
|
|
392
|
+
body = raw
|
|
393
|
+
body = _clamp_body(body, max_chars)
|
|
394
|
+
if not body:
|
|
395
|
+
return ""
|
|
396
|
+
return (
|
|
397
|
+
"# ── Identity Essence (soul) ──\n"
|
|
398
|
+
f"[Identity Essence Loaded: soul]\n\n{body}"
|
|
399
|
+
)
|
|
400
|
+
except Exception:
|
|
401
|
+
return ""
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def gold_context_messages(max_chars: int = 8000) -> list[dict[str, str]]:
|
|
405
|
+
"""GUI/REPL *display* copies of gold memories (NOT the model source of truth).
|
|
406
|
+
|
|
407
|
+
Kept as ``role:assistant`` so `/api/chat/history` and the desktop GUI can
|
|
408
|
+
render "🏆 Gold memory loaded" in the transcript. The agent loop strips
|
|
409
|
+
these markers before the provider call; the real baseline rides in the
|
|
410
|
+
system prompt via ``gold_system_fragment()``.
|
|
411
|
+
|
|
412
|
+
Deliberately NOT gated by the ``mem_palace`` config toggle.
|
|
413
|
+
"""
|
|
414
|
+
messages: list[dict[str, str]] = []
|
|
415
|
+
for name, body in _iter_gold_entries(max_chars=max_chars):
|
|
416
|
+
messages.append({
|
|
417
|
+
"role": "assistant",
|
|
418
|
+
"content": f"[Golden Memory Loaded: {name}]\n\n{body}",
|
|
419
|
+
})
|
|
420
|
+
return messages
|
|
421
|
+
|
|
422
|
+
|
|
270
423
|
def ensure_memory_palace() -> bool:
|
|
271
424
|
"""Initialize missing core buckets + always ensure gold short_memory.
|
|
272
425
|
|
|
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "dulus"
|
|
13
|
-
version = "3.13.
|
|
13
|
+
version = "3.13.5"
|
|
14
14
|
description = "Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS."
|
|
15
15
|
readme = "README.md"
|
|
16
16
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Gold memory (incl. short_memory) must survive `/mem_palace off`.
|
|
2
|
+
|
|
3
|
+
`/mem_palace` is a cost knob for per-turn *semantic search*. It was silently
|
|
4
|
+
also starving the model of the curated `short_memory` scratchpad, and the
|
|
5
|
+
GUI/WebChat surfaces never loaded gold memories at all — only the CLI REPL
|
|
6
|
+
did. These tests lock the contract:
|
|
7
|
+
|
|
8
|
+
1. `gold_context_messages()` returns gold entries regardless of any config.
|
|
9
|
+
2. WebChat's `_preload_gold_memories` seeds a fresh session even when
|
|
10
|
+
`mem_palace=False` (GUI *display* copies).
|
|
11
|
+
3. The preload is idempotent (re-entry must not double-inject).
|
|
12
|
+
4. Model source of truth is the system prompt via `gold_system_fragment` /
|
|
13
|
+
`soul_system_fragment` — always present, even under lite mode.
|
|
14
|
+
5. API path strips baseline display blobs (soul/gold/welcome).
|
|
15
|
+
6. MemPalace per-turn inject skips short_memory / soul (no dual-path noise).
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from agent import AgentState
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_gold_context_messages_includes_short_memory() -> None:
|
|
23
|
+
from memory import gold_context_messages
|
|
24
|
+
from memory.store import is_short_memory_name
|
|
25
|
+
|
|
26
|
+
msgs = gold_context_messages()
|
|
27
|
+
assert msgs, "gold_context_messages returned nothing — short_memory missing?"
|
|
28
|
+
for m in msgs:
|
|
29
|
+
assert m["role"] == "assistant" # GUI display copy
|
|
30
|
+
assert m["content"].startswith("[Golden Memory Loaded:")
|
|
31
|
+
assert any(
|
|
32
|
+
is_short_memory_name(
|
|
33
|
+
m["content"].split("]", 1)[0].removeprefix("[Golden Memory Loaded: ").strip()
|
|
34
|
+
)
|
|
35
|
+
for m in msgs
|
|
36
|
+
), "short_memory must always be among the gold preload"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_gold_system_fragment_is_model_source_of_truth() -> None:
|
|
40
|
+
"""System prompt fragment carries short_memory — not chat turns."""
|
|
41
|
+
from memory import gold_system_fragment
|
|
42
|
+
|
|
43
|
+
frag = gold_system_fragment()
|
|
44
|
+
assert frag, "gold_system_fragment empty — short_memory not in system baseline"
|
|
45
|
+
assert "short_memory" in frag or "Short Memory" in frag
|
|
46
|
+
assert "[Golden Memory Loaded:" in frag
|
|
47
|
+
assert "always-on baseline" in frag.lower() or "Golden Memory" in frag
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_build_system_prompt_embeds_gold_and_soul() -> None:
|
|
51
|
+
from context import build_system_prompt
|
|
52
|
+
|
|
53
|
+
prompt = build_system_prompt({"model": "test-model", "lite_mode": True})
|
|
54
|
+
assert "[Golden Memory Loaded:" in prompt or "short_memory" in prompt.lower()
|
|
55
|
+
# Soul may or may not exist on a fresh machine; gold short_memory is forced.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_preload_runs_with_mem_palace_off() -> None:
|
|
59
|
+
"""The bug this guards: `/mem_palace off` used to also blind the model to gold."""
|
|
60
|
+
import webchat_server as ws
|
|
61
|
+
|
|
62
|
+
state = AgentState()
|
|
63
|
+
cfg = {"mem_palace": False}
|
|
64
|
+
|
|
65
|
+
ws._preload_gold_memories(state, cfg)
|
|
66
|
+
|
|
67
|
+
assert state.messages, "gold preload skipped while mem_palace was off"
|
|
68
|
+
assert any(
|
|
69
|
+
m["content"].startswith(ws._GOLD_MARKER) for m in state.messages
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_preload_is_idempotent() -> None:
|
|
74
|
+
import webchat_server as ws
|
|
75
|
+
|
|
76
|
+
state = AgentState()
|
|
77
|
+
cfg: dict = {"mem_palace": True}
|
|
78
|
+
|
|
79
|
+
ws._preload_gold_memories(state, cfg)
|
|
80
|
+
first = len(state.messages)
|
|
81
|
+
assert first >= 1
|
|
82
|
+
|
|
83
|
+
ws._preload_gold_memories(state, cfg)
|
|
84
|
+
assert len(state.messages) == first, "gold preload double-injected"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_baseline_display_message_detector() -> None:
|
|
88
|
+
from memory import is_baseline_display_message, is_baseline_memory_name
|
|
89
|
+
|
|
90
|
+
assert is_baseline_display_message({
|
|
91
|
+
"role": "assistant",
|
|
92
|
+
"content": "[Golden Memory Loaded: short_memory]\n\nhello",
|
|
93
|
+
})
|
|
94
|
+
assert is_baseline_display_message({
|
|
95
|
+
"role": "assistant",
|
|
96
|
+
"content": "[Identity Essence Loaded: soul]\n\nI am Dulus",
|
|
97
|
+
})
|
|
98
|
+
assert is_baseline_display_message({
|
|
99
|
+
"role": "assistant",
|
|
100
|
+
"content": "<!-- dulus:welcome -->\nWelcome",
|
|
101
|
+
})
|
|
102
|
+
assert not is_baseline_display_message({
|
|
103
|
+
"role": "assistant",
|
|
104
|
+
"content": "Normal reply about OVH",
|
|
105
|
+
})
|
|
106
|
+
assert is_baseline_memory_name("short_memory")
|
|
107
|
+
assert is_baseline_memory_name("short-memory")
|
|
108
|
+
assert is_baseline_memory_name("soul")
|
|
109
|
+
assert not is_baseline_memory_name("ovh_le_beast")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_lookback_strips_baseline_from_api_window() -> None:
|
|
113
|
+
from lookback import apply_lookback_window
|
|
114
|
+
|
|
115
|
+
msgs = [
|
|
116
|
+
{"role": "assistant", "content": "[Golden Memory Loaded: short_memory]\n\nOVH stuck"},
|
|
117
|
+
{"role": "assistant", "content": "[Identity Essence Loaded: soul]\n\nI am Dulus"},
|
|
118
|
+
{"role": "user", "content": "klk"},
|
|
119
|
+
{"role": "assistant", "content": "Klk papi"},
|
|
120
|
+
]
|
|
121
|
+
window, meta = apply_lookback_window(msgs, {"lookback": False})
|
|
122
|
+
assert all(
|
|
123
|
+
not (m.get("content") or "").startswith("[Golden Memory Loaded:")
|
|
124
|
+
for m in window
|
|
125
|
+
)
|
|
126
|
+
assert all(
|
|
127
|
+
not (m.get("content") or "").startswith("[Identity Essence Loaded:")
|
|
128
|
+
for m in window
|
|
129
|
+
)
|
|
130
|
+
assert any(m.get("content") == "klk" for m in window)
|
|
131
|
+
assert meta.get("baseline_stripped", 0) >= 2 or len(window) == 2
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_is_baseline_memory_name_filters_mempalace_hits() -> None:
|
|
135
|
+
from memory import is_baseline_memory_name
|
|
136
|
+
|
|
137
|
+
hits = [
|
|
138
|
+
{"name": "short_memory", "content": "OVH", "keyword_score": 0.9},
|
|
139
|
+
{"name": "palace", "content": "# Short Memory (gold)\n...", "keyword_score": 0.8},
|
|
140
|
+
{"name": "ovh_notes", "content": "le-beast IP", "keyword_score": 0.7},
|
|
141
|
+
{"name": "soul", "content": "I am Dulus", "keyword_score": 0.6},
|
|
142
|
+
]
|
|
143
|
+
kept = [
|
|
144
|
+
h for h in hits
|
|
145
|
+
if not is_baseline_memory_name(h.get("name"))
|
|
146
|
+
]
|
|
147
|
+
names = {h["name"] for h in kept}
|
|
148
|
+
assert "short_memory" not in names
|
|
149
|
+
assert "soul" not in names
|
|
150
|
+
assert "ovh_notes" in names
|
|
151
|
+
assert "palace" in names # generic palace name stays; content filter is separate
|