BDI Cognitive Processes in RDF and Python
How belief-desire-intention agents turn world states into beliefs and plans, and how an ontology can keep an LLM honest.
A belief-desire-intention (BDI) model describes how a rational AI agent moves from what it perceives to what it does. Expressing that model in RDF turns each mental state into data you can query, validate and explain. This guide shows three pieces: how a belief forms from a world state, a Python pipeline that moves between triples and beliefs, and a pattern for grounding an LLM in the ontology.
AI Overview
A BDI agent forms beliefs from world states, derives desires from those beliefs, and commits to intentions and plans. Modeling the process in RDF/Turtle makes it explicit, and a small Python pipeline (triples to beliefs to triples) can run the loop. Logic Augmented Generation then constrains an LLM to produce only valid triples that use the ontology's own predicates.
Key Facts
| Item | Detail |
|---|---|
| Model | Belief-desire-intention (BDI) |
| Formats | RDF/Turtle, Python with rdflib |
| Pattern | Triples to beliefs to triples (T2B2T), plus LAG |
| Difficulty | Intermediate |
| Read time | 3 minutes |
Why It Matters
Agents that act on opaque prompts are hard to audit. A symbolic layer gives you traceable beliefs, justifications and plans, which is the kind of structure that artificial intelligence systems need before they can be trusted with consequential actions.
1. Belief Formation Process (RDF/Turtle)
A BeliefProcess is a temporal event triggered by a WorldState that generates one or more Belief instances.
@prefix bdi: <http://bdi-ontology.org/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
:WorldState_notification a bdi:WorldState ;
rdfs:comment "Push notification: Payment request $250" ;
bdi:triggers :BeliefProcess_BP1 .
:BeliefProcess_BP1 a bdi:BeliefProcess ;
bdi:generates :Belief_payment_request .
:Belief_payment_request a bdi:Belief ;
rdfs:comment "There is a payment request for $250" ;
bdi:isJustifiedBy :Justification_push_notification ;
bdi:hasValidity :TimeInterval_TI1 .
:Justification_push_notification a bdi:Justification ;
rdfs:comment "Source: push notification received at 10:00am" .
2. T2B2T Pipeline (Python + rdflib)
Triples-to-Beliefs-to-Triples: bidirectional flow between RDF knowledge graphs and internal mental states.
from rdflib import Graph, Namespace, URIRef, Literal
from rdflib.namespace import RDF, RDFS
BDI = Namespace("http://bdi-ontology.org/")
# ── Phase 1: Triples → Beliefs ──────────────────────────────────────────────
def triples_to_beliefs(rdf_source: str) -> Graph:
"""Parse external RDF context and construct belief graph."""
g = Graph()
g.parse(data=rdf_source, format="turtle")
belief_graph = Graph()
belief_graph.bind("bdi", BDI)
for world_state in g.subjects(RDF.type, BDI.WorldState):
belief = URIRef(str(world_state).replace("WorldState", "Belief"))
belief_graph.add((belief, RDF.type, BDI.Belief))
belief_graph.add((belief, BDI.refersTo, world_state))
# Carry over rdfs:comment as justification
for comment in g.objects(world_state, RDFS.comment):
belief_graph.add((belief, RDFS.comment, comment))
return belief_graph
# ── Phase 2: BDI Reasoning ───────────────────────────────────────────────────
def deliberate(belief_graph: Graph) -> Graph:
"""Simple deliberation: generate desires and intentions from beliefs."""
intention_graph = Graph()
intention_graph.bind("bdi", BDI)
for belief in belief_graph.subjects(RDF.type, BDI.Belief):
desire = URIRef(str(belief).replace("Belief", "Desire"))
intention = URIRef(str(belief).replace("Belief", "Intention"))
plan = URIRef(str(belief).replace("Belief", "Plan"))
intention_graph.add((desire, RDF.type, BDI.Desire))
intention_graph.add((desire, BDI.isMotivatedBy, belief))
intention_graph.add((intention, RDF.type, BDI.Intention))
intention_graph.add((intention, BDI.fulfils, desire))
intention_graph.add((intention, BDI.specifies, plan))
return intention_graph
# ── Phase 3: Beliefs → Triples ───────────────────────────────────────────────
def beliefs_to_triples(intention_graph: Graph) -> str:
"""Serialize mental states back to RDF output."""
result = Graph()
result.bind("bdi", BDI)
for intention in intention_graph.subjects(RDF.type, BDI.Intention):
plan = next(intention_graph.objects(intention, BDI.specifies), None)
if plan:
execution = URIRef(str(plan).replace("Plan", "PlanExecution"))
outcome = URIRef(str(plan).replace("Plan", "WorldState_complete"))
result.add((execution, RDF.type, BDI.PlanExecution))
result.add((execution, BDI.satisfies, plan))
result.add((execution, BDI.bringsAbout, outcome))
result.add((outcome, RDF.type, BDI.WorldState))
result.add((outcome, RDFS.comment, Literal("Goal achieved")))
return result.serialize(format="turtle")
# ── Full pipeline ─────────────────────────────────────────────────────────────
def run_t2b2t(rdf_input: str) -> str:
beliefs = triples_to_beliefs(rdf_input)
intentions = deliberate(beliefs)
rdf_output = beliefs_to_triples(intentions)
return rdf_output
# Example usage
if __name__ == "__main__":
example_input = """
@prefix bdi: <http://bdi-ontology.org/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
:WorldState_payment a bdi:WorldState ;
rdfs:comment "Payment request for $250 received" .
"""
print(run_t2b2t(example_input))
3. Logic Augmented Generation — LAG (Python + LLM)
Augment LLM outputs with BDI ontology constraints for neuro-symbolic reasoning.
import json
import re
from rdflib import Graph
def serialize_ontology(graph: Graph) -> str:
return graph.serialize(format="turtle")
def extract_rdf_triples(llm_response: str) -> str:
"""Extract turtle block from LLM markdown response."""
match = re.search(r"```(?:turtle|ttl)?\n(.*?)```", llm_response, re.DOTALL)
return match.group(1).strip() if match else llm_response.strip()
def validate_triples(triples: str, ontology_graph: Graph) -> bool:
"""Check if generated triples parse and use known BDI predicates."""
try:
g = Graph()
g.parse(data=triples, format="turtle")
BDI = "http://bdi-ontology.org/"
known = {str(p) for p in ontology_graph.predicates()}
used = {str(p) for p in g.predicates()}
unknown = used - known
if unknown:
print(f"Unknown predicates: {unknown}")
return False
return True
except Exception as e:
print(f"Parse error: {e}")
return False
def augment_llm_with_bdi_ontology(
prompt: str,
ontology_graph: Graph,
llm_call, # callable(prompt: str) -> str
max_retries: int = 3
) -> str:
"""
Send a BDI-grounded prompt to the LLM and validate the output.
Retries with feedback on failure.
"""
ontology_context = serialize_ontology(ontology_graph)
system = (
"You are a BDI cognitive agent. "
"Respond ONLY with valid RDF/Turtle using the ontology below.\n\n"
+ ontology_context
)
for attempt in range(max_retries):
full_prompt = f"{system}\n\nTask: {prompt}"
response = llm_call(full_prompt)
triples = extract_rdf_triples(response)
if validate_triples(triples, ontology_graph):
return triples
# Feedback loop
prompt = (
f"Previous output was invalid. Fix and retry.\n"
f"Invalid output:\n{triples}\n\nOriginal task: {prompt}"
)
raise RuntimeError("LAG failed after max retries")
raise RuntimeError("LAG failed after max retries")
# ── Example with Claude via anthropic SDK ────────────────────────────────────
# import anthropic
# client = anthropic.Anthropic()
#
# def call_claude(prompt: str) -> str:
# msg = client.messages.create(
# model="claude-sonnet-5-5",
# max_tokens=1000,
# messages=[{"role": "user", "content": prompt}]
# )
# return msg.content[0].text
#
# ontology = Graph()
# ontology.parse("bdi_ontology.ttl", format="turtle")
# result = augment_llm_with_bdi_ontology(
# "Model a belief that the meeting room is booked at 10am",
# ontology,
# call_claude
# )
# print(result)
SPARQL — Query active mental states at a point in time
PREFIX bdi: <http://bdi-ontology.org/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT ?mentalState WHERE {
?mentalState bdi:hasValidity ?interval .
?interval bdi:hasStartTime ?start ;
bdi:hasEndTime ?end .
FILTER(
?start <= "2025-01-04T10:00:00"^^xsd:dateTime &&
?end >= "2025-01-04T10:00:00"^^xsd:dateTime
)
}
Competency Questions (SPARQL)
# CQ1 — What beliefs motivated a given desire?
SELECT ?belief WHERE {
:Desire_D1 bdi:isMotivatedBy ?belief .
}
# CQ2 — Which desire does an intention fulfill?
SELECT ?desire WHERE {
:Intention_I1 bdi:fulfils ?desire .
}
# CQ3 — Which process generated a belief?
SELECT ?process WHERE {
?process bdi:generates :Belief_B1 .
}
# CQ4 — Ordered task sequence in a plan
SELECT ?task ?nextTask WHERE {
:Plan_P1 bdi:hasComponent ?task .
OPTIONAL { ?task bdi:precedes ?nextTask }
} ORDER BY ?task
The BDI Grounding Framework
Taken together, the three pieces form one framework. The ontology defines what an agent can believe, desire and intend, the T2B2T pipeline moves data through those states, and LAG keeps a language model inside the same vocabulary. Each layer is independently testable, which is the point.
Limitations
- Illustrative code. The deliberation step maps each belief to a desire and intention by renaming URIs, which shows structure but is not real reasoning.
- Placeholder ontology. The
bdi-ontology.orgnamespace is a stand-in; use your own published ontology. - Validation is shallow. The LAG check verifies that predicates are known, not that the triples are true or consistent.
The Bottom Line
Representing belief, desire and intention as RDF makes an agent's reasoning visible, and validating LLM output against the ontology makes it dependable enough to build on. Start small, keep every stage inspectable, and grow the ontology with the problem.
Related
- The Future of Robotics
- The Future of Robots
- AI agents and machine learning
- More in Artificial Intelligence
References
Explore Related Concepts
Frequently Asked Questions
What is a BDI agent?
A belief-desire-intention agent keeps beliefs about the world, desires it would like to achieve, and intentions it commits to, usually with plans to carry them out. It is a classic architecture for rational software agents.
Why use RDF for agent reasoning?
RDF makes beliefs, justifications and plans into explicit, queryable triples, so an agent's reasoning trace can be validated, shared and explained rather than hidden in code.
What is Logic Augmented Generation?
It is a neuro-symbolic pattern where an LLM is given an ontology as context and must answer in valid RDF, and the output is checked against the ontology and retried with feedback if it fails.
Does this work with any LLM?
Yes. The function takes any callable that sends a prompt and returns text, so you can plug in Claude or another model through its SDK.
Is this production ready?
No. It is a teaching sketch that shows the structure of each stage. A real system needs a proper ontology, richer deliberation, testing and error handling.