AgentFieldbuild

Memory events

Reactive programming with memory change subscriptions: trigger agent logic when shared state changes.

Reactive memory change events

Trigger agent logic the instant shared state changes -- no polling, no delays.

Agents often need to react when another agent writes data: reorder inventory when stock drops, alert a human when risk spikes, kick off downstream processing when upstream results land. AgentField's memory event system lets you subscribe to key patterns and run handlers when matching keys change -- across any scope, from any agent.

from agentfield import Agent

app = Agent(node_id="inventory-monitor")

# Reactive inventory — auto-reorder when stock drops below threshold
@app.on_change("warehouse.stock.*")
async def on_stock_change(event):
    product_id = event.key.split(".")[-1]
    stock_level = event.data.get("quantity", 0)
    threshold = event.data.get("reorder_threshold", 10)

    if stock_level < threshold:
        await app.call(
            "procurement.reorder",
            product_id=product_id,
            current_stock=stock_level,
            order_quantity=threshold * 3,
        )
        await app.memory.global_scope.set(
            f"alerts.reorder.{product_id}",
            {"status": "reorder_triggered", "quantity": threshold * 3},
        )

# Cross-agent notification — alert when any agent flags high risk
@app.memory.global_scope.on_change("risk.scores.*")
async def on_risk_spike(event):
    if event.data.get("level") == "critical":
        await app.call(
            "notifications.slack",
            channel="#risk-alerts",
            message=f"Critical risk: {event.key} = {event.data}",
        )

What just happened

  • A memory write became an event source for downstream automation
  • Handlers reacted immediately without polling loops
  • The same reactive pattern worked across agents because events flow through shared memory

Example event payload:

{
  "key": "warehouse.stock.widget-42",
  "scope": "global",
  "scope_id": "global",
  "action": "set",
  "data": { "quantity": 3, "reorder_threshold": 10 },
  "previous_data": { "quantity": 11, "reorder_threshold": 10 }
}
What you get
  • Pattern-based subscriptions -- watch specific keys, wildcard ranges, or entire namespaces with glob patterns.
  • Scoped listeners -- subscribe at global, session, actor, or workflow scope for precise targeting.
  • MemoryChangeEvent -- rich event object with key, new value, old value, scope, timestamp, and source agent.
  • Cross-agent reactivity -- one agent writes, another agent's handler fires immediately.
  • No polling -- push-based delivery via the control plane's internal event bus.
  • Multiple patterns -- TypeScript supports arrays of patterns in a single subscription.
MemoryChangeEvent structure

Every handler receives a MemoryChangeEvent with the following fields:

FieldPythonTypeScriptDescription
Keyevent.keyevent.keyThe memory key that changed (e.g., "warehouse.stock.sku-42")
New valueevent.dataevent.dataThe new value written to the key
Previous valueevent.previous_data / event.old_value--The previous value (Python only)
Scopeevent.scopeevent.scopeMemory scope: "global", "session", "actor", "workflow"
Scope IDevent.scope_idevent.scopeIdThe scope identifier
Timestampevent.timestampevent.timestampTimestamp of the change
Source agent--event.agentIdThe agent that wrote the change (TypeScript only)
Actionevent.action--"set" or "delete" (Python only)
@app.on_change("orders.*")
async def on_order(event):
    print(event.key)            # "orders.ord-123"
    print(event.data)           # {"status": "shipped", "tracking": "1Z999..."}
    print(event.previous_data)  # {"status": "processing"}
    print(event.old_value)      # {"status": "processing"} (alias for previous_data)
    print(event.scope)          # "global"
    print(event.timestamp)      # "2026-03-24T10:30:00Z"
    print(event.action)         # "set"
Pattern syntax

Memory event patterns use a glob-like syntax to match keys.

PatternMatchesDoes Not Match
"orders.pending"orders.pending (exact)orders.shipped, orders.pending.item1
"orders.*"orders.pending, orders.shipped, orders.pending.item1inventory.stock
"*.stock"warehouse.stock, store.stockwarehouse.stock.sku42, inventory.item
"**"Everything--

Rules

  • * is converted to .* regex internally, so it crosses dot boundaries. Unlike filesystem globs, * and ** are functionally identical here -- both match across dot-separated segments.
  • Exact strings match literally.
  • Patterns are case-sensitive.

Multiple Patterns (TypeScript)

// Watch multiple patterns in one subscription
agent.watchMemory(
  ["orders.*", "payments.*", "refunds.*"],
  async (event) => {
    console.log(`Transaction event: ${event.key}`);
  }
);

