Primitives at a glance
Culvii Kit is a node-graph SDK. You build a Workflow out of Step instances, wire them together with connectTo(), and one step type, agent, hands control to an LLM defined separately with MultiAgentEngine. A Surface optionally controls what a person sees when a step pauses or completes. The SDK reference is the source of truth; this page is the map.
The four things you actually import
| Construct | What it is | When you reach for it |
|---|---|---|
Workflow | A named container for a graph of steps | Once per process you want to automate. |
Step | One node in that graph: a trigger, an HTTP call, an if-branch, a set, an agent invocation, and so on | For every unit of work. |
MultiAgentEngine | A primary agent, optionally with secondary agents it can delegate to | Inside an agent-type step, when you want an LLM to reason instead of following a fixed path. |
Surface | A versioned UI definition for what a step shows once it completes, or while it's paused | When the platform's default rendering isn't enough. A custom output view, or a form for the person approving a paused step. |
A tool is either a built-in step type (HTTP, code, a connector) or an existing workflow wrapped with defineWorkflowTool(). A model is a plain config object (AgentModelConfig, with a provider, model ID, and a credentialId) attached to an agent, not something you create separately.
Workflow and Step
import { Step, Workflow } from '@culvii/kit'
const fetchVendor = new Step({
name: 'Fetch Vendor',
type: 'core.http',
params: { method: 'GET', url: '={{$json.vendorUrl}}' },
})
const checkRating = new Step({
name: 'Check Rating',
type: 'core.if',
params: {
conditions: [/* rating >= 4, see the If node reference for the exact shape */],
combineOperation: 'all',
},
})
const screenVendor = new Step({
name: 'Screen Vendor',
type: 'core.agent',
params: { agentReferenceKey: 'vendor-analyst', prompt: '={{$json}}' },
})
const autoReject = new Step({
name: 'Auto Reject',
type: 'core.set',
params: { values: { decision: 'reject' } },
})
fetchVendor.connectTo(checkRating)
checkRating.connectTo(screenVendor, { fromOutputIndex: 0 }) // true branch
checkRating.connectTo(autoReject, { fromOutputIndex: 1 }) // false branch
export const vendorScreen = new Workflow({
id: 'vendor-screen',
name: 'Vendor Screen',
steps: [fetchVendor, checkRating, screenVendor, autoReject],
})
Steps don't run in a linear array. They're connected via ports. Each node type defines how many outputs it has and in what order; the If node always emits two, [true, false], so checkRating wires each one to a different downstream step by index. fromOutputIndex on connectTo() is what selects which output you're wiring. See Output indexes for the full mechanism, which is the same one behind error outputs and any other multi-output node.
MultiAgentEngine
The core.agent step above doesn't contain any agent logic itself. It just names an already-deployed agent (agentReferenceKey) and sends it a prompt. The agent's actual behavior, its model, system prompt, and tools, is defined once, separately, with MultiAgentEngine, and reused by any workflow that references it:
import { MultiAgentEngine } from '@culvii/kit'
export const vendorAnalyst = new MultiAgentEngine({
id: 'vendor-analyst',
name: 'Vendor Analyst',
role: 'primary',
systemPrompt: 'Decide whether a vendor passes basic risk screening.',
model: { provider: 'anthropic', modelId: 'claude-sonnet-4-5', credentialId: 'anthropic-credential' },
})
Add secondaryAgents when the primary needs to delegate a narrower task to a different prompt, model, or permission set. Permissions (which built-in tools an agent can call, and which workflow tools via defineWorkflowTool()) are scoped per agent. A primary can't reach into tools granted only to a secondary. See MultiAgentEngine and Tools and permissions.
An agent can also call an entire workflow as a tool. defineWorkflowTool() wraps one so the model can invoke it like any other tool. Two limits fall out of that: an agent run has 10 minutes of wall-clock time before the platform kills it, and when that agent calls a workflow-as-a-tool, the call has 5 minutes to resolve before it's treated as failed.
Surface
By default, a step that pauses or completes renders with a generic platform UI. A Surface overrides that: a custom output view for a completed step, or a form for whoever's approving a paused one. What actually pauses the run is params.pauseAfterExecution: true on the step itself; you can set that directly with no surface at all and the Console falls back to a default resume form. Attaching a Surface to the step's wait slot just replaces that default, and the Kit sets pauseAfterExecution for you as a convenience when you do. See Human in the loop and Surfaces overview.