Sprinter Platform

Agent System

Define agents, resolve prompts and tools, delegate tasks, run heartbeats, and manage external connections.

Overview

Agents are AI actors that operate on entities. They populate fields, answer questions in chat, run background tasks, and delegate work to other agents. The agent system provides:

  • Registration -- code-defined or DB-managed agents
  • Resolution -- assemble prompt, tools, and permissions for execution
  • Execution -- streaming (chat) or one-shot (background tasks)
  • Delegation -- agents calling other agents via tools
  • Heartbeat -- scheduled autonomous execution
  • Connections -- external agents via MCP, A2A, OpenClaw, HTTP

Defining Agents

Code-defined

import { defineAgent } from "@sprinterai/core";

const analyst = defineAgent({
  slug: "analyst",
  name: "Research Analyst",
  description: "Researches entities and populates data fields",
  systemPrompt: `You are a PE research analyst. Your job is to:
    1. Research companies using web search
    2. Extract key financial metrics
    3. Submit scored responses for each field`,
  model: "claude-sonnet-4-20250514",
  toolGroups: ["entity", "web", "response"],
  customTools: ["roi-calculator"],
});

DB-managed

Agents can also be created through the Admin UI or via the updateAgentConfig admin tool. DB agents are stored in the agents table and resolved at runtime.

Agent Config

interface AgentConfig {
  toolGroups?: string[];    // "entity", "web", "response", "workflow", "context", "admin"
  customTools?: string[];   // tool slugs
  skills?: string[];        // skill identifiers
  heartbeat?: {
    enabled: boolean;
    schedule: HeartbeatSchedule;
    prompt: string;
    mode: HeartbeatMode;
  };
}

// Parse from raw JSON (validates and defaults)
import { parseAgentConfig } from "@sprinterai/core";
const config = parseAgentConfig(rawJson);

Agent Resolution

Resolution is the process of assembling everything an agent needs to execute:

import { resolveAgent } from "@sprinterai/runtime";

const resolved = await resolveAgent({
  agent: agentDefinition,
  stores,
  permissions: userPermissions,
  context: {
    tenantId,
    entityId,       // optional: current entity context
    conversationId, // optional: current chat
  },
});

// resolved.prompt      -- assembled system prompt
// resolved.tools       -- permission-gated tool set
// resolved.model       -- model identifier
// resolved.agentSlug   -- for tracking

What resolution does

  1. Looks up the agent definition (code registry, then DB)
  2. Builds the system prompt from sections (system, context, memory)
  3. Resolves tool groups into concrete tools
  4. Filters tools by the caller's permissions
  5. Adds delegation tools if other agents are available
  6. Returns a ready-to-execute agent configuration

Agent Execution

Streaming (chat)

import { executeAgentStream } from "@sprinterai/runtime";

const stream = await executeAgentStream({
  agent: resolvedAgent,
  messages: conversationHistory,
  onToolCall: (call) => {
    console.log(`Tool: ${call.name}`, call.args);
  },
});

// Stream is compatible with AI SDK v6 streamText response

One-shot (background)

import { executeAgentGenerate } from "@sprinterai/runtime";

const result = await executeAgentGenerate({
  agent: resolvedAgent,
  messages: [{ role: "user", content: "Analyze ACME Corp revenue trends" }],
});

// result.text       -- generated text
// result.toolCalls  -- tools that were called
// result.usage      -- token usage

Delegation

Agents can delegate tasks to other agents using the built-in delegation tool:

import { createDelegateToAgentTool } from "@sprinterai/runtime";

const delegateTool = createDelegateToAgentTool({
  agentRegistry,
  stores,
  permissions,
});

// When an agent calls this tool:
// "Delegate to researcher: find revenue data for ACME Corp"
// -> The researcher agent executes and returns results

Heartbeat

Agents can run autonomously on a schedule:

const agent = defineAgent({
  slug: "monitor",
  name: "Portfolio Monitor",
  systemPrompt: "Monitor portfolio companies for changes...",
  model: "claude-sonnet-4-20250514",
  toolGroups: ["entity", "web"],
});

// Heartbeat config (set via Admin UI or updateAgentConfig tool)
// agent.config.heartbeat = {
//   enabled: true,
//   schedule: { type: "cron", expression: "0 9 * * *" },
//   prompt: "Check for news about portfolio companies",
//   mode: "autonomous",
// }

In heartbeat mode, the agent uses its own role's permissions (not a user's), and execution is tracked in the agent_heartbeat_runs table.

External Connections

Connect to agents running on other platforms:

import {
  discoverMCPTools,
  createA2AAgent,
  createOpenClawModel,
  createHTTPAgent,
} from "@sprinterai/runtime";

// MCP: discover tools from an MCP server
const tools = await discoverMCPTools({
  serverUrl: "http://localhost:3001/mcp",
});

// A2A: connect to an agent via the Agent-to-Agent protocol
const externalAgent = createA2AAgent({
  endpoint: "https://agent.example.com/a2a",
});

// OpenClaw: use an OpenClaw model
const model = createOpenClawModel({
  apiKey: process.env.OPENCLAW_KEY,
});

// HTTP: generic HTTP agent adapter
const httpAgent = createHTTPAgent({
  baseUrl: "https://api.example.com/agent",
});

Connection types are stored in the agent_connections table and managed via Admin > Connections.

Permission Model

Agents share the same RBAC system as users:

  • Each agent has a role_id in the agents table
  • Supervised (chat): agent inherits the user's permissions
  • Autonomous (heartbeat): agent uses its own role's permissions
  • Tools the agent cannot use are excluded from its tool set -- agents never see unavailable tools
import { getPermissionsForRole } from "@sprinterai/supabase";

// For heartbeat execution
const agentPerms = await getPermissionsForRole(agent.role_id);
const tools = await resolveAgentTools({
  agent,
  stores,
  permissions: agentPerms,
});

Agent Registry

import { AgentRegistry } from "@sprinterai/runtime";

const registry = new AgentRegistry();
registry.register(analyst);
registry.register(researcher);
registry.register(monitor);

// Lookup
const agent = registry.get("analyst");

// List all
const all = registry.list();