$ whoami

Hataf Atif

I wire things up.

Production systems that have to keep working when a teacher in a village opens WhatsApp at 7am. Below: how each one is actually put together, which decisions had a trade-off in them, and what broke.

// How the production systems are built, and what broke on the way. Press m to switch sides.

$ cat systems/*.md

Dars

درسprivate repo2026 — now

Orenda Project · Taleemabad

A FastAPI service over Supabase Postgres, fronted by two published npm packages — @dars/client for the API and @dars/react for the components. Consuming teams integrate against an org API key rather than forking the codebase, which is the whole reason it exists as infrastructure instead of an app. Deployed on Railway; the image ships the Claude Agent SDK and the Claude Code CLI because the chapter planner invokes an agent at request time, not at build time.

Decisions

The planner was reverted before it was rebuilt

An LLM planner went in, came straight back out in favour of a deterministic placeholder, and was only then rebuilt properly as a standalone Chapter Planning Engine with explicit planning principles in the prompt. Shipping the intelligent version early would have meant debugging generation quality and scheduling logic at the same time.

Holidays and exams are inputs, not exceptions

Class paths are buffer-budgeted and re-plannable, so a two-week exam period changes the plan instead of invalidating it. Modelling lost time as a first-class input is what makes the plan survive a real school calendar.

Generation is per-slot and on demand

Lesson plans and formative assessments are generated for the slot a teacher is actually looking at, rather than batch-generating a term nobody reads. Cheaper, and it means a re-plan doesn't invalidate a pile of finished work.

Tenancy is an org API key, and that cut both ways

Per-org keys keep regional teams isolated, but a stale key surviving signup/login leaked across tenants until it was explicitly cleared on both paths.

What broke

  • uuid/text type mismatch at the DB boundary returning 500s on generate-lp
  • int grade codes reaching a parser that expected strings — another 500
  • NULL book_chapter_id meaning is_generated/status silently skipped slots
  • hydration race in the teacher-app auth gate: a hard refresh dropped the session
  • chapters marked broken-down as soon as they were dated, conflating dated with generated
FastAPISupabasePostgresTypeScriptReactRailwayPlaywrightBruno

NIETE-Rumi

2026 — now

Orenda Project × National Institute of Education

A Node service on the WhatsApp Cloud API: interactive list messages for navigation, WhatsApp Flows for multi-select quiz questions, and a web portal sharing the same session model. Media sits in Cloudflare R2 and is presigned inline so training videos play inside the thread instead of bouncing users to a browser. An Android wrapper handles Play distribution for the portal.

Decisions

One source of truth for training rules

Levels, attempt ceilings, pass marks and cooldowns resolve from a single place rather than being restated in the bot, the portal and the dashboard. Three copies of a pass mark is three chances to disagree with itself in front of a teacher.

A chat thread has to behave like a gradebook

Attempts, terminal states and cooldowns need to be strict even though the interface is a conversation. A level with no exam still needs a terminal state, or a teacher gets stuck in a lesson that never ends.

Certificates are minted on demand

PDFs are generated the moment they're earned and delivered in-thread, rather than pre-generated or emailed later. The reward has to land inside the same conversation that produced it.

Questions are pooled and varied per attempt

Fewer questions served per attempt, drawn varied from a pool — so a retake isn't a memory test of the first attempt.

What broke

  • /menu falling back to Meta's plain-text list instead of the interactive one
  • sessions lost on an Android force-close until state survived the lifecycle
  • "/" starting a fresh session instead of honouring the existing one
  • slash commands failing to end a reflective coaching conversation
  • exam captions counting the whole question pool rather than the served paper
  • R2 media 403ing in the dashboard until presigning moved inline
Node.jsWhatsApp Cloud APIWhatsApp FlowsCloudflare R2PostgresAndroid

Agent skill packs

private repo2026

Orenda Project · Taleemabad

A skill pack that gives each team's coding agent real Slack and Notion access — per-team tokens, no shared credentials, roughly ten minutes from clone to working. Built on MCP and the Claude Agent SDK.

Decisions

Route point lookups instead of iterating

slack-search was rewritten so a direct lookup goes straight to the message rather than walking search results. About 20% fewer tool hops at equal correctness — the win came from removing a reason to guess, not from adding capability.

Per-team tokens over a central credential

Every team holds its own token. Slower to set up once, but it removes the shared secret and the person who owns it from the critical path.

Label placement is a solvable problem

The worldmap skill de-collides overlapping labels and picks label contrast per region automatically, so output is legible without a human nudging text boxes.

What broke

  • labels colliding into illegible stacks before de-collision existed
  • fixed label colours disappearing against dark regions
Claude Agent SDKMCPPythonSlack APINotion API

EasyDost data platform

private repo2023

EFT

The data side of a customer-messaging product: a scheduled DynamoDB → RDS ETL, a serverless function extracting parts-of-speech and named entities from message text, a Jupyter-based analysis layer over the result, and a serverless websocket endpoint feeding realtime WhatsApp into a support inbox.

Decisions

Extraction as a separate serverless function

POS and NER ran as their own endpoint rather than inside the pipeline, so language-model work could fail, retry and be redeployed without touching the ETL.

Websockets for the inbox, not polling

Support agents needed messages to arrive, not to be fetched. A serverless websocket endpoint kept that realtime without a server to babysit.

What broke

  • idempotency: reruns double-writing into RDS before the pipeline was made safe to repeat
PythonDynamoDBRDSspaCyWebSocketsJupyter

Orchestration harness

private repo2026

A Slack + Notion + GitHub harness that runs my own projects. It provisions its own Notion database schema on setup, then reconciles issues, agent runs and status back to a single board. Slack is the control surface, GitHub is the record.

Decisions

Provision the schema, don't document it

Setup creates the full Notion database schema itself. A README describing the columns you're supposed to add by hand is a README that goes stale in a week.

ShellNotion APISlack APIGitHub API

$ cat toolkit.txt

  • FastAPI + PostgresBoring, fast, and the type errors surface at the boundary where I can see them.
  • SupabasePostgres with auth and migrations attached, when I don't want to run the auth myself.
  • TypeScriptFor anything a teammate will touch after I stop looking at it.
  • KotlinWhen the thing has to live on a phone and behave.
  • MCP + Claude Agent SDKMost of my tooling is now agents calling tools I wrote for them.
  • PlaywrightAuth regressions are the ones that hurt, and they only show up in a real browser.
  • BrunoAPI collections that live in the repo instead of someone's desktop app.
  • Docker + RailwayShip the image, not the instructions.
  • OllamaLocal models for the things I'd rather not send to an API.
  • NeovimConfig in Lua, permanently half-finished by design.

$ git log --format=timeline

  1. 2026 — now

    Orenda Project · Taleemabad

    Lesson-plan infrastructure (Dars) and a teacher-training bot running inside WhatsApp for the National Institute of Education. Agent tooling for the teams around me: skill packs, harnesses, MCP servers.

    FastAPI · Supabase · WhatsApp Cloud API · Claude Agent SDK · MCP

  2. 2025

    Orenda Project · Taleemabad

    Content authoring tooling, and the groundwork that Dars was later built on top of.

    TypeScript · React · Python

  3. 2024

    Product work, mostly Kotlin

    Kotlin backends with TypeScript frontends — OmenBoost, Carender. Dockerised Django on EC2 when something needed to just be up. Rebuilt my Neovim config from scratch in Lua, which is its own kind of engineering.

    Kotlin · TypeScript · Django · Docker · Lua

  4. 2023

    EFT · EasyDost

    The data side: DynamoDB→RDS pipelines, serverless POS and named-entity extraction over customer messages, an analysis layer on top, and realtime WhatsApp feeding a support inbox. Studied DSA and data engineering properly in parallel.

    Python · DynamoDB · RDS · NLP · WebSockets

  5. 2021 — 2022

    Teaching myself, in public

    Statistical learning ported from R to Python, SQL analysis projects, matplotlib and seaborn until the plots stopped being ugly, then The Odin Project and MDN for the web half. All of it committed, badly, in public.

    Jupyter · SQL · JavaScript · scikit-learn

$ cat now.md

LAST UPDATED AUGUST 2026

  • *Shipping training levels, exams and on-demand certificates in NIETE-Rumi — the WhatsApp side of national teacher training.
  • *Extending Dars: the chapter planner, formative assessments, and getting book import from taleemabad-core to behave.
  • *Retiring an old school app gracefully, which is a surprisingly delicate thing to do to software people depend on.
  • *Writing agent skills that measurably reduce tool calls rather than just adding capability.
  • *Running Odysseus on my own hardware and local models through Ollama.
  • *Taking Android APKs apart with Morphe patches and a decompiler, for no commercial reason whatsoever.

$ ls writing/

// nothing published yet. /writing is wired up and waiting.

$ cat contact

Open to interesting problems. Fastest way to reach me is email.

hataf.dev · Pakistan · press m to switch sides, 14 to jump