Get started Entities

Entities

Stateful objects with automatic persistence and single-writer consistency

An Entity is a stateful object identified by a unique key, with automatic persistence and single-writer consistency per key.

Think of an Entity like a database row with methods — each key is an isolated instance with its own state that survives crashes and serializes concurrent access.

Why It Exists

When two concurrent requests update the same conversation history, one overwrites the other. When a process crashes, in-memory state is lost. Normal objects don’t coordinate concurrent access or survive failures.

Entities solve this. Each key is an isolated instance. State persists automatically. Concurrent updates to the same key are serialized — no race conditions, no lost updates.

When to Use

  • Conversation history that survives crashes
  • User preferences or session state
  • Counters, budgets, or balances that need atomic updates
  • Any state that multiple requests might access concurrently

Example

from agnt5 import Entity

class Conversation(Entity):
    async def add_message(self, role: str, content: str) -> dict:
        messages = self.state.get("messages", [])
        messages.append({"role": role, "content": content})
        self.state.set("messages", messages)
        return {"message_count": len(messages)}

    async def get_history(self) -> list[dict]:
        return self.state.get("messages", [])

Call it via the client:

from agnt5 import Client

client = Client()

# Each key is an isolated instance
conversation = client.entity("Conversation", "session-abc")
conversation.add_message(role="user", content="Explain quantum computing")

# Same key = same state
history = conversation.get_history()  # Returns the message we just added

How It Works

Entities provide three guarantees:

  • Automatic Persistence — State is saved after each successful method call and loaded before the next
  • Single-Writer Consistency — Only one method executes at a time per key (concurrent calls are serialized)
  • Isolation by Key — Different keys are independent instances that can run in parallel
Entity Method Execution Flow
      sequenceDiagram
  participant C as Client
  participant G as Gateway
  participant E as Entity
  participant S as State Store

  C->>G: entity(type, key).method()
  G->>S: Load state for key
  S-->>G: Current state
  G->>E: Execute method with state
  E-->>G: Result + updated state
  G->>S: Save state for key
  G-->>C: Result
  Note over G,S: Concurrent calls to same key wait in queue

    

Transactional semantics: If a method throws an exception, state changes are not saved. Either the method succeeds and state is persisted, or it fails and state remains unchanged.

class TokenBudget(Entity):
    async def consume_tokens(self, tokens: int) -> dict:
        budget = self.state.get("remaining", 0)

        if tokens > budget:
            raise ValueError(f"Insufficient budget: {budget} < {tokens}")  # State NOT saved

        self.state.set("remaining", budget - tokens)  # Only saved if no exception
        return {"remaining": budget - tokens}

Configuration

State API

Access state through self.state inside entity methods:

Method Description
self.state.get(key, default) Get value (returns default if not found)
self.state.set(key, value) Set value
self.state.delete(key) Delete key
self.state.clear() Clear all state
class ResearchSession(Entity):
    async def add_finding(self, source: str, content: str) -> dict:
        findings = self.state.get("findings", [])
        findings.append({"source": source, "content": content})
        self.state.set("findings", findings)
        return {"total": len(findings)}

    async def reset(self) -> dict:
        self.state.clear()
        return {"reset": True}

Key Patterns

The key identifies which instance you’re operating on. Choose keys based on your isolation boundary:

Pattern Example Use Case
Session-scoped Conversation:session-abc Chat history per session
User-scoped Preferences:user-123 Settings per user
Resource-scoped TokenBudget:org-456 Shared budget per org
# Different keys = different instances = parallel execution
session_a = client.entity("Conversation", "session-a")
session_b = client.entity("Conversation", "session-b")

# These run concurrently (different keys)
session_a.add_message(role="user", content="Hello")
session_b.add_message(role="user", content="Hi there")

Guidelines

Common Patterns

Entity for conversation history (keyed by session)
Entity for user preferences (keyed by user ID)
Entity for rate limiting (keyed by API key or user)
Workflow → Entity (checkpoint workflow state in entity)

Common Pitfalls

  • Don’t access self.state outside methods — State is only available inside entity methods. It’s loaded before and saved after each call.

  • Don’t use entities for stateless operations — If you don’t need persistence or coordination, use Functions instead.

  • Don’t share keys across unrelated data — Each key should represent one logical instance. Don’t overload a key with multiple concerns.

What Entities Don’t Do

  • Not for stateless computation — Use Functions for LLM calls, embeddings, or pure transformations
  • Not for multi-step orchestration — Use Workflows for pipelines with checkpointing
  • Not for LLM reasoning — Use Agents for autonomous decision-making

API Reference

  • Entity — Base class for stateful entities
  • self.state.get(key, default) — Get state value
  • self.state.set(key, value) — Set state value
  • self.state.delete(key) — Delete state key
  • self.state.clear() — Clear all state
  • client.entity(type, key) — Get entity proxy for calling methods