Sentinel:一个在点击任何东西之前先读懂代码的开源QA代理
英文标题:🔶 Show HN: Sentinel – open-source QA agent that reads your code before it clicks
Sentinel 是一个开源的 QA 代理,能够先阅读代码库并推导业务流,再进行端到端的前后端测试。在酒店 PMS 演示中,它自动生成了九条核心业务流,并发现了仅通过 UI 测试无法捕获的后端状态机错误、前后端数据不一致等问题,一次全面运行成本仅为 1.95 美元。该工具基于 MIT 许可证开源,支持任意 JS 技术栈,并可注入模拟钱包来测试 Web3 应用。对开发者而言,它提供了一种低成本、深度的自动化质量保障手段,将测试从盲目点击提升到理解与推理的层面。
272.0 KB
130.2 KB
88.3 KB
120.6 KB
104.7 KB
103.6 KB
中文
Sentinel:一个在点击任何东西之前先读懂代码的开源 QA 代理
2026 年 7 月 16 日
Sentinel 阅读代码库,理清真实的业务流程,并在前端和后端之间对它们进行端到端测试。它以 MIT 许可证开源。
点击与理解之间的鸿沟
把一个典型的 AI 代理放到你的应用上,它会打开一个页面,点击几个按钮,注意到一个未对齐的元素或一个控制台错误,然后宣告运行结束。这就像冒烟测试一样有用。但这个代理没有建立起你的产品实际做什么的任何模型。
一个值得雇用的 QA 工程师不会只是随便点点。他们会先了解产品,然后进行推理:这是一个酒店系统,所以我需要测试单次预订、团体预订、将房间释放回来的取消操作、入住、退房和夜审,并且我需要确认每一项都真实地持久化在服务器上,而不是仅仅相信 UI 看起来没问题。
当我们给它一个真实的应用且没有任何指示时,发生了什么
我们将 Sentinel 指向了一个运行中的全栈酒店 PMS(物业管理系统):一个 Next.js 前端、一个独立的 API 服务、一个 Postgres 数据库。该 PMS 是 KaribuKit,我们自己的产品,这也就是为什么我们可以将管理员凭证交给代理,用于一个可丢弃的测试租户。它也只得到了这些——代码仓库和那些凭证。没有测试计划,没有流程列表。
它阅读了代码,得出结论该产品是一个精品和游猎酒店 PMS,并自行推导出九条关键的业务流程:完整的预订生命周期、团体预订、线索到报价的管道、房价管理、宾客自助服务、入住期间的换房、夜审、从支付到发票再到退款,以及 AI 副驾驶。取消操作也在其中,不过这次运行将其作为边缘案例和后端检查纳入其他流程中,而非推导出一个独立的流程。这几乎就是一个人类 QA 负责人第一天会列出的清单,而且没有人把它交给代理。
运行本身:从仓库推导出的计划,前两个流程各进行了两次深度测试,然后是对它访问过的每一个屏幕的视觉审查。
然后它运行了其中前两个流程,每个两遍,其执行轨迹看起来就像在观察一个人工作:
- 它调用了
GET /api/availability,收到了400,找出了缺失的参数,然后用adults=2&children=0重试,得到了200。 - 它用
POST /api/reservations(201) 创建了一个真实的预订,然后把它取回来以确认它已使用正确的房间和房价持久化。 - 它走了一遍状态生命周期,通过尝试找到了入住端点(
/checkin返回404,/check-in返回400,然后一次有效的调用返回了200),并检查了账本。
它正在验证的账本:三晚房间费用每晚 178 美元,应付余额 534 美元。角落里的红色提示是正在被现场捕获的一个 bug:“没有可用房间”出现在一个已经占用了房间的预订上。
它揭示的 bug 是那种你无法仅从 UI 中发现的:
- 确认预订返回了
NO_AVAILABILITY,尽管同一个预订早已占用了该房间。这是一个后端状态机 bug,UI 在一次尝试中将其变成了误导性的“无可用房间”提示,而在另一次尝试中则完全没有反馈。 - 日历显示一个房间在已有预订后仍为可用状态。API 和 UI 说法不一,只有同时检查这两层才能捕获它。
- 入住返回了
200,但宾客的registrationStatus在服务器上仍然保持NONE:一个只完成了一半的状态转换。
运行中的日历,填满了代理自己创建的预订,临时和已确认的并存。
它是如何工作的
这个 QA 代理最核心的引擎 flow 是一条流水线:
- 阅读仓库。一个确定性的侦察步骤(使用 grep 和 find,不调用模型)提取出结构:前端路由、API 路由模块、服务、数据库实体。模型基于这份摘要进行推理,而不是盲目地在一个单体仓库中爬行,那样既慢又容易遗漏。
- 推导流程。Mimo,即我们通过
pi代理框架用于决策的小米模型,将摘要转化为一个按优先级排序的端到端业务流程列表,每条流程都包含 UI 步骤、后端断言和边缘案例。该计划按提交进行缓存,因此它只在代码变更时重新推导。 - 将每条流程作为一个代理循环来运行:模型决定下一步操作,Playwright 驱动浏览器,一个一等公民的
api_request工具在每一步通过执行页面内的fetch来检查服务器状态,并复现前端自身发送的Authorization头部。模型只能拿到浏览器和 API 工具,永远拿不到你的 shell 或文件系统,并且每次尝试有一个严格的预算限制。在超过 90 次工具调用后,操作工具将拒绝执行,唯一可用的动作就是finish。 - 不止运行一次。自主代理是非确定性的。我们在一个流程上测量过,其中一次尝试发现了零个 bug,另一次则发现了五个。因此默认情况下每条流程运行两次(
FLOW_ATTEMPTS,一个可调参数),并将发现结果合并:任何一次尝试发现的 bug 都会写入报告,每条流程在所有尝试中保留其最差的结论。 - 对设计进行评级。代理访问过的每一个独特屏幕(按 URL 去重,默认每次运行最多八个)也会接受一个多模态模型的视觉审查,对视觉层级、间距、可能不符合 WCAG 对比度的文字、排版以及异常状态进行评分。这是仅靠 DOM 检查无法看到的一层。
侦察和报告是纯代码;模型只存在于需要判断的地方:推导流程、决定每一步的操作、对屏幕进行评分。
它完全不了解任何关于酒店的知识。侦察步骤可以消化任何常见 JS 技术栈的代码仓库(Next.js 路由、Express 和 Fastify 路由模块、Prisma、Drizzle 或纯 SQL 模式),所有下游操作都基于摘要进行推理,因此酒店 PMS 只是我们手头的一个演示;其他技术栈只需一个侦察补丁。
所有这一切都按计划运行:一个每十五分钟触发一次的 launchd 计时器检查每个仓库并运行任何到期的任务,对每个新提交进行代码审查,并按照你设定的频率(我们的情况是每 12 或 24 小时)进行 QA。该 QA 代理有三个兄弟代理:一个阅读每个新差异的代码审查代理,一个让你的 Markdown 文档与代码保持同步的文档同步代理,以及一个将每个仓库中的变化提炼到一个共享团队知识库的大脑同步代理。这两个负责写操作的代理在孤立的工作树中工作并开启 PR;它们自己从不合并自己的改动。
我们是如何走到这一步的
我们以最不具战略性的理由从 Mimo 开始:机器上的 pi 代理框架已经指向了它。悬而未决的问题是,一个廉价的模型是否能足够好地驱动浏览器以发挥作用,而对于第一版来说,答案是否定的。
第一版是一个确定性的 Node 循环。脚本控制着流程,并在每一步中将 Mimo 当作一个无工具的一次性大脑来调用:这里是 DOM,指出下一个单一动作,循环通过 Playwright 执行它。它在简单的事情上奏效了。面对一个小型的产品搜索应用(上传一张照片,获取视觉上相似的物品),它只花了几分钱就找到了真实的 bug。一次 3 美分的运行捕获了像“₹4,19”这样被截断数值的商品价格,以及商品卡片被切掉的问题;最便宜的一次运行花费 0.0044 美元。
但这个循环就是上限。模型一次只能看到一步,不记得它为什么采取了上一步,因此它永远积累不起足够的上下文来测试一个含有多个动作的流程。它无法超越在单个页面上点击的范围。
所以我们把循环的控制权交给了模型。第二版,我们称之为 pi-native,将基于 Playwright 的浏览器工具注册为一个 pi 扩展,并让 Mimo 在 pi 自己的代理循环内驱动它们,具有完整的会话记忆。它在单个目标上走得更深。但它仍然需要一个目标,而手工编写目标正是我们试图消除的苦差事。
第三版就是上面描述的流引擎:阅读仓库,推导流程,针对浏览器和后端执行每一条流程。
为什么用 Mimo,以及最初的运行打破了什么
当我们添加设计审查时,模型选择变得更有趣了。我们把截图发给 Mimo 进行 UI/UX 审查,但收到了胡言乱语,因为我们运行的模型 mimo-v2.5-pro 是纯文本的。解决办法是去阅读小米 API 实际提供的内容。除了文本模型,它还有 mimo-v2-omni,这是多模态的,因此视觉调用直接通过普通的 OpenAI 兼容 API 调用 omni,每次调用一张截图;一个基于评分标准的一次性调用不需要代理框架。
而 mimo-v2-omni 是一个推理模型,所以在它输出 JSON 裁定之前总是会被截断,直到我们将它的预算提高到 6000 个最大 token。一件小事耗掉了一个下午。
我们确实运行其他模型,只是不在热循环中。claude 在受保护的工作树中为文档同步和大脑同步代理进行精确的 Markdown 编辑,在这里精确性比价格更重要,而且它运行得不频繁。codex,基于 gpt-5.5,作为一个可选的只读审查引擎,用于当你想要一个更慢、更深入的第二意见时。让 Mimo 作为持续工作的默认选择的原因是体量:前提是有一个代理舰队,在每一个新提交时重新审查,并在你提供的每个仓库上按固定频率重新运行 QA,在这种体量下,成本是一个设计约束。一次 Mimo 决策只需几分之一美分,一次浅层 QA 运行只需几美分,而对酒店 PMS 最深的多尝试运行是 364 步,花费 1.95 美元。
不过,我们没有对这个领域进行基准测试。Mimo 是默认的,因为它已经在用了,而且结果证明它足够好,并非因为我们证明了它是最优的。整个东西是供应商无关的(“试用”部分有调节旋钮),我们希望得到的是关于“每花费一美元发现的 bug 数量”的实地报告。如果在同一个仓库上,另一个模型以更低的成本发现了更多的 bug,请带着数据提交 issue。
首次针对一个真实全栈应用的全量运行是集成 bug 出现的地方,而且没有一个 bug 是模型的错:
- 我们的启动包装器分离了开发服务器的会话,
next dev悄悄死掉了。我们退回到一个简单的后台启动,并带有一个收割整个进程树的清理操作。 - Playwright 在 React 水合完成之前就填好了登录表单,空提交返回了
400。一个水合安全的重新键入操作,会验证字段确实保存了它的值,解决了这个问题。 - CORS 阻止了每次调用,因为前端向
localhost通信,而代理使用的是127.0.0.1。一行配置丢了一个小时。 - 后端断言返回
401,直到我们停止重建认证令牌,而是直接复用了前端正在发送的Authorization头部。
全都不够光鲜。那才是大部分的实际工作。
最困难的部分:对一个你甚至无法登录的应用进行 QA
很多应用没有登录表单。它们有一个“连接钱包”的按钮,而此后的所有内容都被 MetaMask 或 Rabby 挡在门外。一个无头代理无法点击浏览器扩展弹窗,因此一段时间内这些应用都无法触及。
进入的方式比我们预期的更干净,而且它完全没有触碰应用的代码。一个 Web3 应用通过一个标准接口与其钱包通信:window.ethereum,或者对于较新的应用,是钱包向页面广播的 EIP-6963 公告。所以,在页面加载前,Sentinel 注入它自己的该接口实现,该实现由一个它在 Node 中保存的一次性私钥支持。就应用所知,这看起来就是一个普通的 MetaMask,但它是一个代理可以驱动的钱包。应用连接它,读取链上数据,并像为真人服务一样进行签名,而私钥永远不会进入页面。
应用看到一个标准的提供者;密钥和方法过滤器保留在 Node 中,发送类方法在那里止步。
需要注意的是,这些是真实链上的真实应用,涉及真实的金钱。将一个有资金的钱包指向一个活跃的交易所,“QA”会悄悄变成“开了一个杠杆头寸”。因此,这个钱包是一个新生成的、没有资金的一次性钱包,而且我们即使在配置错误的情况下也使花费成为不可能:任何提交交易的方法都不会被转发到网络。
我们进行了一次对抗性审查来寻找这个承诺中的漏洞,而且这次审查完全物有所值。初版代码会检查钱包余额,如果持有资金则中止,但当余额查询失败时,检查会悄无声息地通过,而这正是你最希望它停止的时刻。我们把它修正为故障关闭,然后完全不再信任余额。广播按方法名称被阻止,任何不在显式“只读”允许列表上的方法都会被全盘拒绝,因此一个我们从未预料到的发送变体也不会溜过去。读取可以通过,发送永远不行;一个曾被用于任何用途的密钥会中止运行。
然后我们将它指向了一个永续合约交易所。准确地说,是它的前端:应用从一个特性分支在一个一次性 git 工作树中本地启动,真正的后端从未运行,链上读取指向了真实链。该应用通过钱包白名单控制访问,因此 Sentinel 在网络层桩掉了门控端点,而白名单桩是最有意思的部分:它用应用自己的密钥加密了临时钱包的地址,应用对其进行解密,发现该临时钱包已经在白名单中了。
那些无聊的问题如期而至:
- 着陆页获取了一个 backen
English / 原文
Sentinel: an open-source QA agent that reads your code before it clicks anything
July 16, 2026
Sentinel reads the codebase, works out the real business flows, and tests them end to end across the frontend and the backend. It's open source under MIT.
The gap between clicking and understanding
Put a typical AI agent on your app and it opens a page, clicks a few buttons, notices a misaligned element or a console error, and calls the run done. That's useful the way a smoke test is useful. The agent has no model of what your product actually does.
A QA engineer worth hiring doesn't click around. They learn the product first, then reason about it: this is a hotel system, so I need to test a single booking, a group booking, a cancellation that frees the room back up, check-in, check-out, and the night audit, and I need to confirm each one actually persisted on the server instead of trusting that the UI looked happy.
What happened when we gave it a real app and no instructions
We pointed Sentinel at a working full-stack hotel PMS (a property management system): a Next.js frontend, a separate API service, a Postgres database. The PMS is KaribuKit, our own product, which is why we could hand an agent admin credentials for a disposable test tenant. That's also all it got — the repo and those credentials. No test plan, no list of flows.
It read the code, concluded the product was a boutique and safari hotel PMS, and derived nine critical business flows on its own: the full reservation lifecycle, group bookings, the lead-to-proposal pipeline, rate management, guest self-service, mid-stay room changes, the night audit, payment through invoice to refund, and the AI copilot. Cancellations are in there too, though this run folded them in as edge cases and backend checks inside the other flows rather than deriving a standalone flow. That's close to the list a human QA lead would write on day one, and nobody handed it to the agent.
The run itself: plan derived from the repo, top two flows deep-tested twice each, then the vision pass over every screen it visited.
Then it ran the top two of those flows, twice each, and the trace read like watching a person work:
- It called
GET /api/availability, got a400, worked out the params it was missing, and retried withadults=2&children=0to get a200. - It created a real reservation with
POST /api/reservations(201), then fetched it back to confirm it had persisted with the right room and rate. - It walked the status lifecycle, found the check-in endpoint by trial (
/checkingave404,/check-ingave400, then a valid call returned200), and checked the folio.
The folio it was verifying: three room-charge nights at $178, balance due $534. The red toast in the corner is a bug being caught live: "No rooms available" on a reservation that already held its room.
The bugs it surfaced are ones you can't find from the UI alone:
- Confirming a reservation came back
NO_AVAILABILITY, even though that same reservation already held the room. A backend state-machine bug the UI turned into a misleading "no rooms available" toast on one attempt, and into no feedback at all on another. - The calendar showed a room as available after a booking already existed for it. The API and the UI disagreed, and only checking both layers caught it.
- Check-in returned
200, but the guest'sregistrationStatusstayedNONEon the server: a state transition that only half-completed.
The calendar mid-run, filled with reservations the agent created itself, tentative and confirmed side by side.
How it works
The QA agent's deepest engine, flow, is a pipeline:
- Read the repo. A deterministic recon pass (grep and find, no model calls) extracts the structure: frontend routes, API route modules, services, database entities. The model reasons over that digest instead of crawling a monorepo blind, which is slow and misses things.
- Derive the flows. Mimo, the Xiaomi model we run for decisions through the
piagent harness, turns the digest into a prioritized list of end-to-end business flows, each with UI steps, backend assertions, and edge cases. The plan is cached per commit, so it only re-derives when the code changes. - Run each flow as an agent loop: the model decides the next action, Playwright drives the browser, and a first-class
api_requesttool checks server state at each step by runningfetchinside the page, replaying theAuthorizationheader the frontend itself sent. The model only ever gets browser and API tools, never your shell or filesystem, and a hard budget bounds each attempt. Past 90 tool calls the action tools refuse to act, and the only move left isfinish. - Run it more than once. Autonomous agents are non-deterministic. We measured it on one flow, where one attempt found zero bugs and another found five. So each flow runs twice by default (
FLOW_ATTEMPTS, a knob) and the findings are unioned: a bug found by any attempt makes the report, and each flow keeps its worst verdict across attempts. - Grade the design. Every distinct screen the agent visits (deduped by URL, up to eight per run by default) also gets a vision pass from a multimodal model, scoring visual hierarchy, spacing, text likely to fail WCAG contrast, typography, and broken states. That's the layer a DOM-only check can't see.
Recon and the report are plain code; the model sits only where judgment is needed: deriving the flows, deciding each next action, grading the screens.
None of it knows anything about hotels. The recon pass digests any repo on the common JS stacks (Next.js routes, Express and Fastify route modules, Prisma, Drizzle, or plain SQL schemas) and everything downstream reasons over the digest, so the hotel PMS was just the demo we had handy; other stacks are a recon patch away.
All of it runs on a schedule: a launchd tick every fifteen minutes checks each repo and runs whatever is due, review on every new commit and QA on whatever cadence you set (every 12 or 24 hours, in ours). The QA agent has three siblings: a code-review agent that reads each new diff, a docs-sync agent that keeps your Markdown matching the code, and a brain-sync agent that distills what changed in each repo into a shared team knowledge repo. The two that write work in isolated worktrees and open PRs; neither ever merges its own changes.
How we got here
We started on Mimo for the least strategic reason possible: the pi agent harness on the machine was already pointed at it. The open question was whether a cheap model could drive a browser well enough to matter, and for version one the answer was no.
Version one was a deterministic Node loop. The script owned control flow and called Mimo as a toolless one-shot brain, once per step: here is the DOM, name the single next action, and the loop ran it through Playwright. It worked on something simple. Against a small product-search app (upload a photo, get visually similar items) it found real bugs for pennies. A three-cent run caught prices truncated mid-value, like "₹4,19", and product cards cut off; the cheapest run came in at $0.0044.
But the loop was the ceiling. The model saw one step at a time with no memory of why it took the last one, so it never accumulated enough context to test a flow with more than a couple of moves. It couldn't get past clicking around a single page.
So we gave the loop to the model. Version two, which we called pi-native, registers Playwright-backed browser tools as a pi extension and lets Mimo drive them inside pi's own agent loop, with full session memory. It went deeper on a single goal. But it still needed a goal, and writing goals by hand is the exact chore we were trying to delete.
Version three is the flow engine described above: read the repo, derive the flows, run each one against the browser and the backend.
Why Mimo, and what the first runs broke
The model choice got more interesting when we added the design review. We sent screenshots to Mimo for a UI/UX pass and got nonsense back, because the model we were running, mimo-v2.5-pro, is text-only. The fix was to read what the Xiaomi API actually serves. Alongside the text models it has mimo-v2-omni, which is multimodal, so vision calls omni directly over the plain OpenAI-compatible API, one screenshot per call; a rubric-scored one-shot doesn't need an agent harness.
And mimo-v2-omni is a reasoning model, so it kept cutting off before it emitted its JSON verdict until we raised its budget to 6,000 max tokens. A small thing that ate an afternoon.
We do run other models, just not in the hot loop. claude makes the surgical Markdown edits for the docs-sync and brain-sync agents inside guarded worktrees, where precision matters more than price and it runs rarely. codex, on gpt-5.5, sits in as an optional read-only review engine for when you want a slower, deeper second opinion. What kept Mimo as the default for the always-on work is volume: the premise is a fleet re-reviewing every new commit and re-running QA on a cadence across every repo you give it, and at that volume cost is a design constraint. A Mimo decision is a fraction of a cent, a shallow QA run is a few cents, and the deepest multi-attempt run on the hotel PMS was 364 steps for $1.95.
We haven't benchmarked the field, though. Mimo is the default because it was already there and turned out to be good enough, not because we proved it optimal. The whole thing is provider-agnostic (the Try it section has the knobs), and the number we want field reports on is bugs found per dollar. If a different model finds more for less on the same repo, open an issue with the numbers.
The first full run against a real full-stack app is where the integration bugs lived, and none of them were the model's fault:
- Our boot wrapper detached the dev server's session and
next devquietly died. We went back to a plain background boot, with a teardown that reaps the whole process tree. - Playwright filled the login form before React had hydrated, and the empty submit came back a
400. A hydration-safe retype that verifies the field actually holds its value fixed it. - CORS blocked every call because the frontend spoke to
localhostwhile the agent used127.0.0.1. An hour lost to a one-line config. - The backend assertions returned
401until we stopped reconstructing the auth token and simply reused theAuthorizationheader the frontend was already sending.
Unglamorous, all of it. That was most of the actual work.
The hard one: QA an app you can't even log into
Plenty of apps don't have a login form. They have a Connect Wallet button, and everything past it is gated behind MetaMask or Rabby. A headless agent can't click a browser-extension popup, so for a while those apps were out of reach.
The way in was cleaner than we expected, and it never touched the app's code. A web3 app talks to its wallet through a standard interface: window.ethereum, or for newer apps an EIP-6963 announcement the wallet broadcasts to the page. So before the page loads, Sentinel injects its own implementation of that interface, backed by a throwaway private key it keeps in Node. As far as the app can tell it's an ordinary MetaMask, but it's a wallet the agent can drive. The app connects, reads the chain, and signs exactly as it would for a real person, and the key never crosses into the page.
The app sees a standard provider; the key and the method filter stay in Node, and send methods stop there.
The catch is that these are real apps on a real chain with real money. Point a funded wallet at a live exchange and "QA" can quietly become "opened a leveraged position." So the wallet is a freshly generated, unfunded burner, and we made spending impossible even under misconfiguration: no method that submits a transaction is ever forwarded to the network.
We had an adversarial pass go hunting for holes in that promise, and it paid for itself. The first cut checked the wallet's balance and bailed if it held funds, but the check passed silently whenever the balance lookup failed, which is precisely the moment you'd want it to stop. We fixed it to fail closed, then stopped trusting the balance at all. Broadcasting is blocked by method name, and anything not on an explicit read-only allow-list is refused outright, so a send variant we never anticipated can't slip through either. Reads go through and sends never do; a key that has ever been used aborts the run.
Then we pointed it at a perpetuals exchange. Its frontend, to be precise: the app booted locally from a feature branch in a throwaway git worktree, the real backend never ran, and the on-chain reads went to the live chain. The app gates access by wallet whitelist, so Sentinel stubs the gate endpoints at the network layer, and the whitelist stub is the fun one: it encrypts the burner's address with the app's own key, and the app decrypts it and finds the burner already whitelisted.
The boring problems arrived on schedule:
- The landing page fetched a backend during server rendering, so with no backend it returned a 500 and the health check never went green. We started the agent on the trading route instead.
- The public RPC handed back a malformed CORS header (
'*,*', a doubled wildcard browsers reject), and the app's own on-chain reads all failed. A seven-step run racked up 71,597 console errors and 35,797 failed requests, cost $1.16, and found zero functional bugs. We routed the app's RPC calls through Node, where CORS doesn't apply and we could retry the flaky ones. - A wallet SDK with a placeholder project id threw an error that tripped the framework's full-screen dev overlay, and the overlay silently swallowed every click, so the agent declared the whole page broken. We tore the overlay down and capped the error stream so a noisy app can't bury a run again.
What came out was a real report. On an unfunded burner the agent connected, opened the isolated-margin trade screen, and worked the form like a tester:
- The Open Position button hung outright on click (an eight-second timeout, no confirmation modal ever appearing).
- No order preview after entering collateral, no slippage control anywhere in the UI.
- No balance check: it typed 999,999 and the form shrugged.
- A wallet connection that silently dropped when you switched margin modes.
- A leverage slider showing its internal id (
slider-ex-2) as the label. - A negative amount the validation only half-caught.
Nine functional bugs and thirteen design findings in a 61-step session, for $0.28, with not one transaction ever reaching the chain.
Every run leaves its evidence on disk: a screenshot per step, the structured report, and the vision findings.
The stack
Sentinel is deliberately boring to operate — about 2,500 lines of bash, Node, and TypeScript, a launchd scheduler, and the CLIs it shells out to. There's no service to host and no database of its own, and you can read the whole thing in an afternoon. That's on purpose. We weren't going to leave an agent running unattended without being able to read everything it can do.
The same goes for what leaves the machine. Sending a repo's code, DOM, or diff to a model is opt-in per target (ai_allowed), so a work repo can't ride along by accident.
What it can't do yet
- Exploration still varies run to run; that's why every flow runs more than once.
- Booting a complex stack takes the right config (ports, auth, a test database), and you should never point write-capable QA at production data.
- Depth trades off against time and cost, all of it knobs: flows per run, attempts per flow, steps per attempt.
- For wallet apps it drives an unfunded burner, so it tests everything up to the moment of settlement, not a trade actually filling. A local
anvilfork (Foundry's local Ethereum node) gives it real chain state to quote against, but the broadcast block stays on even there. Actually filling a trade on a fork is a thing we haven't built.
Try it
Sentinel is MIT-licensed and on GitHub: github.com/Simbastack-hq/sentinel.
git clone https://github.com/Simbastack-hq/sentinel.git && cd sentinel
npm install && ( cd pi-ext/qa-browser && npm install )
cp config/sentinel.env.example config/sentinel.env
cp config/targets.json.example config/targets.json # placeholder targets inside; add your own repo
bin/sentinel doctor && bin/sentinel run <your-app> qa
The one hard prerequisite is the pi CLI with a provider configured; Mimo, authed with a Xiaomi key, is the default. Swapping it is config: QA_PROVIDER/QA_MODEL and the VISION_* variables point the decisions and the vision pass at any OpenAI-compatible endpoint, OpenRouter and local models included. doctor will tell you what's missing.
examples/ has ready-to-copy target registries, and the placeholder targets.json includes a complete web3 wallet-dApp sample like the exchange run above.
Point it at something real and see what it finds. PRs and issues welcome.
— Hemanshu, building Sentinel and KaribuKit at SimbaStack
#sentinel#ai-qa#agents#open-source
Published with SlopIt
Your AI writes the post, SlopIt publishes it live in seconds — no setup, no hosting.