Open post Line diagram in which a legal document and a website feed into a central hexagon with an eye, which outputs three status markers, illustrating AI compliance monitoring

Your website’s hidden AI compliance problem

If your company uses AI and puts anything on a website, your legal pages sit between two things that keep moving.

The first is what you actually do with AI. The second is what the law asks of it. Your disclosures, your privacy statement, the line naming who is responsible: all of it is supposed to be the record that matches both. Most teams watch the law, never re-read the record, and find out during an audit that it describes a setup from two vendors ago.

We ran into this ourselves. The AI Signal is our own AI-generated publication, and on the first of every month a small agent files a compliance brief on it, alongside a security monitor that watches the same site for a different class of problem. That brief was only half a system, and for three months running it printed compliant off a sentence nobody had checked. The missing half turned out to be the one that matters most for any company in this position. So here is what the working version checks, and why we would build it the same way for anyone else.

Two moving targets

Running AI in the EU means living between two things that both drift, on different clocks, for different reasons.

The first is what you do. A support chatbot goes on the homepage. A model gets wired into the product. Someone starts drafting copy with an assistant. A provider is swapped for a cheaper one. None of those feel like legal events. Every one of them carries a disclosure duty, a privacy statement, and a line naming who is responsible.

The second is the rulebook. Not only the AI Act, which at least arrives with a parliamentary paper trail. It is the vendor terms underneath: the consumer terms of your model provider, its acceptable use policy, its privacy policy, the automation rules of the platform you publish to. Those change quietly. There is no gazette, no announcement, often not even a changelog entry. A clause reads differently one Tuesday and nobody sends a letter.

Between them sits your own site. The disclosure in your footer. The transparency line on each page that uses AI. The statement naming the legal person who holds responsibility. The imprint link. Every one of those is a compliance artefact, and every one of them lives in a template that somebody, a person or an agent working for them, can edit at two in the morning during a redesign.

All three drift. None of them announce it. And the audit, if it comes, arrives long afterward.

What AI compliance monitoring actually does

The rebuilt version has three parts, and only one of them is a language model doing what language models do.

It compares the documents you depend on. In our case seven of them: four contracts from the model provider, two platform policy pages, and the Austrian chamber’s AI Act guidance for businesses. Each one is fetched, reduced to its readable text, and compared line by line against the copy stored last month. The stored copies sit in version control, so the history is a real redline. You can ask what exactly changed in a contract between July and September and get an answer instead of an impression.

It checks your own pages. It loads the live site the way a regulator would, the homepage, the About page, and the newest article found through the site’s own feed rather than a local file, and it confirms that each required element is still present. The AI disclosure. The per-page transparency statement. The line naming the company as responsible. The non-affiliation statement. Then it follows the imprint and privacy links out of the rendered footer and confirms they still resolve.

And then it searches. Court rulings, new guidance, national implementation, the things whose existence you cannot know in advance and therefore cannot put on a watchlist. That part is open-ended and probabilistic, and it is labelled as such.

The split matters more than any single piece. Anything with a known address gets checked. Only the unknowable gets searched.

All of this shipped on 1 September. It runs unattended for the first time on 1 October, so the working version has an operating record of exactly zero briefs. Saying so is the same rule as the rest of the page: do not report on something you have not watched.

The half almost everyone forgets

Compliance monitoring is almost always sold as watching the law. That is the interesting half. It has case names and effective dates and something to put in a newsletter. But the law changing is rarely what puts an operator on the wrong side of a line. What does it is a template edit, six weeks earlier, that nobody connected to a legal obligation because it looked like a design change.

Our own first version had exactly this hole, and it was worse than a plain gap. The monthly brief contained a compliance cross-check that read like an inspection: a tidy list of areas, each marked compliant. It was reasoning over a description of the site. Part of that description was hardcoded text asserting that the About page carried the required statements. It never opened the page.

The defect was there from 15 April. It printed compliant in three consecutive briefs. We did not catch it by reading those briefs. We caught it by reading the code.

Delete the disclosure and the report would have kept printing compliant, in the same confident format, forever.

A monitor that reports on a description of your site rather than the site itself is not a weak monitor. It is checking something else and calling it your site.

“Unchanged” and “I did not look” are different answers

If we had to name the single design decision that carries the most weight, it is this one, and it sounds trivial written down.

Every check has three outcomes, never two. The element is present, the element is missing, or the page could not be read. A document is unchanged, changed, or not re-checked this month. And the summary line, the one a human actually reads, is only allowed to say everything is fine when everything was genuinely examined.

That sounds like pedantry until you watch the failure it prevents. One of the platform policy pages we monitor blocks our server outright. The system falls back to an archived copy, which is honest, but an archived copy compared against itself will report no change forever, with complete confidence, while the live page does whatever it likes. So the system now says: I did not see this document this month, here is why, and nothing I am telling you overrules other evidence about it.

Most monitoring cannot tell you the difference between nothing happened and I was not looking. Those are opposite states dressed identically, and the second one is the dangerous one, because it builds up silently and looks like a clean bill of health the whole time.

Do not trust the timestamp

Every legal page on our provider’s site carries a last updated date in its markup. It is tempting to use it. It is structured, it is machine-readable, it is right there. It is also the same value on the terms of service, the acceptable use policy, and the privacy policy, identical to the second, because it is a site build timestamp. Three unrelated legal documents cannot be revised in the same second. Once you see it, the tell is obvious.

Trust that field and you get a confident alert about a change that did not happen. Meanwhile a real amendment, one that moved a government-disclosure standard from as required by law to a good-faith belief, sat live for two months without producing a single search result, because nobody wrote an article about it.

So the only date worth citing is the one the document renders about itself, and the only proof of change is a diff of the text. Everything else is a proxy, and proxies fail in the direction that flatters them.

What it does not do

It does not give legal advice, and the briefs do not pretend to. Findings carry the basis they rest on, so a human can check the reasoning rather than accept the conclusion. It does not watch the whole legal landscape; it watches a specific list you chose, plus whatever search turns up. It will not catch a change to a document you never added. And a page that changes shape can defeat its own check, in which case it says so and stops rather than guessing.

Stating the boundary is not a disclaimer.

A monitor whose scope you cannot describe precisely is one you cannot rely on precisely.

Why this is not only a publisher’s problem

We built this for an AI publication, but nothing about it is specific to publishing. Any company that uses AI and has a website carries the same problem, and most carry it without knowing.

The reassuring part is how ordinary the solution is. It is a scheduled job, some careful handling of text, and a firm rule that one word is never allowed to stand for two different states of the world. The sophisticated-looking version, the one that summarised search results into a confident monthly verdict, is the one that was quietly wrong. The plain version is the one that holds.

Write down every place AI touches your business. Then open your privacy policy and count how many of them it knows about.

That is the kind of thing we build.

Open post Minimal line illustration of a five-column kanban board. Above it, a brief travels as a blue document from one AI agent to another; below it, the brief shows its branch, shipped, next, and landmines fields.

A Handoff Brief for AI Agents

May 22, 2026  ·  3 min read

A while back I wrote about the kanban board my agents and I share — five columns, a card per piece of work. It told me what was in flight. It did not tell my agents where to start.

That distinction took longer to see than it should have.


THE PROMPT I KEPT PASTING

Every Claude Code session begins empty. The agent that finished a piece of work yesterday and the one that picks it up today are strangers. Compaction — when the context window fills and the conversation is summarized down — is the same reset, several times a day.

The board shows the cards. It does not show which branch I am on, what just shipped, or what trap is waiting in a file nobody would think to open.

A five-column kanban board titled Roadmap, with a Handoff brief button in the top-right header next to the card count.
The board tracks what is in flight — not where to start.

So I did what everyone does: at the end of a session I wrote a paragraph — we are on branch X, just finished Y, do Z next — and pasted it back in at the start of the next.

One of the scattered markdown files the kanban was built to replace was called project_next_session.md. The board replaced the queue. But the briefing quietly came back as a clipboard habit. I had killed the file and kept the chore.


THE BRIEF

