Skip to content

[Submission] Long-running multi-agent researcher with memory #130

Description

@familom

Project link

https://github.com/xmemory-ai/temporal-xmemory-events-agent

Language

Python

Short description (max 256 chars)

Long-running AI agents that research AI events for months on end. Temporal makes every model call, web fetch and memory write a durable Activity and resumes the cycle after a crash; xmemory keeps their findings as shared records anyone can query.

Long Description

Long-running AI agents that research AI conferences, meetups, hackathons and summits for months on end, and keep what they learn in a shared memory that people and other agents query in plain language. Temporal keeps the cycle running through worker crashes and redeploys; xmemory turns each agent's prose into structured, queryable records and enforces the domain rules of the shared state.

One cycle: the entity workflow starts Discovery, reads the queue of waiting events, and starts one Processor per event; both agents write prose to the events memory and read the web; the entity opens and closes each run on the coordination memory

What it does

The recurring job is real: find upcoming AI events, collect the details a team needs to decide where to participate, and keep that catalogue current. Two agents divide the work.

Agent Does Writes
Discovery Searches the web, deadline directories and event platforms Each event it finds: canonical name, website, where it was found
Processor Crawls one waiting event's pages Dates, venue, call for papers, registration, prices, topics, and the status processed or failed

Both are built on the OpenAI Agents SDK with tools for web search, page fetching and memory. Inside a stage the model decides what to search, fetch and write; the only fixed structure is the cycle around it.

Agents build the catalogue of events, deadlines and topics; people add attendance plans. After a cycle completes, its findings remain available for questions such as "Which conferences in Europe have an open call for papers?" or "Who is attending an event in December?", and the answer comes from the records, not from a transcript.

A domain constraint the memory enforces: one event per person, per day

The schema expresses attendance as a three-part relation connecting TeamMember, CalendarDay and Event. A person enters one sentence:

The sentence 'Maya Chen attends ExampleConf 2027 on 2027-03-03' becomes one attendance link between a TeamMember, a CalendarDay and an Event; the uniqueness key covers the attendee and the date, not the event

xmemory interprets it as a link between three records. The date describes this person's attendance: a conference may run for several days while a teammate attends one, and the event's own dates stay on the shared Event record. The relation's uniqueness key covers the attendee and the date, so assigning the same person to a different event on the same day is a conflict, declared directly in the memory schema:

attendance:
  objects:
    attendee: {type: TeamMember}
    date: {type: CalendarDay}
    event: {type: Event}
  keys:
    one_event_per_person_per_day: [date, attendee]
Another attendance statement What the key means
Maya Chen attends ExampleConf 2027 on 2027-03-04. A different date permits a separate link.
Leo Martin attends ExampleConf 2027 on 2027-03-03. A different attendee permits a separate link.
Maya Chen attends another event on 2027-03-03. The same attendee and date cannot hold two event links.

A backend test asserts that one attendance link remains after a conflicting write. A second memory instance stores run logs and notes about useful sources, so the two instances hold both the team's event knowledge and the agents' experience of researching it.

Shared state makes the hand-off explicit

The agents never call each other; the memory is the hand-off.

Hand-off through shared state: Discovery writes an event without a processing status, the workflow reads the events whose status is empty or unprocessed, and Processor writes the details with the status processed or failed

Discovery writes an event without a processing status and without looking it up first: its tool list has memory writes and coordination-board reads, and no event lookup. xmemory resolves records by the canonical event name, so a second mention updates the record instead of duplicating it. The workflow reads the records still waiting and passes each to a Processor, which adds the researched details and marks the event processed or failed.

processing_status deliberately has no schema default: rediscovering an event, or recording someone's attendance, leaves its existing status intact. A regression test checks that a later discovery preserves the processed status and the researched dates.

How it uses Temporal

An always-alive workflow orchestrates the two agents through Temporal's OpenAI Agents SDK integration and xmemory's Temporal integration. Each cycle follows a small, explicit sequence:

  1. Open a run and launch Discovery as a child workflow.
  2. Query the events memory for records waiting to be processed.
  3. Launch a Processor child workflow for each event, limiting how many run at once.
  4. Record the outcome and Continue-As-New, carrying execution forward with a fresh history.
Pattern Where Why it matters
Entity workflow, state set in @workflow.init, cadence via wait_condition with a timeout, continue_as_new after every cycle workflows/scout.py The loop runs for months with a history that never grows beyond one cycle
Signals run_now, pause, resume, stop, instruct and the status query, consumed by the wait loop same Unattended, yet steerable from the CLI while it runs
Child workflows with ids {workflow_id}-{run_id}-{stage}, per-child run_timeout, semaphore-bounded fan-out same A retried task or a replay cannot start a duplicate child; a failed child is recorded, never fatal
Model calls as Activities workflows/stage_runner.py On replay the stored response is returned; the model is not invoked again
Memory writes as one enqueue Activity plus idempotent status polls agents/tools.py A worker restart resumes the wait instead of re-submitting the text

Model calls, page fetches and memory operations do their external work through Activities. In the agent's memory tool the integration is one call:

status = await _events(ctx).write_durable(
    text, extraction_logic="deep", max_wait=WRITE_MAX_WAIT, max_poll_interval=WRITE_MAX_POLL_INTERVAL
)

The call starts the extraction and waits for its result through a workflow-managed polling loop; once the write id is recorded, the wait resumes after a worker restart, and Temporal reuses Activity results already in history during replay. Retrying an unfinished write is treated as a separate concern: the write policy excludes server, availability and unknown errors from automatic retries, because the record's key is extracted from the text and a second extraction could fork the record, and the tool tells the agent to report a failed write without resubmitting the same text. A forced-replay test runs a stage with max_cached_workflows=0 and asserts the enqueue is scheduled exactly once per write.

Who it's for

Developers building agents whose work never finishes, runs unattended, or is shared between several agents, and who want a worked example of the division of labour: Temporal holds the cursor of the work in progress, xmemory holds what is known, and the agents hold nothing. The broader pattern is reusable: define the identity and update rules for the knowledge, make hand-offs visible in shared state, and let Temporal orchestrate the work that changes it.

The repository is small enough to read in an afternoon and is tested at three levels: unit tests without credentials, memory-backed tests that run the real workflows on a local Temporal dev server against real xmemory instances with a scripted model, and a live test with a real model against the real web.

Getting started

The README's step-by-step guide covers install, credentials, configuration, creating the two memory instances, and running the Temporal dev server, the worker and the entity. It also states the requirements and costs: an OpenAI API key, an xmemory API key and the Temporal CLI; there is no keyless demo.

Author(s)

Alexander Gusak (LI: https://www.linkedin.com/in/agusak/), founding engineer at xmemory.ai

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    code exchange submissionCode and/or content about Temporal!triageIssues that Temporal folk need to look at

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions