Skip to main content

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

PrimitiveWhat it representsWhen you reach for it
model()A configured language-model endpointOnce at startup. Pick a model and pin its parameters.
tool()A typed function the agent can callWhen 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 resultWhen you want the LLM to figure out a sequence of actions toward a goal.
workflow()A deterministic graph of steps, agents, and decision pointsWhen 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:

PrimitivePurpose
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, test primitives.

Each will get its reference page when it ships. Until then, they're listed on the roadmap.