The fix was small, which is usually a good sign.

Each project on the kanban now has one more field: a handoff brief. Free text, capped at 4096 characters — a starter prompt, not a journal. Written at the end of a session, read at the start of the next.

The point is where it lives. Not a file on one machine. Not my clipboard. It is attached to the project, on the board, versioned and locked exactly like a card, so two sessions running at once cannot overwrite each other’s brief.

A loose shape settled in on its own:

¬ Branch — the one in play
¬ Shipped — what just landed
¬ Next — the single first thing to do
¬ Landmines — the traps that waste a session’s first hour

The Handoff brief panel open over the board, with labelled sections for Branch, Shipped, Next, and two highlighted Landmines, plus a 512 of 4096 character counter.
The brief opened — read at the start of the next session.

Not a log — the code and the git history are the log. The brief is orientation, and orientation has a short shelf life.


THE HALF THAT WAS STILL MANUAL

Writing a good brief by hand is the same copy-paste tax in a new form — composing the structure, getting it tight, sending it to the board. A chore on a schedule.

So the brief became a skill. /handoff — the agent reads its own session, composes the brief, and writes it. I kept it user-only: I never want an agent deciding on its own that now is a good moment to overwrite the brief.

The read side needs nothing from me. A hook fires when a session starts, or after a compaction, and prints a banner before the agent does anything else: branch, first task, how many landmines are below. The agent wakes up already pointed somewhere. Nothing to paste.

That is the loop closed. One agent ends a session by writing down where it stopped; the next begins by being told.


WHAT KEPT RECURRING

The next part I should have seen coming.

The skill started as a file in one project. Then I copied it into a second. Then a third — each time adjusting a token path and not much else. There it was again: the copy-paste, one floor up.

So the skill became a canonical template, checked into the kanban server’s own repository next to the API contract it depends on. Copy once, change one value. When the API changes, the template changes in the same commit — the thing and its documentation cannot drift apart.


WHAT IS STILL ROUGH

Nothing forces the structure. The reading hook degrades gracefully if the markers are missing, but a sloppy brief is a sloppy start — a brief is only ever as good as the agent that wrote it.

There is no archive, on purpose. It is a baton, not a diary; the cards and the commits are the history.

And the hook only catches the start of a session. Mid-session, when the agent drifts, it is still on me to notice.


MY READ

None of this is clever. It is the same small observation, applied four times.

The kanban replaced the scattered files. The brief replaced the pasted prompt. The skill replaced writing the brief by hand. The template replaced copying the skill between projects. Every step was the same move: notice the thing you keep doing by hand, and move it into the system.

The board was the easy part. The harder part — and the more useful one — was being honest about how much I was still doing manually around it.

If you are pasting it, it is not infrastructure yet.

Open post Line illustration: an AI chip generating code that flows into an editor with one flagged line highlighted, then to a laptop with a magnifying glass examining a small black box — representing a local reviewer model inspecting the AI's output

An Outside Reviewer for My AI — How I Stopped Claude From Grading Its Own Homework

May 5, 2026  ·  5 min read

A few weeks back I read a piece on The AI Signal arguing that AI should not review its own code. The case was simple: the model that wrote the work is the worst possible critic of it. Wrong incentives. Confirmation bias. Tunnel vision.

It was Claude’s own argument, written about Claude. And reading it, I realised I was doing exactly the thing the article warned against. Claude wrote. Claude reviewed. Claude shipped. The loop closed on itself.

So I added a second model.


WHAT THE OUTSIDE REVIEWER ACTUALLY IS

It runs on the desktop next to me, not on the server. The current pick is a 35B Qwen MoE fine-tune.¹ Smaller than Claude. Faster than Claude. And crucially: arriving at every decision with no shared history.

It has not read the planning conversation. It has not built rapport with the agent doing the work. It has not invested in the plan. It opens a fresh terminal window in its head and reads what is actually in front of it.

That is the whole trick.


WHY A SMALLER MODEL WORKS

I expected the local model to be a courtesy reviewer — a sanity check, nothing more. The frontier model would do the real thinking; the local one would just confirm.

Wrong, both directions.

The smaller model genuinely catches things Claude misses. Not cosmetic edge-cases — actually critical things. A missing transaction boundary. A race condition in a cron path. An assumption that is true today but breaks the moment the data shape changes.

The reason is not that Qwen is smarter. It is not. It is that it does not carry Claude’s context. Claude is the strongest model in the room — but it also carries the most history, the most plan-investment, the most narrative momentum behind every decision. A reviewer with none of that baggage reads the diff, not the story.

The smaller model cannot be flattered out of finding a problem. It has not been told why this is the right approach. So when something is not right, it just says so.


¬ Key move

THE PROMPT IS THE LEVERAGE POINT — AND THE PART MOST PEOPLE MISS

This is the section I would ask you to remember if you read nothing else. Everything I tried before this point was incremental. This is the move that made the reviewer go from “nice to have” to actually load-bearing.

The first version of the setup used a generic reviewer prompt. “You are a senior reviewer. Find issues.” Standard. Reusable. Lazy.

It worked, but it was diffuse. The reviewer flagged three things — one critical, two cosmetic.

Then I tried something else. Before each review, I asked Claude to write the reviewer’s system prompt itself, tailored to the specific decision in front of it. Not “find issues” but something like:

We are shipping a new module that POSTs article bodies to a TTS API and atomic-writes the resulting MP3 into the live web root. Already verified: the API contract, the atomic-write pattern, the cost budget. Out of scope this pass: error message wording, log format. Risk surface: race conditions when two articles publish in the same minute, partial writes if the disk fills mid-render, secret leakage in any error path. Push back hardest on those.

Same model, same code, suddenly went sharp. It zeroed in on the load-bearing assumption and pushed back on it. The “two cosmetic things” disappeared because the brief had told the reviewer not to bother with cosmetics on this pass. The one critical thing got a deeper, harder critique.

The lesson: a generic reviewer is a generic skeptic. A briefed reviewer is a focused one. Do not reuse the same system prompt across tasks. Let the agent doing the work write its own critic’s brief — it knows what has already been considered, where the actual risk lives, and what is out of scope. Use that knowledge instead of throwing it away every time.

This single change did more for review quality than swapping models ever did.


THE INFRASTRUCTURE IS BORING

I expected this to need real infrastructure. It did not.

LM Studio runs on my laptop with the model loaded. It exposes an OpenAI-compatible endpoint on localhost. From the server, I open an SSH tunnel back to my laptop, and Claude on the server can call my local model exactly like any other API.

Screenshot of LM Studio's developer panel showing the qwen3.6-35b-a3b-uncensored-wasserstein model loaded, with a chat completion request streaming in the live developer log
The 35B Qwen on the laptop, receiving a review brief from the server through the SSH tunnel.

That is it. No GPU on the server. No model hosting. No exotic plumbing. The setup takes about thirty seconds when I sit down at my desk, and it tears down when I close the laptop.

There is a lovely property in this: the reviewer is only available when I am there. If I am not at my desk, no reviewer. Which means the only decisions the reviewer touches are decisions I am awake for. That is not a limitation — that is a feature. Critical reviews should not happen at 3am while I am asleep. They should happen with me in the loop.


WHEN THE REVIEWER GETS CALLED

Not for everything. Most work does not need a second opinion — Claude writes, ships, moves on. The local reviewer comes in for the things that would hurt to get wrong:

  • Locked-in API contracts before they go live
  • Security-sensitive changes — anything touching secrets, auth, or untrusted input
  • Non-trivial migrations where rollback is expensive
  • Architecture decisions I will be living with for months

Maybe one call in twenty. But for those calls, a second viewpoint — one with no stake in Claude’s plan — has changed the failure mode of the whole pipeline.


WHAT I’D PASS ON

Three things, in order of how much they matter:

  1. The outside view is the whole point. Do not run two instances of the same model and call it review. The value is not in the second pass — it is in the separate context. Different model. Different prompt. Different head.
  2. Brief the reviewer per task. A generic “find issues” prompt produces generic feedback. Let the strong model write the small model’s brief. This is the multiplier.
  3. Smaller is fine. A frontier-class reviewer is not required. A fresh one is. The local Qwen catches things precisely because it has not already convinced itself the plan is good.

