Skip to content

Stores

A ChunkStore is the ordered log behind a node, and a deliberate extension point for custom storage. Readers and writers are its cursors.

ChunkStore

The main store operations are intentionally low-level: put appends a fragment, get waits at a sequence position, and close_writes_with_status publishes the terminal state to every reader.

a11.stores.chunk_store.ChunkStore

ChunkStore()

Construct the abstract base. Subclass this in Python to back an agent with a custom asynchronous chunk store; every data method returns an awaitable so callers never block the event loop.

clear_data

clear_data(seq: SupportsInt | SupportsIndex) -> Any

Erase the payload of the fragment at a sequence number while keeping its slot, and await the resulting fragment. Use this to reclaim memory for chunks an agent has already consumed.

close_writes_with_status

close_writes_with_status(status: Any, return_status_if_already_closed: bool = False) -> Any

Seal the store against further writes with a terminal status and await completion. Waiting readers are released. With return_status_if_already_closed, a repeated close returns the status recorded by the first.

Examples:

Publish clean producer completion to all readers:

await store.close_writes_with_status(Status.ok())

get

get(seq: SupportsInt | SupportsIndex, deadline: Any | None = None) -> Any

Await the fragment stored at a sequence number. The future resolves when the fragment is available or the optional deadline elapses.

Examples:

Read back a fragment after retaining its assigned position:

fragment = await store.get(seq)

get_by_arrival_order

get_by_arrival_order(arrival_order: SupportsInt | SupportsIndex, deadline: Any | None = None) -> Any

Await the fragment identified by the order in which it arrived rather than its sequence number. Use this when an agent needs to replay chunks in ingestion order; the future resolves when the fragment is present or the optional deadline passes.

get_final_seq

get_final_seq() -> Any

Await the explicitly marked final sequence, or None if no fragment has declared finality. Finality is independent of write closure: closing the store does not create a final sequence.

get_id

get_id() -> str

Return the store's node identifier. Raises if a Python subclass does not override get_id.

get_seq_for_arrival_order

get_seq_for_arrival_order(arrival_order: SupportsInt | SupportsIndex) -> Any

Await the sequence number that corresponds to a given arrival order. Use this to translate ingestion-order references into the sequence numbers the rest of the API expects.

next

next(deadline: Any | None = None, limit: SupportsInt | SupportsIndex = 1) -> Any

Await up to limit of the next available fragments as a stream. This is the primary way an agent consumes chunks as they are produced: the future resolves with whatever is ready before the optional deadline, and slots may be None when a fragment is missing. Loop over successive calls to follow a growing store.

put

put(fragment: NodeFragment) -> Any

Append a single fragment and await its assigned sequence number. The future resolves once the backing store accepts the write.

Examples:

Store a fragment and retain its assigned position:

seq = await store.put(fragment)

put_many

put_many(fragments: Sequence[NodeFragment]) -> Any

Append several fragments in one batch and await their assigned sequence numbers. Prefer this over repeated put calls when an agent emits many chunks at once, to reduce round-trips.

size

size() -> Any

Await the number of fragments currently in the store. Useful for an agent to gauge backlog or progress without reading chunks.

a11.stores.chunk_store.ChunkStoreFactory module-attribute

ChunkStoreFactory = Callable[[NameString], ChunkStore]

LocalChunkStore

a11.stores.local_chunk_store.LocalChunkStore

LocalChunkStore(node_id: NameString)

Bases: ChunkStore

An in-memory store whose state and synchronization live in C++.

The small forwarding layer keeps Python overrides virtual when a subclass is passed back into native readers, writers, nodes, or sessions.

get async

get(seq: int, deadline: Time | None = infinite_future()) -> NodeFragment

Wait for and return the fragment at sequence number seq.

Use a ChunkStoreReader for ordinary sequential consumption; direct lookup is useful for replay, inspection, and custom retention logic.

get_by_arrival_order async

get_by_arrival_order(arrival_order: int, deadline: Time | None = infinite_future()) -> NodeFragment

Wait for a fragment by its zero-based ingestion order.

Arrival order can differ from sequence order when fragments reach a store out of order.

next async

next(deadline: Time | None = infinite_future(), limit: int = 1) -> list[NodeFragment | None]

Read from the store's shared logical-sequence cursor.

The cursor advances through sequence numbers 0, 1, 2, and so on, waiting at gaps. None is the clean end sentinel, not a placeholder for a missing fragment. Use get_by_arrival_order for ingestion order; most agent code should let ChunkStoreReader manage this cursor, buffering, ordering, and end-of-stream handling.

put async

