~/sujit $ whoami

Sujit Shakya

Senior Software Engineer · backend & platform · Berlin

I build the backend and platform pieces other teams build on: event pipelines, partner integrations, logging and monitoring, and the framework upgrades that keep it all current. I've been at it since 2012, and what carries over from stack to stack is the fundamentals: how data moves, how systems fail, and the trade-offs between simple and scalable. Lately that work includes AI agents, and the guardrails they need to work in a real codebase.

// how I work: make failures visible, make the bug class impossible, leave the next engineer faster.

Impact$ git show HEAD --stat

What I built that other people now build on.

platform/event-bus

I took event-driven integration from discovery and RFC to its first production consumer, with the platform team. Teams now write one handler and a small Terraform file, and the runtime handles delivery. Sales, marketing, product and QA built consumers in my workshop. Case study ↓

practices/decisions

I started the Engineering Discovery practice: the what and why before the how. Other engineers now write their own, and one became an official RFC. I also co-designed the system-design interview and its interviewer guidelines.

ops/observability

I defined the shared JSON logging standard. 8 other engineers have used it across 41 PRs, and another team's billing code depends on my error fingerprinting. I led the Ruby 4 / Rails 8.1 upgrade canary-first, with zero regressions found in QA.

ai/boundaries

I write agent skills with explicit boundaries: what the agent may change, and what it hands to an engineer. The first lets marketers build event consumers.

Career$ git log --graph --reverse

The long version, from 2012 to now. Real git refuses to combine --graph with --reverse. I did it anyway, so you can read it from the beginning.

main software ai the side branch that grew up lancerbooks my own product

  1. 3a7f0c1 Aug 2012 · Kathmandu

    Initial commit

    Internship at Sastra Creations: PHP, Joomla, MySQL, and keeping e-commerce sites alive.

  2. 7e57ed0 Mar 2013 · Sastra Creations

    feat: switch to Ruby on Rails

    Web developer, building Rails apps from planning to deploy. This is where I wrote my first test before the code. Rails stayed.

  3. a15eed0 Jan 2014 · Nepal Engineering College

    feat(ai): movie recommender system

    My Computer Engineering thesis used K-means clustering plus collaborative filtering. It was my first taste of ML, about ten years before everyone wanted some. source

  4. b16da7a Feb 2014 · Deerwalk

    feat: big data, Java edition

    Associate engineer on a healthcare plan-analytics product. I shipped features across releases 2.7 → 3.7 and tuned a Hadoop engine and a Grails + Elasticsearch service.

  5. 1eade25 Mar 2015 · Esignature

    refactor(role): developer → team lead

    Rails team lead two years in: planning the workload, training an intern, and running workshops for teammates who were falling behind. I learned that training people is a lot harder than training models.

  6. 5ea4c40 Sep 2015 · Codyssey

    feat(search): information retrieval in Go

    Senior engineer. I built an IR system in Go, designed an intranet search service on top of it, and wrote social-analytics APIs that help businesses find the right influencers. Out of it came mspm, an open-source Aho–Corasick-style pattern matcher.

  7. 7a1c18f Jan 2018 · Ruby Meetup Nepal

    docs: talk "Kafka with Rails"

    My first public talk about events and queues. Eight years later I'm still building event-driven systems. watch

  8. b111ab1 Sep 2018 · DesignFiles (remote)

    feat(payments): own the money paths

    Senior full-stack engineer on business software for interior designers. I architected invoices, Stripe card and ACH payments, refunds, QuickBooks sync, and the financial dashboard. That's where I picked up a healthy fear of anything that moves money.

  9. 1b0c5a1 Jan 2020 · Kathmandu · side project

    feat(lancerbooks): (free)lancer books, for my own invoices

    My DesignFiles work was remote and billed by the hour, and tracking hours, invoices and payments by hand was eating my evenings. So I built myself a bookkeeping app: hours in from Toggl, PDF invoices out, payments tracked.

  10. fa31b0c Jul 2020 · Kathmandu · side project

    feat(lancerbooks): extend it to the family business

    My wife started an online shop, Addtocart, and it needed orders, products and inventory managed somewhere. Instead of starting a new app, I extended mine: my account kept the freelance bookkeeping, and business accounts got a store admin. When the shop's TikTok went viral, it handled around 1,000 orders a month.

  11. c105ed0 2022 · Kathmandu · lancerbooks

    chore(lancerbooks): archive the branch, we're moving to Berlin

    We shut down Lancerbooks and Addtocart when we moved from Nepal to Berlin. The branch was never merged into main, but what it taught me was: own a problem end to end, from the first commit to the last order (the full story).

  12. be71170 Oct 2022 · getolo, Berlin

    feat: insurance, at scale

    I joined getolo to work on a pet and dental insurance platform. When the acquisition squad formed, I was its first backend engineer, building referrals, vouchers, multiple pets per customer, digital payments and partner integrations.

  13. c4a7001 ~2023 · the chat era

    feat(ai): a rubber duck that talks back

    AI arrived as a chat box next to the editor: fast, confident, and sometimes confidently wrong. The hard part of the job moved from writing code to reading it.

  14. f01ca11 Jan 2024 · Berlin · side quest

    feat(ai): RNN that writes Irish folk music

    I worked through MIT's Intro to Deep Learning and trained a recurrent network to compose tunes. listen (at your own risk).

  15. acc0de5 2024 · getolo

    feat(team): hiring and Engineering Discovery

    I co-designed the system-design interview and sat on the panel for 13 of them. I also started the Engineering Discovery practice (write down the what and why before an RFC decides the how), and other engineers now write their own.

  16. 10661ed 2025 · getolo

    feat(observability): one logger to rule them all

    I built the backend for the France launch, then a company-wide JSON logging standard that other teams build on. That's the nicest thing anyone can say about a logger.

  17. a6e7101 hackathon · getolo

    feat(ai): an agent that unblocks stuck sign-ups

    It correlates data across systems, finds the root cause, and hands customer support a link to send so the customer can finish signing up. This is when the chat box got hands.

  18. e7e47b5 2026 · getolo

    feat(events): an event bus other teams can build on

    I introduced event-driven integration, so marketing, sales and data can react to domain events without a new webhook each time (case study). I also led the Ruby 4 / Rails 8.1 upgrade.

  19. 5c1115a 2026 · agents

    feat(ai): agents, with guardrails

    I write agent skills, not just prompts. One lets marketers build event consumers with their AI assistant: a green zone they can edit freely, and a handoff zone (payloads, queues, secrets, consent) where the agent drafts a request for an engineer. Another, guruji, mentors me on architecture.

  20. 4e46e00 2026 · HEAD → main

    Merge branch 'ai' into main

    AI isn't a side project any more; it's part of how the work gets done. My job now is partly writing the fences: docs, conventions and boundaries that let an agent (or a new hire) contribute safely. Agents get least privilege, same as people, and anything irreversible needs a human "yes."