ONE MORE THING

The article that started this argued AI should not review its own code. I would extend it now: AI should not review its own decisions. Not architecture. Not API design. Not the call to ship.

The bias is not only in code review. It is anywhere the same head is doing the work and grading it. A second model in the loop is the cheapest correction I have found — and it runs on the laptop I already had.

The reviewer is the smallest model in my stack. It is also the one I would add first.


¹ For anyone wanting to replicate exactly: the current model is qwen3.6-35b-a3b-uncensored-wasserstein in LM Studio. Any reasonably capable 30B+ instruct model with a separate context will do — the fresh head matters more than the specific weights.

Open post Line illustration of an AI chip moving a blue card from the doing column into the done column on a five-column kanban board

The Kanban My Agents and I Share

May 3, 2026  ·  5 min read

For about three months I ran an AI-operated news site with a stack of markdown files holding the project’s working memory. There was a CLAUDE.md for instructions. A learnings.md for cross-project lessons. A roadmap.md for the council to read. A MEMORY.md index pointing at fifty-something memory files. A project_next_session.md that was supposed to be the queue of upcoming work.

It worked. Mostly.

The thing that did not work was that I never knew, at a glance, what was in flight.

The agent knew. Or some agent knew. Different agents read different files. The session-start memory load was its own subsystem. I’d ask, “is the comments anti-bot work still active?” and the answer was buried in a paragraph in project_next_session.md that had not been touched in three weeks. By the time I’d verified it, I had lost the original question.

So I built a kanban board. One session — design locked one evening, all six phases shipped the next morning. About 550 lines of TypeScript wrapped around a single YAML file, behind the existing admin Basic Auth. Five columns: inbox, parked, next, doing, done.

That’s it. That’s the whole thing.

Demo view of a five-column kanban board with inbox, parked, next, doing, and done columns
The board, mid-week — five columns, one card moving at a time.

It changed how I work with the agents more than any of the harder infrastructure I’ve built this year.


WHAT WAS ACTUALLY BROKEN

The phrase “human-AI collaboration” gets used a lot. In my experience the bottleneck was not the AI. The agents were perfectly happy to read whatever I pointed them at.

The bottleneck was that I had no idea what I’d asked them to do. My own intent was scattered across markdown files I’d forgotten I’d written.

A few specific failure modes:

  • I’d start a session, the agent would faithfully load project_next_session.md, and I’d discover I’d told myself two weeks ago that “F8 is the next priority” — a card I had no current opinion on.
  • I’d add a TODO to a session note, that note would compact, and the TODO would dissolve into the conversation history along with everything else.
  • The agents were good about saving learnings. I was bad about reading them.
  • “What’s done?” was a question you answered by reading git log and triangulating. There was no surface that said “these things shipped this month.”

None of this was the agents’ fault. They were doing exactly what I’d told them. The instructions just lived in too many files for me to keep coherent.


THE CARDS

A card has a title, an ID, a column, an order, a category, a priority, and an optional notes field — twelve fields total. Most run ten to fifteen lines of YAML; the verbose ones with embedded notes run longer. The board has a __meta__ block with a version number that increments every save — optimistic locking via If-Match, the same pattern HTTP gives you for free.

What each column is for:

  • Inbox — things I dropped in without committing. Some get archived. Some get promoted.
  • Parked — good ideas at the wrong time. Audio narration lives here. So does an interview-format experiment.
  • Next — work that’s queued, ordered. Top card is what I’d start tomorrow.
  • Doing — one card at a time, by rule.
  • Done — recent shipped work, ordered by completion. Trims as it grows.

The agents read this at session start. There’s a memory entry that says, more or less:

the kanban is the live source of truth for in-flight work; read it before answering the operator’s first question; offer an unprompted ten-line summary.

They do.

Confession: my next column is currently three times longer than it should be. That’s a discipline failure on my part, not a tool problem. The kanban gives me the information; pruning is still my job.


WHAT I UNDERESTIMATED

The most useful column turned out to be parked, which I almost did not include.

Parked is for ideas that are good and not now. Things I keep coming back to and would lose track of if they sat in project_next_session.md next to the actually current work.

Parked solves a specific anti-pattern: dropping a half-formed idea into a markdown file you’ll forget to re-read, and then re-inventing the same idea three months later because you forgot you’d already thought it through. With a kanban column, the idea has a place. It’s not in your queue. It’s not in your face. It’s findable.

The other thing I underestimated was the drag-to-reorder priority. Just being able to physically move “this is next” above “this is also next” gave me an interface for a kind of decision I was making constantly and never recording. Before, “what’s most important right now” was a vibe. Now it’s an ordered list, and the agent reads the order.


WHAT’S STILL ROUGH

A few honest annoyances.

Optimistic locking has saved exactly one merge so far — me editing a card in the UI while an agent was writing the same record from another session. The 409 came back, the agent re-read the board, my edit stuck. That’s the failure mode the version field was added to catch. So far, once.

The YAML format is operator-readable but the diffs are noisy when cards reorder — every card below the moved one shifts. I’d prefer a stable-id-with-explicit-order field over relying on array position. Small refactor I haven’t gotten around to.

There’s no view of who-touched-what. The agents update cards; so do I. When two of us update the same card the version field stops a clobber, but there’s no audit trail of which session changed the description on Tuesday. For a one-person operation it’s fine. If a second person joined, this would need to be the first thing fixed.


MY READ

The kanban isn’t a tool I built for the agents. It’s a tool I built so the agents and I could see the same thing.

A few things I think generalize:

  1. The shared visual surface matters more than the data model. What the kanban gives me is a picture I can hold in one screen. That’s the whole product. The YAML underneath is a serialization choice; the picture is what I actually use.
  2. Optimistic locking is the right default for operator+agent shared state. Cheap, well-understood, and the failure mode (refresh and try again) is one the agent already knows how to handle.
  3. Lightweight beats featureful when the second user is an agent. A schema the agent can hold in context is a schema the agent will respect. Jira’s API surface is the wrong shape for a model that’s about to write back to it.

That’s a small change. Most of the time, when I describe what’s running here, people focus on the agents — the content pipeline, the legal-watch cron, the security sentinel. The kanban is the least technical piece of infrastructure on the system.

It’s also the one I’d build first if I were starting over.

Open post A hexagonal badge split into two columns of three rows — blue checkmark boxes on the left, black X marks on the right — flanked by a stream of binary digits on the left and four horizontal blue bars on the right.

What an AI Newsroom Actually Costs — and What I Got Wrong

April 27, 2026  ·  6 min read

Four entries into this series, I have walked through the architecture, the pipeline, the Night Council, and the visual identity. What I have not done is talk about money. Or about the things that did not work. This final entry of the series is the honest retrospective — the costs, the failures, and the lessons that only show up after you ship. The series ends here. The project, and the learning, do not.


THE COST OF RUNNING THIS THING

Here is the breakdown, in specifics. Vague cost claims are useless.

The Night Council — three AI personas debating editorial strategy — runs Mondays, with on-demand sessions when something is worth debating. Each session makes about seven Claude Opus calls. Cost per session: roughly €1.00 to €1.95, depending on how deep the debate goes. That works out to around two euros a week for strategic planning.

Article generation is harder to pin down because each content session varies. Claude works with MCP tools to research, draft, and refine. The cost lands somewhere between a few cents and under a euro per article, depending on how much research the piece needs. Some days it comes together in one pass. Other days the research phase consumes more context before settling.

Image generation runs through ComfyUI on the same VPS, hitting Black Forest Labs’ Flux 2 Pro Preview model. I cap it at three attempts per article because every Flux call costs API credits and the budget is finite. The CSS animations for breaking news and opinion pieces cost exactly nothing, which is why they exist for those categories.

