10 KiB
Memory And Skills
Last updated: dev@28d27ee | 2026-07-17
Scope
This spec covers persistent memory and user skills in:
- app wiring in
app.pyandsrc/app_initializer.py; - active legacy memory managers
src/memory.pyandsrc/memory_vector.py; - canonical memory routes in
routes/memory/memory_routes.py, withroutes/memory_routes.pyas a compatibility shim; - chat memory/skill gating in
routes/chat_helpers.py; - memory compatibility modules in
services/memory/memory.py,services/memory/memory_vector.py, andservices/memory/service.py; - provider abstractions in
src/memory_provider.py; - LLM extraction/audit in
services/memory/memory_extractor.py; - skill storage, format, import, and extraction in
services/memory/skills.py,services/memory/skill_format.py,services/memory/skill_importer.py, andservices/memory/skill_extractor.py; - skill routes in
routes/skills_routes.py; - prompt/tool call sites in
src/chat_processor.py,src/agent_loop.py,src/ai_interaction.py,src/tool_implementations.py,src/tool_execution.py,src/tool_schemas.py, andsrc/tool_security.py; - MCP and Codex surfaces in
mcp_servers/memory_server.pyandroutes/codex_routes.py; - backup/admin/CLI surfaces in
routes/backup_routes.py,routes/admin_wipe_routes.py,scripts/odysseus-memory,scripts/odysseus-skills, andscripts/odysseus-backup; - frontend modules
static/js/memory.jsandstatic/js/skills.js; - tests under
tests/test_memory_*,tests/test_builtin_memory_consolidation.py,tests/test_skill_*, andtests/test_skills_*.
Memory Runtime
src.app_initializer.initialize_managers() creates the active src.memory.MemoryManager and src.memory_vector.MemoryVectorStore used by app startup. routes.memory.memory_routes imports through services.memory but is passed the startup manager instances; top-level routes.memory_routes is a sys.modules compatibility shim.
MemoryManager owns JSON-backed memory storage in data/memory.json, validation, owner fields, pinned state, use counts, and text/keyword similarity. MemoryVectorStore owns semantic lookup when Chroma and embeddings are reachable. services/memory/memory.py and services/memory/memory_vector.py are compatibility imports for these canonical src classes. src.memory_provider.NativeMemoryProvider is the provider interface around the native manager/vector pair.
Chat memory behavior:
- chat preferences and incognito state gate memory preface use;
- pinned memories are loaded for the owner;
- retrieved memories use keyword matching plus optional vector scoring;
- inserted memory is wrapped as untrusted context;
- memory use counts are incremented after insertion.
services/memory/memory_extractor.py owns LLM-assisted extraction, audit, and validation flows. It requests model behavior and writes through the memory manager; it does not own chat session persistence.
Extraction handles reasoning-model response shapes and records explicit dislike/drop preferences as dislikes rather than losing them to generic fact handling.
Skills Runtime
services/memory/skills.py owns disk-backed skill storage under data/skills/<category>/<name>/SKILL.md, plus _usage.json usage/audit sidecars. Legacy data/skills.json is a read-only fallback/import source, not the current write shape.
services/memory/skill_format.py owns frontmatter/body parsing and emission. services/memory/skill_importer.py resolves public GitHub/skills URLs, fetches bundle files with strict public-network URL safety, and chooses/imports SKILL.md. Import fetches disable automatic HTTP redirects, follow at most five redirects manually, and re-run the private/loopback/link-local guard before every hop; GitHub final-host checks still apply after the network guard. routes/skills_routes.py owns HTTP skill CRUD/search/index/markdown/import behavior, owner filtering, tag/search handling, skill test jobs, audit-all jobs, scheduled audit entry points, and admin-gated built-in tool instruction overrides.
Skill extraction is owned by services/memory/skill_extractor.py. It can suggest or save skills from conversations, tries valid brace-delimited JSON candidates with JSONDecoder.raw_decode(), rejects ambiguous multiple top-level JSON objects instead of guessing, and saved skills remain user-editable data.
Agent skill behavior:
- matched skills are owner-scoped, confidence-gated, usage-counted, and wrapped as untrusted context;
index_for()exposes published skills plus teacher-escalation drafts gated by platform and toolsets;active_toolsets=Nonemeans the caller has no explicit toolset knowledge and does not hiderequires_toolsetsskills, while an explicit list applies the gate;- user prefs such as skills enabled, auto-approve, and max injected skills shape runtime insertion;
- the level-0 base skill index currently calls
index_for(owner=None), so it is not fully owner-scoped.
Tools, MCP, And Backup
Native manage_memory and manage_skills tool paths pass owner context and use in-process policy gates. Manual memory add can choose a memory category instead of always defaulting to fact, and route-side manual add validates the source session owner before attaching session-derived memories. mcp_servers/memory_server.py lazy-initializes src managers and exposes list/add/edit/delete/search. It can scope to ODYSSEUS_MCP_MEMORY_OWNER or ODYSSEUS_MEMORY_OWNER; if the JSON store contains owner-bearing entries and no owner env is configured, it returns an owner-scope error instead of listing or mutating across owners. Ownerless stores remain ownerless compatibility mode.
The direct odysseus-memory add CLI tolerates non-object legacy/corrupt rows
when checking whether its newly added entry is already present; it ignores
those rows instead of calling mapping methods on them and crashing the add.
/api/export owner-filters memories and skills. /api/import imports skills through current disk-backed SkillsManager APIs, stamping missing owners to the importer and preserving supported skill metadata. Full data snapshots through scripts/odysseus-backup preserve on-disk skill trees, memory JSON, and caches differently from JSON import/export.
Compatibility State
Memory and skills are partially migrated:
- app startup, MCP, and some tools still use
src.memory*; - services memory modules remain relevant for imports/tests, with memory and vector modules re-exporting canonical
srcimplementations; services/memory/service.pyis a compatibility facade around the canonical managers, but it remains ownerless and should not be assumed equivalent to route/tool owner policy;- skills are service-owned and disk-backed, while backup import and some compatibility paths still expect older JSON/list shapes.
Degraded Vector Memory
Chroma is an external HTTP service. Native defaults use localhost:8100; Docker uses chromadb:8000. Embeddings prefer configured HTTP endpoints and can fall back to local FastEmbed.
Startup can degrade to keyword-only memory when vector initialization fails. Extraction/audit paths catch vector failures and continue with text/JSON behavior. Vector dedup is checked against the current owner before suppressing a candidate, and audit rebuilds preserve other owners' vector rows. Chat retrieval assumes a healthy startup vector store remains usable, so post-start vector failures can still break memory retrieval unless handled by the caller.
Admin wipe currently has a vector cleanup compatibility gap because it imports a nonexistent helper before attempting vector clearing.
Policy
Saved memories and skills are untrusted source data when shown to the model. A stored skill may contain useful instructions, but it is still user-editable content and must be framed consistently with prompt-injection policy.
Owner isolation is surface-specific:
- HTTP memory and skills routes are expected to owner-filter normal user data;
- native memory/skill tools are expected to pass owner context;
- Codex exposes scoped token memory behavior separately;
- normal memory/skills routes are cookie/current-user surfaces, not scoped token APIs;
- MCP memory uses an environment-configured owner for owner-scoped stores, while the agent level-0 skill index currently has ownerless/global behavior;
- vector dedup during memory extraction suppresses only same-owner or legacy-ownerless vector matches.
Skill test/audit flows intentionally run user-editable SKILL.md content as instructions inside controlled jobs. Those jobs rely on route owner checks, admin gates where applicable, and tool execution policy.
Skill import is admin-gated defense-in-depth, but imported URLs are still
untrusted network input. Initial and redirected targets must remain public;
httpx redirect following must stay disabled so validation happens before
each connection rather than after a redirect has already reached a private
service.
User rename flows update skill frontmatter owner fields and _usage.json owner keys alongside memory/upload/research ownership migrations.
Testing Coverage
Existing tests cover memory bullet extraction, extraction degraded-vector behavior, manager owner isolation, auto-memory owner isolation, MCP memory list shape and owner-scope behavior, skill owner update/delete, skill prompt-injection wrapping, toolset gating, skill save no-rename behavior, skill-import private-target and redirect-hop SSRF rejection, CLI non-object row handling, and selected route owner checks.
Route-level memory CRUD/security, skills route security, MCP memory behavior, vector degraded writes, compatibility facade owner behavior, backup skill import, admin vector cleanup, and frontend endpoint wiring need broader coverage.
Current Gaps
services/memory/service.pyneeds an explicit owner-scope/support decision before it is treated as a public memory API.- The agent level-0 skill index should thread owner or be documented as an intentional local/global index.
- MCP memory still needs a deliberate multi-user UX/config decision, but current behavior avoids cross-owner access when owner-bearing rows exist without an explicit MCP owner env.
- Memory JSON import does not rebuild vector indexes.
- Admin wipe vector clearing is currently ineffective.
- Chat memory retrieval needs a graceful path for vector failures after startup.
- Route-level memory and skills security coverage is incomplete.