Primitives at a glance
Culvii Kit ships four stable primitives in v1.0. Each has a single responsibility, and they compose in predictable ways. This page is a tour; the SDK reference is the source of truth.
The four shipped primitives
| Primitive | What it represents | When you reach for it |
|---|---|---|
model() | A configured language-model endpoint | Once at startup. Pick a model and pin its parameters. |
tool() | A typed function the agent can call | When the agent needs to do something concrete — query a system, send an email, fetch a record. |
agent() | An autonomous reasoner with goals, tools, and a typed result | When you want the LLM to figure out a sequence of actions toward a goal. |
workflow() | A deterministic graph of steps, agents, and decision points | When you want to choreograph multiple agents, branches, or human approvals. |
How they relate
flowchart LR
M[model] -->|binds to| A[agent]
T[tool] -->|equips| A
A -->|step in| W[workflow]
T -->|step in| W
W -->|callable as a tool by| A
A few things worth pulling out from that picture:
- A model belongs to an agent. Tools don't have models; only agents reason. Tools just do.
- A workflow can contain agents and tools as steps. Workflows are how you compose multiple agents, add deterministic logic, and gate approvals.
- Workflows can be called as tools by agents. This is how you build hierarchical systems — an agent that delegates a sub-task to a workflow that itself contains other agents. See Composing agents and workflows for the full picture.
- Both agents and workflows are deployable units. You can ship either standalone, and either can call the other.
Minimal example
A workflow with one agent and one tool, hitting a model:
import { agent, tool, model, workflow, step } from '@culvii/kit'
import { z } from 'zod'
const lookupVendor = tool({
name: 'lookup-vendor',
description: 'Look up a vendor by ID',
input: z.object({ vendorId: z.string() }),
output: z.object({ name: z.string(), rating: z.number() }),
execute: async ({ vendorId }) => {
const res = await fetch(`https://internal-api.example.com/vendors/${vendorId}`)
return res.json()
},
})
const vendorAnalyst = agent({
name: 'vendor-analyst',
goal: 'Decide whether a vendor passes basic risk screening.',
model: model('anthropic/claude-sonnet-4-20250514'),
tools: [lookupVendor],
result: z.object({
decision: z.enum(['pass', 'fail']),
reason: z.string(),
}),
})
export const vendorScreen = workflow({
name: 'vendor-screen',
steps: [
step('analyze', { agent: vendorAnalyst, output: 'screening' }),
],
})
That's the full surface for v1: configure a model, define typed tools, equip an agent with them, place the agent in a workflow.
Composition primitives inside workflows
Inside a workflow, beyond step(), you can use:
| Primitive | Purpose |
|---|---|
step() | Run an agent or a tool as one node |
parallel() | Run multiple steps concurrently |
branch() | Pick one of several paths based on state |
loop() | Repeat a sub-graph until a condition is met |
gate() | Pause for a human approval (in-the-loop) |
handoff() | Hand control from one agent to another |
guard() | Validate a precondition or postcondition |
These are syntax for the workflow graph, not standalone primitives. See How-to → Compose a workflow.
What's not in v1
These are designed but not shipped. Don't write code against them yet — interfaces may change.
memory()— long-lived state across runs.- Context and retrieval primitives — RAG abstractions.
plan()— DSPy-style optimizer.group()— multi-agent committees and crews.skill()— packaged capabilities composed of tools and prompts.- Governance API — declarative
permit()-style policies. - Identity API — programmatic DID handling.
- Observability API — pluggable trace exporters.
optimize,messages,testprimitives.
Each will get its reference page when it ships. Until then, they're listed on the roadmap.
Where to read next
- Composing agents and workflows — the patterns that emerge when you put these primitives together.
- SDK reference — the precise contracts.
- How-to guides — recipes for common tasks.