Skip to content
AgentField
Coordination

Cross-Agent Calls

Call reasoners and skills on other agents through the control plane execution gateway.

Cross-agent call trace waterfall

Call any reasoner or skill on any agent -- one line, full context propagation, automatic DAG tracking.

Building one agent is easy. Building five agents owned by different teams is where the infrastructure work starts: service discovery, routing, correlation IDs, workflow tracing, and failure debugging.

app.call() is the abstraction that collapses that distributed-systems work. The caller only needs node_id.function_name. AgentField handles the routing, workflow propagation, and trace construction behind it.

@app.reasoner()
async def triage_ticket(ticket_id: str, message: str, customer_id: str) -> dict:
    sentiment = await app.call("sentiment-agent.analyze_text", text=message)
    history = await app.call("customer-history.recent_issues", customer_id=customer_id)

    if sentiment["urgency"] == "high" or history["issue_count"] > 3:
        escalation = await app.call(
            "escalation-agent.create_case",
            ticket_id=ticket_id,
            reason="high urgency or repeated incidents",
        )
        await app.call("notifications.send_alert", case_id=escalation["case_id"])
        app.note(f"Escalated ticket {ticket_id} to {escalation['case_id']}", ["triage", "escalation"])
        return {"status": "escalated", "case_id": escalation["case_id"]}

    return {"status": "queued", "sentiment": sentiment, "history": history}
agent.reasoner('triageTicket', async (ctx) => {
  const sentiment = await agent.call('sentiment-agent.analyzeText', {
    text: ctx.input.message,
  });
  const history = await agent.call('customer-history.recentIssues', {
    customerId: ctx.input.customerId,
  });

  if (sentiment.urgency === 'high' || history.issueCount > 3) {
    const escalation = await agent.call('escalation-agent.createCase', {
      ticketId: ctx.input.ticketId,
      reason: 'high urgency or repeated incidents',
    });
    await agent.call('notifications.sendAlert', { caseId: escalation.caseId });
    ctx.note(`Escalated ticket ${ctx.input.ticketId} to ${escalation.caseId}`, ['triage', 'escalation']);
    return { status: 'escalated', caseId: escalation.caseId };
  }

  return { status: 'queued', sentiment, history };
});
a.RegisterReasoner("triage_ticket", func(ctx context.Context, input map[string]any) (any, error) {
    sentiment, _ := a.Call(ctx, "sentiment-agent.analyze_text", map[string]any{"text": input["message"]})
    history, _ := a.Call(ctx, "customer-history.recent_issues", map[string]any{"customer_id": input["customer_id"]})

    if sentiment["urgency"] == "high" {
        escalation, _ := a.Call(ctx, "escalation-agent.create_case", map[string]any{
            "ticket_id": input["ticket_id"],
            "reason": "high urgency or repeated incidents",
        })
        return map[string]any{"status": "escalated", "case_id": escalation["case_id"]}, nil
    }

    return map[string]any{"status": "queued", "sentiment": sentiment, "history": history}, nil
})

What just happened

  • The caller referenced services by logical target, not URL
  • Workflow context flowed through every child call automatically
  • Every child execution became a node in the same DAG
  • The note on escalation was attached to the current execution timeline

Example workflow proof:

{
  "target": "support-triage.triage_ticket",
  "status": "completed",
  "children": [
    { "target": "sentiment-agent.analyze_text", "status": "completed" },
    { "target": "customer-history.recent_issues", "status": "completed" },
    { "target": "escalation-agent.create_case", "status": "completed" },
    { "target": "notifications.send_alert", "status": "completed" }
  ]
}