For a single provider call — including comparing across options — ai_call inside a logic block is enough (see AI Call, in Logic Blocks). Agent crews are for genuine multi-agent workflows: a deployable unit where one or more agents can plan, call tools, hand off work, and pause for a human — all built on infrastructure this platform already had (entities, connectors, the WebSocket gateway), not a parallel stack.
Anatomy of a crew
A crew is a graph you build on a canvas: nodes connected by edges, optionally guarded by a condition. Nodes with no unresolved incoming edge in the same level run concurrently; a crew with no edges at all runs its nodes sequentially in array order — a fully-supported shape, not something you're required to outgrow.
| Node kind | Does something today? | What it is |
|---|---|---|
| agent | Yes | A model + system prompt + tools + guardrails + memory. Does the actual reasoning and tool-calling. |
| humanGate | Yes | A graph-level checkpoint — pauses the WHOLE run before its outgoing edges for a human decision, independent of any single tool call. |
| output | Yes (typed, passthrough) | Declares the run's final shape: json, notification, or storeToTable. |
| condition, loop, parallel, merge | Not yet | Drawable and saved fine, but the executor doesn't give them real branching/looping/merge behavior yet — using one today gets no error, just no effect. Don't build logic that depends on them. |
- Model & provider — each agent can pin a specific model, and a specific tenant AI-provider key (
provider) instead of the crew's own default. See Multi-provider AI below. - Guardrails — allowed/blocked topics, PII scrubbing, turn/token caps, and
onFailure(stop / retry / escalate to a human). Per-agent guardrails override the crew's own defaults field by field, not as a full replacement. - Tool turns —
maxToolTurns(a whole number from 1 to 25, default 6) is how many model turns an agent may use to call tools before it must answer. One turn can hold several tool calls, so tell the agent to batch independent calls (for example reading three files together). An agent that works through many sequential steps, such as reading code, branching, committing and opening a pull request, needs a plan that fits its turns, or a higher limit. When an agent spends them all it gets one last call, told it is out of tool turns, to write its answer, and the run's turns show that the limit was reached. - Memory — none, a sliding window, a running summary, full history, or session-persisted across separate invocations by
sessionId. - Tools — see the full catalog below.
Deploy intercepts missing guardrails
Deploying a crew with no guardrails prompts you to add them first — a nudge to make agent behaviour intentional before it goes live.
Crew status
A new crew starts as a draft. Only an active crew can be invoked from your app — a draft, staged or paused crew answers 422 Crew is not active. Move a crew along with the console's status controls, PUT /api/agents/{crewId}, or the MCP update_crew tool (the status field).
| From | Can move to |
|---|---|
| draft | staged, delete |
| staged | active, draft, delete |
| active | paused, delete |
| paused | active, delete |
Approval defaults for crews written through the API or MCP
A tool that leaves requiresApproval out is saved with the catalog default — approval on for data.write, connector.slack.postMessage and human.askQuestion — so a write tool never runs unattended by accident. Set requiresApproval: false explicitly to turn it off. Crews saved earlier are not changed.
The tool catalog
Every tool an agent can call reuses real platform code — never a bespoke, agent-only reimplementation. data.read/data.write call the exact step-runners a Logic Block's own entity steps call; connector.invoke calls the exact function a direct API call or a Logic Block connector step calls, with the same stored credentials and the same org-level allowlist.
| Tool | Approval by default | What it's for |
|---|---|---|
| data.read | No | Read a platform entity — filters, sort, limit. |
| data.write | Yes | Create, update, or delete a record — one tool, an operation argument picks the action. |
| connector.invoke | Per-config | Call any org-enabled connector's action — Slack, GitHub, a payment provider, Tavily/Serper web search, URL screenshots, and the rest of the connector catalog. Connector + action are picked once at build time; a {{args.KEY}} field mapping resolves the model's own call-time arguments. |
| crew.delegate | No | Hand a task to another crew, in-process, and use its output. |
| glyph.invoke | No | Run one of your own Glyphs (custom Python or JavaScript) — the Glyph is picked once at build time and the model's own arguments become its request. Runs the Active version unless configured for Draft. Not available in builder test runs; use a real trigger. |
| human.askQuestion | Always | Ask a human free text and pause until answered. The answer is injected as this tool's own result on resume. |
| document.extractText | No | Extract the text of a document the run was given. |
| builtin.calculator / builtin.dateTime / http.custom | No | Arithmetic, current time in a timezone, and a single pre-configured HTTP request. |
Crew delegation has real guards
crew.delegate is checked against the calling crew's own delegation chain before it runs — direct self-delegation (A delegating to A) is caught on the very first attempt, and any chain longer than 3 hops is refused. If the crew you delegated to itself pauses for approval, your crew's call comes back as a clear error naming that sub-run, not a silent empty result.
Human-in-the-loop
Four distinct situations pause a run — all four share one mechanism (persisted state, one decision, one resume path), never four separate ones. A paused run's status is awaiting_approval.
| Reason | Triggered by | Resumes as |
|---|---|---|
| tool_approval | A tool call whose requiresApproval is true (data.write by default) | The call executes for real, the turn continues. |
| escalate | A guardrail block, or an agent error, with onFailure: 'escalate' | The human's approval *is* the retry. |
| question | The agent's human.askQuestion call | The submitted answer is injected as the tool's own result. |
| humanGate | Execution reaching a humanGate node in the graph | Continues into that node's outgoing edges; the approved content becomes the node's own output. |
A humanGate node also carries its own timeoutMinutes (default 30) and onTimeout — escalate (the default, flags it overdue but doesn't auto-decide), abort, or autoApprove — checked every 5 minutes.
To answer a question pause, open the Jobs tab on the Dashboard (it lists every kind of job — crews, Logic Blocks, Glyphs, indexing and bulk updates): the paused run shows an Answer button instead of Approve. It opens the agent's question with a box for your reply; sending it resumes the run with your text as the tool's result. Reject ends the run. The other pause reasons keep the plain Approve and Reject buttons.
Your own app can decide a paused run too, without the console: POST /app/{orgcode}/agents/decision with { "runId": "...", "decision": "approve" | "reject", "answer": "..." } (answer only for an approved question pause). The run is always in the signed-in user's own workspace — you never send one. A user needs the createdecision permission; a custom role needs an ACL policy that grants it.
Using the tenant's settings
An agent's system prompt can include {{params.settings.company}} (or any key from Tenant Management → General; a dotted path reaches a nested value). It is filled in when the run starts, and the Test tab fills it from the tenant you have selected, so a test reads what a live run will.
Text you write in a prompt is the same for every tenant; use {{params.settings.…}} for anything each tenant sets itself. The filled prompt goes to your AI provider, so keep anything sensitive in Secrets, not General. Secrets are never put in a prompt and never reach the model — not in a prompt, a tool call or a tool result. They go only to the tools that need them to call something: a glyph.invoke tool hands them to the Glyph as ctx.params.
Multi-provider AI
A crew resolves one AI provider for the whole run by default. Any single agent can instead name a specific tenant provider key — resolved fresh right before that agent's own model call. This is strict, not silent: if the named provider isn't actually configured for the tenant, that agent's turn fails clearly (routed through its own onFailure) rather than quietly running on a different, substituted provider.
In the builder's Prompt tab the model list shows every provider your organization allows. A provider with no API key on the workspace you are working in is labelled no key on workspace <code>, and picking one shows a warning — the run would fail there until a key is added in Tenant Settings. Each tenant sets up its own providers, so a crew built for several tenants can still name a model only some of them have.
Testing a crew
The builder's Test tab runs your saved crew with a message you type, on the workspace you have selected in the console, using that workspace's own AI provider and key — add one under Tenant Settings first, or the test tells you the workspace has none. Results stream in as the run progresses. A test run can't read or change real records or send anything through a connector: tools that touch real data answer that they aren't available in a test, so use a real trigger to try those. A crew's saved memory isn't recalled in a test either. If a test run stops for a person (an approval or a question) it only reports the pause — run the crew from a real trigger and answer it in the Jobs tab. Test output is kept for about an hour and then deleted.
Save a message you reuse with the Save button next to the message box, give it a name, and pick it from Saved tests next time — up to 20 per crew and workspace. If the crew stops to ask a person (an approval or a question), the test ends as paused: run the crew from a real trigger and answer it from the Jobs tab.
Live activity
Any tenant user can watch every crew run happening in their own tenant over the WebSocket gateway — every turn event, in real time, not just runs they personally started. Any org admin can watch every run across the whole org (status transitions only). The channel a client joins is always computed server-side from who's actually signed in, never from anything the client asks for — there's no way to end up watching another tenant's or org's activity.
Invoking a crew
{orgApiEndpoint}/app/{orgCode}/agents/{crewSlug}/invoke with a JWT, or POST {orgApiEndpoint}/app/{orgCode}/webhook/agents/{crewSlug} with a webhook key for server-to-server calls. Both run asynchronously: the invoke returns a runId, and your app polls GET {orgApiEndpoint}/app/{orgCode}/jobs/{runId} for the status and, once completed, the answer in result.output (kept for about an hour).run_crew step. The logic block handles structured data flow; the crew handles reasoning and multi-agent coordination.agent_crew destination, so a crew can fire on any data change a logic block could.create_schedule). The crew has no schedule of its own.crew.delegate tool, in-process.{
"type": "run_crew",
"crewSlug": "customer-support-crew",
"input": "{{s1.output}}",
"timeoutMs": 120000,
"outputVar": "crewResult"
} Logic block: processComplexInquiry
s1: get_entity(Customer, id: {{input.customerId}})
s2: run_crew(crewSlug: "customer-support-crew", input: {{s1.output}})
s3: create_entity(SupportTicket, {
resolution: {{s2.outputSnapshot}}
}) A gated crew still returns immediately
Invoking a crew always returns right away with a run reference. If the run then pauses (any of the four human-in-the-loop reasons above), polling its status comes back awaiting_approval instead of completed/failed until a human decides — your calling code should handle that state, not assume every invoke resolves straight to a final answer.
AI Call (in Logic Blocks)
The single-call alternative to a full crew.
AI integration
Configure the providers agents and ai_call both use.
Glyphs
Custom code you write yourself — a different, related capability.
Events & automations
Trigger logic and crews on data changes.
Logic block step types
Where run_crew fits among the other steps.