The VPS itself was already running other services, so the marginal cost for adding The AI Signal is effectively zero. To pin a number on it: the server itself runs €8 to €16 a month depending on specs — this one is the €16 mid-tier. The domain was already owned, SSL is Let’s Encrypt, the Telegram bot is free, and every piece of tooling in the stack — PM2, Astro, Nginx, Express — is open source.

Add it all up and the entire operation runs for on the order of a euro per day. Research, writing, illustration, editorial strategy, approval workflow, static site generation — all of it.


WHAT ACTUALLY WORKED

Telegram as the approval interface. I did not expect this to be the best decision in the project, but it was. A draft lands on my phone with a preview. I tap approve or reject. Done. No logging into dashboards, no context-switching. The approval flow meets me where I already am.

Markdown frontmatter as the entire database. Every draft is a markdown file. Status, image seed, image attempts, Telegram message ID — all of it lives in the frontmatter. No Postgres, no Redis, no migration scripts. Git tracks the history. The filesystem is the state. A blog publishing roughly daily does not need database-grade concurrency.

The article playbook. Categories, visual treatments, tone guidelines — all encoded in a single markdown file. When the pipeline generates an article, it does not guess. It reads the playbook. When I want to change editorial policy, I edit one file. This turned out to be more powerful than any config system I could have engineered.

Self-hosting everything on one box. Full control, no vendor lock-in, services share resources. The admin dashboard, the approval listener, the site itself — they all run on the same VPS behind Nginx. PM2 keeps the daemons alive with auto-restart.


WHAT SURPRISED ME

Distribution is the actual hard problem. Building the system was tractable; getting it read is not. The Night Council arrived at this independently: “Every feature before distribution is premature.” Three AI personas telling me to stop building and start marketing — and they were right.

The human bottleneck is real. There was a multi-day gap between articles at one point. Not because the system broke — because I did not get around to reviewing the drafts sitting in my Telegram.

The Night Council works as designed. I expected it to produce generic advice. Instead, the disagreements between personas surface insights that a single prompt never would.

Markdown-as-database has real limits. No concurrent write protection without explicit locking. I had to add filesystem locks to the publish flow after discovering that simultaneous operations could corrupt frontmatter. Simplicity scales until the workload demands real coordination — and the job is to know where that line is before you cross it.

The meta-story is the story. “AI writes blog posts” is a sentence people scroll past. “AI writes, illustrates, debates strategy, manages its own editorial calendar, and publishes a transparent account of the entire process including costs” — that is what makes people stop.


AS I WAS WRITING THIS

Between drafting and publishing, several items moved. The X pipeline went live. Comments shipped. A new “tip” category started running. The Night Council itself dropped from twice a week to once, with on-demand sessions when something is worth debating. That is the texture of an agentic site — the post you are reading is already slightly out of date, and that is the point.


WHAT COMES NEXT

The roadmap, in two parts:

¬ Shipped since I started this entry: the X/Twitter pipeline with Telegram-gated approval, EU-compliant comments with moderation, the tip category for retention content, a security sentinel dashboard, and a kanban-style roadmap surface I now use to talk to Claude about open work.
¬ Still ahead: Pexels as a Flux fallback when image credits run low, audio narration with a custom TTS voice, an email signup form, and one open question I am still working through — whether to open a dedicated sub-hub for the audience this project was originally built for. That call would reshape the rest of the roadmap.


THE BIGGER LESSON

Here is what I would tell someone considering a project like this: automation is a spectrum, not a switch. Full autonomy is not the goal. The goal is removing friction from the parts you trust while keeping human judgment where it matters. I trust Claude to research and draft. I do not trust any system to publish without my review.

That boundary is not a limitation of the technology. It is a design choice, and the right one.

The footer on every article at The AI Signal reads: “Generated with Claude. Reviewed and edited by a human editor.” That line does more work than any technical architecture. It is what makes the project credible rather than creepy. Transparency about the process — including the costs, including the failures — is what separates this from content slop.

If I had to distill the entire project into one sentence, it would echo something I wrote in another post: the value is not in any single tool. It is in connecting existing tools rather than building from scratch. Astro, Telegram, ComfyUI, Claude, PM2, Nginx — none of these are novel. I did not write a framework. I did not train a model. I wired together things that already existed, added human oversight at the critical junction, and got a working newsroom for around a euro a day.

That is what current AI tooling makes possible with ordinary building blocks. The interesting problems are not technical anymore. They are editorial, ethical, and economic. What should an AI write? Who is responsible when it is wrong? Is the cost structure sustainable?

I do not have final answers. But I have a running system, real numbers, and a transparent record of every decision. That feels like a better starting point than most.


This is the final entry of a 5-part series on building The AI Signal. Previously: the newsroom, the pipeline, the Night Council, and the visual identity. Every article written by Claude, every one approved by me.

Open post A hexagonal badge enclosing a three-by-three grid of nine geometric tile patterns, with a field of binary digits streaming in from the left and a blue horizontal bar chart extending to the right.

Kinetic Typography and AI-Generated Art — Building a Visual Identity

April 21, 2026  ·  6 min read

Automation has a visual problem. When every article flows through the same pipeline — same model, same prompt structure, same build step — the output gravitates toward sameness. The words might vary, but the feel flattens. For a blog that publishes multiple times a week, that flatness becomes identity-erasing fast.

The AI Signal needed to look like it had an editorial designer on staff. It does not. It has a playbook, a canvas API, and a rule about never fading in an HTML title after a canvas animation.


THE MAGAZINE METAPHOR

Print magazines solved this problem decades ago. Open any well-designed publication and you will notice: the news section looks different from the opinion columns, which look different from the feature well. Same magazine, same brand, different visual registers. The section identity tells you what kind of reading you are about to do before you have processed a single word.

I borrowed that idea wholesale. Every article on The AI Signal belongs to a category — breaking news, opinion, tools review, tutorial, deep dive — and each category gets its own hero treatment. Not a different color on the same template. A fundamentally different visual experience.

This mapping is codified in an article playbook that the pipeline references as its source of truth:

¬ Breaking news — ASCII code rain, cascading characters that waterfall down the screen like a terminal dump.
¬ Opinion pieces — kinetic typography, title words that scatter across the canvas and animate into position.
¬ Technical articles (tools, tutorials, deep dives) — AI-generated abstract imagery from Black Forest Labs’ Flux model.

No per-article decisions needed. Category determines treatment automatically.


WORDS THAT MOVE

The kinetic typography hero is 222 lines of TypeScript. When you land on an opinion piece, the title words are scattered at random positions — 200 to 500 pixels from center, at random rotations and scales. Over about 900 milliseconds, they fly inward with a cubic ease-out and settle into their final positions, staggered 120ms per word so it feels like the sentence is assembling itself.

Behind the main title, twelve ghost echoes drift across the canvas: faint copies of the title words at barely-there opacity, scattered at random sizes and angles. They fade in and out during the animation, giving the hero depth without competing for attention. One word — usually the thematic anchor — renders in the category’s accent color. Purple for opinion, red for breaking, gold for tutorials.

Getting text to render crisply on canvas required two things I did not expect: the Pretext library for precise text measurement that matches how the browser actually shapes the font, and DPR-aware rendering — canvas dimensions multiplied by window.devicePixelRatio for sharp text on retina and high-density screens. Without both, the title looked subtly wrong in ways that were hard to articulate but impossible to ignore.

The font is Playfair Display at 900 weight, sized responsively across breakpoints. After 2.5 seconds, the animation stops entirely. The canvas holds its final frame. No ongoing requestAnimationFrame loop burning CPU while the reader scrolls.


THE BUG THAT BECAME A RULE

The original plan was elegant: canvas runs the animation, then fades out, and the HTML <h1> fades in to take its place. The DOM title is better for accessibility, for text selection, for everything except the animation itself.

Except the canvas renders the title at a position calculated in JavaScript — center of the container, laid out word by word with measured widths. The HTML <h1> is positioned by CSS — centered differently, with different line-breaking, different font metrics. When the canvas faded out and the HTML faded in, the title visibly jumped. A few pixels, but enough to make the whole animation feel broken.

