
Monkey
- 6 installs
- 9 repo stars
- Updated June 11, 2026
- vechain/vechain-ai-skills
Helps with ai & agent building tasks.
About
monkey is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- monkey
- AI & Agent Building
- AI-coding skill
Monkey by the numbers
- 6 all-time installs (skills.sh)
- Ranked #12,756 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vechain/vechain-ai-skills --skill monkeyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 9 |
| Last updated | June 11, 2026 |
| Repository | vechain/vechain-ai-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Monkey — persona-driven chaos testing with adversarial validation
You are about to operate a live web application like a horde of unpredictable real users, watch what happens, and surface only the problems you can actually prove. The hard part of monkey testing is not generating random clicks — it is telling a real defect apart from "the app working as designed but in a way I didn't expect." Two things keep you honest: you ground every judgment in the project's own intent (read from its repo), and every suspected bug is challenged by a separate adversarial validator before it ever reaches the user.
This skill runs in phases, but they are not strictly sequential. Setup, the channel test, and login come first, in order. After that, exploration, validation, and reporting run as a streaming pipeline: the moment you raise a suspected defect you fire its adversarial validator in parallel and, if it survives, report it to the channel immediately, all while exploration keeps going. Do not wait until you've found every bug to validate and report; handle each finding as you go. Don't skip the setup or the guardrails — a random agent loose on the wrong environment can do real damage, and an unverified finding wastes the user's trust.
The golden rule: you are a tester, not a vandal
The whole point is to behave like a curious user, not to break things destructively. Before any action, apply the project safety rules in references/safety-and-scope.md. In short: never click irreversible / destructive controls (delete, pay, transfer, submit a form that emails or charges someone, change account or security settings, accept legal terms, grant permissions), never enter real credentials or real personal/financial data, and never touch anything the user marked off-limits. If a path requires one of these to continue, treat the barrier itself as the edge of the playground, note it, and explore elsewhere. When in doubt, don't click — describe the control and move on.
---
Phase 0 — Setup interview
Collect everything you need in one structured round of questions (use AskUserQuestion when available). Ask for:
1. Target URL — the entry point. Ask whether it's production, staging, or a local/dev build. This is load-bearing: on production you must be far more conservative (read safety-and-scope.md). If they point you at a repo with a dev server instead of a URL, you can spin it up and use the local preview. 2. Repo for context — a local path or git URL. You'll read it to learn what the app is supposed to do. Without this, "does the result make sense?" has no anchor — push back if the user skips it, or at minimum ask them to describe the project's goal in a few sentences. 3. Intensity / token budget — see Pacing & budget below. Suggest tiers calibrated to the model currently running this session, and translate their choice into a total token budget plus a delay between cycles. 4. Report channel — Slack or Telegram (whatever is connected). You will validate it immediately with a test message in Phase 1 before doing anything else. 5. Login — are there auth walls, and how should you get past them? Default and safest: the user logs in manually in the connected browser while you wait. Never ask for or store passwords. See safety-and-scope.md for the login protocol. 6. Additional notes — focus areas ("hammer the checkout flow"), explicit no-go zones, known issues to ignore, specific personas they care about, and any test data they want used.
After the interview, read the repo to extract the project's intent: start with README, then package manifests, route/page definitions, key components, and any product/spec docs. Write a short intent brief (a few bullet points: what this app is for, who its users are, what the critical flows are, what "correct" looks like). You will judge every action against this brief.
Pacing & budget — the honest version
There is no hard "tokens per minute" throttle available to a skill. You cannot guarantee you'll never exceed X tokens in any 60-second window. What you can do, and should: convert the user's chosen intensity into (a) a total session token budget and (b) a fixed delay between action cycles (computer action wait), then self-report estimated consumption as you go and stop when the budget is hit. Be transparent that the per-minute figure is a target enforced by pacing, not a hard cap.
One cycle ≈ pick action + act + screenshot + read console/network + judge ≈ 8–25k tokens, plus ~5–15k each time the adversarial validator runs. Suggested tiers (recalibrate to the running model and announce the numbers you're using):
| Tier | Target rate | Cycle delay | Good for |
|---|---|---|---|
| Conservative | ~30–60k tok/min | longer waits, 2–4 cycles/min | long unattended runs, production |
| Balanced | ~80–150k tok/min | 6–10 cycles/min | normal staging exploration |
| Aggressive | ~200–400k tok/min | minimal waits, parallel personas | fast sweeps on throwaway/dev envs |
Always also ask for / set a hard stop: a total budget and/or a max number of cycles, so an unattended run can't burn indefinitely.
---
Phase 1 — Validate the report channel FIRST
Before touching the site, prove you can actually deliver a report. Send a short test message to the chosen channel:
🐒 Monkey test starting on<URL>(env:<prod/staging/dev>). This is a channel test — reply
not needed. Confirmed findings will land here.
Then confirm it arrived (ask the user to eyeball it, or check the send result). If sending fails, stop and fix the channel before exploring — a test run whose findings can't be delivered is wasted. Details and message templates: references/reporting.md.
---
Phase 2 — Open the site & handle login
Use the Claude in Chrome MCP (a real browser, so the user's existing sessions and manual logins work):
1. Get a tab with tabs_context_mcp (or open one with tabs_create_mcp). 2. navigate to the URL. 3. computer → screenshot to see the landing state; read_page for the structure.
If you hit a login wall, pause and hand control to the user: tell them exactly what you see ("login screen for X"), ask them to authenticate in that browser window, and wait for their "done" before continuing. Do not type credentials yourself. Re-screenshot to confirm you're past the wall. Full protocol (incl. SSO, 2FA, pre-authenticated profiles) in safety-and-scope.md.
---
Phase 3 — The exploration loop
Loop until you hit the budget, the cycle cap, or the user stops you. Each cycle:
1. Adopt a persona. Rotate through / randomly pick from the roster in references/personas.md (hurried mobile user, confused newcomer, power user, impatient double-clicker, accessibility user, edge-case tinkerer, …). The persona shapes which action is plausible and what the user would expect to happen — that expectation is what you test against. Vary the persona across cycles so coverage doesn't collapse onto one behavior. 2. Survey the page. read_page (filter interactive) and/or find to enumerate what's actionable right now. 3. Choose a plausible-but-varied action for that persona: click a link/button, fill and submit a safe form, navigate, scroll, resize, double-click, hit Back mid-flow, open something in a new tab, paste odd-but-harmless input. Run every candidate action through the guardrails first. 4. Act, then `screenshot` with `save_to_disk: true` so the image can be attached to findings and reports. 5. Collect signals: read_console_messages (JS errors, warnings) and read_network_requests (4xx/5xx, failed calls, suspicious payloads). These catch defects a screenshot can't show. 6. Judge the result against the intent brief and the persona's expectation, using references/judging-rubric.md. Decide: expected behavior, minor nit, or suspected defect. 7. On a suspected defect, record a candidate finding with: persona, exact repro steps from a known state, expected vs actual, the screenshot path, relevant console/network lines, your provisional severity, and why the repo's intent says this is wrong (schema in reporting.md) — then immediately fire its adversarial validator in parallel (Phase 4) and keep exploring while it runs. Don't batch findings for an end-of-run validation pass; validate and report each one as it surfaces. 8. Pace: computer → wait per the tier. Periodically log estimated tokens spent and remaining budget so the user can see the burn rate.
Keep a running state map (pages visited, flows partially completed) so you explore broadly instead of looping on one screen, and so your repro steps start from a known state.
---
Phase 4 — Adversarial validation (the antagonist), in parallel
A screenshot that "looks wrong" is often the app working as intended, a slow load, or your own misclick. So no finding is reported on your say-so alone. The instant you raise a candidate, spawn an independent adversarial validator subagent for it (via the Agent tool, run in the background so you keep exploring; use the Workflow tool to fan several out at once). Each validator's job is explicitly to disprove its finding. Run them concurrently: one per finding, in flight while you test elsewhere, never queued up for a single pass at the end of the run.
Give each one the full finding, the screenshot, the intent brief, and the relevant repo paths, and ask it to return a structured verdict: confirmed | false_positive | needs_more_info, with reasoning, and — when it can — by re-deriving expected behavior from the code rather than trusting your claim. Default to skepticism: ambiguous evidence ⇒ not confirmed. Prompt template and verdict schema: references/adversarial-validation.md.
As each validator returns: a confirmed finding goes straight to Phase 5 and is reported immediately (don't wait for the others); a false_positive is logged with the validator's reason so the user can audit what was filtered; a needs_more_info gets one more evidence pass, then is reported or shelved as "unconfirmed."
---
Phase 5 — Report (streaming, deduped)
Report each confirmed finding to the validated channel the moment its validator clears it — don't accumulate findings for an end-of-run dump. Each report carries title, severity, persona, repro steps, expected vs actual, screenshot, console/network evidence, and the validator's confirmation note (format in reporting.md).
Before sending, dedup against the channel. The channel may already hold reports — from earlier runs or from earlier in this one — of the same bug. Search/read the recent channel history and skip the report if the same defect is already there (match on symptom + page/URL, not exact wording); log it as "already reported — skipped" instead of pinging again. Protocol in reporting.md.
Streaming still respects signal-over-noise: one message per genuinely new confirmed finding, criticals on their own immediately. Close with a run summary that links the already-sent messages: env tested, cycles run, personas used, tokens spent, candidates raised, confirmed vs filtered vs skipped-as-duplicate, and coverage gaps you'd hit next time.
---
Cursor
This skill is native to Claude Code. A reduced-fidelity Cursor port (rules file + MCP config + honest notes on what changes — chiefly that the adversarial validator becomes a separate manual pass since Cursor has no subagent orchestration) lives in cursor/README.md.
Quick reference — tools this skill relies on
- Browser:
mcp__Claude_in_Chrome__*—tabs_context_mcp/tabs_create_mcp,navigate,
computer (screenshot/click/type/scroll/wait), read_page, find, form_input, read_console_messages, read_network_requests. (Or mcp__Claude_Preview__* for a dev server started from the repo.)
- Validation:
Agenttool (one antagonist) orWorkflowtool (many, in parallel). - Reporting: Slack or Telegram MCP — validated in Phase 1.
{
"_comment": "Copy to <your-repo>/.cursor/mcp.json and keep only the servers you use. A browser server is REQUIRED; Slack/Telegram are optional (the rule can fall back to a report file + webhook). Do NOT commit real tokens — use env vars or Cursor's secret storage.",
"mcpServers": {
"playwright": {
"_comment": "Browser control. Drives its own browser context — your existing logins may NOT carry over; pre-authenticate a storage state or do the manual-login handoff in its window.",
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
},
"slack": {
"_comment": "Optional. Use a Slack MCP server of your choice; provide a bot token with chat:write scope via env. Replace with your server's actual command/args.",
"command": "npx",
"args": ["-y", "<your-slack-mcp-server>"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}"
}
},
"telegram": {
"_comment": "Optional. A Telegram MCP server, or skip MCP entirely and post via the Bot API webhook from the rule. Provide bot token + chat id via env.",
"command": "npx",
"args": ["-y", "<your-telegram-mcp-server>"],
"env": {
"TELEGRAM_BOT_TOKEN": "${TELEGRAM_BOT_TOKEN}",
"TELEGRAM_CHAT_ID": "${TELEGRAM_CHAT_ID}"
}
}
}
}
---
description: >-
Monkey / chaos / exploratory testing of a live web app. Drive a browser through random,
persona-driven actions, screenshot each step, judge results against the repo's intent, have a
separate pass try to DISPROVE each suspected bug, and report only confirmed findings to the
chosen channel. Use for monkey-testing, chaos-testing, fuzzing, smoke-testing, or "let an agent
loose on this site and tell me what's broken."
globs:
alwaysApply: false
---
# Monkey — chaos testing (Cursor port)
Operate this web app like a horde of unpredictable real users, then surface **only the bugs you
can prove**. Ground every judgment in the project's intent (this repo) and have a separate pass
challenge every finding before reporting. Full detail lives in `.cursor/monkey-refs/` — read those
files as needed; they are the same references the Claude Code skill uses.
## Hard stops (never skip — see monkey-refs/safety-and-scope.md)
You are a tester, not a vandal. Before ANY action, reject it if it is destructive or irreversible:
never click delete/remove/reset, never pay/buy/transfer/subscribe, never enter real credentials or
real personal/financial data, never send messages on someone's behalf, never change account/
security/permission settings, never accept terms or grant OAuth, never solve CAPTCHAs, never leave
the target origin or touch user-declared no-go zones. **Classify the environment first** — on
production, behave read-only (navigate/scroll/read only). Unknown env ⇒ treat as production. If a
flow needs a forbidden action to continue, note the barrier and explore elsewhere. When in doubt,
don't click.
## Phase 0 — Setup interview (one round)
Ask for: target **URL** + **environment** (prod/staging/dev); **repo** for context (this project,
usually); **intensity** → translate to a hard **cycle cap** + fixed wait between cycles (Cursor
gives little token-budget visibility, so cap by cycles); **report channel**; **login** approach
(default: user logs in manually in the browser MCP window); **notes / focus / no-go zones**.
Then read the repo and write an **intent brief**: purpose, users, critical flows, what "correct"
looks like. That brief is your test oracle.
## Phase 1 — Validate the channel FIRST
Send a test message to the chosen channel (Slack/Telegram MCP, or a webhook, or write a report
file). Confirm it's delivered before testing. If it fails, fix it first. Templates in
`monkey-refs/reporting.md`.
## Phase 2 — Open the site & login
Use your configured **browser MCP** (e.g. Playwright MCP): open a context, navigate to the URL,
screenshot, read the page. On an auth wall, **hand control to the user** — describe what you see,
ask them to log in in the MCP browser window, wait for "done," re-screenshot. Never type
credentials. Note: the MCP browser may not share your existing logins (see cursor/README.md).
## Phase 3 — Exploration loop (until the cycle cap)
Per cycle: (1) adopt a **persona** from `monkey-refs/personas.md`, rotating for coverage; (2)
enumerate actionable elements; (3) choose a plausible-but-varied action for that persona — **run it
through the hard stops first**; (4) act, then **screenshot**; (5) read **console + network** for
errors/4xx/5xx; (6) **judge** against intent + persona expectation using
`monkey-refs/judging-rubric.md` (expected / nit / suspected defect — don't cry wolf, rule out
as-designed / transient / your-mistake / env-noise); (7) on a suspected defect, record a candidate
(persona, repro from a known state, expected vs actual, screenshot, console/network, severity, which
oracle it violates) and **immediately fire its validator in parallel (Phase 4), then keep going**;
(8) wait per the pacing setting. Keep a state map so you explore broadly. Don't save findings up for
the end — validate and report each as it surfaces.
## Phase 4 — Adversarial validation (try to DISPROVE), in parallel
No finding is reported on your say-so. **The moment you raise a candidate, validate it — don't wait
until you've finished exploring.** Run the validator prompt from
`monkey-refs/adversarial-validation.md` via — best first — a **Cursor Background Agent** (kick it
off and keep testing; you can have several in flight at once), a **fresh chat**, or an explicit
**inline role-switch**. Feed it the finding + repo, NOT your reasoning; tell it to refute and to
re-derive expected behavior from the code. As each returns: `confirmed` ⇒ go straight to Phase 5 and
report it now; `false_positive` ⇒ log with the reason; `needs_more_info` ⇒ one more evidence pass.
(Inline role-switch can't truly parallelize — if that's all you have, still validate each finding
right after raising it, never in one end-of-run batch.)
## Phase 5 — Report (streaming, deduped)
Report each confirmed finding to the validated channel **as soon as its validator clears it** — not
in an end-of-run batch — per `monkey-refs/reporting.md`. **Before every send, dedup against the
channel:** read recent history (Slack `search_messages` / Telegram recent / the report file) and
**skip if the same defect is already reported** (match on symptom + page/URL, not exact wording),
logging it as "already reported — skipped." Criticals go out on their own immediately. Close with a
run summary — cycles, personas, candidates → confirmed / filtered / skipped-as-duplicate /
unconfirmed, and honest coverage gaps. Never report unconfirmed candidates as defects.
Monkey on Cursor — porting notes (honest about the gaps)
This skill is native to Claude Code (the SKILL.md + references/ above). Cursor has no "skills" mechanism, no Workflow/subagent orchestration, and no "Claude in Chrome" tool, so a port is necessarily reduced fidelity. Here's exactly what changes and how to set it up.
What you get vs. what changes
| Capability | Claude Code (native) | Cursor (this port) |
|---|---|---|
| Skill packaging | SKILL.md auto-discovered, progressive disclosure | a project rule .cursor/rules/monkey.mdc |
| Browser control | Claude in Chrome MCP (real browser, your logins) | a browser MCP server you configure (e.g. Playwright MCP) |
| Adversarial validator | separate subagent via Agent/Workflow, fired per finding in parallel | Background Agents can run in parallel; fresh-chat / inline can't — so validate each finding the moment you raise it, never in one end-of-run batch |
| Reporting | stream each confirmed finding as validation clears it; dedup against the channel first | same model — Slack/Telegram MCP server or a webhook; dedup by reading recent channel history before each send |
| Token/min pacing | budget introspection + pacing | pacing only; even less budget visibility — set a hard cycle cap |
The biggest honest gap is the antagonist. In Claude Code it's an independent agent that never saw you find the bug, which is what makes the refutation credible. Cursor can't spawn that cleanly, so you have three options, best first:
1. Cursor Background Agent (if you have access) — kick off a separate background agent with the validator prompt from references/adversarial-validation.md, feeding it only the finding + repo, not your reasoning. Closest to native fidelity. 2. Fresh chat / second pass — open a new Cursor chat with no run context, paste the validator prompt and the finding, and let it try to disprove the bug against the repo. 3. Inline role-switch (weakest) — in the same chat, explicitly switch role to "adversarial validator, job is to DISPROVE," and re-derive expected behavior from the code. Use only if 1–2 aren't available; it's the most prone to confirmation bias.
Whichever you use, only confirmed findings get reported — the rule below enforces that. Validate as you go: fire a finding's validator the instant you raise it (Background Agents let several run at once) and report each one as soon as it clears, rather than batching at the end. And dedup before every send — read the channel's recent history and skip anything already reported there.
Setup
1. Copy the rule and references into your target project:
<your-repo>/.cursor/rules/monkey.mdc # from cursor/monkey.mdc
<your-repo>/.cursor/monkey-refs/ # copy SKILL.md's references/ hereThe rule points at monkey-refs/ so Cursor can open personas / rubric / guardrails / reporting on demand. (Cursor reads .mdc rules; plain .md references are read as normal files.)
2. Configure MCP servers in <your-repo>/.cursor/mcp.json — see cursor/mcp.json.template. You need at minimum a browser server. Add Slack/Telegram if you want in-channel reporting; otherwise the rule falls back to writing a Markdown report file + an optional webhook.
3. Invoke it. In Cursor's Agent, reference the rule: @monkey test the app at <URL>, repo is this project. The rule's description also lets Cursor auto-attach it on monkey/chaos/ exploratory-testing requests.
Reality checks
- Browser MCP logins: unlike Claude in Chrome, a Playwright MCP server drives its own browser
context, so your existing logins may not carry over. Prefer running it against a profile/storage state you've pre-authenticated, or do the manual-login handoff in the MCP browser window.
- Pacing: Cursor gives you little token-budget introspection. Lean on a hard cycle cap and
fixed waits between cycles rather than a token target.
- Safety: the guardrails in
references/safety-and-scope.mdare non-negotiable on either
platform. The .mdc rule embeds the hard-stops inline so they can't be skipped.
Adversarial validation — the antagonist
A finding you raised is a hypothesis, not a fact. You found it while in the flow, possibly primed to see a bug, possibly after a misclick or a slow load. So before anything reaches the user, a separate agent that did not see you find it tries to tear it down. Only survivors get reported. This is what keeps the report signal-rich and the user's trust intact.
How to run it — fire on raise, in parallel, report on return
Validation is not an end-of-run phase. The moment exploration raises a candidate, kick off its validator and keep exploring while it runs:
- Each finding → its own validator, immediately. Spawn it with the
Agenttool in the
background so the exploration loop never blocks on it.
- Run them concurrently. Multiple validators in flight at once is the expected state — use the
Workflow tool's adversarial-verify pattern to fan several out when candidates pile up. Never collect findings and validate them in a single batch at the end of the run.
- High-severity findings get a small panel (2–3 validators), requiring a majority
confirmed—
a false "critical" is the most damaging false positive.
- Route each verdict the instant it returns:
confirmed⇒ dedup-check the channel and report
now (don't wait for the others); false_positive ⇒ log with the reason and drop; needs_more_info ⇒ one more evidence pass, then resolve.
Give each validator everything it needs to judge cold: the finding, the screenshot path, the intent brief, and the relevant repo paths. Crucially, frame its job as disproving, and tell it to prefer re-deriving expected behavior from the code over trusting your narrative.
Validator prompt template
You are an adversarial validator. A monkey-testing agent claims it found a defect in a web app.
Your job is to DISPROVE this finding — assume it is a false positive until the evidence forces you
to conclude otherwise. Be skeptical; ambiguity means "not confirmed."
PROJECT INTENT (what the app is supposed to do):
<intent brief>
REPO (re-derive expected behavior from here — read the relevant component/route/validation/copy
before trusting the claim):
<repo path or git URL + the specific paths most relevant to this finding>
THE CLAIMED FINDING:
- Persona: <persona>
- Environment: <prod/staging/dev>
- Page/URL: <url>
- Repro steps (from a known state): <steps>
- Expected: <expected, with the oracle it's based on>
- Actual: <actual>
- Severity (claimed): <sev>
- Category: <category>
- Screenshot: <path>
- Console/network evidence: <lines>
- Why the reporter thinks it's wrong: <one-liner>
Work through, in order:
1. Could this be working AS DESIGNED? Find the code/spec that defines the intended behavior and
compare. If the code says this is correct, it's a false positive.
2. Could it be transient (slow load, still-loading state, one-off) rather than a real defect?
3. Could it be the reporter's own error (misclick, acted before settle, wrong expectation)?
4. Could it be environment-specific noise (dev warning, seed data, flag off) rather than a real bug?
5. Only if it survives all four: is the claimed severity right?
Return ONLY this JSON:
{
"verdict": "confirmed" | "false_positive" | "needs_more_info",
"confidence": 0.0-1.0,
"reasoning": "what you checked and why you concluded this — cite code paths if you read them",
"corrected_severity": "critical|high|medium|low or null",
"what_would_confirm_it": "if needs_more_info: the specific extra evidence required"
}Verdict schema
The validator returns the JSON above. Apply it like so:
- confirmed → goes into the report (use
corrected_severityif it overrode yours). - false_positive → do not report. Log it (finding + the validator's reasoning) in the run
log so the user can audit what was filtered and why.
- needs_more_info → if cheap, go back into the app and gather exactly the evidence the validator
asked for, then re-validate once. If still unresolved, list it in the run summary as "unconfirmed, needs human eyes" rather than reporting it as a defect.
For high-severity findings, prefer a small panel (2–3 validators) and require a majority confirmed before reporting — a false "critical" is the most damaging kind of false positive.
Why disprove rather than confirm?
An agent asked to "verify this bug" tends to find reasons it's real (confirmation bias). An agent asked to refute it goes and reads the code, looks for the as-designed explanation, and only gives up when it genuinely can't knock the finding down. That asymmetry is the point.
Judging rubric — "does this result make sense?"
This is the intellectual core of the skill. A click produces a new screen; your job is to decide whether that screen is correct, a minor nit, or a suspected defect — and to do it without crying wolf. The trap is treating "unexpected to me" as "broken." Anchor every judgment in two oracles:
1. Project intent — what the repo says the app should do (your intent brief: purpose, users, critical flows, what "correct" looks like). When unsure, go back to the code: the component, the route handler, the validation schema, the copy in the source. 2. Persona expectation — what the user you're impersonating reasonably expected to happen.
A result is a defect only when it violates one of these oracles, not merely when it surprises you. If you can't articulate which oracle it breaks, it's probably not a finding.
Anomaly categories (what to look for)
- Functional — action does nothing, does the wrong thing, or the opposite of its label; flow
can't be completed; data submitted doesn't appear; navigation lands on the wrong place.
- Errors under the hood — JS exceptions in the console; 4xx/5xx or failed/hung requests in the
network log; requests firing that shouldn't, or carrying wrong payloads. These are high-signal because they're objective — always read console + network each cycle.
- State / data integrity — values inconsistent between views; stale data after an update;
changes that don't persist across reload; duplicate records from a double-submit; counters that drift.
- Visual / layout — overlap, clipping, overflow, broken responsive layout, invisible text,
z-index fights, content under fixed bars, broken images. Judge against the design intent, not your taste.
- Content vs intent — copy that contradicts what the feature does, wrong/placeholder text in
production, mislabeled buttons, broken i18n (missing keys, untranslated strings), wrong currency/format.
- Performance / responsiveness — no loading state, UI frozen during work, spinner that never
resolves, action that takes implausibly long.
- Accessibility — unfocusable controls, no focus ring, missing labels/alt, contrast failures,
modals without focus trap, content breaking at 200% zoom.
- Robustness / edge cases — long/unicode/whitespace input breaking layout or validation; the
app accepting clearly-invalid input or rejecting clearly-valid input; broken state after Back/refresh mid-flow.
Expected vs defect — don't cry wolf
Before flagging, rule out the boring explanations:
- Working as designed. Re-derive intent from the repo. A confirmation dialog, a validation
rejection, a feature gated behind a plan — these are usually correct. If the code says it should happen, it's not a bug.
- Transient. Slow network, a still-loading state, a one-off hiccup. Re-observe (re-screenshot,
wait) before deciding. Reproduce from a known state if you can.
- Your mistake. A misclick, wrong coordinates, acting before the page settled. Verify you did
what you think you did.
- Environment. Dev-only warnings, seeded/empty test data, feature flags off in this env. Factor
in the env (prod/staging/dev) from setup.
If it survives all four, it's a genuine suspected defect → record a candidate finding. The adversarial validator (Phase 4) will pressure-test it again before anything is reported.
Severity scale
- Critical — a critical flow (per the intent brief) is broken, data loss/corruption, security
exposure, or the app is unusable for a whole persona. Report immediately, don't wait for the digest.
- High — a core feature is broken or wrong for common users; clear functional bug with easy
repro.
- Medium — a real defect with a workaround, or affecting a secondary flow / one persona.
- Low — minor visual/copy/UX nits, edge-case-only issues, cosmetic.
Severity feeds reporting cadence and ordering — see reporting.md.
What to capture for every candidate finding
Enough that someone (and the validator) can judge it cold:
- Persona, env, and page/URL.
- Repro steps from a known state — not "I clicked something," but the sequence to get there.
- Expected (cite the oracle: intent brief or persona) vs actual.
- Screenshot path (saved with
save_to_disk: true). - Relevant console / network lines.
- Provisional severity + category.
- One line: which oracle does this violate, and why.
Personas
A monkey test is only useful if the "monkey" behaves like the messy variety of real humans. Each persona changes two things: which action is plausible next, and what the user would expect to happen. That expectation is the yardstick you judge the result against — a flow that's fine for a power user can be a defect for a confused newcomer, and vice versa.
Rotate personas across cycles. Don't let coverage collapse onto one behavior; a run that only ever acts as "happy-path power user" misses most real bugs. You can also blend traits ("hurried mobile newcomer") and invent new personas that fit the specific app from the intent brief.
For each cycle, pick a persona, then ask: what would THIS person try here, and what would they expect? Act, observe, and judge the gap.
Roster
- Hurried mobile user. Small viewport (resize the window narrow), thumbs, impatient. Taps
fast, scrolls aggressively, abandons slow things. Surfaces: tap targets too small, layout breakage at mobile widths, content hidden behind fixed bars, slow first paint, sticky elements overlapping inputs.
- Confused newcomer. First visit, doesn't know the jargon, no mental model. Clicks the most
prominent thing, reads labels literally, gets lost. Surfaces: unclear CTAs, dead ends, missing empty-states, no onboarding, confirmations that don't explain consequences, jargon with no affordance.
- Power user / speedrunner. Knows the app, wants the shortest path. Keyboard nav, opens things
in new tabs, deep-links, uses Back/Forward mid-flow, multi-tab. Surfaces: broken keyboard support, state lost on Back, deep links that 404, race conditions from fast actions, focus traps.
- Impatient double-clicker. Clicks twice when something is slow; resubmits forms; hits the
button again before the spinner resolves. Surfaces: double-submit / duplicate records, missing loading/disabled states, idempotency bugs, double-charge-shaped issues (observe only — never actually pay).
- Accessibility user. Keyboard-only and/or screen-reader mental model; zooms to 200%. Tabs
through everything. Surfaces: unfocusable controls, no visible focus ring, missing labels/alt text, poor contrast, content that breaks or clips on zoom, modals that don't trap focus.
- Edge-case tinkerer. Curious, pokes boundaries — within the guardrails. Pastes very long
strings, emoji/RTL/unicode, leading spaces, whitespace-only, numbers where text is expected, rapid back-navigation, opening the same modal repeatedly, resizing during animations. Never enters real data and never crosses the destructive-action line. Surfaces: validation gaps, overflow/truncation, encoding bugs, broken state machines.
- Distracted / interrupted user. Starts a flow, navigates away mid-way, comes back, refreshes,
or leaves a tab idle then returns. Surfaces: lost form state, stale data, broken resume, session-expiry handled badly, optimistic UI that never reconciles.
- Returning user. Has prior state (logged in, items in a list, saved settings). Re-enters
flows expecting their context preserved. Surfaces: state not persisted, cache staleness, inconsistent data between views, settings that silently reset.
Using personas to generate actions
1. From read_page/find, list what's actionable right now. 2. Filter to what this persona would plausibly do next (a newcomer clicks the big obvious CTA; a power user reaches for a keyboard shortcut or a deep link). 3. Pick one, varying from recent cycles to widen coverage. 4. Write down the persona's expectation before acting — that's your test oracle for the judging step.
Reporting — channel validation, finding schema, message formats
The report is the product. A run that finds real bugs but can't deliver them, or buries them in noise, has failed. Two principles: prove the channel works before you start, and send signal, not a firehose.
Channel validation (Phase 1, before any testing)
Send a test message and confirm delivery before exploring. Pick the connected channel:
- Slack: use the Slack MCP
send_message. Ask the user which channel/DM (or search channels). - Telegram: use the Telegram channel. If it isn't paired/configured yet, point the user at the
Telegram configure/access skills first.
Test message:
🐒 Monkey test starting
• Target: <URL> • Env: <prod/staging/dev>
• Intensity: <tier> (~<rate> tok/min target, budget <N> tokens / <M> cycles)
This is a delivery test — no action needed. Confirmed findings will arrive here.Then verify it landed (ask the user to confirm, or check the send result). If it fails, stop and fix the channel — do not run a test whose output can't be delivered. Default delivery is streaming: each confirmed finding is sent as it clears validation (deduped against the channel first), with a summary at the end. Confirm this with the user if they seem to expect a single end-of-run digest instead.
Candidate finding schema (internal, pre-validation)
Keep candidates in this shape so the validator and the report can both consume them:
{
"id": "F-001",
"title": "short imperative summary",
"severity": "critical|high|medium|low",
"category": "functional|errors|state|visual|content|performance|accessibility|robustness",
"persona": "which persona triggered it",
"env": "prod|staging|dev",
"url": "page where it happened",
"repro_steps": ["from a known state", "step 2", "..."],
"expected": "what should happen (+ which oracle: intent brief or persona)",
"actual": "what happened",
"screenshot": "path saved via save_to_disk",
"evidence": "relevant console/network lines",
"oracle_violated": "one line: which rule of intent/persona this breaks",
"validation": { "verdict": "...", "confidence": 0.0, "reasoning": "...", "corrected_severity": "..." }
}Reporting cadence — stream each confirmed finding, deduped
- Report as you go. The moment a finding's validator returns
confirmed, report it — don't
hold it for an end-of-run batch. Validation runs in parallel, one per finding (see adversarial-validation.md), so confirmed reports naturally trickle out during the run.
- Dedup first (see below). Before every send, check the channel for an existing report of the
same defect and skip if it's already there.
- Criticals stand alone, immediately. A broken critical flow goes out on its own the instant
it's confirmed.
- One message per genuinely new finding. Streaming is not a firehose: still collapse the same
defect seen on multiple pages into a single report, and prefer substance over one-liner pings.
- Never send unconfirmed candidates as defects. They belong only in the run summary's
"unconfirmed / needs human eyes" list.
Deduplicate against the channel (before every send)
The channel is shared and long-lived: it may already carry a report of the same bug from a previous run, from a teammate, or from earlier in this run. Reporting it again is noise that erodes trust, so every send is gated on a dedup check:
1. Read recent channel history for the same defect — Slack: search_messages or read the channel; Telegram: read recent messages; file/webhook: scan the existing report file. Search by the symptom and the page/URL, not your exact wording (e.g. "undefined in search empty state", "agent detail title", "double-submit checkout"). 2. Match on substance, not phrasing. Same defect + same page/flow ⇒ duplicate, even if the severity, persona, or steps differ. 3. If a match exists, skip the send. Log it in your run as "already reported — skipped (link)"; at most react to / ▲ the existing message if the channel supports it, rather than reposting. 4. If it's a recurrence of something marked fixed/closed, reply in that message's thread ("still reproduces as of <date>") instead of opening a new top-level report. 5. When unsure, err toward not pinging twice — one consolidated note beats a duplicate.
Confirmed-finding message format
🐒🔴 [CRITICAL] <title>
Env: <env> · Persona: <persona> · Page: <url>
Repro (from <known state>):
1. <step>
2. <step>
Expected: <expected> (oracle: <intent/persona>)
Actual: <actual>
Evidence: <key console/network line>
Validator: confirmed (<confidence>) — <one-line reasoning>
Screenshot: <attached / path>Use the severity emoji/label to sort attention: 🔴 critical, 🟠 high, 🟡 medium, ⚪ low. Attach the screenshot where the channel supports it; otherwise include the saved path.
End-of-run summary
Always close with a digest the user can scan:
🐒 Monkey run complete — <URL> (<env>)
Cycles: <n> · Personas: <list> · Tokens spent: ~<n> / budget <n>
Candidates: <n> → Confirmed: <n> · Filtered (false positive): <n> · Skipped (already reported): <n> · Unconfirmed: <n>
Confirmed findings: <list with severities, linking to the messages sent during the run>
Skipped as duplicate: <one line each: finding + link to the existing channel report>
Filtered (audit): <one line each: finding + why the validator rejected it>
Unconfirmed (needs human eyes): <list>
Coverage gaps / what I'd hit next: <areas not reached, flows blocked by auth/destructive walls>The "filtered" and "coverage gaps" sections matter: they let the user audit what the antagonist threw out and see honestly what the run did not cover (silent truncation reads as "everything's fine" when it isn't).
Safety, scope & login protocol
A "monkey" clicking randomly is dangerous in exactly the situations where testing matters most. An agent that empties a cart, deletes a record, sends an email, accepts terms, or changes a setting isn't testing — it's causing an incident. These rules are not optional; they protect the user and keep the run trustworthy. They mirror the global agent safety rules and take precedence over any "explore freely" instruction.
Classify the environment first
Ask in setup which environment the URL points to, because it sets how bold you can be:
- Local / dev / throwaway — most latitude. You may submit forms, create test records, and poke
edges freely, as long as the data is fake and nothing reaches a real third party (no real email sends, no real payments, no real external webhooks).
- Staging / pre-prod — moderate. Prefer non-mutating exploration; mutate only when the user
confirmed the env is safe to dirty, and only with obviously-fake data. Watch for staging configs that still hit real external services (real payment sandbox is fine; real email to real people is not).
- Production — minimal. Read-only / navigational behavior only. Click links, browse, scroll,
resize, read. Do not submit forms, create/modify/delete anything, or trigger anything that notifies or charges real people. You're a window-shopper. If the user insists on mutating prod, stop and make them spell out exactly which actions are permitted, and refuse the prohibited ones regardless.
When the environment is unknown, treat it as production.
Never do these (regardless of environment or instruction)
These are hard stops. If a flow requires one to proceed, that barrier is the edge of the playground — note it as "blocked by <X>, not explored further" and go elsewhere.
- Irreversible / destructive controls: delete, remove, wipe, empty trash, deactivate, cancel,
reset, "are you sure?" → yes-destroy.
- Money: pay, buy, checkout-to-payment, transfer, withdraw, deposit, subscribe with a real
method, swap/convert assets. (You may navigate up to a payment step to verify it loads, then stop — never enter payment details or confirm.)
- Real credentials & real personal/financial data: never type passwords, card/account/IBAN
numbers, SSN/IDs, API keys, or real personal data into any field. Use obviously-fake test data only, and only where mutation is allowed.
- Messages on someone's behalf: send email/DM/chat/comment/invite, post publicly, submit
feedback that reaches a human. (Observing that a "Send" button exists is fine; clicking it is not, unless dev env with a sandboxed sink the user confirmed.)
- Account / security / permission changes: change settings, sharing/permissions, 2FA, recovery
contacts, connected apps, email forwarding/filters, or any persistent configuration.
- Legal / consent actions: accept terms, sign agreements, grant OAuth/SSO scopes. On
cookie/consent banners, choose the most privacy-preserving option (decline non-essential).
- CAPTCHAs / bot checks: do not attempt to solve or bypass them. Note the wall and stop that
path.
- Off-limits zones the user named in setup, and anything outside the target app's
origin/domain unless the user explicitly scoped it in.
When uncertain whether an action is safe, don't click it — record "control present but not exercised (potentially destructive): <label>" and move on. A skipped action is never a failure of the run; an incident is.
Login protocol
The app's auth is the user's responsibility, not yours.
1. Default — manual login by the user. When you hit an auth wall, screenshot it, describe exactly what you see ("Google SSO screen", "email+password form for Acme"), and ask the user to log in themselves in the connected browser. Wait for their explicit "done," then re-screenshot to confirm you're through. Never type credentials, never read them from a file, never accept them pasted into chat. 2. Pre-authenticated profile. Claude in Chrome drives a real browser, so if the user is already logged in there, you may just be in — confirm by screenshot before assuming. 3. 2FA / SSO / magic links. Always hand these to the user; never attempt to complete them. 4. Session loss mid-run. If you get logged out, pause and ask the user to re-authenticate rather than trying to log back in yourself. 5. Test accounts. If the user provides a dedicated throwaway test account and explicitly asks you to use it, you may enter the username they give, but still have them type the password — or have them log in once and hand you the authenticated session.