Use this file to discover all available pages before exploring further.
The Cross-Agent SDK lets one agent operate on another agent’s data within the same tenant. Read executions, walk conversation threads, write reviews against an agent’s work, and curate its memory — all from inside agent code.Without these primitives, client.chat() always targets the current agent. The cross-agent surface closes that gap and unlocks three common patterns:
Reviewer agents — judge another agent’s recent executions and write reviews automatically
Follow-up agents — find conversations idle for >24h and ping the original agent to re-engage
Metrics / curator agents — emit custom domain events from another agent’s logs, or curate its long-term memory
All operations are tenant-scoped via the runtime API key. Cross-tenant references return 404 (not 403) so existence is never leaked across tenants.
Every cross-agent endpoint accepts the same three identifier forms for the target agent. Resolution is server-side; the database always sees the canonical UUID.
1
UUID — always works
Keyed on (id, tenantId, ACTIVE). Single-match.
2
Slug — recommended
Exact match on (tenantId, slug, ACTIVE). Slugs are URL-friendly identifiers, unique per tenant. Auto-generated from the agent name on creation when not set explicitly.
3
Name — case-insensitive
Must be unambiguous. If two active agents share the name, the call returns 409 with a “use UUID or assign each a unique slug” hint.
// All three work — pick whatever reads best at the call site:await agents.invoke('a1b2c3d4-...', { message: 'oi' }); // UUIDawait agents.invoke('customer-support', { message: 'oi' }); // slugawait agents.invoke('Customer Support', { message: 'oi' }); // name
This applies to Agents.*, Reviews.{create,list,stats,exportForTraining}, Executions.list({ agentId }), Threads.list({ agentId }), and MemoryAdmin.*.
The slug field on the agent is optional in the create modal. Leave it blank and the backend derives one from the name (Customer Support Bot → customer-support-bot). Manual edits make the slug “sticky” — renaming the agent won’t overwrite a slug you customized.
The entire input object is forwarded to the Go executor and exposed under request.* in the target’s handler. message is not required — pass arbitrary structured payloads.
Returns the execution row plus the hierarchical trace tree (LLM calls, tool calls, sub-spans). What you see on the “execution detail” page in the portal.
const { execution, traces, tracesTotal, tracesHasMore } = await executions.getDetails(executionId);walkTrace(traces); // each trace node has .children
A pathological execution (deep workflow, tool loop, RAG-heavy turn) can produce thousands of traces. Before pagination, one bad execution could pin Postgres and return a multi-megabyte payload.
Caller
Mode
Per-request limit
Why
Portal
Bulk
10 000 (hard cap)
UI renders aggregations from the trace array. Cap is a safety net — normal traffic never hits it.
SDK runtime
Paginated
500 default, 1 000 max
Forces SDK consumers to walk pages instead of pulling everything.
iterateTraces — walk every page without boilerplate
Async generator that walks pages until tracesHasMore is false. Yields one page at a time so memory doesn’t spike on huge executions.
for await (const page of executions.iterateTraces(execId, { pageSize: 500 })) { console.log(`offset=${page.tracesOffset} / total=${page.tracesTotal}`); for (const root of page.traces) audit(root);}
iterateTraces has a hard safety cap of 100 pages (~100k traces). If you hit it, something is wrong upstream — investigate the agent, don’t crank the limit.
Thread conversation between user Y and agent X (1 per entity) └─ Execution one turn in that conversation (1 per user message) └─ Trace step inside that turn (many per execution) ├─ agent_execution ├─ llm_call (GPT/Claude call) ├─ tool_call (knowledge_search, connector, ...) └─ tool_call (etc)
getFullThread — thread + executions + traces in one call
const full = await threads.getFullThread(threadId, { agentId: 'customer-support', maxTraces: 20, // cap on executions to deep-fetch (default 20) traceLimit: 500, // per-execution trace page size});// full.threadId// full.total ← total executions// full.executions[].execution ← row (input/output/cost/duration)// full.executions[].traces[] ← trace tree// full.executions[].traces[].children[] ← sub-spans
Fetches in two stages: list executions, then executions.getDetails for each (concurrency capped at 5). Individual failures are silently dropped so a single bad execution doesn’t break the whole batch.
The default Memory module is scoped to the caller’s agent. To curate another agent’s memory (audit messages, inject system context, clear stale sessions, summarize), use MemoryAdmin.
import { MemoryAdmin } from '@runflow-ai/sdk/memory-admin';const admin = new MemoryAdmin();// Inventory — list every memory slot owned by the target agentconst { sessions } = await admin.list('customer-support');// Filter by activity windowconst since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();const recent = await admin.list('customer-support', { dateFrom: since, dateField: 'updated_at', limit: 50,});// Read another agent's memoryconst data = await admin.get('customer-support', 'phone:+5511999999999');// Inject a system message (curator agent)await admin.append('customer-support', 'phone:+5511999999999', { role: 'system', content: 'IMPORTANTE: cliente prioritário, responder em <2min.',});// Search within a time windowconst hits = await admin.search( 'customer-support', 'phone:+5511999999999', 'erro', { dateFrom: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), limit: 100, },);// Summarize the slot — generates an LLM summary and persists itconst { summary } = await admin.summarize( 'customer-support', 'phone:+5511999999999', { prompt: 'Resume em até 5 bullets, em português:' },);// Clear (deletes the session)await admin.clear('customer-support', 'phone:+5511999999999');
Memory keys are prefixed with the target agent’s id (not the caller’s), so the existing data isolation model stays intact. Each agent has its own namespace; MemoryAdmin just flips which namespace you target.
@runflow-ai/sdk >= 1.2.0. Older versions don’t have the new namespaces on the API client and throw a clear “namespace missing” error at construction time.