The fix was to abandon the handoff entirely. The canvas stays visible, holding its final frame as a static image. The HTML title only appears as a fallback — when JavaScript is disabled, the CDN fails, or prefers-reduced-motion is enabled.

I encoded this as an explicit rule in the project documentation. Future sessions of Claude will read it and skip the mistake. Institutional knowledge that usually lives in a senior developer’s head, stored in a markdown file instead.


RAIN ON A DARK SCREEN

The breaking news treatment is simpler but no less deliberate. A grid of code characters — braces, brackets, operators, the punctuation of programming — cascades top to bottom with row-by-row delays, creating a waterfall effect over about 2.5 seconds. Ten percent of the cells get occasional character swaps after settling, a subtle flicker that keeps the hero feeling alive without demanding attention.

The whole layer sits at 0.25 opacity — a number arrived at through iteration. Lower and the rain disappears; higher and it competes with the headline. After ten seconds, all animation stops completely. Battery life matters more than atmosphere.


GENERATED IMAGES AS EDITORIAL ILLUSTRATION

For technical categories, the pipeline generates abstract images through Black Forest Labs’ Flux 2 Pro Preview model. The interesting part is not the generation — it is the semantic mapping that builds the prompt. Developer jargon does not translate to good visuals. “Database” as a prompt gives you clip art. So the prompt builder maps concepts to visual language: “database” becomes “structured data layers,” “API” becomes “interface connections.”

The category determines the mood:

¬ Tools and tutorials — architectural imagery: geometric lines, minimal shapes, dark backgrounds with a single accent color.
¬ Deep dives — topology: network graphs, connected nodes, luminous constellation patterns.

Each category also maps to a color palette — green for tools, blue for deep dives, gold for tutorials. A quality suffix prevents the model from adding text or logos, which Flux is otherwise eager to do.

The result is that every tools article shares a visual family without looking identical. The images feel like they belong to the same publication — which is exactly what section identity should achieve.


ACCESSIBILITY AS ARCHITECTURE

Every visual treatment has a fallback path. The prefers-reduced-motion media query skips all animations entirely — no code rain, no flying words, just the static title. Canvas elements carry aria-hidden="true" because the content already lives in the DOM as a proper heading. If the Pretext CDN fails, the kinetic animation degrades to static text instead of breaking.

The HTML title is not decoration. It is the load-bearing structure. The animations are paint.

This was not bolted on. The accessibility path was designed first, and the animations were layered on top. When you build it that way, the fallback is always the solid version, and the enhanced experience is the one that needs to prove itself.


WHAT DESIGN AUTOMATION ACTUALLY LOOKS LIKE

The playbook approach means I never make visual decisions per article. The category determines the treatment, the treatment determines the mood, and the mood determines the palette. A new opinion piece gets kinetic typography in purple. A new tools review gets an architectural image in green. No human in the loop for aesthetics — just for content.

That is the real answer to the sameness problem. You do not fight automation’s tendency toward uniformity by adding randomness. You fight it with structure — with a design system that maps meaning to visual language, the same way a magazine art director would. The automation just executes the system faster than any art director could.

You can see every one of those treatments in motion at vibe.cerridan.com.


This is Part 4 of a 5-part series on building The AI Signal. Previously: the newsroom, the pipeline, and the Night Council. Final entry: what an AI newsroom actually costs — and what I got wrong.

Open post Content pipeline funnel diagram showing the flow from AI research to published article on The AI Signal

From Prompt to Published — The Content Pipeline

April 13, 2026  ·  5 min read

FROM PROMPT TO PUBLISHED

Generation is cheap. Verification is where the engineering lives.

Everyone fixates on generation. How do you get AI to write a good article? That is the question I get most often.

It is also the wrong question.

Writing is the easiest part of the pipeline. The harder part, and the part that took far longer to build, is everything that happens after the words exist: preview rendering, approval routing, asset handling, deployment sequencing, and avoiding race conditions between systems that can both trigger a publish.

That is the layer that makes the whole thing trustworthy.

This is how a single article goes from idea to live on The AI Signal. Every step reflects a deliberate design decision.


FINDING SOMETHING WORTH WRITING ABOUT

The pipeline starts with a scheduled Claude Code session that searches for trending AI and development news through DuckDuckGo. Before it writes anything, it checks existing posts and drafts for overlap.

That duplicate detection is not glamorous, but it matters. Without it, I would already have published several slightly different versions of the same article.

Each article follows an internal playbook with five categories: breaking news, opinion, tools, tutorial, and deep-dive. Each has its own editorial rules and visual treatment. That distinction matters partly because it also affects cost.

The generator writes a 600 to 800 word draft in an opinionated voice and saves it as a markdown file in a drafts directory. There is no CMS or database involved. The article begins as a file with frontmatter.

That choice is intentional. Markdown is version-controllable, diffable, and readable without a running service. The tradeoff is fragility: a missing field or invalid date can sit quietly until the build fails. So far, that has been a trade worth making.


MAKING IT LOOK RIGHT

Image generation is the most cost-sensitive part of the pipeline.

I run a local ComfyUI instance connected to Black Forest Labs’ Flux 2 Pro Preview model. It produces strong editorial illustration, but keeping costs acceptable means being deliberate about when and how often to generate.

The playbook assigns each category its own visual treatment. Tools pieces use architectural imagery, geometric lines, and structural patterns. Deep-dives lean on topology, network graphs, and layered systems. Opinion pieces use kinetic typography, and breaking news uses animated code rain. Those last two run entirely in CSS, which is easily the most effective cost decision in the pipeline. The animations run client-side in vanilla JavaScript and require no generation budget at all.

The prompt builder translates topics into visual concepts instead of literal objects:

¬ A database becomes “structured data layers”
¬ APIs become “interface connections”
¬ React becomes “component architecture”

That indirection works better because it pushes the model away from literal illustration and toward the visual language of the design system.

Each image renders at 1152 × 768 pixels, with guidance scale 3.5 and 28 inference steps. If the result fails or is unusable, the pipeline retries up to three times. That limit is part of the strategy. Without it, a single bad prompt can quietly push costs past acceptable levels.

The output PNG is converted to WebP with Sharp for size efficiency.


THE PREVIEW PROBLEM

This is where I spent the most engineering time.

When a draft is ready for review, I need to see exactly what the published article will look like. Not a markdown preview. Not a close approximation. The actual page, with the real CSS, layout, and hero treatment.

If the reviewed version differs from what goes live, the approval step becomes theater.

So the pipeline runs through a build sequence. The draft is copied into the posts directory, its status is switched to published, the Astro site rebuilds with --base /3001, and the result is served on a preview port behind an Nginx proxy.

That base path flag took an embarrassing amount of debugging. Without it, CSS and JavaScript references break because the proxy rewrites the URL structure. An earlier version used serve -s in SPA mode, which quietly returned index.html for missing paths. The article looked as if it loaded correctly, but it was often rendering the wrong content.

Once the preview is live, the pipeline sends a Telegram notification to my phone with the preview link and two buttons: Approve or Reject.

That is the entire review interface.

An approval listener runs as a PM2 daemon and polls the Telegram API every 30 seconds for button presses.


WHAT HAPPENS WHEN I TAP “APPROVE”

Approval triggers a chain of actions. The draft moves into the posts directory, frontmatter is updated with published status and timestamp, the site rebuilds, and the result deploys.

The tricky part was concurrency. The admin dashboard can also trigger publishing, and two publish attempts against the same article can corrupt the build.

The fix is a lockfile at /tmp/vibe-publish-{slug}.lock.

The entire state model still lives in markdown frontmatter. There is no database and no external service to maintain. In practice, grep and git log do a surprising amount of monitoring work.


THE REAL BOTTLENECK

The pipeline can research, write, illustrate, and stage an article in under four minutes. My fastest review-to-publish cycle was about ninety seconds.

But the system does not ship at machine speed. It ships at human speed.

The generation layer can produce daily. Whether anything goes live depends on when I sit down, read the draft, check the sources, and decide it is ready. Some weeks that means several articles. Other weeks, none.

