Custom agents API
Author workspace agents, publish versions, chat with them, start runs from the API, on a schedule or from other agents, and review runs, policies and decisions.
Custom agents have a mutable draft and immutable published versions. Use the VegaFlow API base /api/v1/vegaflow/orgs/{org_id}/workspaces/{workspace_id}/agents.
Author and publish
POST /agents takes name, optional display_name, description and icon, and an initial draft definition using vega.agent/v1.1. Names begin with a lowercase letter and contain 1–64 lowercase letters, digits, underscores or hyphens. Names are unique within a workspace; vega_ai is reserved.
GET /agents supports search, a keyset cursor and limit (1–100). It returns agents the caller can read. List items and GET /agents/{id} return metadata, the current version ID and number, a draft summary (resource version, digest, updater and time), and effective permissions. The full definition comes from draft GET. PATCH /agents/{id} changes display metadata in the draft.
Read the draft at GET /agents/{id}/draft. Replace it with PUT /agents/{id}/draft, sending the returned resource version in If-Match. A stale version returns 412. Drafts may be incomplete; POST /agents/{id}/draft/validate returns errors with paths.
Publish with POST /agents/{id}/versions, an If-Match draft resource version and optional notes. Publication checks the definition and the publisher's authority. Named connections must be usable by the publisher and allow the granted effects through ai_access. Actions must exist on their connector; MCP tools must be on the reviewed allow-list. Skills and model profiles must be available. The running user's permissions are checked again at call time.
Versions have sequential numbers, a digest, publisher, publication time and notes. Publishing an unchanged digest returns the existing version. List versions at GET /agents/{id}/versions, read one at GET /agents/{id}/versions/{version_id}, and select an older release with PUT /agents/{id}/current-version and {"version_id":"uuid"}.
GET /agents/catalog returns grantable platform tools, workspace connections and connector actions, reviewed MCP tools, enabled skills, model-profile names and the agents you can call as tools (agents). Custom definitions name workspace connection IDs and MCP tools explicitly.
Permissions
Grants apply to one agent or to all agents in a workspace. read allows agent metadata, definitions and versions; use allows published chat and includes visibility; edit allows draft changes and test chat; publish allows publishing, selecting the current release, disabling and enabling. admin includes all agent capabilities, deletion and grant management. A workspace-level edit grant permits creation. The creator receives admin on the new agent.
Use GET, POST and DELETE /agents/{id}/permissions to list, grant and revoke user, group and role grants using the standard permissions request schema. Unknown permissions and service-principal entries return 422 with field paths. Built-in Turing keeps its separate permissions.
Chat and test drafts
POST /agents/{id}/conversations accepts version (a published version UUID, "draft", or omitted for the current version) and optional approval_mode. Published chat requires use; selecting an older version additionally requires edit or publish. Draft chat requires edit and a valid draft. Each conversation pins its definition. Later draft edits and current-version changes do not alter it. After a rollback or other current-version change, a use-only owner can still read the pinned transcript, but ask and steer return 409 with code AGENT_VERSION_CHANGED and message "This agent's version changed; start a new conversation." Owners with use plus edit or publish may continue. Draft snapshots are excluded from published version lists.
The question and event schemas match Turing chat:
GET /agents/{id}/conversations/{conversation_id}GET/POST /agents/{id}/conversations/{conversation_id}/questionsGET /agents/{id}/questions/{question_id}POST /agents/{id}/questions/{question_id}/cancelPOST /agents/{id}/questions/{question_id}/messagesfor steeringGET /agents/{id}/questions/{question_id}/events: SSE or JSON replay withAccept: application/json; supportscursorandLast-Event-ID
Asking requires a UUID Idempotency-Key. The owner still needs use for published chats or edit for drafts to read their history. Agent admins can review another owner's transcript, questions, events and inline approvals, with connection prompts hidden and personal account identities redacted. That reviewer view is read-only. Other users receive 404. Only the owner can ask, steer, cancel or decide. Approvals also appear in the workspace inbox. Event requests recheck permissions before waiting; revocation takes effect on the next request after the current wait window, at most 25 seconds. Custom conversation listing and forking are not available yet.
DESTRUCTIVE tools require explicit connection and action/tool names, ai_access=allowed_destructive, and human approval on every call, including auto mode, unless an agent admin sets a DESTRUCTIVE override for runs without a chat. allowed covers only READ, DRAFT and WRITE. Destructive ceilings cannot widen non-destructive actions or wildcard grants. Approved destructive executions are audited with their effect. Built-in Turing grants no destructive tools. Custom agents use their authored instructions; personal and workspace Turing instructions apply only to the built-in agent.
Runs without a chat
A definition's triggers list how runs of a published version can start: chat, api, schedule, event and agent (called by another agent). A run whose trigger the version does not list is refused with 422 AGENT_TRIGGER_NOT_ENABLED.
Runs without a chat act as one user, the run-as user: the caller for an API run, or the owner of the schedule or event trigger. Each start checks again that this user holds use on the agent, that the agent is active and that the version is published. These runs use workspace connections only; a version that grants a personal connection is refused with 422 AGENT_UNATTENDED_PERSONAL_CONNECTION. Service principals cannot start runs yet (403 AGENT_RUN_AS_UNSUPPORTED).
Start a run from the API
POST /agents/{id}/runs requires a UUID Idempotency-Key. Its body takes:
input: any JSON value, at most 256 KiB;message: optional text, at most 32 KiB;version: optional published version number; the current version when omitted (draftis refused);approval_mode:ask(default) orauto.
It returns 202 with the run summary; poll GET /agents/{id}/runs/{run_id} until the run ends. Repeating a request with the same key returns the same run. The agent receives the message followed by the input as untrusted data, so the run starts with the api_input untrusted label and the Rule of Two applies to changes it makes afterwards.
When the version defines output.schema, the final answer must be JSON that satisfies it. The summary's output holds the validated value; an answer that does not satisfy the schema ends the run FAILED with AGENT_OUTPUT_INVALID.
A workspace runs a limited number of runs without a chat at once (10 by default), counting runs that are starting, running or waiting for approval. Over the limit, an API start returns 429 AGENT_RUN_LIMIT with Retry-After.
Schedules
Schedules start runs with stored input at set times. Each belongs to an agent and has an owner, the user its runs act as.
GET /agents/{id}/triggersandGET /agents/{id}/triggers/{trigger_id}needread.POST /agents/{id}/triggersneedspublish, and the caller becomes the owner; the owner needsuse.PUT /agents/{id}/triggers/{trigger_id}withIf-Matchsaves changes; a stale version returns 412.DELETE /agents/{id}/triggers/{trigger_id}needspublish. Runs already started carry on.POST /agents/{id}/triggers/{trigger_id}/run-nowstarts a run immediately.POST /agents/{id}/triggers/{trigger_id}/take-ownershipmakes the caller the owner. It needsadminand resetsapproval_modetoask.GET /agents/{id}/triggers/{trigger_id}/firingslists starts, newest first, withcursorandlimit.
A schedule takes name, version_mode (current, which follows new publishes, or pinned with pinned_version_id), input, optional message, approval_mode, enabled and schedule. The schedule has schedule_kind cron, interval (with interval_seconds) or calendar, a timezone (UTC by default), overlap_policy SKIP (default; skip a start while the previous run is still going) or ALLOW, and missed_run_policy SKIP (default), LATEST or CATCH_UP.
Only the owner changes what runs as them: the input, message, version, schedule and approval mode, turning it back on, and run-now (with publish). Anyone else with publish can only pause it (renaming it at the same time) or delete it; other changes return 403 AGENT_TRIGGER_OWNER_REQUIRED. An agent admin can take ownership first.
Each start is recorded with an outcome: started (with the run ID), skipped_limit (the workspace limit was reached), skipped_overlap, or refused with a code. A refused start pauses the trigger and records the reason in paused_reason, for example when the owner no longer holds use (AGENT_USE_REQUIRED) or the agent is disabled. Turn it back on with PUT after fixing the cause; the checks run again.
Event triggers
An event trigger starts a run for each platform event that matches it, such as vegagraph.alert.fired or vegaflow.pipeline_run.completed. It uses the same /agents/{id}/triggers routes, permissions and owner rules as schedules, with kind: "event":
event_match:types(1–10 type globs;*matches one segment, or every remaining segment at the end), optionalsubject(urn,urn_prefixorkinds), optionaltargets_any(URNs in the event'sdata.targets) and an optional CELconditionoverevent,dataandsubject.input_template: optional JSON object rendered for each event; any value may be{"$cel": "<expr>"}. Without a template, the run's input is the event itself: itsid,type,time,dataandsource, with the subject inmetadata.message,version_mode,approval_modeandenabledas for schedules.inputandscheduleare refused, andkindcannot change after creation.
The owner must be able to see the events the trigger matches, checked when it is saved, when ownership changes and at every event. If the owner loses that access, the start is refused with AGENT_EVENT_NOT_OBSERVABLE and the trigger pauses. A condition or template that fails on an event refuses that start with AGENT_EVENT_TEMPLATE_FAILED, and the trigger stays on. Every matching event starts its own run; overlap policies do not apply, and each event starts at most one run per trigger. Over the workspace limit, the start is recorded as skipped_limit and not retried. Run-now returns 422 AGENT_EVENT_RUN_NOW_UNSUPPORTED. Firings carry the event_id.
Event input is untrusted: the run starts with the event_input label, and the Rule of Two applies to changes it makes afterwards. Event triggers fire only while platform events are on for the workspace (GET/PUT /pipeline-triggers/settings, platform_events_enabled), the same switch as pipeline platform-event triggers.
Approvals for runs without a chat
In chat, the chatting user decides every approval. For runs without a chat, the definition's approval.assignees decide: up to 20 entries of {"type": "user", "id": "uuid"} or {"type": "group", "id": "uuid"}, which must be users or groups of the organization. With no assignees, the run-as user decides. Deciders also need use on the agent when they decide. Approval requests appear in each eligible decider's workspace inbox. An approved call still runs as the run-as user, not as the approver.
approval.on_expiry sets what happens when a request expires: reject (default) rejects the call and the run carries on; fail ends the run FAILED with AGENT_APPROVAL_EXPIRED. Chat approvals always reject on expiry.
The approval_mode of an API request or a trigger is the run-as user's consent to auto mode for that run. In auto, WRITE calls an auto_approve policy allows run without asking. DESTRUCTIVE calls ask unless a DESTRUCTIVE override covers the tool, and the Rule of Two always asks.
Runs without a chat have a conversation that is not listed with chats; asking into it or steering it returns 409 AGENT_CONVERSATION_NOT_CHAT.
DESTRUCTIVE overrides
An agent admin can let one named DESTRUCTIVE tool run without a person approving, in runs without a chat:
GET /agents/{id}/destructive-overridesneedsreadand lists active overrides;include_revoked=trueadds revoked ones.POST /agents/{id}/destructive-overridesneedsadminand the caller's own access to the tool. It takestool, a tool key (connector_action/{connection}/{connector}/{action}ormcp/{connection}/{tool}, no wildcards), andacknowledgement, which must be exactlyrun <tool> without approval in unattended runs; otherwise 422AGENT_OVERRIDE_ACK_REQUIRED, with the phrase indetail. The tool must be a DESTRUCTIVE grant of the current published version on a connection withai_access=allowed_destructive(422AGENT_OVERRIDE_TOOL_INVALID). One active override per tool; a duplicate returns 409.DELETE /agents/{id}/destructive-overrides/{override_id}needsadminand revokes it from the next call. Revoked overrides stay listed for audit.
An override applies only to API, scheduled and event runs in auto mode, and only while the admin who set it still holds admin on the agent and access to the tool. Chats and ask mode always ask. Deny and require-approval policies win, and so does the Rule of Two unless an exempt_rule_of_two policy covers the tool. A call it allows is recorded as an approval with decision_via: "destructive_override" and the override's ID in the decision reasons. If the override is revoked or its admin loses access before the call runs, the call waits for a person instead. GET /agents/{id} returns destructive_overrides_active, the number of active overrides.
Agents as tools
A definition can call up to 10 other custom agents in the same workspace as tools:
"tools": {
"agents": [
{ "agent": "invoice_checker", "mode": "tool" },
{ "agent": "kyc_summary", "mode": "tool", "version": 3, "description": "Summarise a customer's KYC file" }
]
}agentis the other agent's name; it cannot be the agent itself orvega_ai.versionpins a published version number. Without it, each call uses the other agent's current version.descriptionis what the model reads about the tool. It defaults to the other agent's display name and description.modeistool, which waits for the answer, ortask, which starts the agent in the background (see Background tasks).
The called agent's published version must list agent in its triggers, and the publisher needs use on it. Otherwise draft/validate and publication report AGENT_TRIGGER_NOT_ENABLED or AGENT_PUBLISH_AUTHORITY at tools.agents[i]. Publication records the agent's identity, so deleting it and creating a new agent with the same name never changes what an existing version calls.
Calls may not loop and nest at most three agents deep. Publishing, or selecting a current version with PUT /agents/{id}/current-version, returns 422 AGENT_TOOL_CYCLE or AGENT_TOOL_DEPTH when it would break either rule. The same checks run again on every call.
The model sees one tool per agent, which takes a task (up to 32 KiB of text) and an optional JSON input (up to 256 KiB). When a definition omits tools.core, its agent tools load up front with its platform tools (at most 30 in all); an explicit tools.core decides which tools load up front and leaves the rest to tool search. Each call starts a run of the other agent with its own conversation:
- Who it acts as. It runs as the calling run's user, who must still hold
useon the called agent. Under an API, scheduled or event run, the called agent may use workspace connections only. - Limits. Its limits are the lower of its own and what remains of the caller's model requests, tokens, cost and time. Its usage counts against the caller.
- Approvals. These use the approval mode of the run that started the chain. They go to whoever decides the caller's approvals: the chatting user, or the first run's approvers. In chat, they appear in the caller's question events with
requested_bynaming the agent that asked, and the caller's question accepts the decision. Time spent waiting for a decision does not count against any run's time limit. - Labels. Untrusted and sensitive labels flow both ways: the caller's labels apply to the called run, and the called run's labels apply to the caller.
- Result. The tool returns
{"run_id", "status", "output"}.outputis the called run's validated output, or its answer text. - Failure and cancellation. A called run that fails returns a tool error with its status and error code. Cancelling the caller cancels the called runs.
- Interactions. A called run can ask for approvals but nothing else. It cannot ask for a connection to be chosen or connected (
AGENT_CHILD_INTERACTION_UNSUPPORTED).
Background tasks
With "mode": "task", the agent's tool starts the other agent and returns at once with {"task_id", "run_id", "status": "RUNNING"}. The task keeps running after the answer, and its result comes back to the conversation that started it. Task entries follow the same rules as agent tools: the called agent must list agent in its triggers, the publisher needs use on it, and calls may not loop or nest more than three agents deep.
"tools": {
"agents": [
{ "agent": "market_research", "mode": "task", "description": "Research a market in depth" }
]
}A definition with a task entry also gets four tools for the tasks of its conversation. They see only tasks started in that conversation, in any turn:
| Tool | Input | Returns |
|---|---|---|
task_status | optional task_ids (up to 20) | Each task's agent, status, start and finish times and error code. Without task_ids, the conversation's 20 newest tasks. |
task_wait | task_ids (1–10), timeout_seconds (1–300) | When every listed task has finished, or at the timeout, each task's status and timed_out. The wait counts against the run's time limit and ends before it. |
task_result | task_id | {"run_id", "status", "output"} for a task that succeeded, or a tool error naming its status. |
task_cancel | task_id | Cancels a running task. A finished task is left as it is. |
Policies can name these tools by their keys task/status, task/wait, task/result and task/cancel, and the agent's own tool by agent/<agent_id> as for agent tools.
How a task runs:
- Who it acts as. As for agent tools: the calling run's user, who must still hold
useon the called agent, and workspace connections only under an API, scheduled or event run. - Limits. A task's limits are the lower of its own and the calling agent's configured limits. Its usage counts on its own run, not against the run that started it.
- How many. A conversation runs at most five tasks at once, and a workspace runs at most five. Starting another returns the tool error
AGENT_TASK_LIMIT; nothing is queued. - No tasks inside tasks. A task, and any agent it calls, is not offered task tools; its trace lists them in
withheld_toolswithAGENT_TASK_NESTING_UNSUPPORTED. It can still call agents withmode: tool. - Lifetime. A task keeps running when the turn that started it ends or is stopped. It ends when it finishes, through
task_cancelor the cancel route, or when the conversation is deleted. Disabling or deleting the called agent stops new tasks, not running ones. - Approvals. Approvals go to the same people as for agent tools, and can be decided after the turn that started the task has ended, from that question or the approvals inbox. While a task, or an agent it called, waits for a decision, its status is
WAITING_APPROVAL. - Labels. The task starts with the caller's labels. Its labels apply to the conversation when its result is read or delivered.
When a task finishes, its result is added to the question that started it as a task_results entry. In a chat:
- If the conversation is idle, the agent answers on its own in a new question with
trigger: "task_completion"andtask_ids. Tasks that finish close together share one such question. It runs on the version the chat is pinned to, as its owner, and the result reaches the model as data, never as instructions. - If a turn is running, its next model step is told which task finished. If that turn reads the result with
task_result, no extra question follows.
Runs without a chat keep the result in their conversation's history and start no new question. Each task's follow_up records what happened: pending, started, coalesced, or skipped_<reason> (for example skipped_unattended or skipped_version_changed).
In the agent's chat, a Background tasks list above the message box shows each task's status, with Show result, Cancel and Open the run, and the agent's own answer appears under a "Background task finished" line. In the agent form, each entry under Agents has How it runs: Call and wait for the answer or Background task.
GET /agents/{id}/conversations/{conversation_id}/tasks lists a conversation's tasks, newest first, with cursor and limit (1–100, default 50), to anyone who may read the conversation. GET …/tasks/{task_id} returns one task, with its result when you can see the task run's content. POST …/tasks/{task_id}/cancel cancels it; it needs the conversation's owner or an agent admin.
Every finished task also publishes vegaflow.agent_task.completed on the workspace event bus, with task_id, agent_id, run_id, conversation_id and status, and without the output.
Runs and traces
Every question to a custom agent, API start, scheduled start and event start is a run. GET /agents/runs lists runs across the workspace's custom agents, and GET /agents/{id}/runs lists one agent's runs. Both are newest first, with a keyset cursor and limit (1–100), and filter by agent_id (workspace list only), status, trigger (chat, api, schedule, event, agent or task_completion), root_run_id (every run in one chain of agent calls), run_as_user_id, version (a number or draft), from/to on the request time, labels (tainted, sensitive) and has_approval. Built-in Turing runs are not included.
You see your own runs on agents you can still use, and every run of agents where you hold admin. Other users receive 404. GET /agents/{id}/runs/{run_id} returns the run summary: status, trigger, pinned version, run-as user, timings, token totals (input, output, cache read and write), cost_usd (null when unpriced, with cost_partial when only some calls are priced), approval count and wait time, trust labels and any error. Runs without a chat add input, the validated output and, for scheduled and event runs, trigger_id; input and output are shown to the run-as user only. Every run carries root_run_id and depth, plus parent_run_id when another agent called it. descendant_usage gives the tokens and cost of the runs it called and waited for. A background task's run has task_id, and a question the agent asked itself after tasks finished has task_ids.
GET /agents/{id}/runs/{run_id}/trace returns the run's steps in order: question, model calls (profile, model, attempt, outcome, tokens, cost, compaction), tool calls (tool, source, effect, connection, the policy decision with its reasons and policy versions, approval state and decider, output trust, code-mode calls nested under their parent, and child_run with the called agent, version, run ID and status for agent calls, plus task_id when the call started a background task), approvals, data access, label changes, the answer and errors. Runs with more than 500 steps page with cursor.
The run owner sees content: question, answer, thinking, tool arguments and result previews of up to 8 KiB per step. An agent admin looking at another user's run gets redacted: true and the same steps without content: no question or answer text, an arguments digest instead of arguments, and table names and row counts instead of SQL.
Policies
Policies decide which tool calls custom agents may make and which must ask a person first. A policy applies to one agent or to every custom agent in the workspace. An agent policy needs admin on that agent; a workspace policy needs workspace-level agent admin.
POST /agents/policies takes name, enabled, optional agent_id and kind:
form: a preset with fields, compiled to Cedar by the server and stored with it.cedar: raw Cedar text. Only workspace-level agent admins can save it. Permits may carry@auto_approveor@approval.
The presets are:
| Preset | Effect |
|---|---|
auto_approve | The named tools of one agent may run without a person approving each call, but only when the chatting user turned auto mode on, or the API request or trigger set approval_mode: auto. It never applies to DESTRUCTIVE tools (see DESTRUCTIVE overrides) or overrides the Rule of Two. Approvals it allows are recorded with decision_via: "policy". |
require_approval | The named tools, or every tool of an effect class, always ask first. |
deny | The named tools or connections may not be called. |
row_limit | VegaDB queries and exports that ask for more than max_rows rows are refused. |
exempt_rule_of_two | The named tools may act after the run read untrusted or sensitive data. Workspace-level agent admins only. |
Tool selectors name a platform tool (vegadb.query) or a connector or MCP tool key (connector_action/{connection}/{connector}/{action} or mcp/{connection}/{tool}). A full tool ID with an @version or @digest suffix is accepted and matched without it, so policies keep applying when an action or MCP tool changes. POST /agents/policies/compile returns the Cedar text and any issues without saving, for previews.
GET /agents/policies lists the policies you can read, and GET /agents/policies/{policy_id} returns one with an ETag. PUT /agents/policies/{policy_id} with If-Match saves a new version: a stale version returns 412 (AGENT_POLICY_VERSION_CONFLICT), and an invalid policy returns 422 (AGENT_POLICY_INVALID) with field paths. GET /agents/policies/{policy_id}/versions lists versions, and POST /agents/policies/{policy_id}/versions/{version}/restore saves an old version as the newest one. DELETE /agents/policies/{policy_id} stops the policy at once, also for runs already going; its versions stay for audit.
Custom agents do not use the workspace's auto-approvable tool list; it applies to built-in Turing only. Without policies, custom agents ask before every WRITE and DESTRUCTIVE call, and the Rule of Two applies.
Decision log
GET /agents/decisions lists every policy decision for custom agents' tool calls, newest first, with cursor and limit (1–100), filtered by agent_id, outcome (allow, approval, deny), tool, from and to. Each entry names the run, tool, effect, outcome, reasons, the policies that matched with their versions, and whether the Rule of Two applied. It requires admin on at least one agent and shows only decisions for the agents you administer. Entries follow the normal retention policy.
Disable and delete
POST /agents/{id}/disable refuses new runs; POST /agents/{id}/enable allows them again. DELETE /agents/{id} returns 409 while runs are active. Deletion hides the agent from lists, prevents new runs and frees its name for reuse. Existing versions and retained history remain readable to authorized callers under the normal retention policy.