A write-up of what I built on the weekend of 24 May 2026. Part technical build log, part editorial stance.
TL;DR
- The real cost of a coding agent like Mistral Vibe, Claude Code or Codex CLI isn't "approval fatigue" from clicking "yes" a hundred times. It's tethering: you have to stay in front of your screen because the agent may ask you something at any moment.
- On the Claude and Codex side, an ecosystem of physical buttons is already taking shape: an official Anthropic prototype (Claude Desktop Buddy, M5StickC Plus, April 2026), The Cook Board, claude-deck, agentsd, AgentDeck. On the Mistral Vibe side: nothing. The French sovereign stack delivered the agent — the peripheral ecosystem hasn't followed yet.
- My weekend answer: appro-vibe, a physical alerter on an M5Stack Fire (ESP32) wired into Mistral Vibe through a Python wrapper that monkey-patches Vibe's approval callback — 100 % coverage of permission prompts, multi-session, native Textual modal and M5Stack racing in parallel (first to respond wins). MIT.
- Honest trajectory: I first tried MCP (the agent forgets to call the tool), then looked at ACP (but I wanted to keep Vibe's TUI), and finally landed on a more pragmatic monkey-patch wrapper.
- During the build: a missing endpoint in the public Mistral API — no official
/v1/usage. I tried reverse-engineering the web dashboard counter, but it requires pasting web-session cookies into environment variables (fragile, dropped). So instead I now display the context saturation of the current session on the M5Stack (agent_loop.stats.context_tokens) — a different signal, but useful to anticipate/compact. Mistral could expose a clean public endpoint.
1. The trigger — a Vibe session on a Saturday morning
I'm sitting in front of Vibe on a Saturday morning. I've asked it to refactor a Python module — the kind of task that takes a good half-hour by hand and ten minutes if I let the agent drive while I review. It starts. It asks permission for the first file write. I approve. It proposes an intermediate commit. I approve. It proposes a shell command. I approve.
Then it codes for six minutes in silence. I watch the TUI window churn. Six minutes during which I have no idea what it will ask me next — maybe in ten seconds, maybe never before the end of the session. I don't dare go make a coffee, because I know the exact moment I leave my desk, the agent will hit a critical question and wait for me. When I come back ten minutes later, the session has been idle for eight of them.
I closed the laptop for five minutes and went to Google to check whether someone had already solved this. For Claude and Codex, I'd already seen the M5Stack AtomC. For Mistral Vibe: nothing. That became the weekend project.
2. The real question: why I didn't find the tool I was looking for
I want to draw a simple distinction before getting to the build.
On one side, a conversational agent — Le Chat, ChatGPT in normal mode, Gemini. You ask it to summarise a report, translate an email, help you structure a note. It answers. It doesn't take action in your IT systems, doesn't write to a database, doesn't push a commit. The question of remote validation doesn't really arise — you read its answer when you want, in whichever window you want.
On the other side, a coding or agentic agent — Mistral Vibe, Claude Code, Codex CLI. It doesn't just answer, it acts. It proposes to write a file, run a shell command, push a commit. At every step, it asks for permission. And that's where everything changes.
It's not "approval fatigue". It's tethering.
The real cost of a coding agent isn't clicking "yes" a hundred times. It's having to stay there to do it. The agent proposes an action, you validate, it codes for ten minutes, it comes back with the next question. You don't know when. You can't step into a meeting, work on another file, leave your desk. You're glued to your screen for fear of blocking the session over nothing.
It's the exact counter-model of the productivity gain these tools are supposed to deliver. The agent frees your keyboard; it chains your attention.
Mistral has partly seen it: Vibe sends an OS-level system notification when it needs you, and the official docs explicitly mention "switching to another window while the agent works". The problem is that a system notification brings you back to the screen. It doesn't bring you back to the room if you've left it.
On the Claude / Codex side, the pattern is already in the community's hands
When I wanted to get started, my first reflex was to look for the tool. A few minutes later, I find a complete ecosystem — but on the Claude and Codex side, not the Mistral Vibe side:
- Claude Desktop Buddy — official Anthropic prototype, published in April 2026, based on the M5StickC Plus (~$30), connected to the Claude desktop over BLE, button A approves / B refuses
- The Cook Board — dedicated Claude Code macropad, 8 Cherry MX keys, pre-order $49
- claude-deck — hardware controller via AJAZZ AKP05E / Mirabox N4 macropad with LCD buttons
- agentsd — Stream Deck plugin for Claude Code (approve/deny via hardware buttons)
- AgentDeck — multi-surface (Stream Deck+, Android, iOS/macOS, ESP32 displays) supporting Claude Code and Codex CLI
- The DIY zero-kb02 + TinyGo + RP2040 for Claude Code
For Mistral Vibe: nothing. No Stream Deck plugin, no dedicated macropad, no official Mistral prototype, not even a community project I could find. The French sovereign stack delivered the coding agent; the peripheral ecosystem that makes it usable day-to-day hasn't followed yet.
That gap is what made me spend a weekend on an old M5Stack Fire I'd previously used for PROTECPéO's people-counting kiosks during COVID. No technical pride here — just no off-the-shelf option.
3. The build — Python wrapper, monkey-patch, and a flashing M5Stack
The project is called appro-vibe. MIT-licensed, full source, fork-friendly.
Architecture in one diagram
PC: `vibe-m5stack` command (Python wrapper)
│ 1. loads the hook → monkey-patches AgentLoop.set_approval_callback
│ 2. launches vibe CLI normally
▼
Vibe runtime
│ every RequiredPermission triggers the patched callback
│ → race between the native Textual modal + the M5Stack bridge
▼
plugin/vibe_m5stack_hook.py (asyncio)
│ USB Serial 115200 baud, ephemeral mode + lock file ~/.vibe/m5stack.lock
▼
M5Stack Fire (ESP32, Arduino firmware)
→ 240×240 screen, A / B / C buttons
→ 10-LED NeoPixel ring + 5×10 NeoMatrix on ports B and C
Usage in two steps: you install the wrapper globally (pip install -e . at the repo root, which creates the vibe-m5stack command). From any project, you type vibe-m5stack instead of vibe. The wrapper loads a Python hook BEFORE Vibe starts. The hook monkey-patches AgentLoop.set_approval_callback. From that point on, every permission request (write_file, mutating bash, search_replace, commit, push) triggers a parallel race:
- Vibe's native Textual modal appears in the terminal
- The M5Stack lights up, the LED ring starts a burgundy/red chase, the matrices flash visible 5 metres across the room
First to respond wins. A press on A from the kitchen or a keypress in the terminal both end up resolving the same Future that the Vibe runtime is awaiting. If the M5Stack isn't plugged in or unreachable, the hook short-circuits in under 100 ms (returns NO on the M5Stack side) and the Textual modal handles it alone. No 30 s block, transparent fallback.
Everything runs on consumer hardware — an M5Stack Fire costs around €50 from European resellers. The firmware is under 1 000 lines. The Python wrapper + hook are under 500.
Multi-session — several Vibe sessions in parallel on the same M5Stack
You can launch vibe-m5stack in several terminals on different projects. The bridge opens/closes the serial port for each approval (ephemeral mode + global lock file), serialising requests FIFO.
# Terminal 1
cd C:\projects\alpha
$env:VIBE_SESSION_NAME = "alpha"
vibe-m5stack
# Terminal 2 (in parallel)
cd C:\projects\beta
$env:VIBE_SESSION_NAME = "beta"
vibe-m5stack
The M5Stack screen shows [alpha] write foo.py or [beta] git commit depending on who's asking. If two sessions request at the same time, the second waits for the first to release the M5Stack (60 s timeout, lock file ~/.vibe/m5stack.lock).
This feature is what turns the project from a personal hack into a daily-driver tool: a dev juggling three projects has one peripheral, three session prefixes, the same button every time.
Why a monkey-patch wrapper (and not MCP)
The honest story deserves to be told — because it illustrates what's really missing from the Vibe ecosystem.
First version: an MCP server. Anthropic's Model Context Protocol has become, in six months, the de facto standard for plugging third-party tools into AI agents, and Vibe adopted it natively. I wrote a mcp_server.py that exposes a single tool, m5stack_request_human_approval(title, body). The agent calls it, the server pushes to the M5Stack, the M5Stack answers, the agent receives the result.
It worked. With one major flaw: MCP delivers a tool that the agent decides whether to call. If I instruct Vibe with "before any commit, call the m5stack_request_human_approval tool", the LLM does it — most of the time. Not always. By the fifteenth iteration, especially with a saturated context, it can "forget". Observed coverage: ~80 %.
Second attempt: ACP. I looked at the Agent Client Protocol, the Zed standard released in August 2025 that does exactly that on the Claude Code side: intercept every RequestPermissionRequest JSON-RPC flowing between the runtime and the TUI. Theoretical coverage: 100 %. Except in practice, I ran into a structural trade-off: ACP seems to force you to abandon Vibe's Textual TUI in favour of a minimal external client — ugly terminal, no more panels, no syntax highlight, no diff formatting. I would have won coverage but lost the product experience. Unless I simply failed to wire ACP correctly during the session — an honest possibility I'll leave on the table. Either way, after a few hours on it, I preferred the wrapper route. If anyone knows how to combine ACP with Vibe's native TUI, I'm listening.
Third attempt, kept: a Python wrapper that monkey-patches AgentLoop.set_approval_callback directly inside Vibe. The hook installs itself before Vibe boots its agent. From there, every permission request passes through it — whether it comes from a native tool (write_file), a bash command, or a future tool. Coverage: 100 %.
Accepted trade-off: the wrapper's robustness depends on Vibe's internal API. If Mistral refactors AgentLoop or changes the signature of set_approval_callback in a future release, the wrapper breaks. But until a Mistral-equivalent ACP exists — and ideally, gets official support — this is the best compromise. The mcp_server.py stays in the repo, disabled by default, for anyone who'd prefer a less intrusive approach (and accepts the ~20 % LLM-forget rate).
Why an M5Stack Fire
Concrete reasons:
- It was sitting in the drawer since 2022, and for a weekend project that's the best reason of all.
- Built-in screen (320×240 ILI9342 IPS) with a clean graphics library. A Mistral cat can plausibly dance on it.
- Three physical A/B/C buttons already wired, already debounced by the M5Stack lib.
- ESP32: powerful enough to handle a 30 fps animation + a serial loop in parallel, well-documented enough that a weekend build is realistic.
- Available from Mouser, Conrad, Reichelt and EU maker shops without single-vendor dependency. Anthropic made the same family choice for Claude Desktop Buddy by starting from an M5StickC Plus — it's become a de facto standard for this kind of prototype.
I optionally hooked up two hexagonal NeoPixel matrices on ports B and C specifically for the tethering scenario. When the agent is waiting for a response, those matrices cycle Mistral rainbow colours visible 5 metres across the room. You can leave your desk, make a coffee, take a call — you catch the alert out of the corner of your eye and come back to validate. It's the wireless-doorbell pattern, transposed to a coding agent.
The serial protocol
I send one JSON line terminated by \n. The firmware parses with ArduinoJson:
{"type":"approval","id":12345,"title":"[alpha] write hello.txt","body":"..."}
{"type":"credit_info","percent":45}
The M5Stack responds with:
{"type":"response","id":12345,"approved":true}
{"type":"ping"} // every 5 s in IDLE
The Python bridge filters out pings and matches by id the response to the in-flight request. A detail that looks trivial but cost me an hour: the first version mistook a ping for an approval, and the agent received approved: true without me touching a button.
The ephemeral mode (opening/closing the port for each request) plus the lock file enable clean serialisation across multiple parallel vibe-m5stack instances.
Rendering — a dancing cat, and a shake easter egg
The firmware has two states:
IDLE: the Mistral cat dances (27-frame animation, 240×240 RGB565, ≈ 3 MB in flash) on a rainbow background recomposed band by band.SHOWING_REQUEST: a full-screenApprovalScreenwith the session prefix, title, request body, and three button zones (A = YES, B = NO, C = cancel).
On the LED ring I animate a burgundy/red chase at 180 ms per step while a request is pending. On the NeoMatrix boards I cycle Mistral colours every 400 ms. The FastLED current cap is set to 400 mA to avoid brown-out resets on a weak USB port.
The dancing cat has two functions:
- Visual diagnostic: if the cat dances, the firmware is running. If the screen is frozen, I have a bug before I even launch Vibe.
- Tethering affordance: most of the time, the M5Stack sits idle. Having a screen that lives in your peripheral vision passively confirms "OK, the agent has nothing to ask me, I can keep doing what I'm doing". Same role as a thermostat or an oven indicator — you glance at it to know whether to act.
It's a weekend build, so the code isn't art. But it works, and it's short enough that a dev with a Saturday to spare can fork it and adapt it.
4. What this says about "real" agentic governance
Zooming out.
The right problem: decoupling the developer's presence from the agent's execution
The pattern spreading fast on the Claude and Codex side — Stream Deck plugins, dedicated macropads, companion ESP32 screens, mobile/desktop multi-surface — isn't a gadget. It's the recognition of a simple fact: a productive coding agent is one you don't have to babysit.
As long as the developer has to stay one metre from their screen to validate the next request, the productivity gain remains theoretical. The agent may do the work of two junior devs, but it consumes the full attention of one senior. Net result: zero.
The pattern that changes the equation is, in fact, very old in other fields. A radiologist doesn't sit in front of the machine for the whole scan; they come read when a beep or a light calls them. An on-call sysadmin doesn't stare at a console for 8 hours straight; they have a pager that wakes them up. A parent cooking doesn't hold a thermometer against the oven; they set a timer that rings. The hardware tethering button is just that pattern, applied to coding agents.
Why MCP isn't enough — and what that says about the standard
The MCP → failed ACP → monkey-patch wrapper detour isn't just a technical anecdote. It's the diagnosis of a gap in the current ecosystem.
MCP is a tool-discovery standard, not an orchestration standard. The agent sees the list of MCP tools, picks some, ignores others. For a use case where you want to intercept everything that flows through — typically permission requests — MCP is structurally insufficient. It's exactly the failure mode I see in Maddyness's writeup on AI agents orchestrating IT systems: the "frictionless" promise hits the real frictions of orchestration. An AI agent that approves its own commits because it "forgot" to call the approval tool is the same category of problem.
ACP would be the right layer, in a world where you'd accept swapping the native TUI for an external client. The Zed protocol intercepts every RequestPermissionRequest JSON-RPC at the source — the agent can't forget. On the Claude Code side, this works via the official bridge. On the Vibe side: neither implementation nor any incentive to lose the Textual rendering.
In the meantime: the monkey-patch. The Python wrapper I wrote does the same job as ACP — intercept 100 % of prompts — at the cost of coupling to Vibe's internal API. If I find the ACP-compatible method that keeps the TUI, I strip a layer. The general lesson: a coding agent needs a stable extension point for the permission layer.
Freeing the developer ≠ slowing the agent
A predictable objection: "If every action goes through a physical button, you'll slow the agent down and kill the productivity gain".
Two-part answer.
First, the button doesn't trigger on every action. It triggers on actions that Vibe's internal policy classifies as sensitive: commit, push, file write, data deletion, mutating bash command, outbound email. For purely internal operations — editing a draft, reading logs, scanning the database — the agent keeps its autonomy. The "sensitive actions / total actions" ratio in a typical agent session is below 5 %.
Second, the supposed slowdown isn't worse with the physical button than with the TUI: the developer was already forced to validate. What changes is where they validate. In front of their screen (TUI) or from anywhere in the room (button + alerter). The round-trip delay isn't bigger. And between two requests, the developer can actually do something else — whereas with the TUI, they stay at their post "just in case".
Net on developer productivity: positive. Net on agent throughput: neutral. That's exactly the pattern to spread.
And for wider rollout?
The appro-vibe object is obviously not distributable as-is at scale. But the pattern is. Three statements that should show up in every operational rollout of coding agents:
- The agent's alert channel must be off-screen: LED visible in the room, ambient sound, smartphone vibration, companion screen. Whatever the channel, it shouldn't require the developer to be in front of their monitor.
- Validation must be doable away from the workstation. Physical button, mobile shortcut, Bluetooth controller, Stream Deck — whatever the hardware, the gesture must be feasible elsewhere.
- The agent's runtime must expose a stable extension point for the permission layer. Otherwise the ecosystem will monkey-patch (my case), or — worse — go through MCP and accept 20 % missed prompts.
If those three statements make it into the operational blueprints of coding agents deployed in organisations, we won't have invented hot water — we'll just have caught up 30 years of industrialisation in other professions with asynchronous supervision.
5. A missing brick: the Mistral usage endpoint
I can't close without mentioning a concrete friction point that deserves an ear at Mistral.
During the build, I wanted to display a Vibe credit gauge on the M5Stack's idle screen. Simple idea: pull the monthly usage percentage, show it as a horizontal bar at the bottom of the screen, flip to yellow above 80 %, red above 95 %. Three lines of code in theory.
Except the public api.mistral.ai API exposes no usage or Vibe quota endpoint. Every obvious path returns 404: /v1/usage, /v1/credits, /v1/billing, /v1/account, /v1/organizations. The public OpenAPI doc doesn't mention this category of endpoint. It's not a bug — it's just a brick that hasn't shipped.
The information does exist: the Mistral web console uses it internally via https://console.mistral.ai/api/billing/v2/vibe-usage (clean JSON response with a usage_percentage field). But that endpoint:
- Isn't documented in the public OpenAPI,
- Refuses the standard Bearer API key,
- Requires an Ory session cookie + a CSRF token header (web frontend auth).
Scraping path tried, imperfect: manually extract the session cookie from DevTools once, paste it into environment variables (MISTRAL_SESSION_COOKIE + MISTRAL_CSRF_TOKEN), have the Python server scrape my own dashboard. It works. But it's ugly, fragile, and needs renewing every month (Ory TTL ~30 days). Dropped.
What's actually shown on the M5Stack — useful, but not what I was originally after: since I already had a hook attached to the Vibe runtime, I read agent_loop.stats.context_tokens (internal Vibe data). Important: this number is not the monthly Vibe credit consumption. It's the number of tokens available in the current session's context — so a context-saturation gauge, not a monthly quota gauge. The M5Stack displays it through the credit_info protocol (a historical name that's now debatable since it tracks session context, not credit). Different signal, but useful: it warns when the session is filling up and you should /compact before the agent loses memory. The real monthly Vibe credit gauge, on the other hand, remains impossible to wire up without hacks.
Public request to Mistral: expose usage_percentage (and ideally per-model details) on the public API authenticated with a standard API key. It's a trivial server-side feature. It would save every Vibe consumer — from solo developer to enterprise rollout — from monkey-patching the runtime or scraping their own dashboard.
It's the second thing missing from the Vibe ecosystem, after the hardware buttons. And it's in the same category: an official extension point that would save the ecosystem from improvising.
It's constructive. It's doable. It's the same scope of work as Anthropic's Claude Desktop Buddy — one team, one prototype, a few weeks.
6. What's next
The appro-vibe repo is MIT-licensed. Everything is there: the PlatformIO firmware, the Python wrapper, the approval hook, the serial bridge with multi-session and lock file, the voice-dictation specification, the known pitfalls. Fork, adapt, break it, tell me what you find.
What I'll add on my side:
- A rotary encoder to scroll the approval request body — the M5's screen size doesn't let me read everything the agent is asking.
- A colour, in addition to the session name, to signal at a glance which session needs my attention — flashing red is Robert on the front-end, green is Daniel on the API, orange is François on the tests, blue is Nicolas on security, and so on.
- Wireless mode, reusing the wireless protocol I built for PROTECPéO, so I can carry the M5Stack around — it has a few hours of battery life (internal + stacked battery extension).
- An Easter egg when I shake Le Chat…
- A voice button to add follow-up instructions without going back to the PC.
What I'd love to see in the coming months:
- An official Mistral extension point for the permission layer — a stable hook (callback, event, or native ACP support) that would let me drop the monkey-patch.
- A public Vibe usage endpoint on
api.mistral.ai— the end of scraping. - An official or community-packaged kit, on the model of Claude Desktop Buddy on the Anthropic side or AgentDeck on the Codex side: visual alerter + remote validation button, running over BLE or Bluetooth, multi-surface (M5Stack, Stream Deck, smartphone), integrated into the Vibe SDK.
A few days of engineering by a French team would do it. If reading this article triggers that kind of conversation, my weekend will have been worth it.
Until then, I stay at my desk — or, more accurately, I can finally leave my desk. The Mistral cat dances to the right of my screen, the LED ring sleeps, and when the agent needs me, I see it flashing from the hallway. Less glamorous than a press release. Exactly the work missing between announcing the stack and actually using it day to day.
All cited sources are verified as of 26 May 2026. If you spot a factual error, write to bonjour@romaindelfosse.fr — corrected within 72 h, tracked in a Git commit.
Sources
- appro-vibe — github.com
- Mistral Vibe — docs.mistral.ai
- Claude Desktop Buddy — yankodesign.com
- The Cook Board — thecookboard.xyz
- claude-deck — github.com
- agentsd — github.com
- AgentDeck — github.com
- zero-kb02 + TinyGo + RP2040 — zenn.dev
- Maddyness's writeup on AI agents orchestrating IT systems — maddyness.com