That is not a limitation. It is the design.

That constraint shaped the whole system. The preview has to be exact because I cannot spend review time second-guessing rendering. Telegram approval is one tap because every extra step adds friction between me and the decision. The pipeline does not auto-publish on a schedule, even though it easily could.

The human review step is what makes the rest of the system worth building.

Building the generator took a weekend.

Building the verification layer that protects human judgment needed real time, iteration, and plenty of trial and error.

That ratio says a lot about where the real engineering lives.


This is Part 2 of a 5-part series on building The AI Signal. Next up: the Night Council — what happens when three AI agents debate your blog’s strategy.

Christian Scherling is a designer and developer at cerridan | design e.U., building AI-assisted tools and wondering how far automation can go before it stops being useful.

Open post Hexagonal AI icon with audio waveform surrounded by binary code, representing an automated publishing system

The AI Newsroom — One Developer, Three Agents, One Bottleneck

April 10, 2026  ·  4 min read

THE AI NEWSROOM — ONE DEVELOPER, THREE AGENTS, ONE BOTTLENECK

I built an AI-assisted publishing system. The interesting constraint was not writing articles — it was deciding what is safe and good enough to publish under my own name.

The system can produce faster than I can verify. That sentence is the entire story of this project.

I built The AI Signal, a blog about AI-assisted development that is itself AI-assisted from top to bottom: research, writing, illustration, even editorial strategy. The pipeline can draft, illustrate, and stage an article without me touching it. But nothing ships until I read it, check the sources, and tap Approve on my phone. That is the bottleneck. And it turned out to be the most interesting design constraint of the entire project.

This is the first in a series of posts about building this system. Not the polished marketing version — the real one, with the bugs, the surprising costs, and the moments where the architecture fought back.


WHY BUILD THIS

I have been running a self-hosted VPS for a while now, deploying side projects with Nginx, Let’s Encrypt, and whatever stack fits the problem. When vibe coding started gaining traction — Claude Code, Cursor, Windsurf, the whole wave — I noticed something: most coverage was either hype or dismissal. Very little sat in the middle, where practitioners actually work.

So I thought: what if an AI wrote about AI development, transparently, with a clear editorial voice — and a human who takes legal responsibility for every word published?

Not a content farm. Not GPT slop. A genuine attempt at automated journalism with human oversight baked in from day one.


THE HUMAN IN THE LOOP

Here is the part that matters most, and the part most “AI automation” stories gloss over: I review and approve every single article before it goes live.

This is not a nice-to-have. It is a requirement. Legally, I am the publisher. If an article contains misinformation, a hallucinated source, or a take that does not hold up — that is on me. No amount of automation changes that responsibility.

The system is designed around this constraint. The pipeline does not publish autonomously. It builds a preview, sends it to Telegram, and waits. I read the article, check the sources, evaluate the angle. Then I tap a button — or I do not.

Some weeks that means I approve three articles in a day. Other weeks, nothing goes out. An early publishing gap made one thing clear: the bottleneck is never generation — it is always review. The system can produce faster than I can verify.

That tension is not a bug. It is the architecture working as intended.


THE ARCHITECTURE IN 30 SECONDS

The AI Signal runs as a monorepo on a single VPS. Five moving parts:

The Site — Astro-based, statically built, served by Nginx. Vanilla CSS, no framework overhead. Each article category gets its own visual treatment: opinion pieces have kinetic typography animations, breaking news gets cascading code rain, technical articles use AI-generated imagery.

The Pipeline — A TypeScript pipeline that researches trending topics, drafts articles, generates images via a local ComfyUI instance connected to Black Forest Labs’ Flux model, and sends the finished draft to my phone for review.

The Admin Dashboard — An Express app behind Basic Auth where I can trigger generation, preview articles exactly as they will appear on the live site, approve or reject drafts, and monitor system status.

The Telegram Bot — When a draft is ready, I get a notification with a preview link and two buttons: Approve or Reject. One tap from my phone publishes the article and triggers a rebuild. That is the entire approval workflow.

The Night Council — This is the one that raises eyebrows. Three AI agents — a Creative, a Strategist, and a Critic — debate the site’s direction in scheduled sessions. Their recommendations are saved as structured briefs that inform future content decisions. More on this in a later post.


WHAT YOU WILL NOT FIND HERE

This is not a tutorial on how to prompt ChatGPT. The AI Signal runs on Claude, uses Model Context Protocol for web research, generates images through a local Flux pipeline, manages state through markdown frontmatter, and deploys via shell scripts and PM2. It is opinionated, specific, and built for one person to operate.

But the patterns are transferable. How do you build a content pipeline that is reliable enough to trust but transparent enough to audit? How do you give AI agents real editorial responsibilities without losing control? How do you keep costs under a euro per day?

Those are the questions I will be answering in this series.


WHAT IS COMING

Over the next few weeks, I will walk through each layer of the system:

¬ The Content Pipeline — from a blank prompt to a published article, including every step where things can go wrong
¬ The Night Council — how three AI personas debate strategy and what they have actually recommended
¬ The Visual Identity — kinetic typography, AI-generated art, and the design system that ties it together
¬ The Real Costs — token usage, API bills, hosting, and what I have learned about building sustainably

The AI Signal is live at vibe.cerridan.com. Every article on it was written by Claude and approved by me. The footer says so. That transparency is not a limitation — it is the entire point.


This is Part 1 of a 5-part series on building The AI Signal. Next up: the content pipeline, from research to publication.

Christian Scherling is a designer and developer at cerridan | design e.U., building AI-assisted tools and wondering how far automation can go before it stops being useful.

Open post Stylized eye with blue iris surrounded by concentric circles and radiating dots, symbolizing AI-powered server surveillance

Security Sentinel

SECURITY SENTINEL — HOW I BUILT AN AI SECURITY TEAM FOR MY SERVER

How three AI layers turned a solo dev’s server into a monitored infrastructure.

I run a VPS. On it: an AI coding terminal, an image generation tool, a language learning app, and a news curation site. Five domains, three runtimes, two process managers — all on a single Ubuntu box.

The problem is simple: I’m one person. I don’t have a DevOps team. I don’t have a SOC. I don’t have someone watching Grafana dashboards at 3 AM. What I do have is a stack of internet-facing services that real people use, running 24/7.

So I built a security team — out of shell scripts, two instances of Claude, and a Perplexity subscription.


THE PROBLEM EVERY SOLO DEV IGNORES

If you run your own server, you probably know the pattern: you deploy something, it works, and you move on. Maybe you set up intrusion detection. Maybe you configured HTTPS. But ongoing monitoring? Threat intelligence? Supply-chain vulnerability tracking?

That’s where things get uncomfortable. Not because it’s impossible, but because it never feels urgent — until it suddenly is.

I already had most of the pieces scattered across the server: Telegram bot credentials from a login alert script, email configured for sending, intrusion detection running with custom jails, and nginx logging everything. What I didn’t have was a system connecting those pieces into something that could observe, correlate, and tell me when something actually mattered.


THE ARCHITECTURE: THREE LAYERS, ZERO BLOAT

Security Sentinel isn’t one tool. It’s three layers that work independently, but reinforce each other.

Layer 1: Shell scripts (every 5 minutes)

  • Plain bash. No dependencies. Runs via cron.
  • Checks services across two process managers: systemd and pm2.
  • Monitors disk, memory, load average, intrusion detection bans, and nginx error rates against rolling baselines.
  • If something crosses a threshold — a service is down, disk hits 90%, error rates spike — it sends a Telegram alert immediately.
  • No AI involved. No tokens spent. Just a shell script, jq, and a curl to the Telegram API.

This layer exists because you don’t need intelligence to know nginx is down. You need speed.

Layer 2: Claude Code (hourly + daily)

  • Hourly: Claude reads the health-check JSON files generated since its last run and looks for patterns that individual checks miss — disk creeping up for hours, 401s trending across multiple services, slow-moving problems that don’t trip a single threshold.
  • If nothing looks notable, it stays silent. No Telegram message. No noise.
  • Daily (8:00 AM): Claude reads a threat-intelligence summary that arrived by email, cross-references it against what’s actually installed on the server, and sends a verdict via Telegram.
  • The output is either “nothing actionable” or a short list of specific recommendations with exact commands to run.

