Skip to content
Quick Guides
Quick Guides

Trigger agents on memory changes

Subscribe a reasoner to a memory key pattern. When any agent writes to a matching key, the reasoner fires.

Reactive multi-agent pipelines with memory events, not a separate orchestrator. Stage 1 writes a result. A handler in stage 2 fires automatically. Stage 3 fires on stage 2's write. The chain composes itself.

from agentfield import Agent

app = Agent(node_id="pipeline-coordinator")

# Stage 1: classifier writes the document type
@app.reasoner()
async def classify(document: str, doc_id: str) -> dict:
    classification = await app.ai(system="Classify this document.", user=document)
    await app.memory.global_scope.set(
        f"pipeline.classified.{doc_id}",
        {"type": str(classification), "document": document},
    )
    return {"doc_id": doc_id}

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

# Stage 3: fires when the analyzer finishes
@app.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)

app.run()
import { Agent } from "@agentfield/sdk";

const agent = new Agent({ nodeId: "pipeline-coordinator" });

// Stage 1: classifier writes the document type
agent.reasoner("classify", async (ctx) => {
  const { document, docId } = ctx.input;
  const classification = await ctx.ai(document, { system: "Classify this document." });
  await agent.memory.globalScope.set(`pipeline.classified.${docId}`, {
    type: String(classification),
    document,
  });
  return { docId };
});

// Stage 2: fires automatically when ANY classification lands
agent.watchMemory("pipeline.classified.*", async (event) => {
  const docId = event.key.split(".").pop()!;
  const analysis = await agent.call(`${event.data?.type}-analyzer.analyze`, {
    document: event.data?.document,
  });
  await agent.memory.globalScope.set(`pipeline.analyzed.${docId}`, analysis);
});

// Stage 3: fires when the analyzer finishes
agent.watchMemory("pipeline.analyzed.*", async (event) => {
  const docId = event.key.split(".").pop()!;
  await agent.call("reporter.generate", { docId, analysis: event.data });
});

agent.serve();

You can also scope subscriptions narrowly:

# Only fire on writes inside a specific session
@app.memory.session("session-123").on_change("conversation.*")
async def on_conversation(event):
    print(f"Session conversation updated: {event.key}")
// Only fire on writes inside a specific session
agent.watchMemory("conversation.*", async (event) => {
  console.log(`Session conversation updated: ${event.key}`);
}, { scope: "session", scopeId: "session-123" });

What this gives you

  • One agent's write becomes another agent's trigger — no queue setup, no polling.
  • Pattern matching uses glob-style wildcards across dot-separated keys.
  • Subscriptions can be scoped to global, session, actor, or workflow memory.

Next