Case study: event bus$ git show e7e47b5 -- docs/event-bus.md

How I introduced event-driven integration at getolo. I've generalised internal service, repository and event names, and left out colleagues and partners. The design and the decisions are as built.

The problem

The core app (Rails, Postgres) owns leads and customers. Marketing, sales and data needed to react to changes there: send conversions to ad platforms, notify partners, feed the warehouse. Each need became another webhook or Zapier flow coupled to the core app, fragile and hard to debug.

The goal was for the core app to publish domain events reliably, and for other teams to consume them without learning delivery semantics or touching the core app.

Rails app
 └ one DB transaction:
    ├ UPDATE leads …       # change
    └ INSERT outbox_entry  # event
           │
Relay  FOR UPDATE SKIP LOCKED
           │ publish, delete on success
           ▼
EventBridge  rule per consumer
           ▼
SQS queue ──(max receives)──▶ DLQ
           ▼
Consumer runtime  poll · ack · retry
 └ handle(event)   # team-owned code
Read top to bottom: the producer at the top, the platform in the middle, and team-owned code at the bottom.

Contract and constraints

Delivery is at-least-once. A message can arrive more than once, so every consumer deduplicates on event_id.

Consumers aren't isolated from each other in AWS. They share one task role, so the access policy is one explicit shared policy, not per-consumer policies that would only look like isolation. Real isolation would mean separate tasks, and that's a future decision.

Configuration is explicit. Each consumer sets its own visibility timeout, retention and maximum receives, and the shared module has no hidden defaults.