The important constraint: during automated runs, Claude is strictly read-only. It cannot restart services, modify configuration, or touch anything outside its project directory. It observes, analyzes, and recommends. Action still requires a human.

Layer 3: Perplexity (daily threat intelligence)

  • Every morning, Perplexity sends a scheduled email summary of recent security incidents — npm supply-chain attacks, Python package compromises, and CVEs in common server software.
  • A cron job fetches this email via IMAP, extracts the content, and saves it for Claude to read.
  • Claude doesn’t blindly forward the report. It checks: is this runtime even installed? Which version is on the server? Does the CVE affect this OS?
  • Most days, the answer is simple: nothing actionable.

That one sentence is the most valuable output of the whole system. It means someone checked.


THE DAILY FLOW

Night: Log cleanup runs. Old reports are deleted per retention policy.
Morning: SSL certificate checks run and alert if any domain expires within 14 days.
Morning (weekly): The server verifies that unattended-upgrades is still active.
Morning: IMAP fetch pulls the Perplexity email.
Morning: Claude runs the daily scan, reads the threat report, checks relevance, and sends a Telegram verdict.
Every 5 minutes: health-check.sh writes a JSON report.
Every hour: Claude reads accumulated health data and spots trends.

On a quiet day, I get exactly one Telegram message: the daily verdict. Silence means everything is fine. Two messages means something deserves attention. Five means something is actually wrong.


THE TECH STACK

Component Technology Why
Health checks Bash + jq No dependencies, no runtime, runs everywhere
Process monitoring systemd + pm2 Already managing the services
Threat intelligence Perplexity scheduled email Daily supply-chain and CVE summaries
Email ingestion Python + imaplib Fetches from IMAP and extracts the report
Analysis engine Claude Code (on-server) Reads reports and cross-references installed packages
Alerting Telegram Bot API Instant mobile notifications
Configuration Plain key=value conf file Change a threshold, behavior changes. No code edits.
Secrets Separate env file, restricted permissions Gitignored, never version-controlled

Total new software installed: zero. Everything relies on tools already on the server — bash, curl, jq, Python 3, and Claude Code.


THE COLLABORATION MODEL

What makes this approach genuinely useful is that it isn’t one AI doing everything. It’s a collaboration between different tools, each doing what it’s best at.

  • Shell scripts are fast, cheap, and reliable. They don’t hallucinate. They don’t need tokens. They check if nginx is running and alert if it isn’t.
  • Claude Code on the server has context. It knows the project structure, reads health data over time, and reasons about patterns. But it’s sandboxed — read-only during automated runs, full access only when I’m present.
  • Claude Code on my local machine is where I do the actual work. It designed the system, wrote the implementation plan, SSHed into the server, deployed everything, and debugged issues.
  • When the IMAP fetch wasn’t finding emails because the forwarding setup preserved the original sender header instead of rewriting it, local Claude figured that out by listing the inbox and comparing headers.
  • Perplexity brings external knowledge. It tracks what’s happening in the security world — new vulnerabilities, compromised packages, zero-days — so Claude can decide whether any of it matters for this specific server.

No single tool could do all of this well. Together, they cover monitoring, intelligence, analysis, and action — the four things a security team actually does.


WHAT THIS COST

Item Cost
Server (already running) 5 EUR/month
Perplexity subscription (already had it) Included
Claude Code subscription (already had it) Included
New software installed Nothing
npm packages added Zero (by policy)
Development time One afternoon + one morning of tuning
Daily token cost (Claude) ~15,000–30,000 tokens
Daily Telegram messages (quiet day) 1

The marginal cost of running Security Sentinel is effectively zero. Every tool it uses was already installed or already paid for. The value came from connecting them.


WHAT I LEARNED

  1. THE BEST MONITORING IS BORING
    A good day produces one Telegram message: “nothing actionable.” That isn’t a failure of the system — it’s the point. Monitoring should be silent when things are fine and loud when they aren’t. The temptation to add more alerts, more checks, more messages is real. Resist it. Signal-to-noise ratio is everything.
  2. LAYER YOUR INTELLIGENCE
    Shell scripts catch what’s obvious. AI catches what’s subtle. External intelligence catches what you don’t know to look for. Each layer has different strengths, costs, and failure modes. If Claude goes down, the shell scripts still alert. If the internet is down, the local checks still run.
  3. CONSTRAINTS MAKE AI USEFUL
    An AI agent with unlimited permissions is a liability. Claude’s automated runs are read-only by design — enforced through project rules, Unix permissions, and restricted tool access. It can tell me “restart nginx,” but it can’t do it. That constraint is a feature, not a limitation.
  4. REUSE EVERYTHING
    The Telegram bot already existed. Email was already configured. Intrusion detection was already running. pm2 was already managing processes. Security Sentinel didn’t introduce new infrastructure — it turned existing infrastructure into a system.
  5. AI IS A FORCE MULTIPLIER, NOT A REPLACEMENT
    I still make every decision. I approve every action. I review every recommendation. But instead of manually checking services, scanning CVE feeds, and correlating log patterns, I get a daily briefing and real-time alerts. That’s the difference between “I should probably check my server” and “I know something is checking it right now.”

THE BIGGER PICTURE

If you’re a solo developer or a small team running your own infrastructure, you probably don’t have the budget or headcount for proper security monitoring. But you likely already have the building blocks: a server with logs, a messaging app for notifications, and access to AI tools.

The gap usually isn’t technology. It’s architecture. It’s deciding that these tools should talk to each other, defining clear boundaries for what each one does, and building the glue — which, in this case, was about 300 lines of bash and a well-written CLAUDE.md file.

Your AI tools are already on your machine. Your server is already logging everything. The question isn’t whether you can build this. It’s whether you’ll connect the dots.


FINAL THOUGHTS
Security Sentinel started as a practical response to a solo-dev problem: too many moving parts, not enough time, and no team watching the box around the clock. What emerged was a lightweight monitoring stack that combines speed, context, and external awareness without adding unnecessary complexity. That’s the real lesson for me: you don’t need enterprise tooling to think like an infrastructure team. You need clear roles, hard boundaries, and systems that reinforce each other.

Christian Scherling is a designer and developer at cerridan | design e.U., currently running more services than any solo dev probably should — and now, finally, monitoring all of them.

Open post Arabic letter Lam in black calligraphy between two blue audio waveforms with binary digits, merging language and technology

Arabic 4U — How I Built a Language Learning App Over a Weekend

HOW A WEEKEND OF VIBE CODING TURNED A LEARNING FRUSTRATION INTO A PRODUCTION APP
I'm learning Arabic. Every week, our class of 17 meets to tackle new vocabulary, verb conjugations, and the beautiful complexity of a language written right-to-left with vowel marks most apps get wrong.

The problem? The tools available to us were frustrating. Quizlet cards with bad audio. Apps with robotic pronunciation. Vocabulary lists that don't match our class progression. Verb tables with no way to hide what we haven't learned yet.

I'm a designer and developer. I know what good UX feels like. And what I was using didn't feel good. So I decided to build something better — over a weekend.


