mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
166 lines
5.4 KiB
Plaintext
166 lines
5.4 KiB
Plaintext
@startuml
|
|
!theme plain
|
|
title Turnstone — Structured Memory Architecture
|
|
|
|
skinparam participant {
|
|
BackgroundColor<<session>> #C8E6C9
|
|
BackgroundColor<<facade>> #FFE0B2
|
|
BackgroundColor<<storage>> #B3E5FC
|
|
BackgroundColor<<api>> #E8EAF6
|
|
BackgroundColor<<sdk>> #F5F5F5
|
|
}
|
|
|
|
participant "ChatSession\n(session.py)" as Session <<session>>
|
|
participant "MemoryFacade\n(memory.py)" as Facade <<facade>>
|
|
participant "MemoryRelevance\n(memory_relevance.py)" as Relevance <<facade>>
|
|
participant "StorageBackend\n(SQLite / PostgreSQL)" as Storage <<storage>>
|
|
participant "Server API\n(server.py)" as API <<api>>
|
|
participant "Console Admin\n(console/server.py)" as Admin <<api>>
|
|
participant "SDK Client\n(sdk/)" as SDK <<sdk>>
|
|
|
|
== Phase 1: Tool Path (session.send) ==
|
|
|
|
Session -> Session : pin acting principal\nparse memory(action=...)
|
|
note right
|
|
Tool schema: 5 actions
|
|
save, get, search, delete, list
|
|
Auto-approved (no approval needed)
|
|
end note
|
|
|
|
Session -> Session : resolve live project access\nselect exact/inherited scope
|
|
|
|
Session -> Session : _exec_memory(item)
|
|
|
|
alt action = save
|
|
Session -> Session : require non-empty description
|
|
Session -> Facade : save_structured_memory_strict(\n..., require_active_project)
|
|
Facade -> Facade : normalize_key(name)
|
|
Facade -> Storage : guarded atomic upsert\nON CONFLICT ... RETURNING
|
|
Storage --> Facade : (saved row, was_update)
|
|
Facade --> Session : saved row
|
|
Session -> Session : invalidate prefix/cache\naudit acting principal
|
|
end
|
|
|
|
alt action = get
|
|
Session -> Facade : get_structured_memory_by_name_strict()
|
|
Facade -> Storage : exact scoped-name lookup
|
|
Storage --> Session : full row / not found
|
|
end
|
|
|
|
alt action = search
|
|
Session -> Storage : search exact scope or\nactor-visible scope union
|
|
Storage --> Session : matched rows
|
|
end
|
|
|
|
alt action = delete
|
|
Session -> Facade : delete_structured_memory_returning_strict()
|
|
Facade -> Storage : DELETE ... RETURNING
|
|
Storage --> Session : deleted row / not found
|
|
Session -> Session : invalidate + audit\nmark prefix dirty
|
|
end
|
|
|
|
== Phase 2: BM25 Relevance Injection ==
|
|
|
|
Session -> Session : _init_system_messages()\nevery conversation turn
|
|
|
|
Session -> Session : resolve acting principal\nand live project ACL
|
|
Session -> Session : _list_visible_memories(\nlimit=fetch_limit)
|
|
note right
|
|
**Scope resolution:**
|
|
Interactive: global + workstream
|
|
+ acting user + readable project
|
|
Coordinator: acting user's coordinator
|
|
+ readable project
|
|
end note
|
|
|
|
Session -> Facade : list_visible_structured_memories()
|
|
Facade -> Storage : one visibility-union query
|
|
Storage --> Session : up to fetch_limit rows
|
|
|
|
Session -> Relevance : extract_recent_context(\nmessages, max_messages=3)
|
|
Relevance --> Session : user text context
|
|
|
|
Session -> Relevance : score_memories(\nmemories, context,\nk=relevance_k)
|
|
note right
|
|
**BM25 scoring:**
|
|
Index over name + description
|
|
+ content[:200] for each memory.
|
|
Returns top-k by relevance.
|
|
Empty query returns most recent k.
|
|
end note
|
|
Relevance --> Session : top-k memories
|
|
|
|
Session -> Relevance : build_memory_context(\nrelevant_memories)
|
|
note right
|
|
Formats as XML block:
|
|
<memories>
|
|
<memory name="..." type="..."
|
|
scope="..." description="...">
|
|
content (max 500 chars)
|
|
</memory>
|
|
</memories>
|
|
end note
|
|
Relevance --> Session : XML string
|
|
|
|
Session -> Session : inject into\nsystem message
|
|
|
|
== Phase 3: Server API Path ==
|
|
|
|
SDK -> API : GET /v1/api/memories\n?type=general&limit=20
|
|
API -> API : bind scope to caller\ndefault global + caller user
|
|
API -> Storage : list visible rows
|
|
Storage --> API : rows
|
|
API --> SDK : {"memories": [...], "total": N}
|
|
|
|
SDK -> API : POST /v1/api/memories\n{name, content, description, ...}
|
|
API -> API : validate type, scope,\nname/content/description
|
|
API -> API : reject internal scopes\nowner-bind workstream scope
|
|
API -> Facade : save_structured_memory_strict()
|
|
Facade -> Storage : atomic upsert
|
|
Storage --> API : memory row
|
|
API -> API : record_audit(actor)
|
|
API --> SDK : 201 (created) / 200 (updated)
|
|
|
|
SDK -> API : POST /v1/api/memories/search\n{query, type, ...}
|
|
API -> API : bind scope to caller
|
|
API -> Storage : search visible rows
|
|
Storage --> API : matched rows
|
|
API --> SDK : {"memories": [...], "total": N}
|
|
|
|
SDK -> API : DELETE /v1/api/memories/{name}\n?scope=global
|
|
API -> Facade : delete_structured_memory_returning_strict()
|
|
Facade -> Storage : DELETE ... RETURNING
|
|
API -> API : record_audit(actor)
|
|
API --> SDK : {"status": "ok"}
|
|
|
|
== Phase 4: Console Admin Path ==
|
|
|
|
SDK -> Admin : GET /v1/api/admin/memories\n?type=&scope=&limit=
|
|
Admin -> Admin : require_permission(\n"admin.memories")
|
|
Admin -> Storage : list_structured_memories()
|
|
Storage --> Admin : rows
|
|
Admin --> SDK : {"memories": [...], "total": N}
|
|
|
|
SDK -> Admin : GET /v1/api/admin/memories/{id}
|
|
Admin -> Storage : get_structured_memory(id)
|
|
Storage --> Admin : memory row
|
|
Admin --> SDK : memory JSON
|
|
|
|
SDK -> Admin : DELETE /v1/api/admin/memories/{id}
|
|
Admin -> Storage : delete_structured_memory_by_id_returning()
|
|
Admin -> Admin : record_audit(\n"memory.delete")
|
|
Admin --> SDK : {"status": "ok"}
|
|
|
|
== Configuration ==
|
|
|
|
note over Session, Relevance
|
|
**MemoryConfig** (from [memory] in config.toml):
|
|
relevance_k = 5 -- top-k memories per turn
|
|
fetch_limit = 50 -- max memories fetched for scoring
|
|
max_content = 32768 -- max content length per memory
|
|
nudge_cooldown = 300 -- seconds between metacognitive nudges
|
|
nudges = true -- enable/disable memory nudges
|
|
end note
|
|
|
|
@enduml
|