I Built a Kanban to Learn Autonomous Development on AWS
I built a kanban to learn autonomous development on AWS
Why I built it
I’ve been an avid GenAI user for quite some time now. When I first started I was primarily using Kiro (for AWS) and ChatGPT for everything else.
Early in 2026 I stumbled across the 5 Levels of AI article (I wrote about it already), and it completely changed my approach. After reading it, I realized I’d been using AI as a supercharged search engine or an ad-hoc troubleshooting tool. Not much more.
I decided to drastically change my approach. In the past, when I wanted to learn something new, I’d pick a project and build something as the learning mechanism. Same playbook here. I picked something I use every day in my business and consider important to my workflow, but isn’t customer-facing. If I broke it, I wouldn’t be offline.
So I picked my kanban board. It cost me about $8/month and I’m the only user. A nice side effect: I was able to cancel a SaaS subscription when I was done.
What “autonomous” meant in practice
The world doesn’t need another kanban board. My intention with this project was:
- Change how I worked with AI tools. I needed to exist further up the scale (see earlier).
- Explore the idea of “offloading.” Should I do this, or could I have an agent do it for me instead?
Let’s talk more about offloading. First, I didn’t want to manage the cards myself: creating, moving, archiving, the whole housekeeping loop. So the kanban contains a Model Context Protocol (MCP) component that I integrated with my existing Docker MCP Toolkit setup. This means other systems I’ve built (OpenBrain, time tracking, and others) can manage the cards on my behalf. I can also use natural language and whichever AI tool I’m using can manage cards. Is this truly “autonomous?” Kind of, but not really.
The bigger question is: should I do this work myself? As a solo operator, I spend a lot of time researching topics or finding answers for my customers. Here’s a typical request: “Can you figure out the pricing model for service X? We’re interested in using it but want to understand the cost before starting.” Great question. Before my kanban solution, I’d create a ticket in JIRA, start googling, and end up on the AWS calculator page. Now, I ask my AI tool to ingest the customer request (right now I copy-paste the email), it creates a card, and then I mark it as “agent ready.” Shortly after, a Claude Code agent running in an ECS/Fargate cluster in my kanban AWS account picks up the card, does the research on my behalf, updates the card, and notifies me. I still review the details, verify the source material, and draft the response. The tedious work is done for me.
That’s how I use it now. The first time it actually worked end-to-end was a smaller test.
The good surprise
The first end-to-end dispatcher run. A real card asked the agent to add a one-line note to the README. About 50 seconds total: Fargate task started, DynamoDB query for the card, repo cloned, Claude Code invoked with a 1694-character prompt, agent read the README, found the Tech Stack section, added the bullet, committed, pushed, opened the MR, status note written back to the card.
The surprising part wasn’t designed. On a second dispatch of the same card, the agent recognized the work was already on main and exited cleanly with zero commits. No duplicate MR. That wasn’t in the prompt or the entrypoint logic. The agent inferred it from looking at the repo state.
Architectural choices that mattered
I’ve been happy with the outcome, but I want to be clear: while I was “vibe coding”1 this solution, I instituted several guardrails before I started.
- The solution lives in its own AWS account. This helps manage the “blast radius” if something goes wrong.
- GitLab is the central source of truth. All deploys happen through GitLab. Some MRs are self-merged by AI; others are flagged and have to be reviewed by me.
- Claude Code on my laptop runs read-only. The dispatcher in Fargate has narrowly scoped write access (one DynamoDB table, one secret in Secrets Manager). Nothing else.
- Every stateful resource has a CloudFormation RETAIN policy, so a
cdk destroydoesn’t take the data with it. AWS-level deletion protection (thedeletion_protection_enabledflag on DynamoDB and Cognito) isn’t enabled yet. That’s a gap I’m tracking and will close in a follow-up. - DynamoDB has point-in-time recovery enabled, and the dispatcher’s IAM role can’t disable it. S3 versioning isn’t yet enabled on the audit-log and archive buckets, but it’s on the list.
I’m lucky in the sense that I’ve been working on AWS since the early days and have learned hard lessons along the way. I started this project with Kiro, AWS’ agent solution, and used spec-driven development plus my understanding of best practices to architect the original design. From there, I let Claude Code build everything.
Here are the fundamentals I baked in from the very beginning:
- DynamoDB single-table design for all operational data. The five domain entities (workspaces, work items, labels, status updates, memberships) share one table and one GSI. Static web assets, audit logs, and archived items live in S3.
- I wanted to access the solution from anywhere but keep it secure, so all authentication is handled by Amazon Cognito.
- Building on the idea of autonomous operation, I included EventBridge to manage stale cards, reminders, and “what next” functionality.
- API Gateway is the entry point for both humans and AI. All requests are routed through a public gateway to various Lambda functions (16 in total).
- Automation. Beyond the access scoping covered above, the agent can run commands and gather diagnostic information when troubleshooting, without having to ask me. It also has access to Playwright, so it can look at the UI and troubleshoot there too.
On the automation piece, I realize this isn’t without risks. I’ve read about agents going off the rails and sidestepping their rules and we routinely discuss incidents on Talking Cloud. Remember, this is not a deterministic solution. See #1 above: the dedicated AWS account is my attempt to control the blast radius if an agent decides to sidestep its rules.
The dispatcher’s own infrastructure (Fargate, SQS, Secrets Manager, the risk classifier that decides which MRs the agent can self-merge) is the subject of an upcoming tech writeup. This post stays focused on the kanban substrate.
Trade-offs and what didn’t work
The Lambda DI bug
Every Lambda handler Claude wrote used handler(event, deps?) with deps ?? getDefaultDeps(). Local tests passed because the test harness called handlers directly. But Lambda calls handlers as handler(event, context), and the context object is truthy, so ?? never fell through and getDefaultDeps() was never called. Result: production Lambdas tried to use the context object as their dependency container, and failed silently.
The painful part wasn’t the bug, it was the debugging. After deploying the fix, warm Lambda containers kept running the old code. I had to force cold starts by bumping an environment variable, and only then could I verify whether the fix actually worked. This took hours of back-and-forth between me and Claude. “I deployed it, why is it still broken?”
Lesson: agent tests were passing against a different invocation contract than production. Tests can give you confidence and still hide the real bug.
The dossier parser dropping 14 of 15 entries
The dossier feature was meant to pull related OpenBrain memories onto each card. In production, it returned almost nothing. After much back-and-forth troubleshooting with Claude, I found two compounding issues:
- The parser regex assumed
\n\nbetween memory entries. Real OpenBrain memories use a single\n. Unit tests passed because they synthesized the wrong shape. 14 of 15 real entries were silently dropped. - The HTTP client had a 5-second timeout. Aurora Serverless V2 (OpenBrain’s database) takes about 18 seconds to resume operation from a paused state. Every cold call timed out before the parser even ran.
Both bugs were invisible in tests. Both only showed up in production. Diagnosis required CloudWatch logs and an AWS read-only profile for Claude.
Lesson: tests against synthetic data prove the code parses what you wrote, not what production sends.
The Dockerfile that “passed” a test that never ran
I shipped a Dockerfile MR with a test plan that said “ran X locally.” The issue: Claude hadn’t run it locally. The Dockerfile referenced ripgrep (not in the AL2023 dnf repo) and had a wrong COPY path. The next deploy on main failed. A two-bug fix MR followed.
This one is the most uncomfortable to write about, because the failure wasn’t the agent’s. It was mine. The agent will produce confident-sounding test claims if you don’t enforce verification. After that, my rule became: when a test plan says “run locally,” the test must actually be run, not just documented.
Lesson: the cheapest mistake to make with an agent is to trust its narration of what it did.
A decision that aged badly: MR assignment
Early on, my workflow assigned every merge request to me by default. It made sense at the time. I wanted to see everything. Once Claude started shipping at volume, sometimes 8 to 20 MRs in a single session, my queue turned into a backlog, and trivial changes (typo fixes, comment cleanups) competed for the same review attention as large infrastructure changes.
The fix was to invert the default. The bot self-merges by default; I only get assigned MRs that touch infrastructure or scope. Once the dispatcher landed its rules-based risk classifier, the agent applies the same logic on the way out: low-risk diffs (docs-only, tests-only, under a line cap) merge themselves; anything ambiguous comes to me.
Lesson: assignment defaults should match risk. “I want to see everything” doesn’t survive contact with volume.
Cost: ~$5/month
The total cost for the month of April was $4.66 USD. The three largest contributors were:
- CloudWatch: ~$1.50
- VPC: ~$0.60
- Security Hub: ~$0.60
You’ll notice the core services (API Gateway, Lambda, DynamoDB) aren’t even on this list. In April, API Gateway cost $0.04. Lambda and DynamoDB came in at $0. A typical default-configured AWS build in this shape can easily run $30-50/month before any of the same services would show up on the bill.
I was able to keep costs low because the solution is 100% serverless. No EC2 instances, no RDS databases, everything pay-as-you-go.
I expect costs to increase modestly with the new agent-ready dispatcher. To make it work, I added ECS/Fargate and an Anthropic API key (stored securely in Secrets Manager) used by the agent in the container.
What’s next
I want to keep working on the dispatcher concept. Two near-term items:
- Per-kind routing. Right now every dispatched card runs through the same flow with the same model. The next step is routing by card kind. A docs change shouldn’t need Claude Opus 4.6, and a research card shouldn’t open an MR at all (its output is notes back on the card, not a code change). Different work, different agents, different models.
- A stop button. A kill switch for an in-flight agent. The dispatcher writes a stop flag to DynamoDB; the agent polls it every 30 seconds during long operations and exits cleanly if the flag is set. What I’ll reach for when an agent is heading sideways or running longer than I expected.
This is a personal project and a demo. It’s not something I plan to release. The dispatcher itself is what I’m continuing to invest in, and I’ll write that up properly once it’s further along, so watch for that.
The kanban board is the test track. The orchestrator pattern is the car.
-
I’m using “vibe coding” as I interpreted it in “Vibe Coding” by Gene Kim and Steve Yegge. ↩︎