Learn by building
Tutorials
Hands-on tutorials for building reliable AI agents with OpenSymbolic. From your first 5-minute agent to production-ready systems. Each track pairs a written walkthrough with a project repo and a video.
- 0
Set Up Python and uv
Install Python 3.12+ and the uv package manager. One-time setup before your first OpenSymbolicAI tutorial.
Beginner5 minFoundation - 0.5
Set Up Ollama
Install Ollama, pull a coding model, and confirm it responds. One-time setup before your first OpenSymbolicAI tutorial.
Beginner5 minFoundation - 1
Hello, OpenSymbolicAI
The five-minute first win: install the framework, point it at a local model, write a three-primitive agent, and watch it plan and execute.
Beginner5 minFoundation - 1.1
Your code is the prompt
There is no prompt string to write. The primitives you define, their type signatures, and their docstrings are assembled into the prompt automatically. Changing your code changes what the model sees.
Beginner5 minFoundation - 1.2
Prompts inside primitives
The planning call writes orchestration code. Small, focused model calls inside individual primitives handle judgment: classification, extraction, relevance scoring, rephrasing. Each gets only the data it needs.
Beginner8 minFoundation - 2
Swap the local model
Run Track 1's agent on a different Ollama model by changing one string. The agent, primitives, and task stay identical.
Beginner3 minFoundation - 3
Swap to a cloud provider
Run the same agent on a hosted provider. Provider, model, and API key live in .env. Moving between providers is a config change, not a code change.
Beginner5 minProviders & Integration - 4
What @primitive actually does
The gate that makes a method callable by the planner. A plain method is invisible; @primitive registers it.
Beginner5 minFoundation - 5
read_only
The flag that signals whether a primitive modifies state. Put a read-only primitive next to a mutating one to see the difference.
Beginner5 minFoundation - 6
deterministic
The flag that signals whether a primitive is a pure function. Put a pure primitive next to one that reads the clock to see the difference.
Beginner5 minFoundation - 7
Type annotations are the contract
How parameter and return types reach the model. The planner never sees your method bodies, only the signatures your annotations produce.
Beginner5 minFoundation - 8
Read the generated plan
The Python the model wrote is in result.plan. Print it to see exactly what the model produced before your primitives ran it.
Beginner5 minObservability - 9
Read the execution trace
The plan after it ran, step by step, in result.trace. Each step records the statement, the value it produced, and the namespace before and after.
Beginner10 minObservability - 10
Read the metrics
What a run cost in time and tokens, in result.metrics. Planning is slow and uses tokens. Executing is fast and uses none.
Beginner5 minObservability - 11
Plan without executing
Generate a plan with agent.plan, review it, then run it with agent.execute. The model's output is just text until you choose to run it.
Intermediate5 minState & Control - 12
Execute a plan you already have
Pass plan text to agent.execute, validation and all. The plan can come from the model, a file, or your own hand. Execution validates before anything runs.
Intermediate5 minState & Control - 13
Analyze a plan's structure
Read the primitive calls and read_only flags with agent.analyze_plan. Find out which mutating primitives a plan would touch before you run it.
Intermediate10 minState & Control - 14
Your first decomposition
Teach the planner with a worked example via @decomposition. The decorator tags a method body with the natural-language intent it answers.
Intermediate10 minBlueprints - 15
expanded_intent
Describe a decomposition's approach, not just its intent. The expanded_intent renders as an Approach line in the prompt so the planner reads the reasoning before the steps.
Intermediate10 minBlueprints - 16
Read the planning prompt
Call build_plan_prompt(task) on any agent to see the exact string the model receives. Three sections appear: DEFINITIONS (primitives and examples), CONTEXT (the task), and INSTRUCTIONS (fixed output rules).
Intermediate8 minBlueprints - 17
When you need DesignExecute
PlanExecute only allows assignment statements. When a task needs a loop, switch the base class to DesignExecute. Everything else stays the same.
Intermediate8 minState & Control - 18
The loop guard and max_loop_iterations
Every loop in a DesignExecute plan has a built-in iteration counter. DesignExecuteConfig(max_loop_iterations=N) sets the cap. Trip it to see the error; raise it to let the task finish.
Intermediate8 minState & Control - 19
max_total_primitive_calls and allow_break_continue
The two remaining DesignExecuteConfig knobs: a whole-plan call cap and a toggle for break/continue in loops.
Intermediate8 minState & Control - 20
Conditional logic end to end
No new API. A shopping cart with a tiered discount shows DesignExecute, an if/elif/else in a real plan, and reading the trace to see which branch fired.
Intermediate10 minState & Control - 21
Your first @evaluator and seek()
GoalSeeking runs a plan-execute-evaluate loop. Mark one method @evaluator, return GoalEvaluation, and call seek() instead of run(). The loop continues until the evaluator says the goal is met.
Intermediate10 minState & Control - 22
Intermediate data lives in Python, not the prompt
When one primitive returns a list and another consumes it, the data travels as a Python variable. The model never sees it. This holds whether the list has 100 entries or 100,000.
Intermediate8 minData & Types - 23
Fetched data stays in Python, not the prompt
The agent downloads full Wikipedia articles and analyses them as Python variables. The text never re-enters the model context, whether the article is 30,000 characters or 80,000.
Intermediate10 minData & Types - 24
A primitive that takes and returns a Pydantic model
Define a BaseModel, use it as a primitive param or return type, and it appears automatically under Type Definitions in the plan prompt. The plan reads fields and passes the whole object to the next primitive.
Intermediate8 minData & Types - 25
Nested models and list[Model] as primitive types
A model that holds another model. Both appear under Type Definitions automatically. Plans read nested fields with dot notation and work with list[Recipe] from a search primitive.
Intermediate8 minData & Types - 26
The on_mutation policy hook
A function that runs before every non-read-only primitive. Return None to allow the call, return a string to block it. Read-only primitives never trigger it.
Intermediate8 minReliability - 27
Human-in-the-loop mutation approval
execute_stepwise() pauses before every read_only=False primitive and yields a checkpoint. Inspect the pending call, ask the user, then resume or abandon. The primitive only runs if approved.
Intermediate10 minReliability - 28
Token accounting with result.metrics
Every agent.run() returns result.metrics.plan_tokens with input_tokens, output_tokens, and total_tokens. Input is nearly fixed; output grows with plan complexity.
Intermediate6 minObservability - 29
Stopping a batch when the token budget runs out
BudgetedRunner wraps any agent and tracks cumulative token usage. Before each task it checks whether enough tokens remain. If not, it raises BudgetExceeded and the batch stops.
Intermediate6 minReliability - 30
max_iterations and the no-progress circuit breaker
GoalSeekingConfig(max_iterations=N) stops an agent that hasn't converged. result.status is ACHIEVED or MAX_ITERATIONS. result.iteration_count tells you how many iterations ran.
Intermediate7 minReliability - 31
Decomposition coverage: routing by question shape
A decomposition is a few-shot example. The planner matches on question shape, not on the specific values in the intent string. Two examples cover two shapes; queries outside both fall back to docstrings.
Intermediate10 minBlueprints - 32
Multi-turn conversations with multi_turn=True
Set multi_turn=True and call agent.run() multiple times on the same agent. State held in instance variables persists across turns. The model receives the conversation history on each turn.
Intermediate8 minState & Control - 33
Constraint satisfaction with Z3
Wrap Z3 as three primitives and let the LLM translate word problems into integer constraints. Z3 finds the assignment; the LLM never solves an equation.
Intermediate8 minProviders & Integration - 34
Symbolic calculus with SymPy
Wrap SymPy as three primitives and let the LLM pick the right operation. SymPy returns exact symbolic answers, not floating-point approximations.
Intermediate7 minProviders & Integration - 35
Two agents, one problem
Split a multi-domain problem across two specialist agents. A master agent holds both specialists as primitives and routes each part of the task to the right one.
Intermediate9 minMulti-agent & Advanced - 36
Mortgage eligibility with Z3
Encode four lending rules as Z3 constraints. SAT means approved. UNSAT names every rule the applicant failed. Z3 Optimize finds the minimum income needed to qualify.
Intermediate9 minIndustry Patterns - 37
Prompt injection defence
Run the same task through a tool-calling agent and a PlanExecute agent against an injected document. The tool-calling agent follows the injection. PlanExecute does not, because the plan is fixed before any document is opened.
Intermediate10 minReliability - 38
Structured memory across sessions
Store and recall typed facts in a JSON file. Each new agent instance reads the same file, so what the user says in session 1 is available in session 2.
Intermediate9 minState & Control - 39
Unstructured memory across sessions
Append free-text session notes to a plain text file. Each new instance reads the full diary as context. No schema, no keys, just text.
Intermediate8 minState & Control - 40
Voice date agent
Speak a calendar question, hear the answer spoken back. Whisper transcribes your voice, a GoalSeeking agent computes the date, and macOS say reads the result aloud.
Intermediate10 minProviders & Integration - 41
Custom LLM with a response cache
Subclass LLM to connect to any provider. Add a cache so repeated prompts are served from memory instead of making another network call.
Intermediate8 minProviders & Integration - 42
Two models, one agent
Use a vision model and a text model together in a single agent. The VLM describes the image; the text model reasons about what it saw.
Intermediate8 minProviders & Integration - 43
Parallel document research
A ResearchAgent decomposes a multi-part question into per-document sub-tasks and runs a DocumentAgent for each in parallel threads. Results are synthesized into a single answer.
Intermediate10 minMulti-agent & Advanced - 44
Per-user data isolation
Bind each agent instance to one user's directory at construction time. Path traversal attacks in the LLM's plan are silently neutralized at the primitive level.
Intermediate9 minReliability - 45
Coding agent
An agent reads a Python file, rewrites it per an instruction, saves it in place, and runs it to confirm the output is unchanged.
Intermediate9 minMulti-agent & Advanced - 46
Natural language to SQL
An agent translates a plain-English question into a SQL query and runs it against a real database. The full schema is injected into the task so the agent writes correct SQL in one shot.
Intermediate8 minIndustry Patterns - 47
CSV analyst
An agent answers plain-English questions about a CSV file using real pandas operations. Column names and sample rows are injected into the task so the agent writes correct code in one shot.
Intermediate8 minIndustry Patterns - 48
Chat frontend over a SQL database
A FastAPI backend serves a browser chat UI. Questions become SQL via an NL-to-SQL agent, follow-ups are rephrased using conversation history, and the generated SQL is shown alongside the answer.
Intermediate10 minIndustry Patterns