---
title: "Run sequential appends"
description: "Save and run the atomic session append demo through sequential appends with multi-scope state."
---

# Run sequential appends

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

The demo script is adapted from the [atomic session append example](https://github.com/aerospike-community/adk-aerospike/blob/main/docs/tutorials/atomic-session-append.md) in the [adk-aerospike](https://github.com/aerospike-community/adk-aerospike) repository. Open that source if you want to see how each `append_event` call builds events and `state_delta` values before you run it for yourself.

The script walks through sequential `append_event` calls with scoped state, then runs concurrent writers. Here you run and review the sequential phase only.

The script also defines `WRITERS`, `WRITES_PER_WRITER`, and `MAX_IN_FLIGHT` for the concurrent phase. `MAX_IN_FLIGHT` uses a semaphore (a concurrency limiter) to cap how many `append_event` calls run at once. See [Run concurrent writers](https://aerospike.com/docs/develop/adk-atomic-session-append/step/3/part/1/concurrent-writers) for more information on the constants and the semaphore.

## Save and run the demo script

1.  Create `atomic-session-append-demo.py` with the script in this section (or copy from the [adk-aerospike example](https://github.com/aerospike-community/adk-aerospike/blob/main/docs/tutorials/atomic-session-append.md)):
    
    ```python
    import asyncio
    
    import time
    
    import aerospike
    
    from google.adk.events import Event, EventActions
    
    from google.genai import types as genai_types
    
    from adk_aerospike import AerospikeSessionService
    
    APP, USER, SID = "demo-app", "user-1", "session-1"
    
    WRITERS = 64
    
    WRITES_PER_WRITER = 1000
    
    MAX_IN_FLIGHT = 4
    
    sem = asyncio.Semaphore(MAX_IN_FLIGHT)
    
    def print_session_record(client: aerospike.Client, label: str) -> None:
    
        pk = ("test", "adk_sessions", f"{APP}:{USER}:{SID}")
    
        _, _, bins = client.select(pk, ["cur", "state"])
    
        print(
    
            f"{label} — cur_segment={bins.get('cur')}, "
    
            f"session_state_keys={len(bins.get('state') or {})}"
    
        )
    
    async def main() -> None:
    
        client = aerospike.client(
    
            {
    
                "hosts": [("127.0.0.1", 3000)],
    
                "max_error_rate": 100,
    
                "error_rate_window": 1,
    
            }
    
        ).connect()
    
        session_service = AerospikeSessionService(client, "test")
    
        session = await session_service.create_session(
    
            app_name=APP, user_id=USER, session_id=SID
    
        )
    
        # --- sequential appends: scoped state deltas accumulate ---
    
        for i in range(5):
    
            await session_service.append_event(
    
                session,
    
                Event(
    
                    invocation_id=f"seq-{i}",
    
                    author="assistant",
    
                    content=genai_types.Content(
    
                        role="model",
    
                        parts=[genai_types.Part(text=f"assistant reply {i}")],
    
                    ),
    
                    actions=EventActions(
    
                        state_delta={
    
                            "turn": i,
    
                            "app:tenant": "acme",
    
                            "user:locale": "en-US",
    
                        }
    
                    ),
    
                ),
    
            )
    
            snap = await session_service.get_session(
    
                app_name=APP, user_id=USER, session_id=SID
    
            )
    
            print(
    
                f"after append {i}: events={len(snap.events)}, "
    
                f"state={snap.state}"
    
            )
    
        print_session_record(client, "after 5 sequential appends")
    
        # --- concurrent load: 64 writers × 1000 appends each ---
    
        async def writer(writer_id: int) -> None:
    
            for i in range(WRITES_PER_WRITER):
    
                async with sem:
    
                    await session_service.append_event(
    
                        session,
    
                        Event(
    
                            invocation_id=f"w{writer_id:02d}-{i:04d}",
    
                            author=f"worker-{writer_id}",
    
                            timestamp=time.time(),
    
                            content=genai_types.Content(
    
                                role="user",
    
                                parts=[
    
                                    genai_types.Part(
    
                                        text=f"worker {worker_id} write {i}"
    
                                    )
    
                                ],
    
                            ),
    
                        ),
    
                    )
    
                session.events.clear()
    
        started = time.perf_counter()
    
        await asyncio.gather(*(writer(w) for w in range(WRITERS)))
    
        elapsed = time.perf_counter() - started
    
        expected = 5 + WRITERS * WRITES_PER_WRITER
    
        final = await session_service.get_session(
    
            app_name=APP, user_id=USER, session_id=SID
    
        )
    
        print(
    
            f"\n{WRITERS} writers × {WRITES_PER_WRITER} appends "
    
            f"(max {MAX_IN_FLIGHT} in flight) in {elapsed:.1f}s — "
    
            f"stored {len(final.events)} events (expected {expected})"
    
        )
    
        print_session_record(client, "after concurrent appends")
    
        await session_service.delete_session(app_name=APP, user_id=USER, session_id=SID)
    
        session_service.close()
    
        print("Connection closed.")
    
    if __name__ == "__main__":
    
        asyncio.run(main())
    ```
    
2.  Run the script:
    
    Terminal window
    
    ```shell
    python atomic-session-append-demo.py
    ```
    
    Expected result
    
    ```text
    after append 0: events=1, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 0}
    
    after append 1: events=2, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 1}
    
    after append 2: events=3, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 2}
    
    after append 3: events=4, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 3}
    
    after append 4: events=5, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 4}
    
    after 5 sequential appends — cur_segment=0, session_state_keys=1
    ```
    
    The script continues into the concurrent phase after these lines. Keep the terminal open. On the [Run concurrent writers](https://aerospike.com/docs/develop/adk-atomic-session-append/step/3/part/1/concurrent-writers) page, you review the concurrent output from this run.
    

The sequential phase starts after the script connects and creates one session:

atomic-session-append-demo.py

```python
session = await session_service.create_session(

    app_name=APP, user_id=USER, session_id=SID

)
```

## Review the sequential phase output

1.  Read the per-append lines:
    
    The script loops five times. Each iteration appends one event with a multi-scope `state_delta`, then reads the session back and prints a snapshot:
    
    atomic-session-append-demo.py
    
    ```python
    for i in range(5):
    
        await session_service.append_event(
    
            session,
    
            Event(
    
                invocation_id=f"seq-{i}",
    
                author="assistant",
    
                content=genai_types.Content(
    
                    role="model",
    
                    parts=[genai_types.Part(text=f"assistant reply {i}")],
    
                ),
    
                actions=EventActions(
    
                    state_delta={
    
                        "turn": i,                  # session-scoped
    
                        "app:tenant": "acme",       # app-scoped
    
                        "user:locale": "en-US",     # user-scoped
    
                    }
    
                ),
    
            ),
    
        )
    
        snap = await session_service.get_session(
    
            app_name=APP, user_id=USER, session_id=SID
    
        )
    
        print(
    
            f"after append {i}: events={len(snap.events)}, "
    
            f"state={snap.state}"
    
        )
    ```
    
    ```text
    after append 0: events=1, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 0}
    
    after append 1: events=2, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 1}
    
    after append 2: events=3, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 2}
    
    after append 3: events=4, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 3}
    
    after append 4: events=5, state={'app:tenant': 'acme', 'user:locale': 'en-US', 'turn': 4}
    ```
    
    -   `events=` grows by one on each append because each `append_event` adds one event to the session.
    -   `state=` shows merged session state after each append. The `app:` and `user:` prefixes route values to app-wide and user-wide scopes. `turn` stays session-scoped.
2.  Read the session record line:
    
    After the loop, the script reads the session row directly from Aerospike:
    
    atomic-session-append-demo.py
    
    ```python
    def print_session_record(client: aerospike.Client, label: str) -> None:
    
        pk = ("test", "adk_sessions", f"{APP}:{USER}:{SID}")
    
        _, _, bins = client.select(pk, ["cur", "state"])
    
        print(
    
            f"{label} — cur_segment={bins.get('cur')}, "
    
            f"session_state_keys={len(bins.get('state') or {})}"
    
        )
    
    # ... sequential loop ...
    
    print_session_record(client, "after 5 sequential appends")
    ```
    
    ```text
    after 5 sequential appends — cur_segment=0, session_state_keys=1
    ```
    
    -   `cur_segment=0` is the active segment index. Segment 0 is the first segment, so all five events fit there with no rollover. The demo prints this label for readability. In Aerospike, the value is stored in the `cur` bin on the session row.
    -   `session_state_keys=1` counts session-scoped keys in the `state` bin (here, only `turn`). App- and user-scoped keys are stored on separate Aerospike records and are not counted here. The count stays at 1 in the concurrent phase too (see [Session and segment layout](https://aerospike.com/docs/develop/adk-atomic-session-append/step/2/part/0/session-and-segments)).
3.  Confirm the sequential phase:
    
    The script ran five `append_event` calls in order. Each call carried a `state_delta` spanning three scopes. `AerospikeSessionService` merged each event with its state updates in one round trip per append.
    
    -   Event count grows from one to five across the five lines.
    -   Merged state in each line includes `app:tenant`, `user:locale`, and `turn`.

Each append with a `state_delta` coalesces the event write and session, app, and user state updates into one round trip.

::: undefined
-   I’ve saved atomic-session-append-demo.py.
-   I’ve run the script and reviewed the sequential phase output.
-   I’ve verified five events and its merged state after sequential appends on one session.
:::

[Previous  
Session and segment layout](https://aerospike.com/docs/develop/adk-atomic-session-append/step/2/part/0/session-and-segments) [Next  
Run concurrent writers](https://aerospike.com/docs/develop/adk-atomic-session-append/step/3/part/1/concurrent-writers)