Two years of building a security platform solo with AI agents: the honest architecture tour
I run a security and monitoring platform in production, across two regions, with paying infrastructure and a real on-call rotation. Headcount: one.
AlertKick started in January 2025 as a side project, became a company in May 2026, and has been built almost entirely in the evenings and weekends around a full-time SRE day job. The other thing about it, the thing people ask about most, is that a large share of the code was written by AI agents, with me acting as architect, reviewer, and the person who gets paged when it breaks.
There are plenty of posts arguing about whether AI agents can do real engineering; this is a tour of what two years of it produced: the architecture as it stands, the parts that worked, the two incidents the agents and I caused together, and the decisions I would take back.
The shape of the thing
At the top level it is a classic agent-based security product:
- A Go agent installed on customer hosts, watching the host and shipping events.
- Regional endpoint services the agents connect to over WebSocket.
- A central API serving the web UI and mobile app, with MongoDB behind it.
- Kafka between the API and the endpoints, carrying commands and events.
- Pollers doing uptime and response-time checks from outside.
- Two regions, EU and US, on Hetzner, geo-routed with a Cloudflare Worker.
Nothing exotic. The interesting parts are in why each piece is shaped the way it is, and what it cost to learn that.
The data plane: one static binary on someone else’s server
The agent is the part of the product that lives on machines I do not control, so it gets the strictest engineering budget. It is a single static Go binary. Go was not an aesthetic choice: cross-compilation and static linking are what make “curl one script, works on six Linux families” achievable for one person. There is no interpreter to find, no glibc version to argue with, no dependency tree to apologise for.
What it does on the box: process and network visibility through eBPF, file integrity monitoring, rootkit checks, Docker container monitoring, SSH login and session tracking, and active response - it can auto-block SSH brute-force sources fail2ban-style, and it can hold a host in an SSH-locked posture where nobody gets in outside an approved window.
The hardest engineering in the whole platform is here, and it is not the eBPF plumbing. It is noise. eBPF will happily hand you every exec on a host, and a busy Docker host execs healthchecks every few seconds, forever. The difference between a security product and a spam cannon is the filtering, caching, and first-seen suppression layered on top. I wrote that story up separately: from fork-storm noise to a usable signal.
Transport: WebSockets out, Kafka inside
Agents dial out over WebSocket to an endpoint service in their region. Outbound-only matters: customer hosts sit behind NAT and firewalls, and nobody should open an inbound port for a security tool.
Inside the platform, commands and events ride Kafka between the API and the endpoints. When you toggle a security profile in the UI, that flows frontend -> API -> Kafka -> endpoint -> WebSocket -> agent. Kafka earns its keep as the durable buffer in the middle: endpoints restart, regions deploy, and commands must not evaporate in the gap.
The Kafka deployment itself is deliberately boring: KRaft mode, no ZooKeeper, three brokers per region, deployed by the same Ansible that builds everything else. Managed Kafka would have cost more per month than the servers it ran on.
Boring did not mean painless. During the migration onto Kafka, one producer path in the API was left silently broken - commands queued, returned success, and went nowhere. Broker dashboards stayed green for weeks because the brokers were fine; the path was not. The fix was small, but the lesson stuck: monitor message paths end to end, not component health. A synthetic message you send yourself and expect to arrive is worth ten broker health checks.
Control plane, checks, and the edge
The API is Go over MongoDB, multi-tenant, serving a React web UI and a React Native mobile app. The pollers that run uptime, response-time, TLS and domain-expiry checks are paired, so a poller dying mid-cycle never silently skips a check - a design that also forced me to take dedupe seriously, after a legacy scheduler path briefly ran every check twice.
Multi-region is where a solo budget shows its seams. A Cloudflare Worker geo-routes users and agents to the nearest region. Underneath, the unglamorous plumbing that no cloud provider abstracts away for you on a budget: agent auth tokens replicated across regions, TLS certificates with SANs covering the regional names, sticky sessions, and command routing that tracks which region a host is actually connected to right now, so a command produced in one region reaches an agent connected to the other.
Observability is VictoriaMetrics, VictoriaLogs, and Grafana on a utility node. And the platform monitors itself: the agent runs on every host in my own fleet. Dogfooding a security product is where half the bug reports come from. My own auto-block feature once banned my ops IP mid-deploy.
Operating production alone
Solo means the org chart is: on-call is me, security review is me, change approval is me. The honest response is removing myself from loops wherever a control can stand in for me.
So production hosts sit SSH-locked by default. Deploys go through CI, and CI goes through the same change-management door a human would: request a change window, hosts unlock, deploy runs, integrity checks pass, hosts relock, the change closes with evidence attached. The first time a build rode that loop end to end with zero human touches, I stared at the audit trail for a while. The question “what changed in prod and who authorised it” has a record for an answer, not an investigation - which matters double when some of the hands on the keyboard were not human.
Where the AI agents earned their keep
The breadth is the headline. This platform spans a Go systems agent doing eBPF, a React UI, a React Native app, an Astro website, Ansible for a two-region fleet, Kafka operations, and Grafana dashboards. No individual engineer is expert across all of that, and I am not either. Agents collapse the cost of working outside your home territory: the mobile app and most of the UI simply would not exist without them, not because the work is beyond a person, but because it is beyond a person’s spare evenings.
They are also disproportionately good at exactly the work a solo founder skips: migrations executed methodically across a codebase, test scaffolding, and post-incident write-ups produced while I am still staring at graphs. My rule of thumb after two years: agents are at their best when the task has a verifiable definition of done, and at their most dangerous when correctness is a judgement call.
Where they burned me
Two incidents shaped how I review AI-written code.
The first was the silent Kafka producer path above - code that looked complete, compiled, returned success, and did nothing. The second was worse: the agent, our lightest component, developed unbounded in-memory maps that slowly ate a 2GB host until the kernel OOM killer put it into a kill-restart-regrow loop. The bitter twist was that the remote-command path that could have fixed it had a bug too, in the same binary. The tool for fixing the agent was the agent.
Both bugs are in classes that AI code generation is systemically prone to and human skim-review is systemically bad at catching: growth without bounds, and failure without noise. Agents write confident code. It rarely looks wrong. So my review effort is no longer spread evenly; it concentrates on the questions “what limits this?” and “how would I know if this stopped working?”, and the platform grew guardrails to match - agents report their own resource footprint now, caches are bounded with eviction, and end-to-end path probes exist because green component dashboards once lied to me for weeks.
If an AI agent can touch production, the controls have to assume it will occasionally be confidently wrong. That is the same argument that got us code review and change management for humans.
What I would not do again
Adding a workflow engine because the internet told me to. Fleet provisioning ran on a heavyweight orchestration framework for a while. It was the correct enterprise answer and the wrong solo answer: another cluster to feed, upgrade, and debug. I deleted it and replaced it with an in-process job runner - goroutines and a database table. I lost durable resumability I was not using and gained a system I fully understand at 2am.
Co-locating the database and Kafka. Putting mongod and Kafka brokers on the same nodes to save money produced months of recurring memory pressure and an eventual forced resize. The saving was real; the operational tax was bigger.
Keeping legacy paths alive during migrations. The double-execution bug and the silent producer both trace to the same sin: a migration where the old path was left plugged in “just in case”. Cutover means the old path dies. A checklist, not vibes.
Multi-region on day one was a close call. Two regions this early cost me token replication, read-routing gaps, and cert plumbing while I still had product to build. But the constraint forced designs - outbound-only agents, region-aware routing, replicated auth - that would have been brutal to retrofit. I would do it again, later.
If you are running infrastructure with no security team and want a second pair of eyes on it - your posture, your monitoring, or how AI agents are touching your systems - I do a free 30-minute review. Bring your actual stack.