put(fragment: NodeFragment) -> int

Append one fragment and return its accepted sequence number.

put_many async

put_many(fragments: Sequence[NodeFragment]) -> list[int]

Append a batch and return sequence numbers in matching order.

clear_data async

clear_data(seq: int) -> NodeFragment

Discard one stored payload while retaining its sequence slot.

This supports pop_chunks readers and retention policies without changing the ordering metadata seen by other readers.

get_seq_for_arrival_order async

get_seq_for_arrival_order(arrival_order: int) -> int

Translate a zero-based ingestion position to its sequence number.

get_final_seq async

get_final_seq() -> int | None

Return the logical final sequence, if a final fragment was written.

Finality and closure are independent: closing writes does not create a final sequence, and writing a final fragment does not close the store.

close_writes_with_status async

close_writes_with_status(status: Status, return_status_if_already_closed: bool = False) -> Status

Seal writes with status and wake blocked readers.

This records whether production completed or failed, but it does not mark any fragment as the final data value. Producers that need semantic finality should write a fragment with continued=False first (most commonly through AsyncNode.put_final or put_null_final).

size async

size() -> int

Return the number of fragment slots currently held in memory.

get_id

get_id() -> NameString

Return the node id whose fragment log this store backs.

get_impl

get_impl() -> LocalChunkStore

Return the owned native implementation.

RedisChunkStore

a11.stores.redis_chunk_store.RedisChunkStore

RedisChunkStore(id: str, client: RedisClient | None = None, options: RedisChunkStoreOptions | dict[str, Any] | None = None)

Bases: ChunkStore

A persistent, multi-process ChunkStore backed by Redis Streams.

Open one node's persistent stream with an injected Redis client.

Stores for the same id address the same Redis state. Pass a shared RedisClient in production so many nodes reuse one connection pool; call initialize when metadata must exist before the first write.

client property

client: RedisClient

The explicitly composed RedisClient.

keys property

A copy of the sharding-safe Redis key layout.

options property

A copy of this store's key and payload policy.

create staticmethod

create(id: str, client: RedisClient | None = None, options: RedisChunkStoreOptions | dict[str, Any] | None = None) -> RedisChunkStore

Create a Redis-backed fragment log for one node id.

clear_data async

clear_data(seq: int) -> NodeFragment

Tombstone one payload while retaining ordering metadata.

close_writes_with_status async

close_writes_with_status(status: Status, return_status_if_already_closed: bool = False) -> Status

Atomically seal writes with a terminal status and wake readers.

This closes the producer side but does not mark data final. Write a final fragment first when consumers use whole-value semantics such as AsyncNode.consume.

get async

get(seq: int, deadline: Time | None = None) -> NodeFragment

Wait for and return a fragment by sequence number.

The deadline bounds both Redis work and the wait for a future fragment.

get_by_arrival_order async

get_by_arrival_order(arrival_order: int, deadline: Time | None = None) -> NodeFragment

Wait for a fragment by its zero-based Redis ingestion order.

get_final_seq async

get_final_seq() -> int | None

Return the logical final sequence, if one has been written.

The final marker is independent of Redis write closure. Closing a store does not synthesize it, and a final fragment does not close the store.

get_metadata async

get_metadata() -> RedisChunkStoreMetadata

Read size, finality, and closure state without scanning fragments.

get_seq_for_arrival_order async

get_seq_for_arrival_order(arrival_order: int) -> int

Translate a zero-based ingestion position to its sequence number.

initialize async

initialize() -> None

Ensure node metadata exists without writing a fragment.

This is useful during provisioning or health checks; ordinary writes initialize the store lazily.

next async

next(deadline: Time | None = None, limit: int = 1) -> list[NodeFragment | None]

Read from the persistent shared logical-sequence cursor.

The cursor advances through sequence numbers and waits at gaps; None marks clean end-of-stream. Use get_by_arrival_order for ingestion order. Prefer ChunkStoreReader for normal node consumption; it adds buffering, offsets, and final-sequence handling above this primitive.

put async

put(fragment: NodeFragment) -> int

Atomically append one fragment and return its sequence number.

put_many async

put_many(fragments: Sequence[NodeFragment]) -> list[int]

Atomically append a batch and return its assigned sequences.

size async

size() -> int

Return the number of fragment entries recorded for this node.

a11.stores.redis_chunk_store.RedisChunkStoreOptions

RedisChunkStoreOptions(key_prefix: str = 'a11:', inline_data_threshold: Any = 262144)

Key layout and inline-payload policy for RedisChunkStore.

Construct validated Redis chunk-store options.

inline_data_threshold property writable