THE APPROACH: VIBE CODING WITH CLAUDE
I didn't write this app the traditional way. I didn't sketch wireframes, write user stories, or set up a Jira board. Instead, I sat down with Claude (Anthropic's AI coding assistant) and described what I needed:

"I want a simple online tool to learn Arabic verbs and words. Other tools are bad. I want to control what my classmates see, aligned with our class progress. The admin adds a verb, Perplexity API generates the conjugations, Google Cloud TTS creates the audio. Flashcard mode. Mobile-first."

From that conversation, we went from zero to a deployed, production-ready app in roughly a weekend of focused work. Not a prototype. Not a demo. A real app with HTTPS, session auth, 685 audio files, and a security posture that scored A-grade in penetration testing.

WHAT WE BUILT
Arabic 4U is a mobile-first web app that serves our Arabic class.

For Learners

  • 420+ vocabulary items across 18 categories — nouns, places, greetings, expressions, classroom commands, numbers, professions, and more
  • 10 fully conjugated verbs — present and past tense, singular and plural, all persons — including passive voice
  • 685 audio files — every word and conjugation has high-quality Arabic pronunciation via Google Cloud WaveNet
  • Flashcard mode with three directions: English to Arabic, Arabic to English, and a random Mix mode
  • Tap any word to see it zoomed — large Arabic text for reading practice
  • Play All button on every verb section — listen to all conjugations sequentially with pause/resume
  • Display toggles — hide or show transliteration, translation, and vowel marks to adjust difficulty

For the Admin

  • Type an English verb — Perplexity AI generates the full Arabic conjugation table — Google Cloud TTS creates audio for every form — everything saves automatically
  • Add vocabulary in batches — type a comma-separated list, pick a category, choose input language — AI translates and generates audio
  • One-click conjugate button on verb-like vocabulary items
  • Control what learners can see (past tense, plural forms) to align with class progress

THE TECH STACK
No framework. No build step. No complexity where it wasn't needed.

  • Backend: Node.js + Express + SQLite
  • Frontend: Vanilla HTML, CSS, and JavaScript — zero framework dependencies
  • AI: Perplexity Sonar Pro for Arabic verb/vocabulary generation
  • Audio: Google Cloud Text-to-Speech (ar-XA WaveNet voice, slowed to 0.85x for learners)
  • Security: Helmet, Content Security Policy, HTTPS via Let's Encrypt, rate limiting
  • Deployment: Ubuntu VPS, nginx, pm2 — runs on a 5€/month server

WHAT I LEARNED (AGAIN)
This project reinforced something I wrote about in my previous post on Vibe Coding: the discipline matters more than the tools.


1. START WITH THE USER'S PAIN, NOT THE TECHNOLOGY
I didn't start with "let me build a Node.js app." I started with "the audio on Quizlet is terrible and I can't control what vocabulary my classmates see." Every technical decision flowed from actual learning needs.


2. MOBILE-FIRST ISN'T OPTIONAL FOR LEARNING APPS
People learn on their phones. On the bus. Between classes. Every button is 44px minimum. Every interaction works with a thumb. This wasn't an afterthought — it was the first constraint.


3. AI-GENERATED CONTENT NEEDS EXPERT REVIEW
Perplexity generates Arabic conjugations well, but not perfectly. We had to fix broken vowel marks, correct wrong translations, and separate masculine/feminine forms. The AI gets you 90% there. The last 10% requires human knowledge.


4. AUDIO QUALITY IS A REAL DIFFERENTIATOR
Google Cloud's WaveNet Arabic voice at 0.85x speed sounds genuinely good. This was the feature that got the most positive reactions — and it cost less than $0.10 total for 685 audio files.


5. SECURITY ISN'T HARD, IT'S JUST UNFASHIONABLE
Helmet, CSP, HSTS, session regeneration, rate limiting — adding professional-grade security took about 30 minutes. There's no excuse for shipping without it.

THE NUMBERS

Metric Value
Development time ~1 weekend (with iterations over the following week)
Vocabulary items 421
Verb conjugations 185
Audio files 685
Total audio generation cost < $0.10
Server cost ~5€/month
npm vulnerabilities 0
Frameworks used 0

TRY IT
The app is live at -> arabic.cerridan.com It's built for our class, but the approach works for any language learning scenario where existing tools fall short.

If you're learning a language and frustrated with the available tools — consider building your own. With AI-assisted development, the gap between "I wish this existed" and "I built this" has never been smaller.


Christian Scherling is a designer and developer at cerridan | design e.U., currently learning Arabic and occasionally building tools to make that process less painful.

Open post Binary code block transforming into flowing blue sinusoidal waves, representing code generation through AI prompts

Vibe Coding lessons

WHAT VIBE CODING TAUGHT ME — AND WHY IT MATTERS
Vibe Coding is an emerging mode of software development in which you lean heavily on Large Language Models (LLMs) to generate code from prompts — treating prompt design as the core of development.
In this post, I share refined insights from my own experiments with vibe coding, framed as lessons and best practices you can adopt today.


LESSON 1: KEEP YOUR ENVIRONMENT CLEAN & ISOLATED

  • Use project-specific environments (e.g. virtual environments, containerization) so dependencies don’t conflict.
  • Don’t let cruft accumulate — periodically prune unused packages, clear caches, and reset broken configs.
  • If a codebase becomes too convoluted, sometimes starting fresh on a minimal skeleton is safer and faster than wrestling with a flawed structure.

LESSON 2: PROMPT WITH INTENT — BALANCE CONSTRAINT AND FREEDOM

  • Be precise but not overbearing. Give the LLM enough structure to avoid ambiguity, but allow it space to “fill in” with creativity.
  • Begin by discussing ideas with the LLM — sketch architecture, trade-offs, and alternatives before diving into concrete code.
  • Use multi-stage or modular prompts (breaking a large task into smaller ones) to avoid overwhelming a single prompt.
    This echoes prompt engineering patterns used in software design tasks.

LESSON 3: COMMUNICATE YOUR CONTEXT CLEARLY TO THE AGENT
Whenever you hand off a task to an LLM or agent, always include:

  1. Your workstation setup — what tooling, OS, libraries, or hardware you already have (and what’s missing).
  2. The tech stack you intend to use (languages, frameworks, DBs, etc.).
  3. A layout or skeleton of the desired flow: major modules, components, UI/API layout, etc.
  4. The architecture style you prefer (monolith, modular, microservices), and which modules own which concerns.

LESSON 4: TRACK CONTEXT, LIMITS & PROGRESS

  • Always keep the project description current so that future prompts don’t drift from your intentions.
  • Be acutely aware of context window limits in LLMs — avoid dumping full histories; instead summarize or prune irrelevant parts.
  • Maintain a progress protocol (e.g. change log, decision log, milestone list) to trace how you arrived where you are.
  • When stuck, switch to a different LLM (or version), rephrase your prompt, or rethink the architecture from another angle.

ADDITIONAL PRACTICES I RECOMMEND
In addition to my core lessons, here are practices informed by prompt engineering and software design research that amplify outcomes in vibe coding:

  • Encapsulate responsibilities via modules: design your system so each piece does one thing. That reduces LLM confusion and gives clearer boundaries.
  • Document “why,” not just “how”: embed rationale, constraints, and trade-offs in your prompts or in a linked design doc. Future you (or another agent) will thank you.
  • Write tests early and often: even in auto-generated code, test coverage preserves your ability to refactor or replace modules without fear.
  • Understand the behavior and limitations of LLMs: know what the model is good at, where it hallucinates, where it demands clearer guidance.
  • Iterate prompt versions: treat your prompts like first-class artifacts. Version them, test edge cases, and refine over time.
  • Use controlled natural language (CNL) techniques: recent research explores combining prompt engineering and software engineering to reduce ambiguity in prompts.
  • Embed prompt templates in your IDE or tooling: tools like “prompt managers” help you reuse, classify, and refactor prompts systematically.

STRATEGIC INSIGHTS & TAKEAWAYS

¬ Start clean: a messy environment feeds confusion and error.
¬ Guide, don’t micromanage: precise prompts steer without suffocating.
¬ Share context: articulate setup, architecture, and goals in every handoff.
¬ Respect limits: LLMs have memory bounds; summarize, prune, and modularize.
¬ Treat prompts as code: version, test, refactor — prompt engineering is a discipline.
¬ Expect change & design for it: modules, tests, and logs keep your system resilient.


FINAL THOUGHTS
Vibe coding is a fascinating frontier — it pushes us to treat language prompts as architectural levers. But without discipline, it can easily degrade into chaos.
If you combine clean environments, purposeful prompting, context awareness, progress tracking, and modular design — you’ll turn vibe coding from a fun experiment into a sustainable approach.

Scroll to top