Lifetime and Ownership¶
Three kinds of resources in Tryll outlive any single wire request: string storages, embedded string storages, and language / embedding models. Agents reference them by name, often for the whole conversation. This page explains who owns each one, how long it stays alive, and what actually happens when you destroy it — so you can reason about the edge cases without reading the server source.
The dual-ownership pattern¶
Every resource of the three kinds follows the same shape:
- A manager owns a shared reference plus a retention mode
(
PinnedorOnDemand). The manager is the surface you see on the wire — it answersCreate…/Destroy…requests. - Each node that needs the resource is given its own shared
reference at
CreateAgenttime. - The resource lives as long as any reference still exists — manager or node, whichever is last.
Consequence: destroying a storage from the client never yanks data
out from under a live agent. Destroy…Request drops only the
manager's reference (or demotes it to OnDemand). Any agent whose
nodes still hold the resource finishes cleanly; the bytes are freed
once the last reference drops.
The only difference between the three resource kinds is where the manager lives:
| Resource | Manager scope | Lives until |
|---|---|---|
| StringStorage | Session | Session closes, or DestroyStringStorageRequest (Unpin / unregister) + the last agent holding it is destroyed. |
| EmbeddedStringStorage | Session | Session closes, or DestroyEmbeddedStringStorageRequest (Unpin / unregister) + the last agent holding it is destroyed. |
| Model | Process (shared across sessions) | The server unloads it (UnloadModelRequest for Pinned, or automatically for OnDemand once unused). |
The rest of this page walks through each case.
StringStorage¶
The preferred way to supply a string storage to a node is to set the
node's string_storage param to a relative file path under the
session's storage_data_folder. The server loads the file on demand
at CreateAgent time — no explicit Create… / Destroy… wire round-trip
is needed.
When you need to share the same inline data across multiple agents, use an
explicit CreateStringStorageRequest,
which Pins the storage in the session manager.
flowchart LR
subgraph Session
Mgr["StringStorageManager\n(Pinned | OnDemand)"]
end
subgraph Agent
N1[CannedResponse node]
N2[RegexGuardrail node]
end
Mgr -- "shared ref" --> S[(StringStorage)]
N1 -- "shared ref" --> S
N2 -- "shared ref" --> S
Retention modes:
- Pinned — set by
CreateStringStorageRequest(explicit Pin) or whenNodeFactoryloads a file-backed storage atCreateAgenttime. The manager holds a reference of its own;EvictUnusedOnDemand()will not evict it. - OnDemand — the default for lazy file-backed loads that are then
Unpinned (e.g. after
DestroyStringStorageRequest). The manager releases its reference; once the last node drops its reference the storage is freed.
What each operation actually does:
CreateStringStorageRequest— loads or registers the storage and Pins it. Server responds withCreateStringStorageResponse.DestroyStringStorageRequest— for file-backed storages: demotes from Pinned to OnDemand (eligible for eviction once no node holds it). For virtual / inline storages: removes the named entry. The node references are unaffected in either case.CreateAgentRequest(with a relative-pathstring_storageparam) — the session'sNodeFactoryresolves the path, loads-or-gets the storage from the manager, Pins it while the agent is alive, and gives the node a shared reference.DestroyAgentRequest— each node's shared reference drops. If the storage was loaded as part of this agent and has since been Unpinned (or was never explicitly Pinned), the manager demotes to OnDemand and the entry is evicted on the nextEvictUnusedOnDemandcall.- Session close — the manager is torn down, then every remaining agent is destroyed. The last reference goes with the last agent.
Updating a storage in place is not supported — create a new one under
a different name (and, if needed, rebind via
change_param) instead of
mutating one that agents already hold.
EmbeddedStringStorage¶
The preferred way to supply a knowledge base is to set the node's
embedded_string_storage param to a relative path pointing at the
KB config JSON under the session's storage_data_folder. The server
loads the config and builds (or reuses a cached) HNSW index at CreateAgent
time — no explicit Create… is needed.
flowchart LR
subgraph Session
Mgr["EmbeddedStringStorageManager\n(Pinned | OnDemand)"]
end
subgraph Agent
R[Retrieve node]
end
Mgr -- "shared ref" --> E[(EmbeddedStringStorage<br>records + HNSW index)]
R -- "shared ref" --> E
The in-memory object — records, embeddings, HNSW index — follows the same
dual-ownership rules as StringStorage, including Pinned vs OnDemand retention.
Create = Pin; Destroy = Unpin:
CreateEmbeddedStringStorageRequest (config_path)— resolves the relative path against the session root, loads viaFindOrLoad(reusing a cached entry if available), and Pins the entry.CreateEmbeddedStringStorageRequest (strings)— builds an in-memory index and registers it as a virtual entry undername. Always Pinned while registered; evicted when destroyed.DestroyEmbeddedStringStorageRequest— for file-backed (Path A) entries: demotes from Pinned to OnDemand. For virtual (Path B) entries: removes the entry. Node references are unaffected.
On-disk artifacts are separate. A Path-A storage reads from a
records file and optionally a pre-built .usearch index on the server's
disk. Those files are not owned by the session; they remain on disk
after the session ends and are reused on the next request that points at
the same config. What is rebuilt per session is the in-memory index
object, not the cached bytes on disk.
Models¶
Models follow the same manager + node pattern, but the manager lives on the server process, not on a single session. That is why pinning a model in one session keeps it loaded for every other session on the same server.
flowchart LR
subgraph Server process
MM[ModelManager]
end
subgraph Session A
GA[Generate / ToolCall node]
end
subgraph Session B
GB[Generate / ToolCall node]
end
MM -- "shared ref<br>(only while Pinned)" --> M[(Model)]
GA -- "shared ref" --> M
GB -- "shared ref" --> M
Two things decide how long the model stays resident. The same rules apply to language, embedding, STT, and TTS (each kind has its own cache; VAD is catalog/download-only and follows the STT model that loads it):
- Retention mode.
LoadModelRequestinstalls the model as Pinned — theModelManagerholds a reference of its own. A model loaded implicitly (because an agent's graph, voice input, TTS, or embedding use referenced it without a priorLoadModelRequest) is OnDemand — only the live users hold references beyond the cache entry. - Active users. Each
Generate/ToolCallcontext (and STT / TTS / embedding holder) keeps a reference for the life of its agent or voice-input session.
The model is unloaded when the last reference goes away and the manager decides to drop the cache entry:
- Pinned → released by
UnloadModelRequest, but only after every user has been torn down. If agents are still active the request is acknowledged immediately and the actual unload is deferred until the last context drops. - OnDemand → the server runs
EvictUnusedOnDemandafter everyDestroyAgentRequest, and again immediately before the next validated load into any of the four caches (cross-cache sweep). No explicit unload call is needed.
Session end¶
When the TCP connection closes (or the server shuts down), a single cleanup sequence runs for the session:
- Every active turn is cancelled.
- Every agent in the session is destroyed.
- The session's
StringStorageManagerandEmbeddedStringStorageManagerare torn down, dropping their references. - After the last agent is gone,
EvictUnusedOnDemandfrees any OnDemand models whose last user just left. Any idle OnDemand model that briefly remains cached is swept when the next validated model load starts.
Pinned models survive — that is the whole point of pinning. A fresh session can reuse them without paying the load cost again.
Common questions¶
Do I need to call CreateStringStorageRequest before CreateAgentRequest?
No. Set the node's string_storage to a relative file path and the server
loads it automatically at CreateAgentRequest time. Explicit Create… is
only needed for shared inline data or to pre-warm a Pinned entry.
Can I destroy_string_storage while an agent is running?
Yes. The call is safe at any time. Live nodes keep the data alive
for the rest of the agent's life; the storage name just becomes
available for reuse in the same session.
Can I use any name for an inline storage?
The name must not resolve to an existing real file under storage_data_folder.
If it does, the registration is rejected with 7004 StorageNameInUse to
prevent the virtual entry from silently shadowing a file with the same name.
Do I need to re-pin my model after each agent?
No. Pinned stays loaded across agent create / destroy cycles — and
even across sessions on the same server process. Only
UnloadModelRequest demotes it.
Is the Path-A .usearch index rebuilt every session?
No. The cached index on disk is reused; only the in-memory index
object follows the normal shared-reference rules.
What if two agents reference the same storage? Each holds its own reference. Destroying one agent drops that agent's reference; the other keeps using the storage normally.
Can I mutate a string storage in place?
No — there is no wire request to edit one. Create a new storage and,
if a live agent needs the new content, use
change_param to rebind the
node's string_storage param.