Set Up Python and uv
beginnerInstall Python 3.12+ and the uv package manager. One-time setup before your first OpenSymbolicAI tutorial.
Hands-on tutorials for building reliable AI agents with OpenSymbolicAI. From your first 5-minute agent to production-ready systems. Each track pairs a written walkthrough with a project repo and a video.
Install Python 3.12+ and the uv package manager. One-time setup before your first OpenSymbolicAI tutorial.
Install Ollama, pull a coding model, and confirm it responds. One-time setup before your first OpenSymbolicAI tutorial.
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.
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.
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.
Run Track 1's agent on a different Ollama model by changing one string. The agent, primitives, and task stay identical.
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.
The gate that makes a method callable by the planner. A plain method is invisible; @primitive registers it.
The flag that signals whether a primitive modifies state. Put a read-only primitive next to a mutating one to see the difference.
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.
How parameter and return types reach the model. The planner never sees your method bodies, only the signatures your annotations produce.
The Python the model wrote is in result.plan. Print it to see exactly what the model produced before your primitives ran it.
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.
What a run cost in time and tokens, in result.metrics. Planning is slow and uses tokens. Executing is fast and uses none.
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.
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.
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.
Teach the planner with a worked example via @decomposition. The decorator tags a method body with the natural-language intent it answers.
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.
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).
PlanExecute only allows assignment statements. When a task needs a loop, switch the base class to DesignExecute. Everything else stays the same.
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.
The two remaining DesignExecuteConfig knobs: a whole-plan call cap and a toggle for break/continue in loops.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Wrap SymPy as three primitives and let the LLM pick the right operation. SymPy returns exact symbolic answers, not floating-point approximations.
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.
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.
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.
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.
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.
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.
Subclass LLM to connect to any provider. Add a cache so repeated prompts are served from memory instead of making another network call.
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.
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.
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.
An agent reads a Python file, rewrites it per an instruction, saves it in place, and runs it to confirm the output is unchanged.
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.
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.
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.