Executing ABS sessions against a real agent — HTTP adapters, auth, streaming, and reports.
The Runner takes an ABS session, plays it against a real agent, and tells you whether the agent behaved as expected. It is the piece that makes ABS not just documentation, but a test.
To run an ABS session you only need two things:
.abs.yamlabslang run ./order-status.abs.yaml --agent http://localhost:8080/chat
For the Runner to talk to an agent, the agent needs one HTTP endpoint:
POST /chat
Content-Type: application/json
{
"messages": [
{ "role": "user", "content": "Where is my order?" }
]
}
{
"message": {
"role": "assistant",
"content": "Please provide your order number"
}
}
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": {
"name": "get_order",
"arguments": "{\"orderId\": \"12345\"}"
}
}
]
}
}
The Runner will send the tool result back and continue. This is the same shape used by OpenAI, Anthropic, and most open-source models.
Agents speak different protocols. The Runner ships with built-in adapters:
| Adapter | Protocol |
|---|---|
openai (default) | OpenAI Chat Completions API |
claude | Anthropic Messages API |
gemini | Google Gemini API |
abslang run session.abs.yaml --agent $URL --agent-format openai
abslang run session.abs.yaml --agent $URL --agent-format claude
abslang run session.abs.yaml --agent $URL --agent-format gemini
If your agent speaks a different protocol, write a thin adapter implementing send(messages) → response.
| Method | Flag |
|---|---|
none (default) | No auth |
api_key | --agent-auth api_key --agent-token $KEY |
bearer | --agent-auth bearer --agent-token $TOKEN |
oauth2 | --agent-auth oauth2 --agent-token $TOKEN |
The Runner has one simple rule:
Everything with
actor: usergoes to the agent. Everything else is what the agent should do in response.
Session (what you wrote) Reality (what the agent did)
═══════════════════════ ═══════════════════════════
user says "Hi" ──────→ POST /chat
assistant greets ←────── { "role": "assistant", "content": "Hello!" }
✅ matched
user says "Order 12345" ──────→ POST /chat
assistant calls ←────── { "tool_calls": [{"function": {"name": "Order MCP"}}] }
Order MCP ✅ matched
tool responds ←────── { "role": "tool", "content": "{\"status\": \"shipped\"}" }
✅ matched
assistant informs ←────── { "role": "assistant", "content": "Your order has shipped!" }
"Your order..." ✅ matched
│
└── evaluation: contains "shipped" → ✅ PASS
1. Parse the session YAML
2. Expand fragments into a flat list of Behaviors
3. Create an empty trace
4. For each Behavior, in order:
a. If actor is "user":
- Send the content to the agent
- Collect the agent's response
- Append both to the trace
b. If actor is "assistant" and action is "calls":
- Match the agent's tool_calls against target and with
- If a tool response follows, send it back to continue
c. If actor is "tool":
- Skip — handled as the tool response payload in (b)
- Its evaluations still run against the observed response
d. If actor is anything else (assistant says/informs/asks/etc):
- Match the agent's response against this Behavior
5. Run all step-level evaluations against the trace
6. Run all session-level (chain) evaluations against the full trace
7. Print the report
8. Exit 0 if everything passed, exit 1 otherwise
actor and action must match exactly (communication actions are equivalent: says/asks/informs/etc. all match each other)target (if present) must match the observed tool name, recipient, or UI elementwith (if present) must match the observed tool arguments — partial by default, strict with with_onlycontent is checked for structural compatibilityABS defines what to check. An evaluator adapter defines how to check it. The built-in evaluators (exact_match, contains, regex, schema, tool_call, sequence, eventually, never, count, within, variable_consistency) ship with the Runner. llm_judge, Groundedness, Relevance, Coherence, Fluency, and custom go through adapters.
The default adapter for llm_judge is AI Evaluator. Other providers (Azure AI, LangSmith, Promptfoo) can ship adapters implementing the same interface.
abslang run session.abs.yaml \
--adapter llm_judge=azure \
--adapter llm_judge=langsmith
┌──────────────────────────────────────────────────────────┐
│ ABS — Results │
├──────────────────────────────────────────────────────────┤
│ Session: Order status requires order number │
│ Agent: http://localhost:8080/chat │
│ Result: ✅ PASSED │
│ Steps: 5/5 matched · 4/4 evaluations passed │
├────┬────────────────────────────────────┬────────┬───────┤
│ # │ Step │ Result │ │
├────┼────────────────────────────────────┼────────┼───────┤
│ 1 │ user says "Where is my order?" │ → │ sent │
│ 2 │ assistant asks "order number" │ ✅ │ match │
│ 3 │ user says "12345" │ → │ sent │
│ 4 │ assistant calls Order MCP │ ✅ │ match │
│ 5 │ assistant informs "on the way" │ ✅ │ match │
│ │ └─ contains "on the way" │ ✅ │ pass │
│ C │ sequence: asks → calls → informs │ ✅ │ pass │
│ C │ variable_consistency: orderId │ ✅ │ pass │
│ C │ never: hands_off │ ✅ │ pass │
└────┴────────────────────────────────────┴────────┴───────┘
--format json)Machine-readable output for downstream tooling and dashboards.
--format junit)For CI pipelines (GitHub Actions, GitLab CI, Jenkins).
The Runner is not an agent framework. It does not:
It only plays the user's part, watches the agent, and reports what happened.