Why this design

  • No dual write. The event row commits in the same transaction as the domain change. Either both happen or neither does.
  • A relay that scales out. FOR UPDATE SKIP LOCKED lets several relay processes share the outbox table without stepping on each other. Failed rows stay put and are retried.
  • Managed services, no broker to run. EventBridge routes by event name and SQS buffers per consumer, both managed by AWS, where the platform already runs. I had a different opinion at first and came around.
  • Names that can't drift. Event names are derived from event classes (e.g. <app>.leads.created), never typed by hand.
  • A framework/plugin split. The runtime owns polling, acks, retries and dead-lettering. A team declares a queue, the events it wants, and a handle() function.

Where it stands

Verified
End to end on staging, including a hands-on workshop where non-engineers deployed their own consumers.
Live
Production infrastructure since September 2026, and the first production consumer since October 2026. It sends lead events to an ad platform's conversions API.
Next
Measuring whether it improves lead quality, with the agreed success measures: the platform's event-quality scores and parameter counts.
Failure handling
  • Handler succeeds → the message is deleted. Handler raises → the message stays on the queue, is retried, and after the maximum number of receives moves to a dead-letter queue.
  • If a publish succeeds but deleting the outbox row fails, the relay publishes that row again on its next pass. That window is one reason the contract is at-least-once rather than exactly-once.
  • Dead-lettered messages are inspected manually. The runtime never reads from a dead-letter queue, so the access policy doesn't cover them.
  • Running several consumer tasks is safe because consumers share no state. Even at one task, rolling deploys briefly run two, so deduplication was never optional. I checked the runtime and the deploy config before answering that, rather than relying on intuition.
Governance and change
  • New event types need an engineer's approval, and new infrastructure needs the platform team's. Self-service doesn't mean ungoverned.
  • Changing an event's payload is an engineering change in the core app. A consumer team requests it; it doesn't make it.
  • A platform reviewer spotted that event names were interpolated into generated Terraform. I replaced that with an allow-list validated against the real event classes, which closes the injection path and catches typos.
  • Environment prefixes on queue names are added by the runtime. I argued for an explicit helper; we kept the implicit version for consistency, and I documented the reasoning.
Lessons
  • Read the code before answering a scaling question. The answer was in the runtime and the deploy config, not the architecture diagram.
  • Make the real boundary explicit. A policy that implies isolation you don't have is worse than an honest one.
  • Dogfood your own docs. Running the onboarding commands myself found three real bugs before anyone else hit them.
  • Not every environment deserves migration hygiene. I added state-preserving Terraform moved blocks to a disposable staging setup, then deleted them.

Projects$ ls ~/src

Things I built, and why.

Lancerbooks Rails · Postgres · Sidekiq · Vue · 2020–2022

How a tool for my own problem grew into the system behind my family's business.

My problem: my work for DesignFiles was remote and billed by the hour, close to full-time. Tracking hours, sending invoices and checking what had been paid took more time than it should, so in January 2020 I built myself a bookkeeping app. Hours came in from Toggl per project, invoices went out as PDFs, and payments were tracked against them. That's the name: (free)lancer books. When Toggl retired its old API domain in 2021, I fixed the Ruby client I depended on and sent the fix upstream.

My family's problem: then my wife started an online shop, Addtocart. It needed products and variants, inventory, purchase orders, vendors, customers, and orders from both social media and the shop counter. Rather than starting over, I extended Lancerbooks. One codebase now served two kinds of account: my account kept freelance bookkeeping, and business accounts got a team-based store admin, with store and inventory features switched on per team.

Under load: when Addtocart's TikTok went viral, Lancerbooks handled around 1,000 orders a month at peak, 30–40 a day. Stock only moved through stock-in and stock-out forms, and every order status change was recorded with who made it, so the numbers could always be explained. Bulk order updates ran as background jobs. It all ran on a single server, deployed with Capistrano and tested in CI, and a small Vue storefront read from its API.

My part: the only engineer, from planning to development to deployment, for about three years. We shut down both Lancerbooks and Addtocart when we moved from Nepal to Berlin. lancerbooks.co is offline, the Instagram page is still up, and the source is private.

What it taught me: solve your own problem properly first. A tool built to fit one real need well is a foundation you can extend to the next person's, instead of starting from scratch.

guruji agent skill · markdown

What: an agent skill that turns a coding agent into a software-architecture mentor. It's plain markdown instructions, frameworks and templates, tool-agnostic, and originally built for Zed's agent skills.

