All tutorials
Track 4·Foundation

What @primitive actually does

The gate that makes a method callable by the planner. A plain method is invisible; @primitive registers it.

beginner5 min
Video coming soon
Browse this tutorial's folder in tutorials-pygithub.com/OpenSymbolicAI/tutorials-py/tree/main/04-primitive-gate

Before you start

@primitive is a gate. It registers a method so the planner is allowed to call it. A plain method with no decorator is invisible to the planner. This track shows that gate by taking one method through before and after.

The task is the kind of thing a language model gets wrong on its own: counting the letters in a word. With a primitive doing the counting, the answer is exact.

1. Start without the decorator#

Here is a one-method agent. The method is ordinary Python, no @primitive.

python
# counter.py
from opensymbolicai.blueprints import PlanExecute


class LetterCounter(PlanExecute):
    def count_letter(self, word: str, letter: str) -> int:
        """Count how many times `letter` appears in `word`."""
        return word.count(letter)

Run it and it fails:

python
result = agent.run("how many times does the letter r appear in strawberry")
print(result.success)  # False
print(result.error)    # Function '...' is not allowed. Only primitive
                       # methods and allowed builtins can be called.

The planner had no registered primitive to call. It tried to reach for count_letter and execution refused it.

2. Add the decorator#

Mark the same method with @primitive. Nothing else changes.

python
# counter.py
from opensymbolicai.blueprints import PlanExecute
from opensymbolicai.core import primitive


class LetterCounter(PlanExecute):
    @primitive(read_only=True)
    def count_letter(self, word: str, letter: str) -> int:
        """Count how many times `letter` appears in `word`."""
        return word.count(letter)

3. Run it#

python
# main.py
from counter import LetterCounter
from opensymbolicai.llm import LLMConfig

llm = LLMConfig(provider="ollama", model="qwen2.5-coder:7b")
agent = LetterCounter(llm=llm)

result = agent.run("how many times does the letter r appear in strawberry")
print(result.result)  # 3
bash
uv run main.py

Output:

text
3

Takeaway#

The planner may only call registered primitives. @primitive is what moves a method from "invisible" to "callable."