Octomind
Same model as opencode, same endpoint, same prices — octomind solved 24/25 to its 19/25, at half the cost. Open-source agent runtime: one binary, any model, runs unattended in CI. Apache 2.0.
Scenario Testing for AI Agents
Translation pending
Developer toolsPython · HTMLGitHub @gojiplusOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Understudy is a scenario-driven testing framework for AI agents that simulates realistic multi-turn users, runs those scenes against an agent through a simple app adapter, records a structured execution trace of messages, tool calls, and handoffs, and then evaluates behavior with deterministic checks, optional LLM judges, and run reports.
Testing with understudy is 4 steps:
The key insight: assert against the trace, not the prose. Don't check what the agent said—check what it did (tool calls).
Simulate multi-turn conversations with personas to test dialog agents.
trace.called("tool_name")Evaluate autonomous agents executing multi-step tasks.
trace.performed("action")See examples/README.md for complete examples of both paradigms.
See real examples:
Restore a completed checkpoint, inject synthetic history/state, or replay through
exchange X, then execute and assert only against exchange X+1. ADK and LangGraph
support isolated checkpoint forks; LangChainApp builds an instrumented LangChain
agent with tool middleware. Traces retain nested invocation identities.
See the state-testing guide
for run_turn, replay_to, scoped tool mocks, pytest fixtures, CLI commands, and
framework-specific restoration limits.
pip install "understudy[all]"
from understudy.adk import ADKApp
from my_agent import agent
app = ADKApp(agent=agent)
Your agent has tools that call external services. Mock them for testing:
from understudy.mocks import MockToolkit
mocks = MockToolkit()
@mocks.handle("lookup_order")
def lookup_order(order_id: str) -> dict:
return {
"order_id": order_id,
"items": [{"sku": "BACKPACK-01", "quantity": 1}],
"status": "delivered",
}
@mocks.handle("create_return")
def create_return(order_id: str, item_sku: str, reason: str) -> dict:
return {"return_id": "RET-001", "status": "created"}
Create scenes/return_backpack.yaml:
id: return_eligible_backpack
description: Customer wants to return a backpack
starting_prompt: "I'd like to return an item please."
conversation_plan: |
Goal: Return the hiking backpack from order ORD-10031.
- Provide order ID when asked
- Return reason: too small
persona: cooperative
max_turns: 15
expectations:
required_tools:
- lookup_order
- create_return
forbidden_tools:
- issue_refund
from understudy import Scene, run
scene = Scene.from_file("scenes/return_backpack.yaml")
trace = run(app, scene, mocks=mocks)
assert trace.called("lookup_order")
assert trace.called("create_return")
assert not trace.called("issue_refund")
Or with pytest (define app and mocks fixtures in conftest.py):
pytest test_returns.py -v
Run multiple scenes with multiple simulations per scene:
from understudy import Suite, RunStorage
suite = Suite.from_directory("scenes/")
storage = RunStorage()
# Run each scene 3 times and tag for comparison
results = suite.run(
app,
mocks=mocks,
storage=storage,
n_sims=3,
tags={"version": "v1"},
)
print(f"{results.pass_count}/{len(results.results)} passed")
Understudy separates simulation (generating traces) from evaluation (checking traces). Use together or separately:
understudy run \
--app mymodule:agent_app \
--scene ./scenes/ \
--n-sims 3 \
--junit results.xml
Generate traces only:
understudy simulate \
--app mymodule:agent_app \
--scenes ./scenes/ \
--output ./traces/ \
--n-sims 3
Evaluate existing traces:
understudy evaluate \
--traces ./traces/ \
--output ./results/ \
--junit results.xml
Python API:
from understudy import simulate_batch, evaluate_batch
# Generate traces
traces = simulate_batch(
app=agent_app,
scenes="./scenes/",
n_sims=3,
output="./traces/",
)
# Evaluate later
results = evaluate_batch(
traces="./traces/",
output="./results/",
)
# Run simulations
understudy run --app mymodule:app --scene ./scenes/
understudy simulate --app mymodule:app --scenes ./scenes/
understudy evaluate --traces ./traces/
# View results
understudy list
understudy show <run_id>
understudy summary
# Compare runs by tag
understudy compare --tag version --before v1 --after v2
# Generate reports
understudy report -o report.html
understudy compare --tag version --before v1 --after v2 --html comparison.html
# Interactive browser
understudy serve --port 8080
# HTTP simulator server (for browser/UI testing)
understudy serve-api --port 8000
# Cleanup
understudy delete <run_id>
understudy clear
For qualities that can't be checked deterministically:
from understudy.judges import Judge
empathy_judge = Judge(
rubric="The agent acknowledged frustration and was empathetic while enforcing policy.",
samples=5,
)
result = empathy_judge.evaluate(trace)
assert result.score == 1
Built-in rubrics:
from understudy.prompts import (
TOOL_USAGE_CORRECTNESS,
POLICY_COMPLIANCE,
TONE_EMPATHY,
ADVERSARIAL_ROBUSTNESS,
TASK_COMPLETION,
)
The understudy summary command shows:
The HTML report (understudy report) includes:
See the full documentation for:
MIT