Agents
AI agent configuration and execution
Agents are the core execution units of the Struere platform. Each agent is an AI-powered entity with a system prompt, model configuration, and a set of tools it can use to interact with your domain data.
Architecture
Agent data is split across two tables with different scoping rules:
agents Table (Shared)
The agents table stores identity information that is shared across environments:
| Field | Type | Description |
|---|---|---|
organizationId |
ID | Organization that owns this agent |
name |
string | Display name |
slug |
string | URL-safe identifier for API routing |
description |
string | Human-readable description |
status |
enum | "active", "paused", or "deleted" |
The slug is used for API access via /v1/agents/:slug/chat.
agentConfigs Table (Environment-Scoped)
The agentConfigs table stores the actual configuration and is scoped per environment using the by_agent_env index:
| Field | Type | Description |
|---|---|---|
agentId |
ID | Reference to the agents table |
environment |
enum | "development" or "production" |
version |
string | Semantic version |
name |
string | Config display name |
systemPrompt |
string | Compiled system prompt |
model |
object | Provider, model name, temperature, maxTokens |
tools |
array | Tool definitions with name, description, parameters, handlerCode, isBuiltin |
deployedBy |
ID | User who deployed this config |
This split means an agent can have different configurations in development and production. The struere dev command syncs to the development config, and struere deploy promotes configs to production.
Execution Flow
When a chat request arrives, the agent executes through this pipeline:
POST /v1/chat or POST /v1/agents/:slug/chat
│
▼
┌─────────────────────────────┐
│ 1. Authentication │
│ Extract Bearer token │
│ Validate API key │
│ (SHA-256 hash lookup) │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 2. Load Agent │
│ Resolve agent by ID/slug │
│ Load config via │
│ by_agent_env index │
│ (env from API key) │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 3. Build ActorContext │
│ organizationId │
│ actorType (user/agent) │
│ environment │
│ roleIds (resolved) │
│ isOrgAdmin │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 4. Prepare Thread │
│ Get or create thread │
│ (env-scoped) │
│ Load message history │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 5. Process System Prompt │
│ Resolve {{variables}} │
│ Execute embedded queries │
│ (permission-aware) │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 6. LLM Loop (max 10 iter) │
│ Call LLM API │
│ ├─ Text response → done │
│ └─ Tool calls: │
│ Check permission │
│ Execute tool │
│ Add result to context │
│ Continue loop │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 7. Persist & Respond │
│ Append messages to thread│
│ Record execution metrics │
│ Return response │
└─────────────────────────────┘
LLM Loop
The agent runs an iterative loop with a maximum of 10 iterations. Each iteration:
- Sends the full message history (system prompt + conversation + tool results) to the LLM
- If the LLM responds with text only, the loop exits
- If the LLM makes tool calls, each tool is:
- Permission-checked via
canUseTool - Executed (built-in tools run as Convex mutations; custom tools run on the tool executor service)
- Results are appended to the message history
- Permission-checked via
- The loop continues with the updated history
Tool Execution
Built-in tools run as Convex mutations with full permission checking:
| Tool | Convex Function | Description |
|---|---|---|
entity.create |
tools.entities.entityCreate |
Create entity + emit event |
entity.get |
tools.entities.entityGet |
Get entity (field-masked) |
entity.query |
tools.entities.entityQuery |
Query with scope filters |
entity.update |
tools.entities.entityUpdate |
Update + emit event |
entity.delete |
tools.entities.entityDelete |
Soft delete + emit event |
entity.link |
tools.entities.entityLink |
Create relation |
entity.unlink |
tools.entities.entityUnlink |
Remove relation |
event.emit |
tools.events.eventEmit |
Emit custom event |
event.query |
tools.events.eventQuery |
Query events (visibility filtered) |
agent.chat |
tools.agents.agentChat |
Delegate to another agent |
Custom tools are sent to the tool executor service at tool-executor.struere.dev for sandboxed execution with actor context.
Multi-Agent Communication
The agent.chat tool enables agents to delegate work to other agents within the same organization and environment.
Caller Agent
│
├─ tool_call: agent.chat({ agent: "analyst", message: "..." })
│
▼
Target Agent Resolution
│
├─ Find agent by slug
├─ Create child thread (shared conversationId)
│
▼
Target Agent Execution
│
├─ Full LLM loop with target's own config/tools/permissions
│
▼
Response returned as tool result to Caller Agent
Safety Mechanisms
| Mechanism | Description |
|---|---|
| Depth limit | Maximum chain depth of 3 (MAX_AGENT_DEPTH) |
| Cycle detection | Target slug checked against caller slug |
| Iteration cap | Each agent limited to 10 LLM iterations independently |
| Action timeout | Convex built-in timeout prevents infinite execution |
Thread Linking
All threads in a multi-agent conversation share the same conversationId. Child threads store a parentThreadId linking back to the parent. Thread metadata includes:
{
conversationId: string,
parentAgentSlug: string,
depth: number,
parentContext: object,
}
Model Pricing
See Model Configuration for the full list of supported models, pricing, and configuration options.
API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/v1/chat |
POST | Chat by agent ID (Bearer token with API key) |
/v1/agents/:slug/chat |
POST | Chat by agent slug (Bearer token with API key) |
Both endpoints require a valid API key passed as a Bearer token. The environment is determined by the API key's environment field.
Response Format
{
threadId: string,
message: string,
usage: {
inputTokens: number,
outputTokens: number,
totalTokens: number,
durationMs: number,
}
}