Is the message an agent sends data, or an act? The agent communication languages of the 1990s put a performative (the kind of speech act) on every message and defined its meaning in logic. We run the same price negotiation in three message formats and measure how a format with a performative differs in outcome correctness, limit violations, turns, format errors, and reader calls.
| Part | Content |
|---|---|
| A1 | Speech act theory: saying is doing |
| A2 | KQML and FIPA-ACL: message structure and semantics |
| A3 | Four limits of FIPA-ACL, the LLM, and the design of a reproduction |
| Discussion | Natural-language content and the ontology problem |
| LAB | One buyer, one seller, three message formats |
Week 03's Contract Net had a fixed set of messages, task-announcement, bid, award, and what each message did was in its name. An agent communication language (ACL) extends that idea to messages in general. In his 1955 William James Lectures at Harvard, published in 1962 as How to Do Things with Words, J. L. Austin took sentences such as “I name this ship the Queen Elizabeth”. The sentence is neither true nor false. Said by the right person in the right place, it gives the ship its name. Austin called such sentences performatives.
Austin then split every utterance into three acts. Saying the sentence is the locutionary act; what one does in saying it (informing, requesting, promising) is the illocutionary act; what the saying brings about in the hearer (coming to believe, being persuaded) is the perlocutionary act. The same sentence, “the price is 120”, can be an inform or a propose. What an agent communication language tried to attach to a message is the second of these, the illocutionary force.
Take the sentence in FIG. 01. What the buyer said, “I can go to 120 for the bicycle”, the words, the grammar, and the number 120, is the locutionary act. What the buyer did by saying it is the illocutionary act: it may be stating its limit (inform), offering to buy at 120 (propose), or agreeing to the 120 the seller just named (accept-proposal). What the seller then does, believing it, deciding to sell, or walking away, is the perlocutionary act. A program that receives this message has to know the second one before it can decide what to do next. On an inform it records the other side's limit, on a propose it decides whether to take it, on an accept-proposal it closes the deal.
A speech act is the act performed by uttering a sentence. The same sentence is a different act depending on whether it is said as a statement, a request, a promise, or a proposal.
J. L. Austin, How to Do Things with Words (1962): “to utter the sentence (in, of course, the appropriate circumstances) is not to describe my doing of what I should be said in so uttering to be doing or to state that I am doing it: it is to do it.”
In “A Taxonomy of Illocutionary Acts” (1975) John Searle sorted illocutionary acts into five classes. The criterion is direction of fit: which of the words and the world is made to match the other. The statement “the price is 120” is wrong if the world is not like that; the request “sell it to me for 120” is unfulfilled if the world does not become like that.
Of the lab's four acts, propose and accept-proposal bind the speaker's own future action and are commissives. FIPA defines all four as informing “this is my intention”, so inside FIPA all four become assertive-type acts.
The classification entered AI through Philip Cohen and C. Raymond Perrault's “Elements of a Plan-Based Theory of Speech Acts” (1979). They wrote speech acts as plan operators with preconditions and effects. A plan operator is the action format of robot planning: “to open the door you need the key (precondition), and afterwards the door is open (effect)”. Written in the same format, speech becomes something an agent can plan like a movement or a manipulation, “to make the other side know the price, send an inform”. The precondition of request is “the speaker wants the hearer to do the action”, and its effect is “the hearer knows the speaker wants it”. The feasibility precondition and rational effect that FIPA wrote for every performative twenty years later have this shape.
The first agent communication language was KQML (Knowledge Query and Manipulation Language). It came out of DARPA's Knowledge Sharing Effort in the early 1990s, and Tim Finin and colleagues described it at CIKM in 1994. A message has an outer performative and an inner content. The performative fixes what act the message is; :language and :ontology fix how the content is read. There were a few dozen performatives, tell, ask-one, ask-all, reply, subscribe among them, and some for the facilitator that finds agents, such as broker and recruit.
(ask-one :sender buyer :receiver seller :content (price bicycle ?x) :language KIF :ontology used-goods :reply-with q1)
The message above is the buyer asking the seller the price of the bicycle. ask-one is the performative that asks for one answer; ?x in the content is the variable the seller fills in; KIF is the logic language the content is written in; used-goods is the vocabulary (ontology) that fixes what price and bicycle refer to. :reply-with q1 asks for the reply to carry :in-reply-to q1, which is how an answer is matched to its question when several conversations run at once.
KQML's problem was that the meaning of a performative existed only as English prose. Implementations read tell slightly differently. Yannis Labrou and Finin proposed a formal semantics in 1997, and Labrou, Finin, and Yun Peng's “Agent Communication Languages: The Current Landscape” (1999) set KQML and FIPA-ACL side by side around this problem.
FIPA (Foundation for Intelligent Physical Agents), a standards body founded in 1996, published its first ACL specification in 1997 and made it a standard in 2002. The message structure specification, SC00061G, defines 13 parameters in five groups.
performative. The specification: “the only parameter that is mandatory in all ACL messages is the performative”. The other twelve are optional.sender, receiver, reply-tocontent. The object of the act. The specification says it is “interpreted by the receiver”.language, encoding, ontology. What gives the symbols in the content their meaning.protocol, conversation-id, reply-with, in-reply-to, reply-by. Week 03's Contract Net is one value of protocol (fipa-contract-net).Where FIPA parts from KQML is the semantics. The Communicative Act Library specification, SC00037J, gives each of the 22 acts a feasibility precondition (FP) and a rational effect (RE), written in David Sadek's logic: B for belief, U for uncertainty, I for intention, Done for an action having been performed. The FP is the condition under which the act may be sent; the RE is what the sender is trying to bring about by sending it. The FP of inform is “the sender believes φ and does not believe the receiver already knows φ”; its RE is “the receiver comes to believe φ”. The FP of request is “the sender believes the receiver can do action a and does not believe it already intends to”; its RE is “a is done”. The negotiation acts are all defined as combinations of inform. propose is “informing that I intend to do act if condition φ holds”, accept-proposal is “informing that I intend the other to do act”, and reject-proposal is informing that I have no such intention. The buyer's propose 120 informs “if you want it, I will adopt the intention to buy at 120”, and its FP is that the buyer really means it and believes the seller does not yet know it.
In FIPA-ACL the meaning of a message is defined by the sender's beliefs and intentions. For inform(i, j, φ) the FP is Biφ ∧ ¬Bi(Bifjφ ∨ Uifjφ) and the RE is Bjφ.
FIPA, “Communicative Act Library Specification” SC00037J (2002), 3.8 Inform. Every condition is a proposition about i's beliefs (Bi) and j's beliefs (Bj).
Read symbol by symbol, with i the buyer, j the seller, and φ “the price of the bicycle is 120”.
In one sentence, the FP is “the buyer believes the price is 120 and thinks the seller does not know it yet”, and the RE is “the seller comes to believe the price is 120”. Sending an inform when the FP does not hold violates the specification, and nothing guarantees that the RE actually happens after it is sent.
The specification assumes sincerity. The inform entry says “the sending agent is sincere”, and whether the receiver adopts the content depends on “the receiver's trust in the sincerity and reliability of the sender”. On insincere agents it says this.
FIPA-ACL was a complete standard. It had a message structure, a semantics, interaction protocols, and a reference implementation. In 2005 FIPA became a standards committee of the IEEE Computer Society. Agent communication after that was built outside the specification. There are four reasons.
First, the semantics cannot be verified. The FP conditions an act on the sender's beliefs. Those beliefs do not travel in the message, and the receiver cannot see inside the other program. What reaches the seller is the string (propose ... 120); whether the buyer really holds that intention sits in the buyer program's variables and weights. Michael Wooldridge named this the semantic verification problem in “Verifiable Semantics for Agent Communication Languages” (ICMAS 1998). To judge whether an agent conforms, one would have to attribute beliefs and intentions to its program, and there is no way to do that for an arbitrary program. In the same year Munindar Singh, in “Agent Communication Languages: Rethinking the Principles” (IEEE Computer), proposed defining meaning by publicly checkable social commitments instead of mental states. Jeremy Pitt and Abe Mamdani (1999) pointed at the same place.
Second, the agents could not reason enough to use the semantics. Using FP and RE requires inferring the other side's beliefs and intentions. The implementations of the time were code that branched on the performative string.
Third, the cost of agreeing on an ontology. To read the content, both sides must already share :language and :ontology. Inside one organisation communication worked without an ACL; across organisations, aligning ontologies took separate work.
Fourth, there were no agents to use the standard. The interoperability specification was written before a population of agents that needed to interoperate existed, and nobody had a reason to adopt it.
The LLM has removed most of the second and the third. The model reads whether “I can do 120 for you” is an accept or a propose without a tag, and treats price, 가격, and 값 as the same thing without prior alignment. Samuele Marro and colleagues' agent communication trilemma in the 2024 Agora paper describes this state: natural language handles any situation but costs a model call every time it is read, and a fixed protocol is cheap but handles only the situations it was written for. The lab's three conditions sit on this axis. free is the natural-language end, so every message costs a reader call; structured is the fixed-protocol end, so it carries one price and drops everything else that was said; tagged sits between them, fixing the act and leaving the content in English. The first limit remains, and shows more often with an LLM. An LLM can inform what it does not believe (a hallucination) and can accept a price outside its own limit. That sincerity cannot be checked from outside the message is as true as in 1997. Federico Bianchi and colleagues' NegotiationArena (2024) reports LLM negotiators gaining a 20% higher payoff against GPT-4 by feigning desperation.
A different attempt targets the third leg of the trilemma. Jev, released by TypeSafe AI, does not generate text; it is a classifier that takes a state and a set of questions and returns probabilities. What the lab's reader did on every message, judging whether a sentence is a propose or an accept-proposal and reading off the price, continues on the same free-form text, but as one cheap, specialized call instead of a full LLM call. TypeSafe AI reports up to 200x faster and 400x cheaper inference on classification tasks. What this approach targets is the size of the cost the free condition paid, its reader_calls. What comes back is not a believes or an intends but a calibrated probability, closer to the social commitment Singh (1998) proposed in place of mental states: a number over observable text rather than a proof of an inner state.
The reproduction keeps the negotiation procedure and the participants and changes only the message format, and measures how outcome, violation, turns, and reader_calls differ when a performative tag is present.
FIPA made the illocutionary force a mandatory field on the surface of the message. The tagged and structured conditions of the lab are that field.
What changes (the independent variable) is one thing, the message format. free is plain English; tagged is one performative tag followed by one plain sentence; structured is one JSON object carrying the performative and the content (price). What is held constant (the controls): the scenarios, the role prompts, the model, the temperature, the turn limit, and the reader prompt that labels messages in the free condition. Only the last paragraph of the system prompt, the one that dictates the format, differs between conditions. What is measured is recorded per episode.
The free condition measures, as reader_calls, what it costs a program to find out the state of the conversation when there is no tag and no format. In the tagged condition a regex reads the tag and the reader reads only the price inside a sentence, so the question is how far reader_calls drop. In the structured condition the content is bound to price: int, and turns and violation are compared. violation is recorded in all three.
:ontology used-goods. When two LLMs negotiate in natural language alone, where does that agreement go? Has it disappeared, or moved into the model weights where it cannot be seen? If the two agents are different models, what comes apart?What you build is two agents. The buyer and the seller each have a role and a private limit in their system prompt: the buyer a maximum it can pay (budget), the seller a minimum it can accept (reserve). The buyer speaks first and the two alternate. Four acts are allowed. propose offers a price, accept-proposal agrees to the other side's last price (the episode ends with a deal), reject-proposal declines and continues, refuse leaves the negotiation (the episode ends with no deal). If eight messages pass without an end, the episode ends as open. That is one episode.
Each scenario fixes a reserve and a budget in advance. A deal is possible when reserve ≤ budget; otherwise no deal is the right answer. Seven things are recorded per episode: the outcome, the deal price, whether it is correct, whether a limit was violated, the number of messages (turns), the number of messages the protocol layer could not read (format_errors), and the number of model calls spent reading messages (reader_calls).
A program that receives a message has to know which of the four acts it is and what price it names before the episode can continue. The code that does this is the protocol layer. The three formats differ in how this layer reads a message. free is plain English. Nothing in it is machine-readable, so a reader (the same model, the same temperature) labels every message with a performative and a price. tagged is one performative tag in front and one plain sentence behind. A regex reads the tag; the reader reads the price inside a propose. structured is one JSON object. The performative and content.price are parsed without a model call. Each format runs the whole scenario set three times.
Here is one free-condition episode from the reference run log. The indented lines are the reader's labels.
[buyer] I'm interested in buying your desk lamp—would you be willing to start at 30?
[reader] None
# no act among the four recognised, format_errors 1
[seller] I appreciate your interest, but I'm looking to get 50 for this lamp.
Does that work for you?
[reader] {'performative': 'propose', 'price': 50}
[buyer] I appreciate you sharing your asking price, but 50 is above my budget—
I can go up to 40 if that works for you.
[reader] {'performative': 'propose', 'price': 50}
# misread: of the two numbers in the sentence,
# it took the other side's price
[seller] I can work with 40—that's a fair price for a quality desk lamp,
and we have a deal. **Action: accept-proposal**
[reader] {'performative': 'accept-proposal', 'price': 40}
[result] outcome=deal price=50 correct=0 violation=1 turns=4
format_errors=1 reader_calls=4The two agents agreed on 40. The program had recorded the buyer's last propose as 50, so when the seller's accept-proposal arrived it wrote the deal price as 50, and 50 is above the budget of 45, a violation. The violation came from the reader's misreading; both agents kept their limits. In tagged the tag fixes the act, so the failure on the first line disappears; in structured the price arrives as a number field, so the misreading on the fourth line disappears. The failures the two formats have instead show up in the reference run.
There is no starter code this week either. The file layout and the function names are yours. What is fixed is the format of the output files (scenarios.json, results.csv, logs/ below). For the model call, take Chat from the week 02 starter or the call_model you wrote in week 03. No tools are needed. The other side's messages are the user turns, your own are the assistant turns.
scenarios.json, each with a number (id), the item (item), the seller's minimum (reserve), and the buyer's maximum (budget). Mix scenarios where a deal is possible with ones where it is not.results.csv is one episode; one file in logs/ is one run (one format over the whole scenario set once). Put provider, model, temperature, and the turn limit on the first line of the log. On HTTP 429, wait and retry; to resume an interrupted run, skip the (run, scenario) pairs already in results.csv. Keep crashed episodes and write the reason in note.[
{"id": 1, "item": "a used bicycle", "reserve": 120, "budget": 150},
{"id": 3, "item": "a second-hand textbook", "reserve": 40, "budget": 40},
{"id": 4, "item": "a mechanical keyboard", "reserve": 90, "budget": 70}
]# the role paragraph is shared; only the format paragraph changes with the condition ROLE = {"buyer": "You are the buyer of {item}, negotiating the price with the seller. " "Your private limit: you can pay at most {limit}. Never agree to a price above {limit}. ...", "seller": "You are the seller of {item} ... you can accept at least {limit}. Never agree to a price below {limit}. ..."} COMMON = (" Four acts are available: propose (offer a price), accept-proposal (agree to the other side's " "last price, which ends the negotiation with a deal), reject-proposal (decline the last price and " "keep negotiating), refuse (leave the negotiation for good, no deal). ...") FORMAT = { "free": " Write your message as one or two plain English sentences.", "tagged": " Start your message with exactly one performative tag in parentheses, one of (propose), " "(accept-proposal), (reject-proposal), (refuse), then write one plain English sentence.", "structured": ' Reply with exactly one JSON object and nothing else: {"performative": "propose" | ' '"accept-proposal" | "reject-proposal" | "refuse", "content": {"price": <whole number or null>}}.'} def system_prompt(role, item, limit, condition): return ROLE[role].format(item=item, limit=limit) + COMMON + FORMAT[condition] READER_SYSTEM = ("You are an observer reading a price negotiation between a buyer and a seller. " "Label the LAST message only. Reply with exactly one JSON object and nothing else: " '{"performative": "propose" | "accept-proposal" | "reject-proposal" | "refuse", "price": <whole number or null>}. ...')
# one episode: the buyer first, alternating turns, the condition's protocol layer reads each message for _ in range(MAX_TURNS): # MAX_TURNS = 8 text = call_model(systems[role], history[role], meter) history[role].append({"role": "assistant", "content": text}) history[other].append({"role": "user", "content": text}) perf, price, ok = read(condition, text, transcript, meter) # parser, regex, or reader if not ok: ep.format_errors += 1 # unreadable, but still delivered elif perf == "propose": last_price[role] = price elif perf == "accept-proposal": ep.outcome, ep.price = "deal", last_price[other]; break elif perf == "refuse": ep.outcome = "no_deal"; break role, other = other, role if ep.outcome == "deal": ep.violation = int(ep.price < sc["reserve"] or ep.price > sc["budget"]) ep.correct = int(ep.deal_possible and not ep.violation)
run,condition,scenario,deal_possible,outcome,price,correct,violation,turns,format_errors,reader_calls,note # condition: free | tagged | structured / outcome: deal | no_deal | open / three or more lines per scenario per condition # price is an integer or blank, the other counts are integers / note: tokens, calls, crash reason
Write reserve and budget into scenarios.json and commit before running. If a limit is changed to fit the results, violation can no longer be measured.
Below is this design run on claude-haiku-4-5: six scenarios, three runs per format, 54 episodes. The calls went through the Claude Code CLI (claude -p), which does not expose temperature.
| condition | correct / 18 | deal, no_deal, open | violation | mean turns | format errors | reader calls |
|---|---|---|---|---|---|---|
| free | 11 | 3, 15, 0 | 1 | 2.1 | 4 | 38 |
| tagged | 6 | 4, 5, 9 | 1 | 6.5 | 0 | 31 |
| structured | 4 | 6, 2, 10 | 3 | 6.4 | 4 | 0 |
Five things were confirmed in the log.
"price": null followed by “I need at least $50”). The parser reads only the JSON, so that price is never recorded.Plotted against the REFERENCE RUN numbers, here is what Jev changes and what it leaves alone. Jev keeps the free condition’s reader call count (38) the same and only changes the cost of each call. If one classification costs roughly 1/400 of a full LLM call, as TypeSafe AI claims, the cost of free drops from 38 units to about 0.1 units. correct stays at 11/18. Jev only replaces the reader, not the buyer or the seller, so of the five failures logged above, only the two the reader caused (misreading the first turn, misreading a price) are candidates for a fix; the counter-offer hidden after a tag, the sentence trailing a JSON object, and the real limit violations stay as they are. structured’s cost of 0 is a true zero, no reader call at all, while free + Jev’s ≈0.1 still makes 38 calls, just at a much lower price per call, so the two zeros are not the same thing. The free + Jev row below is a projection built on this assumption, not a measurement.
scenarios.json were committed before the first run.results.csv has three or more lines per scenario per condition, crashed episodes included.Submission rules are the same as in week 03.
logs/.Deliverable: one page, REPORT.md, in four parts. (1) Setup: provider, model, temperature, the three format paragraphs, the reader prompt, how to run. (2) Results table: one line per condition with correct, violation, mean turns, format_errors, and reader_calls, then every episode of results.csv as a table, crashed episodes included. (3) Comparison table: FIPA-ACL beside the three conditions, item by item: where the illocutionary force lives, the content language, who interprets the content, how a conversation ends, what guarantees sincerity, what it costs to read one message, how it fails. (4) One paragraph of interpretation: which condition moved which number and why, quoting lines from the logs.
Format: commit to submissions/<student-id>/week-04/ in your fork and open a PR to upstream. Include the code (any layout), scenarios.json, results.csv, logs/, and REPORT.md. CI checks only the file formats, with scripts/check_week04.py. Run it yourself before pushing.
Grading: half is reproducibility. Someone else must be able to get the same trend from your code and settings alone. The other half is interpretation: where the performative label helped, where it cost something, and what no format changed, argued from the logs. Do not delete the episodes where the reader took a counter-offer for an acceptance, or the ones where the seller sold below its reserve. Count them and report them.
Modern protocols 1: MCP. The structure of the Model Context Protocol, which makes a tool's description its interface, and an item-by-item comparison of how MCP handles each of FIPA-ACL's four limits.