In Python, register multiple decorators:

for pattern in ["orders.*", "payments.*", "refunds.*"]:
    @app.on_change(pattern)
    async def on_transaction(event):
        print(f"Transaction event: {event.key}")
Scoped subscriptions

Subscribe to events within a specific memory scope for precise targeting.

# Global scope — watch shared config changes
@app.memory.global_scope.on_change("config.*")
async def on_config_change(event):
    print(f"Global config changed: {event.key}")

# Session scope — watch conversation state for a specific session
@app.memory.session("session-abc").on_change("conversation.*")
async def on_conversation(event):
    print(f"Session conversation updated: {event.key}")

# Actor scope — watch user preference changes
@app.memory.actor("user-42").on_change("preferences.*")
async def on_prefs_change(event):
    print(f"User preferences changed: {event.key}")

# Workflow scope — watch intermediate results
@app.memory.workflow("wf-789").on_change("step.*")
async def on_step_complete(event):
    print(f"Workflow step completed: {event.key}")
Patterns

Event-Driven Pipeline

Chain agents reactively -- each writes results that trigger the next stage.

# Stage 1: Intake writes classification
@app.reasoner()
async def intake(document: str) -> dict:
    classification = await app.ai(user=document, schema=DocType)
    await app.memory.global_scope.set(f"pipeline.classified.{doc_id}", classification.dict())
    return classification.dict()

# Stage 2: Triggered when classification lands
@app.memory.global_scope.on_change("pipeline.classified.*")
async def on_classified(event):
    doc_id = event.key.split(".")[-1]
    result = await app.call(
        f"{event.data['type']}-analyzer.analyze",
        doc_id=doc_id,
        classification=event.data,
    )
    await app.memory.global_scope.set(f"pipeline.analyzed.{doc_id}", result)

# Stage 3: Triggered when analysis completes
@app.memory.global_scope.on_change("pipeline.analyzed.*")
async def on_analyzed(event):
    doc_id = event.key.split(".")[-1]
    await app.call("reporter.generate", doc_id=doc_id, analysis=event.data)

Cross-Agent Notifications

Decouple producers from consumers -- agents write to known keys, any agent can subscribe.

# Producer agent: writes alerts
await app.memory.global_scope.set("alerts.security.sql-injection", {
    "severity": "critical",
    "source": "waf-agent",
    "details": "SQL injection attempt blocked from 192.168.1.50",
})

# Consumer agent: reacts to any security alert
@app.memory.global_scope.on_change("alerts.security.*")
async def on_security_alert(event):
    if event.data.get("severity") == "critical":
        await app.call("incident.create", **event.data)

Saga Coordination

Coordinate distributed transactions across agents using memory events as signals.

@app.memory.global_scope.on_change("saga.payment.*")
async def on_payment_event(event):
    saga_id = event.key.split(".")[-1]
    status = event.data.get("status")

    if status == "completed":
        await app.call("shipping.dispatch", saga_id=saga_id)
        await app.memory.global_scope.set(f"saga.shipping.{saga_id}", {"status": "dispatching"})
    elif status == "failed":
        await app.call("orders.rollback", saga_id=saga_id)
SDK reference

Event Subscription Methods

FeaturePythonTypeScriptGo
Watch changes@app.on_change(pattern)agent.watchMemory(pattern, handler)Not supported
Scoped watch@app.memory.<scope>.on_change(pattern)agent.watchMemory(pattern, handler, { scope })Not supported
Multiple patternsMultiple decoratorsagent.watchMemory([p1, p2], handler)Not supported
Unsubscribesubscription.unsubscribe() (only from subscribe(), not @on_change)--Not supported

MemoryChangeEvent Fields

FieldPythonTypeScript
Keyevent.keyevent.key
New valueevent.dataevent.data
Previous valueevent.previous_data / event.old_value--
Scopeevent.scopeevent.scope
Scope IDevent.scope_idevent.scopeId
Timestampevent.timestampevent.timestamp
Source agent--event.agentId
Actionevent.action--

Go Alternative

Go does not support in-process memory events. Use the REST SSE endpoint instead:

GET /api/v1/memory/events/sse
GET /api/v1/memory/events/ws

These stream memory change events over Server-Sent Events or WebSocket for any client, including Go agents.

For simple cases, a Go agent can also poll the current value directly with a.Memory().Get(ctx, key) (or a scoped variant such as a.Memory().GlobalScope().Get(ctx, key)) on an interval instead of subscribing to events.