Agent runtime objects
Object hierarchy and API verbs for driving the Wacht agent runtime from code.
Agent runtime objects
This reference is for engineers wiring the agent runtime through the platform API, @wacht/backend, or the bench CLI. If you're using the console UI, see the Agents guide instead.
Hierarchy
actor
└── actor project (binds an actor + an agent)
├── board item (task) (description + schedule)
└── thread (runtime conversation + tool calls)| Object | Identity | API verbs |
|---|---|---|
| Actor | subject_type + external_key | createActor, listActors, lookupActor |
| Agent | id (per deployment) | createAiAgent, getAiAgentById, updateAiAgent, deleteAiAgent |
| Actor project | id (per deployment); binds one actor + one agent | createActorProjectFlat (query: actor_id; body: agent_id, name) |
| Board item | id (per project) | createProjectTaskBoardItem, updateProjectTaskBoardItem, archiveProjectTaskBoardItem, cancelProjectTaskBoardItem, delegateProjectTask, downloadProjectTaskBoardItemFilesystemFile |
| Thread | id (per project) | createAgentThread, executeAgentThreadAsync, getAgentThreadById, getAgentThreadMessages |
| Session ticket | one-time ticket string | createBackendSessionTicket, createSessionTicket |
The agent record has no instructions or schedule field. Task content lives on the board item; that's why a single agent can run many different tasks.
Resolving an actor
lookupActor finds an actor by its (subject_type, external_key) pair — the same pair used at creation. Use it when your service holds the external identifier and needs the Wacht actor id (e.g. to attach a project). Returns { actor: null } when no match exists rather than 404.
import { ai } from "@wacht/backend";
const { actor } = await ai.lookupActor({
subject_type: "user",
external_key: "ext-1234",
});
if (!actor) {
// No actor for that external key yet — call createActor.
}use wacht::models::LookupActorParams;
let response = client
.ai()
.actors()
.lookup_actor(LookupActorParams {
subject_type: "user".into(),
external_key: "ext-1234".into(),
})
.send()
.await?;
let actor = response.actor;Schedule kinds
board_item.schedule_kind (from models::project_task_schedule::schedule_kind):
ONCE— runs atnext_run_at, then stops.INTERVAL— runs atnext_run_at, then repeats everyinterval_seconds.
Tool types
AiTool.configuration is a discriminated union — the type field selects the variant:
type | Use for | Persisted via |
|---|---|---|
Api | An HTTP endpoint you own. Stores endpoint, method, auth, request/URL schemas. | createAiTool |
CodeRunner | Sandboxed code execution. | createAiTool |
Internal | Built-in platform tools the runtime ships. | (read-only catalog) |
Mcp | A self-hosted MCP server you control. | createAiTool |
PlatformEvent | Tools that react to platform events. | createAiTool |
Virtual | Third-party SaaS via Composio. | Not created directly — surfaces at runtime once enableComposioApp is called and the actor authorizes the toolkit. |
wacht api describe createAiTool shows the full per-variant payload schema for the persisted variants. See Composio for the Virtual flow.
Delegating between threads
A board item can delegate work to a different lane thread within the same project. The target thread takes over execution; the new board item carries forward the parent's project context and is routed based on capability_tags.
import { ai } from "@wacht/backend";
const delegation = await ai.delegateProjectTask(projectId, {
target_lane_thread_id: "thread_id",
title: "Run the data-validation lane",
description: "Validate the rows produced by the ingestion thread.",
capability_tags: ["data-validation"],
});
// delegation.task_key, delegation.board_item_id, delegation.assigned_agent_iduse wacht::models::DelegateProjectTaskRequest;
let delegation = client
.ai()
.actor_projects()
.delegate_task(
project_id,
DelegateProjectTaskRequest {
target_lane_thread_id: "thread_id".into(),
title: "Run the data-validation lane".into(),
description: Some("Validate the rows produced by the ingestion thread.".into()),
capability_tags: Some(vec!["data-validation".into()]),
},
)
.send()
.await?;Empty / omitted capability_tags match any agent on the target lane.
Downloading board-item artifacts
Each board item has a workspace filesystem. Listing and reading metadata uses listProjectTaskBoardItemFilesystem / getProjectTaskBoardItemFilesystemFile (returns JSON). To pull the bytes directly — including the server's Content-Type and a suggested filename — use the download variant:
import { ai } from "@wacht/backend";
const file = await ai.downloadProjectTaskBoardItemFilesystemFile(
projectId,
itemId,
"/output/report.pdf",
);
// file.data is a Uint8Array; file.mime_type and file.file_name come from response headers.let file = client
.ai()
.actor_projects()
.download_board_item_filesystem_file(project_id, item_id, "/output/report.pdf")
.send()
.await?;
// file.data: Vec<u8>; file.mime_type / file.file_name parsed from headers.Sharing a session
POST /session/tickets (createBackendSessionTicket server-side, createSessionTicket console-side) issues four ticket types — each redirects to a different vanity surface:
ticket_type | URL | Required fields |
|---|---|---|
agent_access | https://{backend_host}/vanity/agents?ticket=… | agent_ids, actor_id |
impersonation | https://{frontend_host}/sign-in?ticket=… | user_id |
api_auth_access | https://{backend_host}/vanity/api-auth?ticket=… | api_auth_app_slug |
webhook_app_access | https://{backend_host}/vanity/webhook?ticket=… | webhook_app_slug |
Tickets are one-time-redeemable and bounded by expires_in (seconds). See the Agent sessions guide for scoping fields and the per-product vanity-pages guides (API auth, Webhooks) for non-agent flows.
Related
- Agents — user-facing overview.
- Scheduling
- Composio
- Agent sessions