Skip to main content

cetana

Auditable BDI agents — a cognitive architecture for the LLM era.

PyPI version License: MIT

Most LLM agents are a prompt loop: state lives in a transcript, "goals" are whatever the last completion implied, and nobody can say why the agent did what it did. The classical answer to exactly this problem is 30 years old: the Belief–Desire–Intention architecture (Bratman 1987; Rao & Georgeff 1995) — explicit mental state, deliberate commitment, coherent behaviour over time.

cetana is BDI rebuilt for LLMs, on one principle:

The architecture decides when to think. The LLM decides what to think.

  • Beliefs are a revisable, sourced, timestamped store — not lines in a prompt
  • Intentions are commitments: the agent does not re-deliberate every cycle, so behaviour is stable, cheap, and predictable
  • The LLM is consulted at exactly two points — option generation and planning — through a provider-agnostic (prompt) -> str callable
  • Plans may only use registered actions; a hallucinated capability is a caught error, not silent improvisation
  • Every cycle is recorded: agent.explain() answers "why did you do that?" with the actual cognitive history

Zero dependencies. Any LLM client. Fully deterministic under test (script the LLM, assert the behaviour).

cetanā (චේතනා) is the Pali term for volition — in Buddhist psychology, the mental factor that directs the mind toward action.

Installation

pip install cetana

Quick start

from cetana import ActionRegistry, BDIAgent, ok, fail

actions = ActionRegistry()

@actions.register("check_fridge", "See what ingredients are available")
def check_fridge():
    return ok({"rice": True, "eggs": 2})

@actions.register("cook", "Cook a named dish")
def cook(dish):
    return ok(f"{dish} ready")

# Any callable (prompt: str) -> str works: Anthropic, OpenAI, local, or a stub.
import anthropic
client = anthropic.Anthropic()
def llm(prompt):
    msg = client.messages.create(
        model="claude-opus-4-8", max_tokens=1024,
        messages=[{"role": "user", "content": prompt}],
    )
    return msg.content[0].text

agent = BDIAgent("Feed the household dinner", llm, actions)
agent.believe("time", "evening")

agent.run()

print(agent.explain())
# cycle 1:
#   options: cook_dinner: Cook rice for dinner; wait: ...
#   committed to: Cook rice for dinner — it's evening and rice is available
#   plan: check_fridge -> cook
#   executed: check_fridge({}) [ok] obs={'rice': True, 'eggs': 2}
# cycle 2:
#   executed: cook({'dish': 'rice'}) [ok] obs='rice ready'
#   intention: succeeded

The cycle

percepts ──> revise beliefs ──> committed? ──yes──> execute next step ──> observe
                                   │ no                                     │
                                   └─> deliberate (LLM) ─> commit intention ┘

Deterministic architecture code does perception, belief revision, commitment bookkeeping, execution, and failure accounting. The LLM fills two cognitive gaps — what is worth pursuing? and how? — and on step failure, a bounded replan (still worth it? new plan or give up?).

Why commitment matters

A prompt-loop agent reconsiders everything every turn — one odd completion and it wanders. A BDI agent deliberates, commits, and then executes until the intention succeeds, fails, or is deliberately dropped. Fewer LLM calls, stabler behaviour, and a meaningful answer to "what is the agent doing right now?": read agent.intention.

Testing your agent

The LLM port makes agents deterministic under test:

class ScriptedLLM:
    def __init__(self, *responses):
        self.responses = list(responses)
    def __call__(self, prompt):
        return self.responses.pop(0)

Script deliberation, assert on agent.trace — no API key, no flakiness. This library's own test suite works exactly this way.

Status & roadmap

Early release (0.1.0) — the core loop, belief store, commitment machinery, trace, and failure handling are complete and tested. Planned: belief decay and confidence-weighted revision, multiple concurrent intentions with priority scheduling, a structured-output deliberation mode, and provider extras. The design is discussed in my Artificial Cognitive Systems series on Medium.

License

MIT — see LICENSE.

Author

Ravindu Pabasara Karunarathna — also the author of tokscope, slnic, and sinhaladate.

Release files for cetana 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cetana 0.1.0
File Size Uploaded
cetana-0.1.0.tar.gz 17.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cetana 0.1.0
File Interpreter ABI Platform
cetana-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 32.8 kB

Release files / cetana-0.1.0.tar.gz

Download URL cetana-0.1.0.tar.gz
Size 17.4 kB
Tags Source
SHA-256 checksum
How to use checksums
144973fb13764e0e9bdb49041d225008e267afe548a4e0a34ece912f117f80b7
BLAKE2b-256 checksum
How to use checksums
4b73772500c3b5dc226c8f887c0ea73eb53614dcf521ecb7b683560c64d84bc4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.2

Release files / cetana-0.1.0-py3-none-any.whl

Download URL cetana-0.1.0-py3-none-any.whl
Size 15.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1954d06f481207112b87c8e1fa040c38f890e62798e04a31f57070973776dcbe
BLAKE2b-256 checksum
How to use checksums
5a6ec1666ebf13a53bca586bdea5e29646f2526e693d1a894242d08298e8f091
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.2

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page