Getting Started with Procedures
Learn the basics and write your first agentic workflow.
Core Concepts
Procedures
A procedure is a reusable unit of agentic work defined in YAML. It has:
- Parameters: Typed inputs validated before execution
- Outputs: Typed results validated after execution
- Agents: LLM workers that execute tasks
- Workflow: Lua orchestration code
- State: Mutable storage for tracking progress
Agents
Agents are configured LLM instances. Each agent has:
- A system prompt (can use templates)
- Available tools (including other procedures)
- Response filtering and retry configuration
When you define an agent named worker, you get a Lua primitive Worker.turn() that executes one agentic turn.
Workflows
The workflow contains Lua code that orchestrates execution: calling agent turns, managing state, controlling flow, invoking other procedures, and returning results.
Your First Procedure
Step 1: Simple Research Task
Let's create a procedure that researches a topic:
name: simple_researcher version: 1.0.0 description: Research a topic and provide summary params: topic: type: string required: true outputs: summary: type: string required: true agents: researcher: system_prompt: | Research the topic: {params.topic} Use the search tool to find information. When done, call the done tool. tools: - search - done workflow: | -- Loop until done repeat Researcher.turn() until Tool.called("done") or Iterations.exceeded(10) return {summary = State.get("summary") or "No summary available"}
Step 2: Add State Tracking
Track progress across turns:
workflow: | -- Initialize state State.set("count", 0) -- Process each item for i, item in ipairs(params.items) do Worker.turn({inject = "Process: " .. item}) State.increment("count") end return {processed_count = State.get("count")}
Step 3: Add Stages
Use stages for status tracking:
stages: - loading - analyzing - reporting workflow: | Stage.set("loading") load_data() Stage.set("analyzing") repeat Worker.turn() until Tool.called("done") Stage.set("reporting") generate_report()
Working with Parameters
params: topic: type: string required: true description: "The research topic" depth: type: string enum: [shallow, deep] default: shallow max_results: type: number default: 10 include_sources: type: boolean default: true
Supported types: string, number, boolean, array, object
Error Handling
Use Lua's pcall for error handling:
workflow: | local ok, result = pcall(function() Worker.turn() return State.get("result") end) if not ok then Log.error("Operation failed: " .. tostring(result)) return {success = false, error = result} end return {success = true, result = result}
Invoking Other Procedures
Synchronous
workflow: | -- Call and wait for result local research = Procedure.run("researcher", { topic = params.topic }) Log.info("Research complete: " .. research.summary)
Asynchronous
workflow: | -- Spawn in background local handle = Procedure.spawn("researcher", { topic = params.topic }) -- Do other work do_something_else() -- Wait for result local research = Procedure.wait(handle)
Parallel Execution
workflow: | -- Spawn multiple procedures local handles = {} for _, topic in ipairs(params.topics) do local handle = Procedure.spawn("researcher", {topic = topic}) table.insert(handles, handle) end -- Wait for all Procedure.wait_all(handles) -- Collect results local results = {} for _, handle in ipairs(handles) do table.insert(results, Procedure.result(handle)) end
Complete Example
name: data_processor version: 1.0.0 params: items: type: array required: true outputs: processed: type: number required: true stages: - processing - complete agents: processor: system_prompt: | Process items one at a time. Progress: {state.processed}/{state.total} tools: - process_item - done workflow: | Stage.set("processing") -- Initialize State.set("processed", 0) State.set("total", #params.items) -- Process each item for i, item in ipairs(params.items) do Processor.turn({inject = "Process: " .. item}) State.increment("processed") end Stage.set("complete") return {processed = State.get("processed")}