Agents

ModelRelay supports agentic tool loops in three ways. Use the right one depending on reuse, coordination complexity, and governance needs.

Three approaches

Approach Best for Notes
llm.responses with tool_execution: "agentic" One-off logic scoped to a workflow Self-contained; define tools + system prompt inline
agent.run Reusable agents shared across workflows Project-scoped agent resources with versions + auditability
agent.scheduler Multi-agent coordination with iteration Bounded loops, message-based coordination, coordinator-driven

When to use which

Use llm.responses + agentic when:

  • The behavior is specific to a single workflow
  • You want everything defined inline
  • You don’t need agent versioning

Use agent.run when:

  • The agent is reused across workflows
  • You need versioning and audit trails
  • A team manages agents as shared resources

Use agent.scheduler when:

  • Multiple agents need to coordinate iteratively
  • A coordinator should decide when work is complete
  • You need bounded multi-round loops (not just fan-out/join)

Examples

Inline agentic node

{
  "id": "analyze",
  "type": "llm.responses",
  "tool_execution": "agentic",
  "max_tool_steps": 6,
  "tools": ["search", "summarize"],
  "input": [{ "role": "user", "content": "Analyze Q4 results" }]
}

Project agent in a workflow

{
  "id": "research",
  "type": "agent.run",
  "agent": "researcher@2",
  "input": [{ "role": "user", "content": { "$ref": "#/input/query" } }]
}

Run an agent directly

POST /projects/{project_id}/agents/{slug}/run
{
  "input": [{ "role": "user", "content": "Analyze Q4 results" }],
  "options": { "max_steps": 10 }
}

Multi-agent scheduler in a workflow

{
  "id": "code_review",
  "type": "agent.scheduler",
  "agents": [
    {"agent": "coder@1", "count": 2, "input": [{"role": "user", "content": "{{task}}"}]},
    {"agent": "reviewer@1", "count": 1, "input": [{"role": "user", "content": "Review code"}]}
  ],
  "coordinator": {
    "agent": "lead@1",
    "input": [{"role": "user", "content": "Coordinate until review passes"}]
  },
  "policy": {
    "max_rounds": 5,
    "max_concurrency": 3,
    "max_steps": 50,
    "timeout_ms": 120000
  }
}

The scheduler spawns participant agents, collects their messages, and runs a coordinator to decide next steps. See Agent Scheduler for details.

Agent memory

Agents have a persistent memory document that is injected into the system prompt on each run. This allows agents to retain information across runs—user preferences, learned facts, or operational notes.

How memory works

  1. Automatic injection: When an agent run starts, the stored memory is prepended to the system prompt under a ## Agent Memory heading, separated from the original system prompt by a horizontal rule.

  2. Full replacement: The memory_write tool completely replaces the memory content. There is no append or merge—design your agent to rewrite the full document when updating.

  3. Size limit: Memory is capped at 32KB. Writes exceeding this limit return an error.

The memory_write tool

The memory_write tool is automatically available in agent runs—you don’t need to add it to your tools list. Calling it outside an agent context returns an error.

{
  "type": "function",
  "function": {
    "name": "memory_write",
    "description": "Update the agent's persistent memory",
    "parameters": {
      "type": "object",
      "properties": { "content": { "type": "string" } },
      "required": ["content"]
    }
  }
}

Example: learning user preferences

System: You are a helpful assistant. Use memory_write to remember user preferences.

User: I prefer dark mode and metric units.

Agent: [calls memory_write with content: "User preferences:\n- Theme: dark mode\n- Units: metric"]

--- Next run ---

System:
## Agent Memory

User preferences:
- Theme: dark mode
- Units: metric

---

You are a helpful assistant. Use memory_write to remember user preferences.

Messaging

Agents can communicate with each other using built-in messaging tools. This enables coordination patterns like delegation, request/response, and event notification.

Message tools

Tool Description
message.send Send a message to an agent or run
message.inbox Check for new messages (marks them as read)
message.reply Reply to a message in a thread

Addresses

Messages are sent to addresses that identify mailboxes:

  • run:<run_id> - A specific run’s mailbox
  • agent:<slug>@<project_id> - An agent’s persistent mailbox

Example: delegation pattern

A coordinator agent can delegate work to specialists:

System: You coordinate data analysis. Use message.send to delegate to worker agents.

User: Analyze the Q4 sales data

Agent: [calls message.send with:
  to: "agent:analyst@<project_id>"
  subject: "Analyze Q4 sales"
  body: {"dataset": "q4_sales", "metrics": ["revenue", "growth"]}
]

Agent: [calls message.inbox to check for replies]

Agent: [receives reply with analysis results]

Agent: Based on the analysis from our specialist...

Threading

Use thread_id to group related messages into conversations. When you call message.reply, it automatically continues the thread.

For full API details, see the Messages API reference.

Workflows vs Messaging

Workflows and messaging serve different coordination patterns. Use the right tool for your use case.

Workflows: Synchronous DAG

flowchart LR
    A[Agent A] --> B[Agent B] --> C[Agent C]

Workflows execute agents in a predetermined, synchronous flow:

  • Blocking: Agent A calls B, waits for the result, then C runs
  • No cycles: DAG structure means no iteration or loops
  • Single execution: Everything happens in one workflow run
  • Predetermined: You define the exact call sequence upfront

Messaging: Asynchronous Coordination

flowchart TB
    C[Coordinator] <-->|messages| W1[Worker 1]
    C <-->|messages| W2[Worker 2]
    C <-->|messages| W3[Worker 3]

Messaging enables dynamic, asynchronous coordination:

  • Non-blocking: Send a message, continue working, check inbox later
  • Enables iteration: Coordinator can do multiple rounds of back-and-forth
  • Cross-run: Agent mailboxes persist beyond individual workflow runs
  • Dynamic: Don’t need to know responses upfront

Decision matrix

Scenario Workflow Scheduler Messaging
Fixed pipeline (A→B→C) Overkill Overkill
Fan-out, wait for all results map.fanout Works too Works too
Multi-round negotiation ❌ No cycles
“Keep iterating until done” ✅ Bounded
Coordinator decides routing Manual
Bounded multi-agent loops ✅ Policy limits DIY
Agents spawning other agents
Long-running coordination ❌ Single run ❌ Single run ✅ Persistent
Audit trail per step ✅ Run events ✅ Scheduler events ✅ Message log

Example: Choosing the right approach

Workflow works well:

User query → Research Agent → Summarize Agent → Response

Each step runs once, in order. Use a workflow.

Scheduler fits well:

User query → Scheduler
             ├─→ Coder 1 (implements)
             ├─→ Coder 2 (implements)
             └─→ Reviewer (reviews)
             Coordinator (decides if done)
             ... bounded iterations ...
          ← Final response

Multiple agents iterate with a coordinator deciding completion. Use agent.scheduler.

Messaging needed:

User query → Coordinator
             ├─→ Researcher (may need clarification)
             ├─→ Fact Checker (may dispute findings)
             └─→ Editor (may request revisions)
             ... unbounded, cross-run coordination ...
          ← Final response

Long-running coordination across multiple workflow runs. Use messaging directly.

Debugging agent runs

  • Use /runs/{run_id}/steps for per-step logs and tool I/O.
  • Use POST /projects/{project_id}/agents/{slug}/test with mock_tools to replay tool calls.