inline_data_threshold: int

Chunk data larger than this many bytes uses the blob hash.

key_prefix property writable

key_prefix: str

Prefix before the per-node Redis Cluster hash tag.

from_environment staticmethod

from_environment() -> RedisChunkStoreOptions

Read the A11_REDIS_CHUNK_STORE_* environment variables.

validate

validate() -> None

Raise if the key layout policy is invalid.

a11.stores.redis_chunk_store.RedisChunkStoreMetadata

Node-level Redis state read without iterating over chunk entries.

closed property

closed: bool

Whether the store rejects new writes.

final_seq property

final_seq: int | None

The declared final sequence, if one has arrived.

id property

id: str

The owning AsyncNode identifier.

max_seq property

max_seq: int | None

Largest sequence currently present.

next_cursor property

next_cursor: int

Global SPMC cursor used by next().

revision property

revision: int

Monotonic mutation generation published to waiters.

size property

size: int

Number of chunk slots in the store.

status property

status: Status | None

Return the terminal status when closed, otherwise None.

total_chunks_put property

total_chunks_put: int

Number of chunks appended over the store lifetime.

a11.stores.redis_chunk_store.RedisChunkStoreKeys

The sharding-safe Redis keys owned by one node stream.

arrival_index property

arrival_index: str

Arrival-order-to-sequence hash.

blobs property

blobs: str

Hash containing large encoded chunk payloads.

events property

events: str

Pub/Sub channel used for invalidation notifications.

metadata property

metadata: str

Hash containing node-level metadata.

sequence_index property

sequence_index: str

Sequence-to-stream-entry hash.

stream property

stream: str

Redis Stream containing chunk and control entries.

script_keys

script_keys() -> list[str]

Return keys in the stable order used by the Lua state machine.

ChunkStoreReader

Create a reader at an offset and use next for one record or async iteration to drain its configured range.

a11.stores.chunk_store_reader.ChunkStoreReader

ChunkStoreReader(store: ChunkStore, options: ChunkStoreReaderOptions | dict[str, Any] | None = None)

Open a reader over store.

options (a ChunkStoreReaderOptions or plain dict) tunes ordering, buffering, starting offset, sticky mimetypes, and whether chunks are popped as they are read.

buffer_size property

buffer_size: int

Number of prefetched fragments currently held in the reader's buffer.

options property

The ChunkStoreReaderOptions this reader was created with.

store property

store: ChunkStore

The ChunkStore this reader draws fragments from.

cancel

cancel() -> None

Stop the background read pump. Pending next awaitables are resolved and no further chunks are fetched.

ensure_started

ensure_started() -> None

Start the background read pump if it is not already running. Reading normally starts it lazily; call this to begin buffering before the first next.

get_status

get_status() -> Any

Return the reader's current status. An agent can inspect this to distinguish a healthy stream from one that has failed or ended.

next

next(timeout: Duration = ...)

Return an awaitable for the next fragment in this reader's view.

It resolves to None after the configured range or final sequence is exhausted. timeout bounds this wait only; a timed-out read does not close the store or prevent a later call from continuing the stream.

Raises:

Type Description
StatusException

If the timeout expires, the store closes with an error, or the reader encounters invalid stream state.

Examples:

Resume a replay after an application checkpoint:

reader = ChunkStoreReader(store, {"offset": checkpoint + 1})
while fragment := await reader.next():
    await replay(fragment)

wait

wait() -> Any

Await completion of the background read pump. The returned future resolves once the reader has drained the store or been cancelled.

a11.stores.chunk_store_reader.ChunkStoreReaderOptions

ChunkStoreReaderOptions(ordered: bool = True, pop_chunks: bool = False, num_chunks_to_buffer: Any = 32, offset: Any = 0, max_chunks_to_read: Any | None = None, sticky_mimetype: bool = False)

Construct validated options for a ChunkStoreReader.

max_chunks_to_read property writable

max_chunks_to_read: int | None

Optional cap on the total number of chunks to read.

num_chunks_to_buffer property writable

num_chunks_to_buffer: int

Maximum number of chunks to prefetch into the buffer.

offset property writable

offset: int

Sequence number at which reading begins.

ordered property writable

ordered: bool

Whether chunks are delivered strictly in sequence order.

pop_chunks property writable

pop_chunks: bool

Whether chunks are removed from the store as they are read.

sticky_mimetype property writable

sticky_mimetype: bool

Whether ordered chunks inherit the last explicitly set mimetype.

validate

validate() -> None

Raise if the options are not internally consistent.

ChunkStoreWriter

put_chunk returns a confirmation future after bounded admission. Await that future when the application must checkpoint only after storage accepts the chunk, then call drain_and_close at producer shutdown.

a11.stores.chunk_store_writer.ChunkStoreWriter

ChunkStoreWriter(chunk_store: ChunkStore, options: ChunkStoreWriterOptions | dict[str, Any] | None = None)

Open a writer over chunk_store.

options (a ChunkStoreWriterOptions or plain dict) tunes the starting offset, sticky mimetypes, and how much is buffered/flushed at once.

options property

The ChunkStoreWriterOptions this writer was created with.

queue_size property

queue_size: int

Number of chunks currently waiting in the writer's flush queue.

store property

store: ChunkStore

The ChunkStore this writer persists chunks to.

abort_with_status

abort_with_status(status: Any) -> Any

Abort the writer with an error status and await teardown. Use this to propagate a failure downstream so readers observe the error instead of a clean end-of-stream.

attach_stream

attach_stream(stream: WireStream) -> None

Tee stored fragments to an additional wire stream. After the store accepts a batch, the writer calls send on attached streams; a successful send confirms local transport admission, not peer delivery. A transport failure stops later writes but cannot revoke the current batch's store confirmations. The writer keeps the stream alive while attached.

cancel

cancel() -> Any

Stop the writer immediately and await teardown, discarding any chunks still queued. Use this to abandon a stream an agent no longer needs.

detach_stream

detach_stream(stream: WireStream) -> None

Stop mirroring fragments to a previously attached wire stream. Raises if the stream was not attached.

drain_and_close

drain_and_close() -> Any

Flush every queued chunk, close the writer, and await completion. This does not append a final fragment: mark the last chunk final before draining when readers need a final sequence number.

enqueue_chunk

enqueue_chunk(chunk: Chunk, seq: Any | None = None, final: bool = False) -> tuple[Any, Any]

Enqueue a chunk and get back a (confirmation, admission) pair of awaitables. Unlike put_chunk, this exposes backpressure explicitly: admission resolves when the chunk is accepted into the bounded queue (None if it fit immediately) and confirmation resolves with the sequence assigned by the backing store. An agent awaits admission to pace production and confirmation to know the store accepted the write.

ensure_started

ensure_started() -> None

Start the background flush loop if it is not already running. Writing normally starts it lazily; call this to begin flushing before the first chunk is enqueued.

get_abort_status

get_abort_status() -> Any

Return the status the writer was aborted with, or None if it was not aborted. Use this to distinguish a clean close from an error-driven abort.

get_status

get_status() -> Any

Return the writer's terminal status, or None while it is still open. An agent can poll this to detect that the stream has closed or failed.

is_writable

is_writable() -> bool

Return whether the writer still accepts chunks. False once the stream has been drained, closed, or aborted.

put async

put(obj: Any, seq: int | None = None, final: bool = False) -> Future[int]

Write a chunk and return its store-confirmation future.

The writer operates at the chunk level; pass an already-serialized Chunk (use AsyncNode to write arbitrary Python objects). Returns a asyncio.Future resolving to the stored sequence number.

put_chunk async

put_chunk(chunk: Chunk, seq: int | None = None, final: bool = False) -> Future[int]

Enqueue a native chunk and return its store-confirmation future.

Set final=True on the last chunk when readers must know the logical end of the sequence. Calling drain_and_close later only flushes and closes the writer; it does not add that final marker for you.

Examples:

Checkpoint only after the store accepts the final event:

confirmation = await writer.put_chunk(
    a11.to_chunk(event), final=True
)
stored_seq = await confirmation
await checkpoints.save(stored_seq)

wait_for_buffer_to_drain

wait_for_buffer_to_drain() -> Any

Await until the in-flight write buffer empties. An agent can use this as a backpressure checkpoint before enqueuing more chunks.

a11.stores.chunk_store_writer.ChunkStoreWriterOptions

ChunkStoreWriterOptions(offset: Any = 0, max_chunks_to_write_at_once: Any = 8, num_chunks_to_buffer: Any | None = None, sticky_mimetype: bool = False)

Construct validated options for a ChunkStoreWriter.

max_chunks_to_write_at_once property writable

max_chunks_to_write_at_once: int

Maximum number of chunks flushed to the store per batch.

num_chunks_to_buffer property writable

num_chunks_to_buffer: int | None

Optional bound on the in-flight write buffer size.

offset property writable

offset: int

Sequence number at which writing begins.

sticky_mimetype property writable

sticky_mimetype: bool

Whether repeated contiguous chunk mimetypes are omitted.

validate

validate() -> None

Raise if the options are not internally consistent.