Skip to content

Session and segment layout

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

The overview describes a coordinator agent whose sub-agents all write to one session. The demo you run on the next pages is a simplified version of that scenario: a single Python script, atomic-session-append-demo.py, that calls append_event on one session in two phases.

PhaseWhat the script doesWhat you verify
SequentialFive appends, each with a state_delta that updates tenant, locale, and turn counterState merges correctly after every turn
Concurrent64 async workers each issue 1,000 appends on the same sessionEvent count stays exact with no duplicates or gaps under parallel writes

The script does not start real sub-agents. The concurrent phase simulates many workers appending at once, the way sub-agents append in production.

Before you run it, this page explains how AerospikeSessionService stores that one session in Aerospike so both phases stay safe.

How one session maps to Aerospike records

AerospikeSessionService stores one ADK session as two kinds of Aerospike records.

The session row is a small Aerospike record (called a row here because it holds session metadata like a row in a session table). It holds session-scoped state and tracks which segment is currently active.

Segment records hold events in chronological order. Events append to the active segment until it reaches Aerospike’s record size limit, then the service creates the next segment and updates the session row.

The demo script only calls append_event and get_session. It never creates segments or manages rollover itself. AerospikeSessionService handles that behind those ADK APIs.

What each record holds

RecordWhat is stored
Session rowSession-scoped state (merged from each append’s state_delta), the active segment index, and a last-updated timestamp
Segment recordA chronological batch of events (user messages, assistant replies, and tool results)

Merged state means the session row holds the latest value for each session-scoped key. When an append includes state_delta: { "turn": 3 }, the service writes the event to the active segment and updates turn on the session row in the same round trip.

Here is a simplified view after three sequential appends:

Session row
state: { turn: 2 }
cur_segment: 0
Segment 0
events:
[0] assistant reply 0
[1] assistant reply 1
[2] assistant reply 2

The demo prints the active segment index as cur_segment. In Aerospike, this value is stored in the cur bin on the session row.

App- and user-scoped keys from state_delta (for example, app:tenant, user:locale) are stored on separate Aerospike records keyed by scope, not on the session row shown here. The sequential phase of the demo exercises all three scopes.

In Aerospike, all records for one session live in the adk_sessions set.

  • Session row key: app_name, user_id, and session_id
  • Segment record key: the same fields plus a :g:NNNNNNNN suffix (for example, demo-app:user-1:session-1:g:00000001)

Segment 0 holds the earliest events, segment 1 holds the next batch, and higher-numbered segments hold later batches.

How appends work

Every append_event call adds one event to the active segment. The call is optional about state: you can include a state_delta when the turn also updates session metadata.

A state_delta is a dictionary of key-value changes passed through EventActions on the Event object. Keys without a prefix (for example, turn) update session-scoped state on the session row. Keys with an app: or user: prefix update app-wide or user-wide state on separate Aerospike records.

Demo phaseIncludes state_delta?Why
SequentialYes, on every appendShows how tenant, locale, and turn counter merge after each turn
ConcurrentNoThe sequential phase proves state merging. The concurrent phase then tests append correctness under load without also updating session state.

When you run the demo, session_state_keys in the output counts session-scoped keys on the session row. The concurrent phase omits state_delta on purpose, the way production workers often append turns without updating session metadata on every write. Session state was set in the sequential phase, so that count stays at 1 while event count grows.

When a state_delta is present, the service writes the event and applies the state changes in one round trip. An event never lands without its state update, and state never updates without the event.

Idempotent retries

Each event gets a stable map key from event.id and event.timestamp. If a network retry re-appends the same Event (same id and timestamp), the service updates the existing slot. No second copy is added. That is what makes appends idempotent.

Segment rollover

As events accumulate, the active segment grows. When it reaches Aerospike’s record size limit, the service creates the next segment, moves new appends there, and updates the cur bin on the session row (printed as cur_segment in the demo). After five sequential appends, cur_segment stays at 0. Under 64,000 concurrent appends, it increases as segments fill.

On the next page, you run the demo so you can verify this layout under sequential and concurrent appends.

Feedback

Was this page helpful?

What type of feedback are you giving?

What would you like us to know?

+Capture screenshot

Can we reach out to you?