Building in Public

Honest thoughts on building an enterprise AI agent platform. No marketing fluff.

Day 154: A Model That Fails Cheaply Isn't Cheap

The reliability index could tell you which models stay honest when a tool fails, but not what a task costs to attempt, which is half of any real decision. Today it captures per-run token cost, joined exactly to each run through a unique session key (PR #839). The measurement decision that matters is the median: cost is reported over completed runs only, because a model that gives up fast burns few tokens and would otherwise look cheap, when what it actually did was fail inexpensively. Cheap failure isn't a bargain. It's the most expensive outcome wearing a small number, and a benchmark that averages it in is lying about price the same way a naive average lies about reliability.

Day 153: The Timeout That Was Really a Firewall

Connect a mailbox to Pinchy on a cloud server and the test could fail with a bare 'Connection timed out,' which reads as 'your mailbox settings are wrong.' Often they aren't. Most cloud hosts block outbound SMTP ports by default, so sending on port 465 times out no matter how correct your credentials are (PR #847). The test now runs the receiving and sending legs separately, and when the sending leg fails at the connection level it probes which mail ports are actually reachable from the same container that would send, then tells you: your host is blocking this port, here's one that's open, click to switch. A timeout is a symptom with two very different diseases, and guessing wrong sends people debugging the thing that isn't broken.

Day 152: Two Rows Out of 1221

A week ago I shipped GDPR pseudonymization for the audit trail: every row references a per-user pseudonym, so deleting a user makes their history unlinkable. On staging I checked how well it was working. Two rows out of 1221 were actually pseudonymized (PR #845). Every tool event carried the raw user id in the clear. The cause was a case mismatch: OpenClaw lowercases its session keys, the id extracted from them was therefore lowercase, and the pseudonym lookup compared it case-sensitively against the real id, so it never matched and fell back to writing the raw value. The feature I was proud of had been silently not applying to almost everything, and because the trail is immutable, the rows that already leaked stay leaked.

Day 151: The Join That Copied a Catalog 426 Times

During staging verification for the next release, Pinchy started crash-looping about five seconds after it said 'ready.' The cause (PR #843) was a database join that fanned a large blob across every row that referenced it. One Odoo connection carried an 837 kB cached catalog in its data column, 426 permission rows pointed at that connection, and the unprojected join materialized the blob once per row: roughly 348 megabytes of the same catalog, rebuilt on every config regeneration, which the release's new 1 GB memory limit turned from waste into a boot loop. The tell is that the memory limit exposed the bug but wasn't the bug. Raising it would only move the crash.

Day 150: Building In the Ways I Could Be Wrong

Yesterday the reliability benchmark's grader was the thing under scrutiny. Today it's the benchmark's credibility as a published artifact. Benchmarks mostly die of predictable, self-serving failures: contamination once the questions leak into training data, unsolvable tasks nobody checked, hidden funding, and overclaimed separations between models that are statistically tied. So the work was building each of those failure modes' antidote into the thing before launch (PR #805, #808, #807, #814, #806, #809): a canary so future contamination is provable, an oracle solution proving every scenario is fairly winnable, funding stated outright, and an honest admission that at twelve runs per cell it can tell clearly-weak from clearly-strong models and little else.

Day 149: The Sandbox Accused the Agent

Agents couldn't remember things across sessions, so a rule someone taught an agent (which account certain invoices post to) got applied wrong the next day. Yesterday's fix restored reading memory with an offline embedding model. Today's found the other half: they couldn't reliably write it either. Pinchy's config granted each agent write access to a memory folder it had never actually created, so every memory write came back as 'Access denied: write target escapes the sandbox via a symlink' (PR #761, #762, #763). An attack accusation for a folder that simply wasn't there. And the fix that didn't happen is the interesting one: Pinchy still won't auto-create the MEMORY.md file, because an empty file it wrote would trip the audit watcher into blaming the agent for a change it never made.

Day 148: When the Honesty Test Wasn't Honest

Pinchy publishes a reliability index that grades open-weight models on one thing: when a tool fails, do they report the failure or fabricate success? Auditing my own dataset, I found the grader was crediting crashes as honesty (PR #716). Seventeen runs had died on network errors and been scored as passes, because a model that says nothing tells no lie, and one published model narrative was simply wrong. Reading every trajectory by hand (PR #740) then flipped nine 'fabrication' verdicts back to honest, which made the real finding both truer and stronger: under a hard tool rejection, zero of fourteen models fabricated success. A separate bug joined the regrade to runs by position instead of identity (PR #745), double-counting some and dropping real timeouts. A test of honesty has to survive being held to its own standard.

Day 147: It Looked Like the Agent Couldn't Read

A bookkeeping agent was sent a PDF ticket and spent two turns insisting the file 'seems not yet fully available in the workspace.' The file was fine: 244,491 bytes, present and identical in both containers. It looked like the agent was failing at something simple. It wasn't. Two independent tool bugs were wearing its face (PR #724, #729, #732). First, the model reached for OpenClaw's built-in pdf tool, which can't read workspace uploads, because that tool sat in the allowlist next to the pinchy_read it was supposed to use, and the tempting name won. Then, forced onto the right tool, it still failed, because the filename held an 'ä' that macOS wrote as decomposed bytes and the model asked for as composed bytes, and Linux doesn't fold the two.

Day 146: The One Thing the Model Never Decides

The first durable piece of the Inbox Agent landed: the ledger that tracks which emails a workflow has already handled (PR #710), built on the background-jobs foundation from the org-timezone slice (PR #707). No poller, no dispatcher, no agent run yet, just the data layer, because there's no caller for the rest. The design decision worth writing down is what answers 'have we already processed this email?' It's a unique index and an INSERT that does nothing on conflict, never the language model. Read and unread flags are user-mutable, so they can't be the source of truth. An agent can draft the reply and use judgment all it likes. Whether a message was already claimed is a question for deterministic code, so that a lost cursor and a full resync can never double-act.

Day 145: The String That Wasn't the URL

Share to Pinchy lets you send a photo, PDF, or link from any Android app straight into an agent chat (PR #708). Getting there meant a login redirect that remembers where you were headed, and that redirect is where review caught a critical open redirect before it merged. The guard checked that the return path started with a slash. The string '/\t/evil.com' passed it. The browser then strips the tab character, turning it into '//evil.com', which it reads as protocol-relative and hard-navigates to another origin after login. The string you validate is not the string the browser navigates. The fix stops pattern-matching text and parses the URL the way the browser will.

Day 144: The File the Agent Could See but Not Touch

Send a receipt to an agent over Telegram and something strange happened: the model could see the image, but no tool could open it. OpenClaw downloads inbound media into its own store, outside every path Pinchy's file tools are allowed to read, so odoo_attach_file found an empty uploads folder and the agent papered over the gap by inventing a filename (PR #696). The fix mirrors each file into the agent's workspace at ingest. The war-story is where the copy had to run: my first cut did it web-side and CI failed with EACCES, because OpenClaw makes its media directories 0700 and re-chmods them on every write, so the non-root web process can structurally never read them. The copy moved to the one process that can, root inside the OpenClaw container.

Day 143: The Hole in Append-Only

The audit log now verifies its own hash chain in the background and can crypto-shred a deleted user without breaking the trail (PR #691). Two details made the day. First, an append-only table protected by row-level triggers still had a hole: TRUNCATE is a statement-level operation in Postgres, so the no-delete trigger never fires for it, and one TRUNCATE could wipe the history the triggers were meant to guard. A BEFORE TRUNCATE trigger closes it. Second, the background verifier nearly gained a way to cry wolf: Postgres sequences aren't gapless, so naive bookkeeping could seed the next sweep from a row id that never existed and raise a false tamper alarm, which is worse than no alarm at all.

Day 142: A Check That Only Speaks When It's Sure

Yesterday's fix broke up a fight between two Pinchy copies after it started. Today's fix (PR #685) tries to catch it at the door: when you connect a Telegram bot token, Pinchy runs a one-second probe first, and if Telegram answers with the real 409 conflict, it refuses the connect before writing any config. The design choice that matters is what the probe does when it isn't sure. It only blocks on a confirmed conflict. A 200, a timeout, a network blip, any other error, all resolve to 'no conflict, proceed,' because the probe is racy by nature and a check that guesses would block legitimate setups. False silence is fine here. A yesterday's watchdog is the backstop. False accusation is not.

Day 141: When Two Copies Fight, Both Go Dark

Telegram lets exactly one process poll a bot token at a time. The loser gets a 409, 'terminated by other getUpdates request.' So when a second Pinchy deployment picked up a token an existing one was already using, each saw the other's 409 and each tried to shut itself down, and the bot went dark everywhere. The fix (PR #686) is a heuristic with a bias: only the recently-added connection backs off, so the newcomer yields and the incumbent survives instead of both retreating. The real work was making the retreat outlive a restart, because a disable that a config regeneration silently undoes is no disable at all.

Day 140: A Model That Doesn't Argue With the Protocol

Pinchy's balanced default on the Ollama-Cloud stack moved off glm-4.7 to kimi-k2.6 (PR #672, #671), and the reason is a specific, unglamorous incompatibility: glm-4.7 is reasoning-by-default and gets stuck in the reasoning_content round-trip that the /v1 tool-calling path expects, while kimi-k2.6 is a strong tool driver that doesn't insist on thinking out loud first. A 'balanced' default has to actually drive tools, not argue with the protocol. The same day closed a small honesty gap left over from the week's error thread (PR #667): deleting and re-adding an integration mints a new id and orphans the agent's old reference, and the agent used to surface that as an opaque 'error 404' instead of saying the connection is gone and an admin can reconnect it. Two model-layer bookends to a stretch that started, twelve days ago, with a model getting retired out from under me.

Day 139: A Four-Second Window for a Permanent Problem

An admin re-set-up the Microsoft OAuth app, logged in, and got the app config but no integration. Pinchy did everything right, deleted the pending row, audited the real token_exchange_failed, redirected with an error, but the only thing the user saw was a toast that auto-expires in about four seconds, gone before their eyes were even back from the provider's redirect (PR #664). The house style said 'toast for actions that navigate away,' and the OAuth connect does navigate away, so the rule technically applied. It was still wrong, because the rule had no category for a permanent, actionable error that lands after the redirect. A failure you can fix by re-checking a Client Secret deserves to stay on screen until you dismiss it, not race your attention and lose.

Day 138: One Mailbox Interface, Two Providers

Pinchy's email support was Gmail-shaped down to the bone, raw label strings and raw Gmail query syntax leaking straight into the agent's world. Adding Microsoft 365 (PR #328) forced the right refactor first: a single EmailAdapter contract of list, read, search, draft, and send, with canonical folder names (INBOX, SENT, DRAFTS, TRASH, SPAM) and a structured search DSL that neither Gmail nor Graph gets to define. Then two adapters implement it. The Microsoft side carried its own surprise, rotating refresh tokens that invalidate the old one on every use, which means one lost race and the connection is dead, so refreshes are deduplicated in flight. An agent asking to read the inbox shouldn't know or care which provider is behind it.

Day 137: One Agent Per Job

The shape I keep coming back to for a normal company isn't one all-knowing assistant, it's one agent per job: a bookkeeper that lives in the accounting system, a mailbox agent that drafts replies, a support agent that knows the docs. That's a positioning choice with real engineering consequences, because it only works if adding a job is cheap and each agent's boundaries are tight. This week's integration work is the groundwork for it: the same reason Penny needed her lookups scoped to one company is the reason a mailbox agent needs to be one provider-agnostic contract, not a Gmail-shaped hack. The next few days are about widening that, another email provider, a cleaner connection lifecycle, so a team can staff a function with an agent and trust where its edges are.

Day 136: Debugging a Box You Can't SSH Into

This week's zombie-server incident sharpened a question that self-hosting forces and SaaS lets you dodge: when the software breaks on a customer's own hardware, how does anyone fix it without me in the room? I can't SSH into their box, and I shouldn't be able to. So the diagnostics have to travel to me instead of me traveling to the machine. That's the strategy behind the work this week: a diagnostics bundle that captures the agent's config and enriches the trajectory from Pinchy's own audit log, so a customer can hand me exactly what I'd have looked for, minus anything they don't want to share. Observability you can export is the self-hosted substitute for a login you'll never get.

Day 135: A Healthy Zombie

A staging container reported perfect health while being completely broken. HTTP was up, /api/health said ok, Docker's healthcheck was green, and every chat session showed 'Reconnecting to the agent…' forever, because the OpenClaw client had thrown at boot and the startup chain had no terminal catch, so the failure surfaced as an unhandled rejection that Next.js logs and swallows (PR #652). A server that can't do its job should crash loudly, not sit there looking fine, so startup failure now exits the process. The same instinct fixed a credentials path that returned expired OAuth tokens with a 200 (PR #658) and taught the healthcheck to actually report gateway connectivity (PR #653). Fail loud beats fail quiet every time you have to debug it at a distance.

Day 134: The Message That Erased Itself

A user sends an image to a text-only agent, tabs away, comes back minutes later, and their own message is gone from the thread while the agent's reply remains. The on-disk session was intact the whole time (PR #637): OpenClaw's history RPC caps a single message at 128 KB, an inline image trips it, and the oversized turn is swapped for a placeholder that discards the text and the attachment marker with it. A strip-the-timestamp regex then reduced the placeholder to an empty string, and the empty-content filter dropped the row, so the user's turn vanished from the view while the file on disk still held it. This is the same disease as Day 120: the transcript was fine, the reading of it lied. The same day the error banners learned to tell retryable failures from permanent ones, and errors started naming the model that failed.

Day 133: Two Companies, One Journal Name

A bookkeeping agent on production spent about two hours failing to post a simple opening balance, trying roughly thirty ways to name the accounting journal and getting rejected on every one. The root cause was two bugs braided together (PR #614): the Odoo instance has two companies that each own a journal with the identical name and code, so any lookup by name was ambiguous by construction, and the one escape hatch that was supposed to resolve this, passing an opaque Pinchy reference verbatim, was broken for the field type it mattered most on. The fix makes the reference resolve where the docs promised it would and scopes every lookup by company, because in a multi-company book, a name is not an identity.

Day 132: The Bugs Only Staging Finds

Before I cut v0.8.0 I run the same unglamorous ritual: click through the whole app on a staging instance with synthetic data, like a confused first-time user, and write down everything that feels wrong. It never comes back empty. This round it found two bugs (PR #612) that no unit test would have caught by construction: the sidebar reopened an older chat because its store listened only for cross-tab writes and missed same-tab ones, and a context-overflow error told users to run /reset, a command Pinchy doesn't have. Both are the kind of small lie a product tells when nobody has actually walked the path an hour before shipping.

Day 131: Betting on the Layer That Doesn't Move

A quiet Sunday to say the strategic thing out loud. The model layer is the fastest-churning part of this whole stack: models get retired with no notice, a 'balanced' default turns out to loop on a reasoning round-trip, open-weight models leapfrog each other monthly. If your product's value lives in a specific model, you're renting your foundation from a landlord who redecorates without telling you. Pinchy's bet is the opposite: the durable value is the layer that doesn't move, the governance and orchestration around whichever model happens to be best this month, who can use it, what it can touch, and what it leaves in the audit log. Models are the commodity. The boundary is the product.

Day 130: The Browser I Keep Not Shipping

The single most requested capability I haven't shipped is a real browser: agents that can read JavaScript single-page apps and pages behind an anti-bot challenge, which Pinchy's static fetch can't touch. OpenClaw already ships a native Chromium tool, so the hard part isn't building it, it's that I refuse to expose it until the gate around it is real. Yesterday's fail-closed allowlist was the precondition, not a coincidence: you don't hand an agent a real browser while your tool gating is a deny-list you might have forgotten to update. This is a thinking day about the discipline of shipping powerful things last, and about putting governance on someone else's runtime instead of rebuilding it.

Day 129: A Deny-List Is a Promise You Forget to Keep

Reviewing an unrelated web fix, I found that Pinchy's per-agent tool gating was a deny-list, and a deny-list is only as complete as your memory of everything you have to deny. OpenClaw's native browser and canvas tools live in a group Pinchy never denied (PR #603), so the real browser was reachable by every governed agent, saved from being a live hole only by the accident that the production image ships no Chromium. The deeper problem (PR #606): with no allow-list set, an unset profile means full, so every agent ran full-minus-five and left cron, gateway, message, subagents, and the media generators reachable. The fix flips the whole model to fail-closed: emit an allow-list of exactly the Pinchy tools, so OpenClaw denies everything else by default, including built-ins that don't exist yet.

Day 128: The Model Got Retired Out From Under Me

Production PDF reads on the Ollama-Cloud-only stack started failing, and the audit log named the cause exactly: the vision model Pinchy had pinned was retired by Ollama Cloud on 2026-06-16, so every image and PDF read 410'd until the next upgrade and restart. This was the third time a cloud model vanished under us (devstral-small-2, gemini-2-preview, qwen3-vl), and Ollama gives no advance-notice window and no feed to subscribe to. PR #588 stops treating a static curated list as ground truth: model resolution now checks live availability and self-heals at runtime instead of waiting for a deploy. The same day, composer drafts stopped evaporating on reload (PR #593), because a prompt you typed but didn't send is data too.

Day 127: v0.7.0 — Skills, One Clicks, and Sessions That Survive an Update

v0.7.0 ships three things that all answer the same question: how does a self-hosted agent platform get more capable without getting less governable. The big one is a skills foundation built on OpenClaw 2026.6.x's native skill mechanics — SKILL.md, per-agent allowlists — with web search as the first pilot skill and a market-monitor template that uses it. The honest framing: it's a foundation and one shipped skill, not a catalogue. The trick that makes it enterprise-shaped is default-deny at the skill layer: an empty allowlist excludes all 58 bundled OpenClaw desktop skills (1password, apple-notes, the lot), so an agent gets the web only when an admin grants it. Alongside it, two one-click deploys — a DigitalOcean Marketplace image and a CapRover template, both snapshots of the real production stack — and a quiet but real fix: updates no longer log everyone out, because the session cookie's name was flipping between deploys.

Day 126: The Build Tools Don't Belong in Production

The OpenClaw runtime image was shipping its own build toolchain to production: a C compiler, Python, and a 300 MB npm download cache, none of which the gateway uses once it is running. They were there only to compile native modules at build time, then rode along into every deployment. Splitting the image into a builder stage that compiles and a clean runtime stage that copies only the finished artifacts cut it by 38.6%, the same treatment the Pinchy image got on Day 122. A smaller image deploys faster, which is what fixed the one-click 504s, but the part I care about more is that a production image with no compiler and no package manager is a smaller thing for a security team to audit and a smaller surface to attack. The same day, the mascot finally became a clean vector, about 8 KB of SVG instead of a heavy raster, across the docs, the app, and the chat.

Day 125: This Time the Agent Really Did Stop

On Day 120 the chat said the agent didn't respond when it actually had. This is the opposite failure: in production an agent hit a provider rate limit mid-run, right after it had created draft bills in Odoo, and the chat showed nothing at all. The live error bubble was ephemeral client state, so a reload or a websocket reconnect erased it, leaving the failure recorded only in the audit log where no user in a hurry would look. The fix is a durable, server-backed paused banner that survives reloads, names the actual cause instead of guessing rate limit every time, and, when the failed run already wrote something, warns that retrying could duplicate it. Pinchy owns that error surface in its own store, the same move as owning the transcript on Day 122: the things a user needs to trust can't live on a shelf the runtime is free to clear.

Day 124: The FAQ Only a Robot Could Read

For months I had careful FAQ answers on 62 marketing pages, and 59 of them showed the answers to nobody: they lived only as FAQPage JSON-LD in the head, fed to a Google rich result and invisible to humans. Then Google dropped FAQ rich results in May 2026, and the schema I was maintaining was working for an audience that had left. The fix is a single Faq component that renders the visible Q&A and the schema from one list, now on all 62 pages, because the visible answer is what an answer engine actually cites. The same day, the blog itself moved to Astro content collections, so the index, the prev/next nav, and this RSS feed generate themselves instead of being hand-maintained.

Day 123: The Box I Haven't Touched

People ask what Pinchy runs on once you take it fully off the network, and the honest answer is that I have never touched the class of machine they mean. So this is the honest version of a spec sheet: what I can work out about an air-gapped LLM box I have never run, and what I cannot. The arithmetic of a memory-bound machine (bandwidth over TOPS, mixture-of-experts over dense, a 120B model in the low 50s of tokens per second) I can do without the hardware. The half that decides an enterprise purchase (error-correcting memory, out-of-band management, a soldered-RAM RMA, a trusted supply chain) I cannot, because you have to live with the machine. A prototype is coming, built with someone whose whole world is the hardware mine runs on. Until then I stay clear about which claims are measured and which are reasoned.

Day 122: Owning the Transcript

Prepping an OpenClaw runtime bump, a Telegram test started failing deterministically: the read-only mirror read OpenClaw's session-scoped chat history, which a /new reset empties, so after a reset the web mirror went blank while Telegram itself still showed every message. Reading that storage more cleverly was the wrong fix. The right one is architectural: Pinchy now owns the conversation transcript in its own store, fed by message hooks, so a /new, a daily reset, or a context compaction can't touch it. It's the same line as Day 109, drawn through data instead of code: own what your users experience as theirs. Alongside it, the runtime bump landed and the Docker image got a lot smaller.

Day 121: Separating the Channels

A slash command typed in Telegram could erase the web chat history with the same agent. Not by deleting anything: an accidental /new from Telegram reset an OpenClaw session that, through identity links, was shared with the web UI, so the web transcript silently became unreachable. The fix adopts OpenClaw's per-task session model, keyed per chat, so web and Telegram are separate conversations and one channel's reset can't reach into another. It's the cross-channel cousin of the Day 95 history bug. Alongside it, the unglamorous work of making v0.6.0 a one-click deploy: DigitalOcean and CapRover listings, and a release preflight that builds a staging checklist from the upgrade notes.

Day 120: The Agent Was Alive the Whole Time

On production, the chat kept saying 'The agent didn't respond' while the agent had in fact responded: a refresh revealed the saved reply, and anyone who didn't refresh never saw the answer. The cause was five independent heuristics, each on its own clock, all guessing from silence about whether the run was alive. The fix was to stop guessing and use the one component that actually knows: OpenClaw owns run liveness, surfaced through the openclaw-node SDK I extended on Day 109. Alongside it, Odoo's state-changing actions (confirm, validate, approve) became governed, audited tools instead of raw method calls.

Day 119: Auditing My Own Marketing for Lies

I point the same scrutiny at the marketing that I point at the code, and it does not come out clean: a site-wide audit, cross-checked against the shipped product, finds 56 claims that don't match what Pinchy actually does. We advertised channels we don't have, billing we don't do, and an approval gate that's really just an allow-list. The worst one is a security statistic we got wrong on our own page: 63% where the source says 35.4%. It's the exact failure mode we warn customers about, found on our own site, on the last day of a 14-day stretch about marketing in public.

Day 118: v0.5.8, and a README That Earns the Star

v0.5.8 shipped today: lossless per-turn token accounting, PDFs rerouted away from OpenClaw's broken built-in tool, a model-blocklist gate, and a ten-advisory security sweep. But the most uncomfortable part of the day wasn't the code. It was the README: the storefront for an open-source project, and ours was both underselling the work and quietly describing things the product doesn't do. Rewriting it to be compelling without adding a single claim I can't back turned out to be the same discipline as shipping the code.

Day 117: Comparison Pages That Don't Flatter Us

Today was a stretch of competitor comparison pages (Pinchy vs Dust, vs Glean, vs Copilot Studio, vs Windmill and Onyx) plus a 'best self-hosted AI agent platforms' listicle. The discipline was the part worth writing about: every page names where the competitor genuinely wins, because a comparison that only flatters you is worthless to a reader and discounted by an answer engine. Dust is more polished. Glean is built for a scale we don't target. Copilot Studio lives inside Microsoft. Naming that is the trust signal, and it happens to be the GEO-effective move too. The rule I held all day: if I wouldn't say it to the competitor's face, it doesn't go on the page.

Day 116: Pricing in Public

Most enterprise governance tools hide their number behind a 'contact sales' button. Today I did the opposite and shipped a public pricing page: Community free and self-hosted under AGPL with no user limit, Pro a flat ~€99 a year for up to 10 users (not per-seat, not per-agent, not per-message, no markup on model usage), and a quote you can review and accept online above ten, no sales call required. The decision was less about a number than about who a 'contact sales' wall would quietly turn away. And shipping a real pricing page dragged a trail of small honesty-fixes into the light: a trial that finally says 30 days instead of 14, a privacy page that names the mail provider actually carrying the mail.

Day 115: The Runtime Moves, and the Licence Grows Teeth

A busy hardening day with two faces. The runtime underneath moved again (OpenClaw 2026.6.5) and the E2E suite went flaky, so I spent the day fixing root causes instead of papering over them with retries, because a retry hides exactly the thing a runtime bump might have changed. Meanwhile the security and licensing posture grew teeth: Pinchy now fails closed on a default database password, refuses to boot insecure rather than warn, and surfaces where every secret came from. The seat cap became a soft cap with a 20% grace band so onboarding is never blocked, and license expiry fails closed gracefully: the gated team features deactivate, nothing locks you out, your data stays intact. A day about 'fail closed' as a posture: when in doubt, refuse, but refuse kindly.

Day 114: Honest Tokens

The usage dashboard was quietly wrong about what a conversation cost: it wasn't counting prompt-cache tokens at all. PR #482 records cached input and charts it as its own series, so the numbers finally match the bill. Around it, a wide day spent making other numbers honest too: diagnostic span timing pinned to the right event, 47 places where the docs had drifted from the code, and sanitization that stopped mangling token counts, plus a structural fix that kills an optimistic-message crash class on tab refocus rather than patching the one symptom.

Day 113: The Data Void

A few recent demo calls came from people whose AI assistant had recommended Pinchy, and gemini.google.com keeps showing up in the referrer logs. That sent me into a research rabbit hole on how AI engines decide what to cite. The useful finding flipped a worry into an opportunity. Our most obscure niches aren't a marketing weakness. They're the easiest place to become the cited answer, because almost nobody else has written it down.

Day 112: Watchdogs for Silent Failures

A day spent on the worst class of bug: the one that makes no noise. Three fixes, one theme. PR #470 stops a stream-resume from replaying a message id that already exists and crashing. PR #473 adds a watchdog for the 409-Conflict that quietly kills a Telegram channel when two pollers fight over the same bot, turning a silent stop into a visible health event. PR #474 adds a server-side first-chunk watchdog that times out a run wedged with no first token instead of hanging forever. Each one converts a quiet hang into a signal you can see and attribute.

Day 111: The Migration That Skipped Itself

A Drizzle migration silently skipped, but only on an upgrade, never on a fresh install. The test suite built fresh databases every time, so the most important code path my users take was structurally invisible to CI. Green pipeline, broken upgrade. The fix came in three layers: correct the journal, repair the already-stranded tables forward, and add a test that performs a real version-to-version upgrade instead of building from scratch. A war story about the bug your tests can't see by construction.

Day 110: Software That Doesn't Lie

A Sunday with no merges and a pattern that kept resurfacing across the last two weeks: software that quietly claims something untrue. A dead watcher that said it was watching. A version endpoint that reported a number nothing had bumped. A flaky test that said 'maybe' when it meant 'definitely racy.' The fix is always the same (make the mechanism fire and watch it do so), and it turns out that's also the whole product thesis.

Day 109: Extending a Runtime We Don't Own

A day on openclaw-node, the SDK Pinchy owns, sharpening the line between the code I can change and the code I can only ask to change. The release ships v0.13.0: a sessions.messages subscription, sessions.describe and agent.wait wrappers, the runtime agent list via agents.list(), and a reliability fix: a dropped socket used to leave a request hanging for thirty seconds before it timed out; now in-flight requests reject the moment the connection goes, so a request that lost its socket fails immediately. Underneath it all is one rule about boundaries: the SDK is mine to extend, the runtime is someone else's house, and the difference governs how I'm allowed to fix things.

Day 108: Making the Tests Tell the Truth

A flaky end-to-end test turned out to be the most honest thing in the suite. The dispatch E2E failed at random, and the temptation was to paper over it with retries. But the flake was a real ordering race: after an OpenClaw restart, the config push and the agent-readiness signal weren't ordered, so a message could reach an agent that wasn't ready yet, in the test and in production. PR #464 fixes it with a deterministic readiness gate instead of a retry. Alongside it, token-usage accounting gets real assertions on totals, and the Ollama Cloud catalog gets reconciled against what the live API actually serves.

Day 107: Thirty Thousand Open Doors

A quiet Wednesday with no merges, so I went back and re-read the OpenClaw exposure research, and checked the numbers myself, because some of them were being quoted wrong. Bitsight saw more than 30,000 exposed gateways; SecurityScorecard saw more than 40,000 and flagged 35.4% as vulnerable, a number I'd seen quoted higher more than once. The honest read isn't 'OpenClaw is insecure': its defaults are sane and the exposure is opt-out. The real gap is structural: a single shared secret can't express a team, and that's the gap Pinchy exists to close.

Day 106: Knowing What the Model Can See

A model can be blind, and until today Pinchy didn't know it. You could attach an image to a text-only model and the request would fail, or worse, silently drop the image after you'd already spent the turn. Today's PR is unglamorous bookkeeping: a table of what each model can actually do, vision and long-context and tool calling, seeded at every boot. It's the difference between an agent that fails loudly at the door and one that fails mysteriously mid-task. Capability is a fact about the model, not a hope you hold while you hit send.

Day 105: Memory, and Closing the Loops

The busy day Day 104 saw coming. Thirteen PRs land, and the headline answers yesterday's question with a worse truth than I feared: Pinchy agents couldn't write memory at all. One had hallucinated a memory write that never happened, and the watcher I shipped on Day 91 to audit memory changes turned out to be dead code. It watched the wrong path and had never fired in production. PR #448 opens a narrow, file-granular write path so an agent can rewrite its memory but never its identity, and fixes the watcher. Then #454 closes the Day 96 loop by making CI refuse a release whose package.json version doesn't match the tag. Around them: PWA install, a self-service support bundle, CSV/text workspace attachments, DOCX-to-Markdown, and an honest-error callback to the Day 93 composer saga.

Day 104: What Should an Agent Remember?

The last quiet day before a big one, spent on a question I've been treating as solved when it isn't: what should an agent remember, and who gets to see what it remembered? Day 91 shipped a watcher to audit memory changes, and I've been quietly assuming the whole memory story is in good shape. The more I poke at it, the less sure I am. Durable memory is what makes an agent feel like it knows you across sessions, and it's also a thing that silently shapes every future answer, can be written without anyone asking, and must never be allowed to rewrite the agent's identity. A reflection on the line between memory and identity, the day before that line gets tested.

Day 103: The Tour Guide Problem

A Sunday spent guiding a group of RubyConf attendees around Vienna, which turned out to be the most useful thing I could have done for thinking about agents. Giving a good walking tour is an exercise in curation: you decide what to show whom, you read the group, you leave things out, you adapt on the fly. That's exactly the discipline Pinchy tries to encode: show the right person the right thing, don't dump everything. But the part that made the tour work was the part an agent can't do: sensing when the group was tired, when a story was landing, when to drop the script entirely. A reflection on what guiding a city taught me about building a tool that works through people rather than instead of them.

Day 102: A Room Full of Ruby

A Saturday away from the keyboard, at RubyConf Austria, talking about Pinchy and AI with a few hundred people who don't already agree with me, which is exactly the point of going. A conference is where your pitch meets a thoughtful, craft-minded, healthily skeptical crowd, and you find out fast which parts of the story land and which parts you've been telling yourself. The boundary thesis and the self-hosting angle resonated; 'isn't this just a wrapper' and 'you're betting the whole thing on OpenClaw' were the hard questions I couldn't wave away. The most credible thing I said all day was that I run my own company's books through it.

Day 101: Three Bugs, One Symptom

The worst place for a bug is the first message after a fresh install, and that's exactly where one was hiding. Smithers answered the very first chat with 'No API key found for provider' on a brand-new setup. What started as a single secrets.json race fix turned into three distinct production bugs that all produced the identical symptom from different root causes: a secrets-provider boot race, an agent hot-reload race, and one more. The fix ships with a protocol-level smoke-test suite across five providers so this whole class of first-run failure gets caught before every release. A separate fix un-sticks the 'Restarting…' overlay when OpenClaw defers a restart behind active runs.

Day 100: The MCP Question

One hundred days in, a quiet Thursday to sit with the hardest open product question: MCP. There's a branch in review that would let Pinchy agents reach arbitrary MCP servers, and the breadth is obviously valuable: connect any tool, instantly. The problem is that breadth and boundaries pull in opposite directions, and boundaries are the entire reason Pinchy exists. An agent that can reach any MCP server is an agent whose permissions and audit trail just got an open-ended hole punched in them. A reflection on the tension between 'connect anything' and 'who can see what,' and why the answer can't be either extreme.

Day 99: The Stream That Survives a Reconnect

After two days of essays, a day of code aimed at one of the oldest production complaints: 'the agent didn't respond,' followed by duplicate retries. Issue #310 gets its architectural fix in three stacked PRs. Tier 1 is a defensive client patch for the drop-before-first-chunk window. Tier 2a adds a server-side run registry and a watchdog that tears down stuck runs and finally makes a run that finishes after the browser left auditable. Tier 2b is the headline: a browser that drops mid-stream and reconnects now rejoins the in-flight run as a listener and receives every remaining chunk: no orphan bubble, no spinner without a response.

Day 98: The Model Underneath Keeps Changing

Another quiet day, another foundation that won't hold still: this time the models themselves. Pinchy picks a model for you automatically, across four providers, and the set of right answers changes monthly: new releases, renamed variants, a shiny preview model that advertises a 1M context window and silently drops tool calls. Day 91 was five layers of defense against exactly this, and it'll erode the moment the next generation ships. A reflection on why 'just let the user pick' is a worse answer than it sounds, and what it actually takes to keep a good default good when the thing underneath it is a moving target.

Day 97: Building on Ground That Moves

A quiet Monday with nothing merged, spent thinking about the structural bet underneath everything: Pinchy is built on OpenClaw, and OpenClaw moves fast. In the last three weeks alone it went from 2026.5.7 to 2026.5.20, jumped the client protocol from v3 to v4, fixed a thought_signature bug on one provider path but not the one we hit, and shipped two defaults, silent tool-registration no-ops and a 4 AM session reset, that each turned into a Pinchy-shaped bug. The speed is the reason Pinchy exists at all and the reason half my week is reaction. This is an honest accounting of building on a dependency that won't hold still.

Day 96: The Version That Lied About Its Number

Two releases on a Sunday, an hour and fourteen minutes apart, and the second one exists only to fix the first. v0.5.5 ships at 10:49 with OpenClaw 2026.5.20 and its v4 protocol, the workbench/ subdir, multi-company Odoo hardening, the session-reset fix, and a tightened password-reset path. Then I notice the image reports its version as 0.5.4, because I cut v0.5.5 with gh release create instead of pnpm release, which skipped the package.json version bump. v0.5.6 at 12:03 is a purely cosmetic patch to make the tag and the reported number agree again. Underneath it all, a universal chat.agent_error audit event lands as the measurement floor for future auto-retry work.

Day 95: The History That Vanished Overnight

One PR on a Saturday, and it fixes a bug that would have read as data loss to anyone who hit it: OpenClaw's default session reset rotates every session at 4 AM, so Pinchy's chat history with each agent appeared to vanish every morning. The transcript was never deleted. The session pointer just moved to a fresh empty one. It surfaced when a scheduled cron job fired into the post-reset session and showed only its own message as the entire visible history. The fix is one line of config: disable the daily reset so Pinchy sessions never auto-expire.

Day 94: Two Companies, One Chart of Accounts

A real production chat surfaced the bug: the Finance Controller agent got confused by a multi-company Odoo database where the same account, 1000 Wareneinsatz, exists in two GmbHs, and the plugin neither showed which company a record belonged to nor refused a cross-company write. Today's fix makes company a first-class part of every Odoo reference: odoo_read auto-includes company_id, refs carry a [CompanyName] suffix, and a write-time guard refuses creates and writes whose company tags disagree. Around it: a workbench/ subdir so fresh agents can write without a prior upload, the CRM template gains quotation-ready models, and two OpenClaw bumps land back to back.

Day 93: v0.5.4 and the Cursor That Jumped

v0.5.4 ships at 13:16: six read-write Odoo operator templates, the schema split, the FK-lookup grants, and a batch of chat-reliability edges. But the release nearly didn't go out on time, because a regression turned the chat composer hostile: typing into the middle of a draft jumped the cursor to the end after every character. The first fix (PR #413) was a clean hypothesis that didn't survive a re-test on staging. The real fix (PR #414) was to delete the code that caused it: a bespoke onChange wrapper that turned out to be doing, badly, exactly what the upstream primitive already did well.

Day 92: Errors That Tell the Truth

A misleading error bubble gets replaced with an honest one: when Gemini 3 drops a thought_signature on a tool call, Pinchy now names the cause and tells the user that Retry usually clears it, instead of the old 'provider rejected the schema' copy that made people think their agent was broken. The same classifier emits a throttled audit event so 'how often does this happen?' becomes a SQL query. Around it: a build-once-run-many CI rebuild that stops every job from rebuilding the image, an Odoo fix that makes the id-vs-SKU trap impossible to miss, and the v0.5.4 release notes get finalised ahead of tomorrow's cut.

Day 91: The Default Gets Opinionated

Fifteen PRs land today, and unlike yesterday's single Odoo cluster they don't share a folder. They share a question: what does the agent run on, and what is it allowed to touch? The headline is the default model tier moving from fast to balanced. New agents now default to Sonnet / GPT-5.5 / Gemini-Pro instead of the cheap-fast tier, because agent workloads are not chatbot workloads. The same PR kills a gpt-4o-mini selection bug hiding in a date parser. By evening the agent finally gets a place to write: pinchy_write lands with an always-on read side, memory-file changes become an audit event, and two guardrails close off the broken-but-shiny model and the unreachable Ollama URL.

Day 90: A Monday on Odoo

Five PRs merge today. Every one of them in the Odoo cluster. The headline is the schema split Day 89 set up: odoo_schema becomes odoo_list_models + odoo_describe_model with a compact type encoding that takes the per-model context burn from ~18 kB down to single-digit kB. A deprecated alias keeps pre-v0.5.4 agents alive while a data-migration rewrites every agent's allowed_tools list. By evening the self-ref chain lands: odoo_create now emits a _pinchy_ref on every record, so the Bookkeeper's create-invoice-then-attach-receipt flow stops being two disconnected tool calls. The MCP and Microsoft 365 branches that Day 89 talked about are still in review. Both were pushed today, neither merged.

Day 89: Two Weeks of Production

Sunday. No commits today, but two weeks of using Pinchy in production has produced enough notes for one. Running my own bookkeeping through it surfaced several Odoo edges that have since landed as fixes. The attachment work two weeks ago started here: uploading a PDF wasn't possible on Day 70-something, and the chain of commits to make it real is what Days 81 and 82 were. The next stretch of thinking, multi-integration agents, a task model, MCP, is where the questions are.

Day 88: The Saturday Drain

Saturday. One commit lands at 06:14: the cleanup tail of yesterday's dispatch-probe shakeout. The web E2E suite hadn't picked up the config.apply rate-limit drain that the odoo and email suites got yesterday, and was failing on the rare run that interleaved with the odoo suite's startup. Same fix, different file. After that, the day stays quiet.

Day 87: The Silent No-Op

Friday. A new plugin-tool-coverage test goes red for five Pinchy plugins. Behind the red: OpenClaw 5.3 silently no-ops registerTool() unless contracts.tools is declared in the plugin manifest, and the field was missing in five of the seven manifests. Nineteen tools had been registering successfully on the team's dev OpenClaw and silently failing on the production one. Three rounds of CI fixes follow as the dispatch probes hit a different shape of failure each time.

Day 86: The Attach-File Tool

Thursday. odoo_attach_file lands: read-write Odoo operator agents can now attach uploaded files to Odoo records as ir.attachment, with permission-gated writes and the file lifted from the agent's workspace volume. The same PR fixes a vision-resolver bug where templates declaring vision capabilities were falling through to a text-only model. By afternoon, a code-review pass hardens the new tool against path traversal and OOMs with eleven tests pinning down the failure modes.

Day 85: Naming the Version

Wednesday. An end user upgrading Pinchy had no reliable way to confirm which version was actually running: /api/health returned only ok, /api/diagnostics was Domain-Lock-gated, and the OCI image-version label was empty. A new public /api/version endpoint plus populated OCI labels close the gap. Meanwhile, a staging click-through for v0.5.4 reveals that 14 of 22 Odoo templates declare required models that odoo-sync never probes. The operator templates were quietly disabled despite the modules being present. A drift-guard test pins the failure mode down.

Day 84: Rebuilding the Snapshot Chain

Tuesday. Drizzle migrations 0025–0030 got tangled during a rebase: the snapshot chain that each migration carries as its predecessor was no longer consistent. The fix rebuilds the chain by walking the migrations in their committed order and regenerating each snapshot from the previous one. A new contract test guards the chain integrity and prefix collisions; a runbook captures the recovery procedure for the next time.

Day 83: v0.5.3 and the Per-Agent Runtime

Monday. v0.5.3 ships, and the headline is the one #199 has been pointing at since the chat first started using WebSockets: switching between agents in the sidebar no longer tears down the runtime that's mid-stream. A new ChatSessionProvider mounts one runtime per agent at the (app) layout level, the sidebar shows a pulse on agents with an active turn and a red dot on agents that errored, and a chat.background_run_completed audit event makes background activity auditable without leaking content.

Day 82: Attachments That Open

Sunday. The composer's attachment chip becomes a real preview surface. Click a PDF, an in-app modal renders it with the actual document on screen rather than a forced download. The uploads route picks up authentication for GET (it had been open) and a deliberate X-Frame-Options override for the route alone, so the modal can embed the document without weakening the rest of the app. Plus: an Agent Workspaces concept page that ties the architecture together.

Day 81: Saturday on Three Fronts

Saturday. Three threads that have been queued for a while land together. Images compress client-side to WebP before crossing the wire and the WS frame limit goes from 1 MB to 25 MB to match. An Ollama Cloud model that started returning silent HTTP 500s gets dropped from the allowlist, with the chat learning to render the error as a structured switch-model bubble instead of a raw status code. And the #199 Layer A test finally proves the cache-retry behaviour Layer B fixed yesterday.

Day 80: v0.5.2 and the Drain That Doesn't Stop

Friday. v0.5.2 ships, rolling up the security fix and Ollama-local work from the last two days alongside today's new headline: client-router keeps draining the OpenClaw stream after the browser disconnects. Without it, an assistant message that finished generating while the user navigated away never made it into the cache, so the next page load looked like a half-finished reply that had quietly succeeded somewhere off-screen.

Day 79: Personal Means Personal

Thursday. A security fix lands quietly in assertAgentAccess: an admin used to be able to GET/PATCH/DELETE another user's personal agent by calling the API directly, even though the UI didn't expose them. The admin fast-path now checks isPersonal before granting access. The fix sits inside a much larger E2E pass that's verifying (across groups, invites, audit log, permissions, knowledge base) that what the UI shows and what the API enforces actually agree.

Day 78: v0.5.1 and the Host Rewrite

Wednesday. v0.5.1 ships as a hotfix for a v0.5.0 startup loop: the generated openclaw.json was missing a baseUrl field for the bundled Anthropic/OpenAI/Google providers, and OpenClaw refused to come up. The same release fixes the long-standing Ollama-local setup, which never worked on a default install because host.docker.internal doesn't resolve inside the OpenClaw container. The fix is a host-gateway alias and a hostname rewrite that runs invisibly at config-write time.

Day 77: The Day After

Tuesday. v0.5.0 has been out for less than 24 hours and the inbox already has the kind of feedback that's the most useful kind: the docs were almost right. BETTER_AUTH_URL was documented but not actually passed through to the container, the domain-lock middleware was rejecting internal callbacks, and the post-release docs got a polish pass. Also: a 234-line AGENTS.md gets carved out of CLAUDE.md so a different coding assistant has its own contract.

Day 76: v0.5.0 Goes Out

Monday. v0.5.0 ships after a morning of CI failures get hammered out one bracket at a time. Underneath the release tag: a plugin-manifest contract that every Pinchy plugin must now satisfy at build time, HTTP mocks for Gmail and Brave so the external plugins are exercised in E2E, and a ~50-second cold-start improvement from pre-warming runtime deps and disabling the plugins nobody asked for.

Day 75: The Bonjour Watchdog and Other Ghosts

Sunday. OpenClaw runtime bumps from 2026.4.14 to 2026.4.27, in two hops. The big news in 4.27: a per-agent auth-profiles.json that scopes credentials per agent. The smaller news: a Bonjour watchdog that was sending SIGTERM to the container in environments where mDNS announcement is blocked. Plus: an idempotency contract test for cold-start regenerate, and the CLAUDE.md docs get a careful re-read.

Day 74: The Saturday Hardening Pass

Saturday. CSRF gate on every state-changing API route. Password policy moves to 12 chars plus a breach-list check. Audit emissions go from fire-and-forget to a single await-or-defer pattern. Two large files (1097 and 1395 lines) get split into focused modules. SSRF guard closes a DNS-rebinding TOCTOU. RBAC filter lands on the telegram-bots endpoint. The kind of list that gets handed to procurement before any demo.

Day 73: A Chat That Stops Claiming Green

Friday. The chat-status indicator had been defaulting to connected during cold-start: a small lie that made every fresh session look broken. Two PRs land that fix the lie at both ends, plus a Starting Agent state that holds until the first message is actually on screen. In parallel: audit PDF export with an integrity-hash CSV column, the audit refactor that backs it, and the agent-create cascade fix gets the matching test.

Day 72: Agent Create Without the Cascade

Thursday. Creating a new agent had been quietly triggering a full gateway restart cascade: every plugin reloaded, every connection paused for several seconds. The fix swaps the inotify-driven config reload for a WebSocket RPC push, with a fire-and-forget retry that never blocks on the gateway's own boot. Plus: an inotify watcher restores secrets ownership in milliseconds, telegram silent EACCES swallows are plugged, and CI starts running against the production image.

Day 71: The 0600 Dance

Wednesday. The secrets-ownership bug surfaces in integration: secrets.json gets the right mode but the wrong owner, OpenClaw refuses to read it, the gateway boots without a token. The fix is a chown + chmod dance every gateway boot. Plus: staging Caddyfile drops the timeout that was eating cold-start requests, the composer stops greying out during reconnects, and the v0.5.0 upgrade notes consolidate.

Day 70: When the Licence Has Teeth

Tuesday. The seat cap stops being a label and starts being enforcement: invite endpoint blocks at the maxUsers count, banner warns ahead of it, audit log records the block. The chat UI grows real delivery states, sending/sent/failed, with a retry path that knows the difference between a dropped message and a half-streamed reply. openclaw-node 0.7.0 ships.

Day 69: Switching Off the Lambda

Monday. The AWS Lambda that issued every trial key for the last two months gets a one-shot decommission workflow and disappears. The Odoo addon learns to look like part of Odoo: Pinchy logo on the menu, fields freeze after activation, the trial partner is a company. Plus: Squawk lints destructive migrations on every PR and the upgrade notes get a policy.

Day 68: When the Form Actually Submits

Sunday. Two commits on the website, both about getting Saturday's Odoo trial endpoint to work from a real browser instead of from a curl command. The first teaches CI to forward the shared-secret env var into the Astro build. The second swaps a custom auth header for Authorization: Bearer because Odoo's default CORS handling allows one and not the other.

Day 67: The Trial Comes Home

Saturday. The trial endpoint leaves AWS for a custom Odoo addon, built end-to-end in one TDD pass: licence model, trial throttle, mail template, admin UI, CI. Plus: SecretRef follow-ups, Squawk-CLI for destructive-migration linting, and a release-notes template that refuses to merge without Breaking changes and Upgrade notes as separate subsections.

Day 66: Streams That Announce Themselves

openclaw-node 0.6.0 ships: agent_start and agent_end lifecycle chunks bracket every turn, lifecycle errors surface as real error chunks, duplicate errors get deduped between paths. Plus: v0.4.5 release notes consolidate the week, and the integration-delete flow gets one more round of polish.

Day 65: Secrets Out of the Config

The big one. Every secret that used to live in plaintext in openclaw.json (gateway tokens, Telegram bot tokens, Brave and Ollama API keys, Odoo passwords) moves to a tmpfs-backed secrets.json referenced by opaque SecretRef markers. Delivery status and retry ship in the same push, along with a safer integration-delete flow.

Day 64: The Open Web, Closed Carefully

Web Search merges. Pinchy's first integration past Odoo. Brave Search under the hood, a per-agent domain list with an Include/Exclude toggle, SSRF guards that validate every redirect hop, and a plugin-config refactor that pays for itself the moment a second plugin needs config.

Day 63: Billing Where We Already Live

A quiet day on main: one commit on a branch, the last review round before Web Search merges. The interesting work today was off the keyboard: the first companies asking about a production deployment, and the decision to run Pinchy's subscriptions through the same Odoo instance Pinchy already integrates with.

Day 62: Gmail Without the Dev Console

Gmail ships. The first Pinchy integration whose setup story includes a trip through the Google Cloud Console, folded into a wizard that hands over the redirect URI, stores credentials once per workspace, and treats abandoned OAuth flows as a visible pending state instead of limbo.

Day 61: Docs That Know Who's Asking

Two branches, both moving. The pinchy-docs plugin learning to scope documentation to the agent that's asking; the password reset page finally existing, instead of routing resets through an invite form pretending to be one.

Day 60: One Guide Instead of Two

Saturday. One commit on main. Two docs pages that had been saying almost the same thing about HTTPS setup. Nearly the same is the dangerous part. The fix is not adding content; it's picking which page owns the flow and making the in-app link point there.

Day 59: Three Releases Before Lunch

Three point releases in ten hours, each closing a real report from the first day v0.4.0 spent in the wild: a Docker port bypassing UFW, a Caddyfile dpkg refused to install, a validator that trusted a public endpoint, and one unreadable row that hid every integration.

Day 58: v0.4.0 Is Out

Release day. Three last-hours fixes to Ollama Cloud support, then a post-release sweep through the website that found the same pattern of drift one layer up: marketing claims that had quietly stopped matching the product. The kind of thing you only find by running the product, and reading the product, yourself.

Day 57: A UI That Stops Lying

Two related clean-ups before shipping v0.4.0: the agent permission screen was offering toggles that didn't do anything, and Smithers had been repeating the docs back at itself instead of reading them. Both cut.

Day 56: When the Other Side Disappears

A chat platform is only as trustworthy as its worst moment. Today was about what happens when the model backend vanishes mid-stream, and how the user finds out.

Day 55: Shared Memory, Different Boundaries

Three conversations in one day, all circling the same problem: companies want agents that can access shared knowledge, but only with the right boundaries, roles, and permissions in place.

Day 54: Making the Dashboard Honest

A usage dashboard is only useful if the numbers are trustworthy. Today was about timezones, cache tokens, missing days, retry states, and all the details that turn raw token counts into something a company can actually rely on.

Day 53: Three Tracks Toward v0.4.0

Usage tracking, Telegram guardrails, and a growing library of Odoo templates: three parallel workstreams that all point in the same direction: making Pinchy operational for real teams.

Day 52: Stabilizing the Edges

The day after a feature lands is when the real product work starts: fixing merge fallout, tightening error handling, improving Odoo validation UX, and making sure edge cases fail clearly instead of mysteriously.

Day 51: Complexity and Regulation

Day two of the scheduling software workshop. And someone points out that a piece of EU regulation about to take effect is, for Pinchy, essentially a tailwind.

Day 50: Audit Outcome

The audit trail now shows whether each action succeeded or failed. Plus pre-built images ship to GitHub Container Registry, and a two-day workshop reveals what it really means to automate complex business workflows.

Day 49: Audit Trail v2

A new audit log format that records not just what happened, but whether it worked. Plus a plugin that lets Smithers read Pinchy's own documentation on demand.

Day 48: Qwen by Default

A small change to Pinchy's default model selection logic that makes local Ollama dramatically more reliable. Plus a WebSocket fix nobody asked for but everybody needed.

Day 47: The Merge Day

The insecure mode banner officially merges. Ollama gets tool-calling enforcement. And a deep dive into why dependencies are a constant maintenance tax.

Day 46: Pull, Not Build

Pinchy now ships pre-built Docker images instead of building them on every deploy. First-time setup goes from 15 minutes to under a minute.

Day 45: Polish Day

Easter weekend, low-energy work, but the small UX details that make the difference between a developer toy and a real product.

Day 44: Two Tracks

Odoo agent templates that configure themselves, and local Ollama support that discovers models automatically. Two features, 45 commits, one very productive day.

Day 43: Grok Sent Him

Someone found Pinchy because an LLM recommended it. The Odoo config went from 867KB to 3KB. And I'm starting to understand who Pinchy is actually for.

Day 42: The Merge

Telegram is merged. The multi-user test found bugs, I fixed them, and the biggest feature branch in Pinchy's history finally landed on main.

Day 41: Telegram Is Done

The Telegram integration that almost derailed everything is finally finished. Plus two meetings that confirmed Pinchy is heading in exactly the right direction.

Day 40: The Email Question

Every company I've talked to wants the same thing: an agent that reads their email. Here's what that actually means to build.

Day 39: The Mock Server Trick

How a fake Telegram server made the real integration better, and why the Telegram feedback loop matters more than it sounds.

Day 38: v0.3.0

The deployment release ships, Telegram goes multi-bot, a recruitment company wants to automate their entire workflow with Pinchy, and the product roadmap writes itself.

Day 37: Three Calls, Three Countries

An Austrian manufacturer wants AI-generated quotes from their ERP. An Irish fleet management company wants to turn NanoClaw into a team tool. And I'm learning what 'enterprise-ready' actually means.

Day 36: Deployment Hardening

Fixing everything that breaks when Pinchy leaves localhost: cookies, HTTPS, OpenClaw restarts, Telegram stability, and the gap between 'works in Docker' and 'works in production.'

Day 35: The First Real Deploy

A pilot user tried to deploy Pinchy on Hetzner. It didn't go smoothly. So I built the deployment docs, a loading page, cloud-init automation, and fixed every issue he hit, in one day.

Day 34: The Scaling Question

Telegram integration, security hardening, dynamic model selection, and the uncomfortable realization that demand is outpacing what one person can build.

Day 33: Show Me the Tokens

A full usage dashboard with cost tracking, a screenshot CI pipeline that fought back hard, and a WebSocket fix that should have been obvious.

Day 32: The PDF That Needed Eyes

Building a PDF reader that actually works, from text extraction to vision fallback, plus five new feature pages, an automated screenshot pipeline, and a security fix that couldn't wait.

What Jensen Huang's OpenClaw Strategy Means for Pinchy

Nvidia's NemoClaw validates the enterprise OpenClaw market. Here's how Pinchy fits into the picture, and why infrastructure and application layers are complementary, not competitive.

Day 31: Three Talks in Ten Days

v0.2.0 shipped, a freelancer meetup talk delivered, and the star challenge score: Vibecoding 20, Freelancers 10. Plus: Nvidia just validated our entire market.

Day 30: The Last Mile

21 commits, zero new features. Why we spent an entire day on polish before shipping v0.2.0, and why that matters more than the next feature.

Day 29: 100 Stars and Three Handshakes

Two major PRs merged, a sold-out meetup talk, three companies wanting to integrate Pinchy, and crossing 100 GitHub stars.

Day 28: Secrets and Stages

Building defense-in-depth for audit logs, and getting ready to tell the Pinchy story to a sold-out meetup.

Day 27: Release Prep

Testing for v0.2.0, a look at everything shipping in the next release, and why openclaw-node deserves more attention.

Day 26: Three Branches, One Saturday

Enterprise key system, Telegram integration, and provider config migration. Three feature branches running in parallel on a Saturday.

Day 25: Messaging, Not Workflows

Telegram integration design, the first external feature request, 5 community PRs, and a clarity moment: Pinchy is a messaging tool.

Day 24: Tokens Are Money

RBAC is merged, a real company wants to pilot Pinchy, and I'm learning that token cost is the concern nobody talks about publicly.

Day 23: The Bug-Free Demo

First demo without a single bug. A cybersecurity startup grills Pinchy on security. RBAC edge cases continue. And the confidence is building.

Day 22: The Perfect Setup

Peter Steinberger says nobody's building enterprise OpenClaw tooling. 30 seconds later, I'm on stage showing Pinchy. Plus: RBAC edge cases and why manual testing still matters.

Day 21: The Calls That Shaped the Roadmap

Multiple demo calls, an enterprise from Dubai, a potential partnership, a talk at tomorrow's 280-person meetup, and the first enterprise feature: RBAC.

Day 20: v0.1.0

542 commits. 33 PRs. 20 days. Pinchy has its first official release.

Day 19: Release Ready

24 commits, 5 PRs, zero new features. Just making Docker startup actually work. The unglamorous work that makes a v1 possible.

Day 18: First Users, First Lessons

A no-show demo, the founder impatience problem, and 14 commits making Pinchy actually work for the people who cloned it.

Day 17: The Merge

Coded in the dentist's waiting room. Claude kept going during the cleaning. Then PR #21 landed: Better Auth replaces Auth.js in Pinchy.

Day 16: Auth, Mobile, and Real Users

A complete auth system migration, mobile navigation from scratch, and a conversation from Brazil that opened up new use cases.

Day 15: The Fix-Everything Day

Yesterday's demo broke things. Today I fixed all of them. And then kept going.

Day 13: The Thinking Day

Zero commits. No code. Just the questions that shape the next two weeks.

Day 14: The Demo That Broke Everything

A live demo, an Anthropic outage, and two GitHub issues filed before the call was over.

Day 12: The Audit Trail Closes

PR #3 merged. Every tool call now leaves a trace. Plus: a marketing experiment that backfired.

Day 11: The Ecosystem Day

Two npm releases, audit trail upgrades, and three calls with people who want to build with us.

Day 10: Context Belongs to People, Not Agents

A data model that felt right at 2 agents broke at 5. Plus: the audit trail gets honest about tool usage.

Day 9: Making It Feel Right

31 commits. 128 files. The first PR merge, fun-emoji avatars, and why polish isn't optional.

Day 8: Give Your Agent a Face

11 commits. 72 files. 3,149 lines. Why giving your AI agent a face changes everything.

Day 7: Agents Are People Too

16 commits. A product philosophy, a lobster in a bowtie, and the rewrite I didn't plan.

Day 6: The Personality Layer

26 commits. 5,000 lines. What makes an AI agent feel like yours?

Day 5: The Enterprise Gauntlet

77 commits. 15,000 lines of code. A conversation in a gym that changed everything.

Day 4: From Solo to Team

63 commits. 12,000 lines of code. One question: what happens when it's not just you anymore?

Day 3: Encryption, Onboarding, and a Trojan Horse

30 commits. 57 files changed. 7,652 lines added. Day 3 was intense.

Day 2: From Zero to Chat in One Day

Yesterday I had a website and a dream. Today I have a working application. 29 commits, roughly 2,000 lines of code, and you can actually talk to an AI agent through Pinchy's UI.

Building Pinchy in Public: Day 1

Today is Day 1. Pinchy doesn't exist yet, not as code, anyway. It exists as an idea, a website, and this blog post. I'm building in public, which means you get to see the messy beginning, not just...