Install
$ agentstack add skill-monocle2ai-skills-monocle-test-gen ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Generate a Monocle fluent test
You are translating a natural-language description into a runnable pytest file that uses monocle_trace_asserter (the fluent API from monocle_test_tools) to (1) run an agent under Monocle instrumentation and (2) assert against the traces it emits.
Workflow
- Load the test plan. Before anything else, look for a
{project}_test_plan.mdfile in the working directory — where{project}is the project/agent name (e.g.lg_travel_agent_test_plan.md), falling back to a plaintest_plan.mdif no project-prefixed file exists. Use Bashls/Glob to find it (e.g.ls *_test_plan.md test_plan.md 2>/dev/null). If found, READ it and treat it as the primary source for the three things below — the agent topology, exact agent/tool names, inputs, and validation scenarios are usually already enumerated there (e.g. in scenario tables grouped by ID). The user's request then selects WHICH scenario(s) from the plan to generate as tests. If no test plan file exists, fall back to extracting everything from the user's description.
- Extract three things from the test plan (preferred) or the user's description. If any is missing or ambiguous, ASK before generating code — guessing here produces tests that compile but assert the wrong thing.
- Agent under test — the Python import path of the agent/crew/workflow object, AND which framework it belongs to (maps to an
agent_typestring — see table below). - Input — the prompt/message/dict passed to the agent. Preserve it verbatim from the test plan / description.
- What to validate — one or more of: a tool was called (with optional input/output substring), an agent was invoked, the final output contains/equals a string, token/duration limit, an Okahu/BERTScore eval result.
- Pick the framework runner string from the agent's framework:
| agent_type string | Framework | |---|---| | google_adk | Google ADK | | langgraph | LangGraph | | crewai | CrewAI | | llamaindex | LlamaIndex | | strands | Strands Agents | | openai | OpenAI Agents | | msagent | Microsoft Agent Framework / Semantic Kernel |
- Decide sync vs async. Default to
async+run_agent_async+@pytest.mark.asynciounless the user explicitly says the agent is synchronous. Almost every framework runner in this repo is async.
- Locate the file. Place new tests under
test_tools/tests/integration/with filenametest__fluent.py. If a file matching the framework + agent already exists there, ADD a newasync def test_...function to it rather than creating a duplicate file. Use Bashls/ Grep to check first.
- Generate the test following the template below. Then run it (or at least
pytest --collect-onlythe file) to confirm it parses, unless the user said not to.
Test template
import pytest
from monocle_test_tools import TraceAssertion
from import
@pytest.mark.asyncio
async def test_(monocle_trace_asserter: TraceAssertion):
# 1. Run the agent under instrumentation
await monocle_trace_asserter.run_agent_async(
, "", ""
)
# 2. Assertions — one chained call per logical check.
# Chaining narrows context: called_agent(A).called_tool(T) means "tool T inside agent A".
monocle_trace_asserter \
.called_agent("") \
.called_tool("") \
.contains_input("") \
.contains_output("")
# 3. Optional: end-to-end output / performance
monocle_trace_asserter.contains_output("")
monocle_trace_asserter.under_token_limit(1_000_000)
For a sync agent, drop @pytest.mark.asyncio, drop await, and call run_agent(...).
Fluent API cheat sheet
Assertion methods return self. Span selectors (called_tool, called_agent, and their does_not_* counterparts) narrow the span context for every subsequent assertion in the chain — that's how you say "this tool, called inside this agent, received this input."
Every assertion accepts an optional message="..." kwarg for a clearer failure string.
Run / load
| Call | Use | |---|---| | await asserter.run_agent_async(agent, agent_type, *args, session_id=None) | Drive an async agent | | asserter.run_agent(agent, agent_type, *args) | Drive a sync agent | | asserter.load_spans(JSONSpanLoader.from_json("traces/x.json")) | Offline: load pre-recorded spans instead of running |
Span selectors (narrow context)
| Call | Use | |---|---| | .called_tool(tool_name, agent_name=None) | Assert tool was called; optionally only inside that agent | | .does_not_call_tool(tool_name, agent_name=None) | Assert tool was NOT called | | .called_agent(agent_name) | Assert agent was invoked | | .does_not_call_agent(agent_name) | Assert agent was NOT invoked |
Input / output content
| Exact | Substring | |---|---| | .has_input(s) / .has_output(s) | .contains_input(s) / .contains_output(s) | | .has_any_input(*s) / .has_any_output(*s) | .contains_any_input(*s) / .contains_any_output(*s) | | .does_not_have_input(s) / .does_not_have_output(s) | .does_not_contain_input(s) / .does_not_contain_output(s) | | .does_not_have_any_input(*s) / .does_not_have_any_output(*s) | .does_not_contain_any_input(*s) / .does_not_contain_any_output(*s) |
with_comparer("similarity" \| "default" \| "bert_score" \| "metric" \| "token_match") overrides equality for has_* (not contains_*).
Performance
| Call | Use | |---|---| | .under_token_limit(N) | Total tokens across selected spans `) to find the registered names. ADK/CrewAI/LlamaIndex agents and tools have explicit string names that must match exactly.
- Don't omit
@pytest.mark.asyncioon async tests. Without it, the coroutine is never awaited and the test silently passes. - Don't chain unrelated checks on the narrowed context. After
called_tool(T), anunder_token_limitapplies only to that tool's spans. If you want a workflow-wide token limit, call it on a freshmonocle_trace_asserterline (not chained). contains_*ignores the configured comparer. It always does substring matching. Usehas_*+with_comparer("similarity")for semantic matching.- Don't write declarative
TestCaselists in a fluent test. Those are the@monocle_testcasestyle — a different skill. The fluent file should contain onlyasync def test_...functions usingmonocle_trace_asserter. - The pytest plugin is auto-loaded. If running outside the repo where
monocle_test_toolsis installed, the user'sconftest.pymust registerpytest_plugins = ["monocle_test_tools.pytest_plugin"]— the repo root andtest_tools/tests/integration/conftest.pyalready do this, so new tests in that directory need no extra setup.
After generating
- Print the file path you wrote to.
- Show the user the suggested command to run it, e.g.
pytest test_tools/tests/integration/test__fluent.py -v. - Do NOT run the test automatically unless the user asked — most agent runs hit live LLM APIs and cost money.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: monocle2ai
- Source: monocle2ai/skills
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.