01Lexical syntax
AgentSpeak borrows its term language from Prolog. Everything is built from a few
kinds of token. The single rule that trips up newcomers: names starting with a
lowercase letter are constants (atoms); names starting with an uppercase letter or
_ are logic variables.
| Element | Examples | Notes |
| Atom / constant | john, red, 'New York' | Lowercase start, or quoted for spaces/caps. |
| Number | 42, -3, 3.14 | Integers and floats. |
| String | "hello world" | Double-quoted text. |
| Variable | X, Temp, _Cost | Uppercase / underscore start. Bound by unification. |
| Anonymous variable | _ | "Don't care" — matches anything, never bound. |
| Structure / predicate | loc(robot, 3, 4) | A functor with arguments — the core data shape. |
| List | [a, b, c], [H|T] | See Lists & strings. |
| Comment | // line, /* block */ | Ignored by the interpreter. |
// constants vs. variables
position(robot, 3, 4). // robot, 3, 4 are constants
+!move(Dir) <- step(Dir). // Dir is a variable, bound when the goal runs
color('dark green'). // quoted atom keeps the space and case
02Beliefs & annotations
The belief base is the agent's view of the world: a set of ground predicates,
each ending in a full stop. Beliefs are added and removed as the agent perceives the
environment, receives messages, or updates its own mental notes.
temperature(22).
location(kitchen).
is_sunny(true).
friend(alice).
friend(bob).
Strong (explicit) negation uses ~. It states that something is
known to be false, which is different from simply not being in the belief base:
~raining. // the agent knows it is NOT raining
~enemy(alice). // alice is explicitly known not to be an enemy
Every belief carries annotations in square brackets that record
where it came from. The interpreter adds source(...) automatically;
you can read or match on it inside a context.
| Annotation | Meaning |
[source(percept)] | Came from sensing the environment. |
[source(self)] | The agent added it itself (a mental note). |
[source(alice)] | Received from another agent named alice. |
colour(box1, red)[source(percept)].
// only trust a belief that did not come from bob
+!act : open(door)[source(S)] & S \== bob <- go_through.
03Rules
Beyond plain facts, the belief base can hold Prolog-style rules that derive
new conclusions on demand. A rule has the form Head :- Body and is used
whenever the head is queried in a plan context.
// facts
parent(tom, bob).
parent(bob, ann).
// rule: a grandparent is the parent of a parent
grandparent(X, Z) :- parent(X, Y) & parent(Y, Z).
// querying the rule inside a context
+!check : grandparent(tom, ann) <- .print("tom is ann's grandparent").
Rules never appear as events — they are not added or removed dynamically — they exist
purely to keep the belief base small and let contexts stay declarative.
04Goals
There are two kinds of goal, distinguished by their prefix:
| Goal | Prefix | What it does |
| Achievement goal | ! | "Make this true." Triggers the search for a plan that brings the state about. |
| Test goal | ? | "Find out the value." Queries the belief base and binds variables — no plan needed if the belief exists. |
!greet_everyone. // initial achievement goal (runs at start-up)
+!report <-
?temperature(T); // test goal: bind T from the belief base
.print("Current temperature is", T).
A goal placed at the top level of the file (e.g. !start.) is an
initial goal: the agent adopts it as soon as it boots.
05Plans
Plans are the agent's know-how. Each one says: when this event happens, and if this
condition holds, do this. The three parts are separated by : and
<-, and the plan ends in a full stop.
+!stay_cool : temperature(T) & T > 25 <- !find_shade; !drink_water.
└──┬───┘ └────────────┬─────────────┘ └────────────┬───────────┘
triggering event context (guard) plan body
- Triggering event — the change that makes this plan relevant (a new goal, a new belief…). See below.
- Context — a logical condition checked against the belief base with unification. If it fails the plan is skipped. Omit it (with the
:) to mean "always applicable". - Body — a sequence of formulas separated by
;, run in order once the plan is selected.
Multiple plans can share a trigger. The interpreter tries them top to bottom
and commits to the first whose context holds — so order your plans from most specific to
most general, like a fall-through:
+!greet(Who) : friend(Who) <- .print("Hey", Who, "!").
+!greet(Who) : true <- .print("Hello", Who). // fallback
06Triggering events
A plan fires on an event — a change in the agent's mental state. The sign
(+/-) is addition or deletion; the optional ! or
? marks a goal rather than a belief.
| Trigger | Fires when… |
+b | Belief b is added (percept, message or mental note). |
-b | Belief b is removed. |
+!g | Achievement goal g is adopted — the common case. |
-!g | Achievement goal g failed — use for recovery/clean-up. |
+?g | Test goal g is posted. |
-?g | Test goal g failed to find an answer. |
// react to perceiving a new temperature reading
+temperature(T) : T > 30 <- .print("It's getting hot!"); !cool_down.
// recover when an achievement goal fails
-!deliver(Pkg) <- .print("Delivery failed, retrying"); !deliver(Pkg).
07The plan body
The body is a ;-separated sequence of formulas. Each kind of
formula is marked by its leading symbol:
| Formula | Symbol | Effect |
| Environment action | act | Acts on the world (e.g. move(left)). In SPADE BDI, a Python action. |
| Achievement subgoal | !g | Pursue g now, in the same intention. |
| New-focus subgoal | !!g | Pursue g as a separate, concurrent intention. |
| Test goal | ?g | Query the belief base, binding variables. |
| Add belief (mental note) | +b | Insert b with source(self). |
| Remove belief | -b | Delete a matching belief. |
| Update belief | -+b | Remove the old b then add the new one (atomic replace). |
| Internal action | .name(…) | Library call that runs inside the agent. See below. |
| Constraint / expression | X = … | Unify or test in-line; fails the plan step if false. |
+!process_order(Item, Qty) : stock(Item, N) & N >= Qty <-
Remaining = N - Qty; // expression: bind Remaining
-+stock(Item, Remaining); // update the stock belief
.print("Shipping", Qty, "x", Item);
pack(Item, Qty); // environment action
!!notify_warehouse(Item); // spin off a concurrent intention
+shipped(Item). // mental note
08Operators
Operators appear in plan contexts and in body expressions.
| Arithmetic | |
+ - * / | Add, subtract, multiply, divide. |
** | Power. |
div mod | Integer division, remainder. |
| Relational | |
> >= < <= | Numeric comparison. |
== | Term equality (no binding). |
\\== | Term inequality. |
= | Unification (binds variables). |
| Logical (context) | |
& | Conjunction — all must hold. |
| | Disjunction — any may hold. |
not | Negation as failure — true when the goal can't be proven. |
+!buy(Item) : price(Item, P) & funds(F) & F >= P & not blacklisted(Item) <-
NewFunds = F - P;
-+funds(NewFunds);
.print("Bought", Item, "- balance:", NewFunds).
Mind the difference: == compares terms as they are, while = unifies them and can bind a variable. not p (negation as failure)
succeeds when p is simply absent; ~p (strong negation) is a
belief that p is positively false.
09Lists & strings
Lists are ordinary terms written between square brackets. The head/tail
pattern [H|T] is the workhorse for recursion: H unifies with the
first element, T with the rest.
[] // empty list
[a, b, c] // three elements
[H | T] // H = a, T = [b, c] (when matched against [a,b,c])
[a, b | Rest] // first two fixed, Rest = the tail
[name(ann), 42, "hi"] // elements can be any term
Recursing over a list with two plans — one for the empty case, one for head/tail:
+!sum([], 0).
+!sum([H|T], S) <- !sum(T, Rest); S = H + Rest.
+!greet_all([]).
+!greet_all([H|T]) <- .print("Hello", H); !greet_all(T).
Practical example combining both: take a list of usernames, build a message string,
and notify each user one by one.
// users waiting for a reminder
pending_users([alice, bob, carol]).
+!notify_pending <-
?pending_users(Users);
!notify_list(Users, "[SPADE] reminder for ").
+!notify_list([], _Prefix) <-
.print("All reminders sent").
+!notify_list([User|Rest], Prefix) <-
.concat(Prefix, User, Msg); // string concatenation
.length(Rest, Left); // list length helper
.print(Msg, "(left:", Left, ")");
.send(User, tell, reminder_ready(User));
!notify_list(Rest, Prefix).
Common list and string utilities live in the internal-action library — a few of the most
used ones:
| Action | Purpose |
.length(L, N) | Bind N to the number of elements (or string length). |
.member(X, L) | Succeeds if X is in L; can enumerate. |
.nth(I, L, X) | Bind X to the element at index I. |
.concat(A, B, C) | Concatenate lists or strings into C. |
.sort(L, S) · .reverse(L, R) | Order or reverse a list. |
.max(L, M) · .min(L, M) | Largest / smallest element. |
.substring(Sub, Str) | Test or locate a substring. |
10Internal actions
Internal actions start with a dot and run inside the agent (unlike
environment actions, which affect the world). Standard-library actions use a bare
.name; user-defined ones live in a library namespace like
my_lib.action. They never generate events and can fail, which fails the plan step.
| Category | Actions |
| Output | .print, .println |
| Introspection | .my_name(N), .findall(T, Q, L), .count(Q, N), .setof |
| Goals & plans | .desire, .intend, .drop_goal, .drop_intention, .succeed_goal, .fail_goal, .add_plan |
| Lists & strings | .length, .member, .nth, .sort, .concat, .reverse, .max, .min |
| Communication | .send, .broadcast |
| Time & control | .wait, .at, .random(X), .date, .time |
| Agent lifecycle | .create_agent, .kill_agent, .stopMAS |
+!summarise <-
.findall(Name, friend(Name), Friends); // collect all matches
.length(Friends, N);
.print("I have", N, "friends:", Friends).
The exact set of available internal actions depends on the interpreter. The
browser playground above implements a didactic
subset (.print, .send, beliefs & subgoals); SPADE BDI
ships the fuller python-agentspeak standard library and lets you add your
own in Python (next section).
11Communication
Agents talk by sending each other speech acts with
.send(Receiver, Performative, Content). The KQML-style performative says what
the message means — and the receiver's mind updates automatically. In SPADE BDI
these travel as real XMPP messages between distributed agents.
| Performative | Effect on the receiver |
tell | Adds Content to its belief base, tagged source(sender). |
untell | Removes that belief. |
achieve | Adopts Content as a new goal !Content. |
unachieve | Drops that goal. |
askOne / askAll | Requests one / every answer that unifies with Content. |
tellHow / untellHow | Shares / retracts a plan (know-how). |
// inform another agent of a fact
.send(robot1, tell, location(kitchen));
// delegate a goal to a coordinator
.send(coordinator, achieve, patrol_area(zone_a));
// the receiver reacts to the freshly-told belief
+location(Room)[source(Ag)] <-
.print(Ag, "reports something is in the", Room).
12AgentSpeak in SPADE
With the SPADE BDI plugin you put your program in an
.asl file and hand it to a BDIAgent. From Python you can seed,
read and react to beliefs while the reasoning cycle runs over XMPP.
import spade
from spade_bdi.bdi import BDIAgent
async def main():
agent = BDIAgent("agent@server", "password", "agent.asl")
await agent.start()
# seed and query the belief base from Python
agent.set_belief("temperature", 30)
print(agent.get_belief("temperature"))
if __name__ == "__main__":
spade.run(main())
To expose new internal actions or functions to AgentSpeak, subclass
BDIAgent and override add_custom_actions:
import agentspeak
from spade_bdi.bdi import BDIAgent
class MyAgent(BDIAgent):
def add_custom_actions(self, actions):
# a function usable in expressions: X = .square(5)
@actions.add_function(".square", (int,))
def square(x):
return x * x
# an action usable in a plan body: .greet("world")
@actions.add(".greet", 1)
def _greet(agent, term, intention):
who = agentspeak.grounded(term.args[0], intention.scope)
print("Hello", who)
yield
From AgentSpeak these look just like built-ins:
.greet("world") in a plan body, or N = .square(5) in a context.
Custom actions are the bridge between declarative reasoning and the full Python ecosystem.