Skip to content
Talon
Esc
navigateopen⌘Jpreview
On this page

KV Storage Key Format

KV Storage Key Format

Namespaces are organized in their own hierarchy. They are resources, but they are not listed as children in the ordinary resource hierarchy inside a namespace.

Canonical resources

Most persisted Talon objects are resources inside an isolated namespace. Namespace names are defined in a colon-separated hierarchy, for example Impala:Talon.

Each resource is referred to by a <Type>/<Name> pair. Name is the resource’s stable Talon name in that context, not necessarily an opaque database ID. For example, agents use Agent/<agent-name>, while sessions and messages commonly use generated IDs as their names.

Resource paths are nested by appending more pairs. Resource names are percent-encoded before serialization so / remains a structural separator:

<Type>/<Name>/<Type>/<Name>

For example:

Agent/hello-agent/Session/01KSNZ13X37KYK7TNWQH7C3XPG

Structured storage columns

The storage API passes a structured key instead of a pre-rendered string:

namespace, parent_path, kind, name

SQL backends store those fields as separate text columns with the value as protobuf bytes:

PRIMARY KEY (namespace, parent_path, kind, name)

For Postgres this avoids prefix scans for direct children:

WHERE namespace = $1 AND parent_path = $2 AND kind = $3

For SQLite the same key is stored in a WITHOUT ROWID table so the primary-key B-tree is the table storage. This keeps sibling resources clustered by namespace, parent_path, kind, and name.

Canonical debug format

The canonical string form is still used for debugging, documentation, and migrating older tables:

@Namespace/<namespace>/<ParentType>/<parent-name>/.../@/<ChildType>/<child-name>

For example:

@Namespace/Impala:Talon/Agent/hello-agent/Session/01KSNZ13X37KYK7TNWQH7C3XPG/@/SessionMessage/01KSNZ1BAQ8GGHV75342GHSGE4

The segment before /@/ identifies the namespace and parent resource path:

@Namespace/Impala:Talon/Agent/hello-agent/Session/01KSNZ13X37KYK7TNWQH7C3XPG

The segment after /@/ identifies the direct child resource stored at that key:

SessionMessage/01KSNZ1BAQ8GGHV75342GHSGE4

Root-level resources in a namespace use the namespace itself as the parent path:

@Namespace/Impala:Talon/@/Agent/hello-agent

Listing behavior

Direct children are listed by exact namespace, parent_path, and optional kind. For example, sessions directly under an agent use:

namespace = Impala:Talon
parent_path = Agent/hello-agent
kind = Session

It does not include session messages, because messages are stored under the session parent path:

parent_path = Agent/hello-agent/Session/01KSNZ13X37KYK7TNWQH7C3XPG
kind = SessionMessage

Recursive operations walk direct children in application code. Talon does not maintain a second recursive index in the SQL storage layer.

DynamoDB single-table layout

The DynamoDB backend stores the same structured key in one shared on-demand table with only PK and SK in the key schema. The key strings are a DynamoDB-optimized projection of Talon’s canonical ResourceKey, not a separate logical identity:

PK = Namespace/<percent-encoded namespace>/Resource/<parent_path>
SK = <kind>/<name>

The namespace is Talon’s current isolation boundary. Every item for a namespace has that namespace embedded in PK, so ordinary reads and queries cannot cross namespace partitions. Talon reconstructs the structured ResourceKey from PK and SK; the protobuf payload is stored as binary Value.

For session state this gives the following concrete shapes:

Session:
PK = Namespace/<namespace>/Resource/Agent/<agent_id>
SK = Session/<session_id>

Submission / lease record:
PK = Namespace/<namespace>/Resource/Agent/<agent_id>/Session/<session_id>
SK = SessionSubmission/<submission_id>

Journal entry:
PK = Namespace/<namespace>/Resource/Agent/<agent_id>/Session/<session_id>/SessionSubmission/<submission_id>
SK = SessionJournalEntry/<sequence_number>

Leases are stored on the SessionSubmission record. Claim acquisition reads the current protobuf value and writes the updated submission with a DynamoDB conditional write: new submissions use attribute_not_exists(PK), and existing submissions use Value = :expected. The submission contains a UUIDv7 attempt_id, attempt_count, and claim_expires_at; journal appends are fenced by checking that the active submission still has the same attempt_id before writing SessionJournalEntry items. Lease renewal uses the same expected-value condition, so a stale worker cannot renew or append after another worker has claimed a newer UUIDv7 attempt.

Tables are shared across namespaces by default. New namespace onboarding does not need table creation. Production deployments should provision the shared table in infra, while local/E2E tests create their emulator table during test setup. Local development can keep using sqlite or filesystem-backed object storage while production sets control_plane.database.driver: dynamodb.

The Rust backend is async and uses the official AWS SDK client directly. The SDK client is cheap to clone, internally shared, and non-blocking under Tokio, so Talon should keep one DynamoDbKvStore per control plane and share it behind the existing Arc<dyn KeyValueStore + Send + Sync> boundary instead of creating per-request clients.

Namespace hierarchy

Namespace hierarchies are not represented in the ordinary resource hierarchy. A separate NamespaceRef resource defines each child namespace edge:

@Namespace/Impala/@/NamespaceRef/Talon

This implies the existence of an Impala:Talon namespace.

Root namespace edges are stored as system resources:

@Namespace/Sys/@/NamespaceRef/Impala

System namespace

The special namespace Sys is reserved for system and global resources such as namespace metadata and MCP servers. For example, MCP server keys use:

@Namespace/Sys/@/MCPServer/github

Was this page helpful?