Why: I wanted to get better at making and defending architecture decisions, and to keep an honest record of my real decisions, mistakes and wins instead of reconstructing them at review time.

Interesting decisions: "rigor never adapts, explanation depth does": the full trade-off framework runs every time, and only the explaining changes. It asks questions before answering. Safety policies (confirming destructive operations, flagging PII) are chosen by the user, and every confirmation is single-use.

mspm Go · BSD-3

What: multi-string pattern matching. Give it a list of keywords and a document, and it counts every keyword in one pass, using a trie inspired by Aho–Corasick.

Why: it came out of building an information-retrieval system at Codyssey, where matching many terms against many documents had to be fast.

Interesting decisions: two trie node implementations, a plain TrieNode and a map-backed TrieHashNode, benchmarked side by side. Search came out the same, and the hash node inserted about 40% faster.

RecommenderSystem Python · pandas · MongoDB

What: my 2014 thesis, a movie recommender. You rate 20 movies in a web UI, it clusters you with similar users (K-means), and it recommends from the five users closest to you.

Interesting decisions: a later version removed the collaborative-filtering stage and kept clustering alone. Shipping the simpler thing that works was a lesson I'd relearn many times.

Lessons$ fortune sujit

Things I learned the hard way, squeezed into cookies.

A config file is a wish. The running process is the truth.

Postmortems$ ls postmortems/

Blameless format, including when the person to blame was me. Each one changed how I work.

INC-001 The Friday deploy
What happened
A Friday release introduced duplicate webhook deliveries, and thousands of jobs piled up in the dead-job queue.
Response
I coordinated the incident, shipped a hotfix in about 90 minutes, and wrote a blameless postmortem.
Prevention
An anomaly monitor for the duplicate pattern, and a CI/CD guard against Friday deploys for the whole team.
Lesson
"Be more careful" isn't a fix. A rule the pipeline enforces is.
INC-002 Four workers, allegedly
What happened
The config said 4 Puma workers. The servers had quietly been running 3, because a "smart" restart never re-read boot-time config.
Lesson
Check the running process, not the file. Config is a wish; the process is the truth.
INC-003 Falling for my first theory
What happened
On more than one investigation I was sure I'd found the cause, then had to drop the theory when the data disagreed. Twice in one case.
Lesson
Write the hypothesis down, then try to prove it wrong before you tell anyone it's right.
INC-004 Too quiet in the room
What happened
Early on I'd have the right idea and not push for it. A colleague's feedback, in three words: "be more brave."
Fix
I started writing RFCs and leading the discussions, pushing back on vague answers, and holding a position under pressure while conceding the points that were fair.
INC-005 PR description: "see ticket"
What happened
My early pull requests were a ticket link and a prayer.
Fix
Now they carry context, a rollout plan, and a rollback plan that actually runs. Reviewers are noticeably less sad.
INC-006 Rust for everything
What happened
I started a computer-vision side project in Rust because it sounded fun, then found that the ecosystem I needed lived in Python.
Lesson
Choose the tool for where the problem lives, not for the vibe. (I still love Rust.)
INC-007 The 2× that wasn't
What happened
I estimated a search-latency fix would make it about 2× faster. Measured result: 15–25%.
Fix
I said so out loud, kept digging, and found the real bottleneck. The answer was to remove expensive work, not buy bigger machines.
Lesson
Estimates are hypotheses too. Measure, then report what you got.

Tools$ cat package.json

Tools I've used in production. A language is a brush, not the painting.

{
  "name": "blackrabbitt",
  "version": "2026.10.0",
  "dependencies": {
    "ruby-on-rails": "since 2013",
    "postgresql":    "since 2013",
    "elasticsearch": "since 2014",
    "go":            "since 2015",
    "python":        "since college",
    "elixir":        "phoenix, at getolo",
    "aws":           "eventbridge, sqs, ecs",
    "terraform":     "modules, not copy-paste"
  },
  "devDependencies": {
    "datadog":       "always open in a tab",
    "sidekiq":       "trusted, but monitored",
    "docker":        "*"
  },
  "peerDependencies": {
    "llm-agents":    "with guardrails",
    "rag":           "*",
    "agent-memory":  "*"
  },
  "i18n": ["Nepali", "Newari", "English"],
  "interests": ["artificial intelligence", "open source"]
}

Now$ cat now.md

Say hi → sshakya.mail@gmail.com · LinkedIn · GitHub