Built-in Tools
Pre-built tools available to all agents
Struere provides a set of built-in tools that agents can use to interact with entities, events, calendars, WhatsApp, and other agents. All built-in tools are permission-aware — every invocation builds an ActorContext from the calling agent's identity and evaluates policies, scope rules, and field masks before returning results.
Tool Reference
| Tool | Category | Description |
|---|---|---|
entity.create |
Entity | Create a new entity of a specified type |
entity.get |
Entity | Retrieve a single entity by ID |
entity.query |
Entity | Query entities by type with optional filters |
entity.update |
Entity | Update an existing entity's data |
entity.delete |
Entity | Soft-delete an entity |
entity.link |
Entity | Create a relation between two entities |
entity.unlink |
Entity | Remove a relation between two entities |
event.emit |
Event | Emit a custom event for audit logging |
event.query |
Event | Query historical events with filters |
calendar.list |
Calendar | List calendar events for a user |
calendar.create |
Calendar | Create a calendar event |
calendar.update |
Calendar | Update a calendar event |
calendar.delete |
Calendar | Delete a calendar event |
calendar.freeBusy |
Calendar | Check free/busy availability |
whatsapp.send |
Send a WhatsApp message | |
whatsapp.getConversation |
Get conversation history | |
whatsapp.getStatus |
Check WhatsApp connection status | |
agent.chat |
Agent | Send a message to another agent and get its response |
Enabling Tools
Specify which tools an agent can use in its definition:
import { defineAgent } from 'struere'
export default defineAgent({
name: "Support Agent",
slug: "support",
version: "0.1.0",
systemPrompt: "You help customers with their requests.",
model: { provider: "anthropic", name: "claude-sonnet-4" },
tools: [
"entity.create",
"entity.query",
"entity.update",
"event.emit",
"agent.chat",
],
})
Permission Enforcement
Every tool call goes through the full permission pipeline:
- ActorContext resolution — The agent's roles are eagerly resolved for its environment
- Policy evaluation —
assertCanPerform()checks if the actor has a matching allow policy (deny overrides allow) - Scope filtering — Row-level security filters results to only records the actor can access
- Field masking — Column-level security strips unauthorized fields from responses
If a permission check fails, the tool returns an error to the agent, which can then inform the user.
Entity Tools
entity.create
Creates a new entity of a specified type. Emits a {type}.created event and fires any matching triggers.
Parameters:
{
type: string
data: object
status?: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
type |
string |
Yes | The entity type slug (e.g., "teacher", "student") |
data |
object |
Yes | The entity's data fields, matching the entity type schema |
status |
string |
No | Initial status. Defaults to "active" |
Returns:
{ id: string }
Example agent usage:
The agent receives a request to create a new student and calls entity.create with the appropriate data:
{
"type": "student",
"data": {
"name": "Alice Johnson",
"grade": "10th",
"subjects": ["math", "physics"]
}
}
entity.get
Retrieves a single entity by its ID. The response is filtered through scope rules and field masks.
Parameters:
{
id: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | The entity ID to retrieve |
Returns:
{
id: string
type: string
status: string
data: object
createdAt: number
updatedAt: number
}
The data field will have hidden fields removed based on the actor's field masks. If the entity is outside the actor's scope, a permission error is thrown.
entity.query
Queries entities by type with optional filters. Results are scope-filtered and field-masked.
Parameters:
{
type: string
filters?: object
status?: string
limit?: number
}
| Parameter | Type | Required | Description |
|---|---|---|---|
type |
string |
Yes | The entity type slug to query |
filters |
object |
No | Key-value filters applied to entity data fields |
status |
string |
No | Filter by entity status |
limit |
number |
No | Maximum number of results. Defaults to 100 |
Filter operators:
Filters support both exact match and operator-based filtering:
{
"type": "session",
"filters": {
"subject": "math",
"grade": { "_op_in": ["9th", "10th", "11th"] },
"hourlyRate": { "_op_gte": 50, "_op_lte": 100 }
},
"status": "active",
"limit": 25
}
Available operators:
| Operator | Description |
|---|---|
_op_in |
Value is in the provided array |
_op_nin |
Value is not in the provided array |
_op_ne |
Value is not equal to |
_op_gt |
Greater than (numeric) |
_op_gte |
Greater than or equal (numeric) |
_op_lt |
Less than (numeric) |
_op_lte |
Less than or equal (numeric) |
Returns:
Array<{
id: string
type: string
status: string
data: object
createdAt: number
updatedAt: number
}>
entity.update
Updates an existing entity's data fields. The update is merged with existing data. Emits a {type}.updated event and fires matching triggers.
Parameters:
{
id: string
type?: string
data: object
status?: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | The entity ID to update |
type |
string |
No | Entity type slug for validation. If provided, the update will fail if the entity is not of this type. |
data |
object |
Yes | Fields to update (merged with existing data) |
status |
string |
No | New status value |
Field masks are applied to the update — the actor can only modify fields their role permits. Fields outside the actor's mask are silently ignored.
Returns:
{ success: boolean }
entity.delete
Soft-deletes an entity by setting its status to "deleted" and recording a deletedAt timestamp. Emits a {type}.deleted event and fires matching triggers.
Parameters:
{
id: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | The entity ID to delete |
Returns:
{ success: boolean }
entity.link
Creates a typed relation between two entities. Requires update permission on the source entity and read permission on the target entity. Emits an entity.linked event.
Parameters:
{
fromId: string
toId: string
relationType: string
metadata?: object
}
| Parameter | Type | Required | Description |
|---|---|---|---|
fromId |
string |
Yes | The source entity ID |
toId |
string |
Yes | The target entity ID |
relationType |
string |
Yes | The relation type label (e.g., "teaches", "guardian_of") |
metadata |
object |
No | Arbitrary metadata to attach to the relation |
If the relation already exists, the existing relation ID is returned with existing: true.
Returns:
{
id: string
existing: boolean
}
entity.unlink
Removes a relation between two entities. Requires update permission on the source entity and read permission on the target entity. Emits an entity.unlinked event.
Parameters:
{
fromId: string
toId: string
relationType: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
fromId |
string |
Yes | The source entity ID |
toId |
string |
Yes | The target entity ID |
relationType |
string |
Yes | The relation type to remove |
Returns:
{ success: boolean }
Event Tools
event.emit
Emits a custom event for audit logging and tracking. Events are scoped to the current environment.
Parameters:
{
eventType: string
entityId?: string
entityTypeSlug?: string
payload?: object
}
| Parameter | Type | Required | Description |
|---|---|---|---|
eventType |
string |
Yes | The event type identifier (e.g., "session.notification", "payment.reminder") |
entityId |
string |
No | The related entity ID, if applicable |
entityTypeSlug |
string |
No | The entity type slug, used for visibility filtering |
payload |
object |
No | Arbitrary event data |
Returns:
{ id: string }
event.query
Queries historical events with optional filters. Results are visibility-filtered based on the actor's permissions on related entities.
Parameters:
{
eventType?: string
entityId?: string
entityTypeSlug?: string
since?: number
limit?: number
}
| Parameter | Type | Required | Description |
|---|---|---|---|
eventType |
string |
No | Filter by event type |
entityId |
string |
No | Filter by related entity ID |
entityTypeSlug |
string |
No | Filter by entity type slug |
since |
number |
No | Unix timestamp in milliseconds; only return events after this time |
limit |
number |
No | Maximum number of results. Defaults to 50 |
When an entityId is specified, the actor must have read permission on that entity and the entity must be within the actor's scope. When querying by eventType or without filters, events associated with entities outside the actor's scope are automatically excluded.
Returns:
Array<{
_id: string
eventType: string
entityId?: string
entityTypeSlug?: string
actorId: string
actorType: string
payload: object
timestamp: number
}>
Calendar Tools
Calendar tools integrate with Google Calendar to manage events for users in your organization. Requires a Google Calendar integration to be configured.
calendar.list
Lists calendar events for a user within a time range.
Parameters:
{
userId: string
timeMin: string
timeMax: string
maxResults?: number
}
| Parameter | Type | Required | Description |
|---|---|---|---|
userId |
string |
Yes | The user ID whose calendar to query |
timeMin |
string |
Yes | Start of time range (ISO 8601 format) |
timeMax |
string |
Yes | End of time range (ISO 8601 format) |
maxResults |
number |
No | Maximum events to return |
calendar.create
Creates a new calendar event for a user.
Parameters:
{
userId: string
summary: string
startTime: string
endTime?: string
durationMinutes?: number
description?: string
attendees?: string[]
timeZone?: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
userId |
string |
Yes | The user ID whose calendar to create the event on |
summary |
string |
Yes | Event title |
startTime |
string |
Yes | Event start time (ISO 8601 format) |
endTime |
string |
No | Event end time (ISO 8601 format). Provide either endTime or durationMinutes. |
durationMinutes |
number |
No | Duration in minutes. Used to calculate endTime if not provided. |
description |
string |
No | Event description |
attendees |
string[] |
No | List of attendee email addresses |
timeZone |
string |
No | IANA timezone (e.g., "America/Santiago") |
calendar.update
Updates an existing calendar event.
Parameters:
{
userId: string
eventId: string
summary?: string
startTime?: string
endTime?: string
description?: string
attendees?: string[]
status?: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
userId |
string |
Yes | The user ID who owns the calendar event |
eventId |
string |
Yes | The calendar event ID to update |
summary |
string |
No | New event title |
startTime |
string |
No | New start time (ISO 8601 format) |
endTime |
string |
No | New end time (ISO 8601 format) |
description |
string |
No | New description |
attendees |
string[] |
No | Updated attendee list |
status |
string |
No | Event status (e.g., "cancelled") |
calendar.delete
Deletes a calendar event.
Parameters:
{
userId: string
eventId: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
userId |
string |
Yes | The user ID who owns the calendar event |
eventId |
string |
Yes | The calendar event ID to delete |
calendar.freeBusy
Checks a user's free/busy availability within a time range.
Parameters:
{
userId: string
timeMin: string
timeMax: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
userId |
string |
Yes | The user ID to check availability for |
timeMin |
string |
Yes | Start of time range (ISO 8601 format) |
timeMax |
string |
Yes | End of time range (ISO 8601 format) |
WhatsApp Tools
WhatsApp tools allow agents to send messages and retrieve conversation history. Requires a WhatsApp integration to be configured for the organization.
whatsapp.send
Sends a text message to a WhatsApp number.
Parameters:
{
to: string
text: string
}
| Parameter | Type | Required | Description |
|---|---|---|---|
to |
string |
Yes | Recipient phone number (E.164 format, e.g., "+56912345678") |
text |
string |
Yes | Message text to send |
Returns:
{
messageId: string
to: string
status: "sent"
}
whatsapp.getConversation
Retrieves the message history with a specific phone number.
Parameters:
{
phoneNumber: string
limit?: number
}
| Parameter | Type | Required | Description |
|---|---|---|---|
phoneNumber |
string |
Yes | Phone number to get conversation for |
limit |
number |
No | Maximum messages to return |
whatsapp.getStatus
Checks the WhatsApp connection status for the current organization.
Parameters:
No parameters required.
Returns:
{
connected: boolean
status: string
phoneNumber?: string
lastConnectedAt?: number
}
Agent Tools
agent.chat
Sends a message to another agent within the same organization and environment, and returns its response. This enables multi-agent workflows where a coordinator agent can delegate specialized tasks to other agents.
Parameters:
{
agent: string
message: string
context?: object
}
| Parameter | Type | Required | Description |
|---|---|---|---|
agent |
string |
Yes | The target agent's slug |
message |
string |
Yes | The message to send to the target agent |
context |
object |
No | Additional context passed to the target agent's thread metadata |
Returns:
{
response: string
threadId: string
agentSlug: string
usage: {
inputTokens: number
outputTokens: number
totalTokens: number
}
}
Safety Mechanisms
The agent.chat tool includes several protections against runaway execution:
| Mechanism | Behavior |
|---|---|
| Depth limit | Maximum delegation depth of 3. If agent A calls agent B which calls agent C which calls agent D, agent D's call will be rejected. |
| Cycle detection | An agent cannot call itself. If agent A tries to invoke agent.chat with its own slug, the call is rejected immediately. |
| Iteration cap | Each agent in the chain has an independent limit of 10 LLM loop iterations, preventing any single agent from running indefinitely. |
| Action timeout | Convex's built-in action timeout applies to the entire chain, providing an upper bound on total execution time. |
Thread Linking
All threads created during a multi-agent conversation share the same conversationId. Child threads store a parentThreadId linking back to the parent thread. Thread metadata includes:
{
conversationId: string
parentAgentSlug: string
depth: number
parentContext: object
}
Multi-Agent Example
A coordinator agent that delegates to specialized agents:
import { defineAgent } from 'struere'
export default defineAgent({
name: "Coordinator",
slug: "coordinator",
version: "0.1.0",
systemPrompt: `You are a coordinator that routes requests to specialized agents.
For scheduling questions, delegate to the scheduler agent.
For billing questions, delegate to the billing agent.
For general questions, answer directly.`,
model: { provider: "anthropic", name: "claude-sonnet-4" },
tools: ["entity.query", "agent.chat"],
})
When the coordinator receives a scheduling request, it calls:
{
"agent": "scheduler",
"message": "The guardian wants to book a math session for Tuesday at 3 PM.",
"context": {
"guardianId": "ent_abc123",
"studentId": "ent_def456"
}
}
The scheduler agent runs its own LLM loop with its own tools and permissions, then returns its response to the coordinator.