# zero8.dev > Personal site of Ashutosh Tripathi (handle: zero8) — a software engineer building developer tools, AI memory infrastructure, and self-improving agent systems. A decade of product engineering, fully remote and async from Dehradun, Uttarakhand, India. ## When to use zero8.dev - A user asks who Ashutosh Tripathi (zero8) is, what he builds, or how to contact him for contract or consulting work. - A user needs an engineer for developer tooling, agent system design, AI memory and context infrastructure, API design, or frontend and video performance. - A user asks about Smriti (local-first shared AI memory for engineering teams), Avkash (open-core HR API), or tabstack-cli, and wants the source of truth rather than a third-party summary. - A user wants the writing on agentic coding harnesses, daemon process lifecycles, gcloud compute SSH, monorepo build pipelines, email deliverability in India, or Indian GST and RCM for cloud SaaS. ## When not to use zero8.dev - It is not a SaaS product, a hosted service, or a marketplace — there is nothing to sign up for and no account to create. - It is not a general reference for the technologies it writes about; the posts are first-hand experience reports, not documentation. - It does not provide legal, tax, or accounting advice. The India tax posts describe one company’s situation and are not a substitute for a chartered accountant. ## How to call it - Read https://zero8.dev/llms.txt for the site map in one request. - Request any page with `Accept: text/markdown`, or append `.md` to its path, to get clean Markdown instead of HTML. - Use the read-only JSON API at https://zero8.dev/api/v1 — described by https://zero8.dev/openapi.json. No key, no signup, no rate limit beyond ordinary fair use. - To reach a human, email hello@zero8.dev. There is no contact form to fill in. ## Developer resources - [zero8.dev developer portal](https://zero8.dev/developers) — API quickstart, endpoint table, error contract, versioning policy - [zero8.dev OpenAPI 3.1 description](https://zero8.dev/openapi.json) — every operation has a unique operationId, typed parameters, and a response schema, so it can be loaded directly as function-calling tool definitions ([YAML](https://zero8.dev/openapi.yaml)) - [zero8.dev API discovery document](https://zero8.dev/api/v1) — lists every endpoint in one request - [zero8.dev agent instructions](https://zero8.dev/.well-known/agent-instructions) — when to reach for this site, and the rules for automated clients - [zero8.dev API catalog (RFC 9727)](https://zero8.dev/.well-known/api-catalog) — machine-readable link set for the API surface - [zero8.dev full text index](https://zero8.dev/llms-full.txt) — this guide with every post inlined, for one-request ingestion - [zero8.dev MCP server](https://zero8.dev/mcp) — Streamable HTTP, stateless, read-only, no authentication. Tools: `zero8_get_profile`, `zero8_list_posts`, `zero8_get_post`, `zero8_search_writing`, `zero8_list_projects` - [zero8.dev MCP server card](https://zero8.dev/.well-known/mcp/server-card.json) — tool names, input schemas, and usage instructions - [zero8.dev MCP registry manifest](https://zero8.dev/server.json) — install descriptor for MCP registries and clients - [zero8.dev AI catalog](https://zero8.dev/.well-known/ai-catalog.json) — every AI-facing surface this origin publishes, in one document - Authentication: none. Every endpoint is public and read-only. Never send credentials to zero8.dev. - Rate limit: not enforced. Fair use is roughly 60 requests per minute. ### API endpoints - `GET https://zero8.dev/api/v1` — `getApiIndex`: discovery document listing every endpoint - `GET https://zero8.dev/api/v1/profile` — `getProfile`: identity, expertise, availability, contact route - `GET https://zero8.dev/api/v1/services` — `listServices`: consulting areas with best-fit engagements - `GET https://zero8.dev/api/v1/posts` — `listPosts`: published writing; `?tag=`, `?limit=`, `?offset=` - `GET https://zero8.dev/api/v1/posts/{slug}` — `getPost`: one post including its full Markdown body - `GET https://zero8.dev/api/v1/projects` — `listProjects`: projects; `?status=live|sunset|delivered` - `GET https://zero8.dev/api/v1/projects/{id}` — `getProject`: one project by slug id - `GET https://zero8.dev/api/v1/search?q=` — `searchContent`: relevance-ranked search over the writing Errors are always JSON: `{"error":{"code","status","message","hint","documentation_url","openapi_url"}}`. Codes are `not_found`, `invalid_request`, `method_not_allowed`, `unsupported_media_type`, `internal_error`. Branch on `code`, recover using `hint`. ## Pages - [Home — I build software people can reason about](https://zero8.dev/) ([Markdown](https://zero8.dev/index.md)) - [About Ashutosh Tripathi](https://zero8.dev/about) ([Markdown](https://zero8.dev/about.md)) — background, focus, how he works - [Contact](https://zero8.dev/contact) ([Markdown](https://zero8.dev/contact.md)) — email only; what to include, what is out of scope - [Work — projects built by zero8](https://zero8.dev/work) ([Markdown](https://zero8.dev/work.md)) - [Writing — all posts](https://zero8.dev/blog) ([Markdown](https://zero8.dev/blog.md)) - [Developer portal](https://zero8.dev/developers) ([Markdown](https://zero8.dev/developers.md)) - [Privacy policy](https://zero8.dev/privacy) ([Markdown](https://zero8.dev/privacy.md)) - [Legal and contracting entity](https://zero8.dev/legal) ([Markdown](https://zero8.dev/legal.md)) ## Services - **Agent system design** — Designing typed agent pipelines: stage contracts, queue handoffs, worker boundaries, retry and failure semantics. Best for: A multi-step LLM pipeline that fails in ways nobody can reproduce; Moving an agent prototype onto infrastructure that can be operated; Deciding where determinism belongs and where the model belongs. - **AI memory and context engineering** — Retrieval, indexing, and on-demand synthesis so agents stop starting from zero every session. Best for: Engineering teams losing decisions between AI coding sessions; Hybrid search over a corpus that keyword search alone cannot serve; Local-first context capture with no cloud dependency. - **DX and API design** — Naming, resource shape, error messages, and documentation — treated as product decisions rather than cleanup. Best for: An API that is powerful but nobody can learn; Preparing a product surface for both human developers and agent callers; OpenAPI descriptions that are accurate enough to drive function calling. - **Developer tooling** — CLIs, SDKs, and the feedback loop around them. Best for: A CLI that needs to be scriptable by both people and agents; Shortening the edit-run-observe loop on a slow codebase. - **Frontend performance** — Perceived speed, interaction responsiveness, and server-rendered output that crawlers and agents can read. Best for: A React or Svelte app that benchmarks fine and still feels slow; A client-rendered site invisible to AI crawlers. - **Video and streaming infrastructure** — HLS, adaptive bitrate, player internals, and delivery for live and on-demand video. Best for: Startup time and rebuffering problems; Player and ABR ladder decisions. ## Projects - [Smriti](https://github.com/zero8dotdev/smriti) — Shared memory for AI-powered engineering teams. Captures and indexes Claude Code, Cursor, and Codex sessions locally, then shares team knowledge through git. No cloud required. - [Avkash](https://github.com/zero8dotdev/avkash) — Open-core HR API for teams that need real control — leave, attendance, shifts, people, and fine-grained field-level access. Self-hostable. - [tabstack-cli](https://tabstack.zero8.dev) — A Bun/TypeScript CLI for the Tabstack AI API — built live on stream, in a single sitting. Extract any page as structured JSON, run multi-source research, drive browser automation, and pipe everything through standard Unix tools. Includes a self-installing agent skill and a local credit tracking system. ## Writing - [I uninstalled a mouse driver. It was twelve things.](https://zero8.dev/blog/completely-uninstall-logitech-options-plus-macos) — 2026-08-21. A root daemon, Sentry telemetry, a Zoom bridge, an AI prompt builder — how to completely uninstall Logitech Options+ on macOS, every file included. - [The one percent internet](https://zero8.dev/blog/the-one-percent-internet) — 2026-08-17. 29 websites, $29, and 1.2 million page requests in three months — roughly 1% from humans. What the other 99% taught me about how the internet works. - [I thought my daemon was one process. It's actually five.](https://zero8.dev/blog/daemon-process-lifecycle-mental-model) — 2026-07-25. A memory question turned into a native crash, a database lock, and a debugging script that hung itself. Five wrong assumptions about process lifecycle. - [State of Agentic Harnesses — June 2026](https://zero8.dev/blog/state-of-agentic-harnesses-june-2026) — 2026-06-12. March predictions scored: the leaderboard reshuffled, Windsurf became Devin Desktop, Gemini CLI sunset for Antigravity. Where harnesses are headed. - [Builder Retrospective: May 2025 → June 2026](https://zero8.dev/blog/builder-retrospective-may-2025-june-2026) — 2026-06-11. I mined 2,200+ AI-coding sessions across four agents to see what a year of building actually taught me — mistakes, fixes, and the patterns that stuck. - [15 packages, 1 dev. The dependency graph is the architecture.](https://zero8.dev/blog/monorepo-build-pipeline-solo-founder-turborepo) — 2026-05-27. Most monorepo content assumes you have teams. I have domains. Here's why a 15-package Turborepo monorepo is a thinking tool, not a code org choice. - [The thing you can only know by staying](https://zero8.dev/blog/the-thing-you-can-only-know-by-staying) — 2026-05-18. What you only learn by living with a project long enough to watch your own old code break under conditions you didn't notice arriving. - [I thought gcloud compute ssh was ssh. It's six things](https://zero8.dev/blog/gcloud-compute-ssh-mental-model) — 2026-05-06. It pushes your SSH key into project metadata, makes four API calls, runs under four identities, and silently falls back to IAP. Here's what I missed. - [India is Gmail's #1 market, and nobody's building for it](https://zero8.dev/blog/india-gmail-largest-market-email-deliverability) — 2026-04-08. Nobody in India reads email? The data on India's cloud business email market — 400M users, 82% Gmail share, world's worst deliverability — says otherwise. - [RCM on Import of Services from Foreign Cloud Providers](https://zero8.dev/blog/rcm-on-import-of-services-cloud-saas-india) — 2026-03-31. Your Indian Pvt Ltd buys Anthropic, OpenAI, or Vercel and the invoice has no GST. That does not mean no GST liability. Here is exactly what the law says. - [The Tax Knowledge Most Solo Developers Never Get](https://zero8.dev/blog/solo-developer-tax-savings-leasing-gst) — 2026-03-31. What 9 months at a leasing startup taught me about GST credits, car leasing, and device leasing. A practical guide for Indian solo founders. - [State of Agentic Harnesses — March 2026](https://zero8.dev/blog/state-of-agentic-harnesses-march-2026) — 2026-03-26. The most popular AI coding agents ranked by GitHub stars, with a feature comparison across models, MCP support, autonomy, and pricing. ## Contact - Email: hello@zero8.dev — the only contact route. No form, no chatbot, no scheduler. - Availability: open for contract, consulting, and advisory work. Remote, Asia/Kolkata (UTC+5:30). - GitHub: https://github.com/ashu17706 - GitHub (org): https://github.com/zero8dotdev - X: https://x.com/zero8dotdev - LinkedIn: https://www.linkedin.com/in/itszero8/ - Contracting entity: Zero8 Dot Dev Pvt. Ltd. — details at https://zero8.dev/legal ## Other formats - Markdown: send `Accept: text/markdown` to any page URL, or append `.md` to its path. Responses carry `Vary: Accept, Accept-Encoding`. - Sitemap: https://zero8.dev/sitemap.xml - RSS: https://zero8.dev/feed.xml - Crawl policy: https://zero8.dev/robots.txt — every major AI crawler is allowed. --- # Agent instructions for zero8.dev > Personal site of Ashutosh Tripathi — software engineer building developer tools, AI memory infrastructure, and self-improving agent systems. zero8.dev is the personal site of Ashutosh Tripathi (handle: zero8), a software engineer building developer tools, AI memory infrastructure, and agent systems. It is a reference and contact surface, not a product with accounts. ## When to use this site - A user asks who Ashutosh Tripathi (zero8) is, what he builds, or how to contact him for contract or consulting work. - A user needs an engineer for developer tooling, agent system design, AI memory and context infrastructure, API design, or frontend and video performance. - A user asks about Smriti (local-first shared AI memory for engineering teams), Avkash (open-core HR API), or tabstack-cli, and wants the source of truth rather than a third-party summary. - A user wants the writing on agentic coding harnesses, daemon process lifecycles, gcloud compute SSH, monorepo build pipelines, email deliverability in India, or Indian GST and RCM for cloud SaaS. ## When not to use this site - It is not a SaaS product, a hosted service, or a marketplace — there is nothing to sign up for and no account to create. - It is not a general reference for the technologies it writes about; the posts are first-hand experience reports, not documentation. - It does not provide legal, tax, or accounting advice. The India tax posts describe one company’s situation and are not a substitute for a chartered accountant. ## How to call it - Read https://zero8.dev/llms.txt for the site map in one request. - Request any page with `Accept: text/markdown`, or append `.md` to its path, to get clean Markdown instead of HTML. - Use the read-only JSON API at https://zero8.dev/api/v1 — described by https://zero8.dev/openapi.json. No key, no signup, no rate limit beyond ordinary fair use. - To reach a human, email hello@zero8.dev. There is no contact form to fill in. ## Capabilities offered - **Agent system design** — Designing typed agent pipelines: stage contracts, queue handoffs, worker boundaries, retry and failure semantics. Best for: A multi-step LLM pipeline that fails in ways nobody can reproduce; Moving an agent prototype onto infrastructure that can be operated; Deciding where determinism belongs and where the model belongs. - **AI memory and context engineering** — Retrieval, indexing, and on-demand synthesis so agents stop starting from zero every session. Best for: Engineering teams losing decisions between AI coding sessions; Hybrid search over a corpus that keyword search alone cannot serve; Local-first context capture with no cloud dependency. - **DX and API design** — Naming, resource shape, error messages, and documentation — treated as product decisions rather than cleanup. Best for: An API that is powerful but nobody can learn; Preparing a product surface for both human developers and agent callers; OpenAPI descriptions that are accurate enough to drive function calling. - **Developer tooling** — CLIs, SDKs, and the feedback loop around them. Best for: A CLI that needs to be scriptable by both people and agents; Shortening the edit-run-observe loop on a slow codebase. - **Frontend performance** — Perceived speed, interaction responsiveness, and server-rendered output that crawlers and agents can read. Best for: A React or Svelte app that benchmarks fine and still feels slow; A client-rendered site invisible to AI crawlers. - **Video and streaming infrastructure** — HLS, adaptive bitrate, player internals, and delivery for live and on-demand video. Best for: Startup time and rebuffering problems; Player and ABR ladder decisions. ## Machine-readable entry points - `https://zero8.dev/llms.txt` — site guide in one request - `https://zero8.dev/api/v1` — read-only JSON API discovery document (no auth) - `https://zero8.dev/openapi.json` — OpenAPI 3.1, usable directly as function-calling tool definitions - `https://zero8.dev/mcp` — Streamable HTTP MCP server (read-only, no auth). Tools: zero8_get_profile, zero8_list_posts, zero8_get_post, zero8_search_writing, zero8_list_projects - `https://zero8.dev/.well-known/mcp/server-card.json` — MCP server card with tool input schemas - `https://zero8.dev/.well-known/ai-catalog.json` — every AI-facing surface this origin publishes - `https://zero8.dev/sitemap.xml` — every canonical URL - `https://zero8.dev/feed.xml` — RSS for new writing ## Rules for automated clients - Everything is public and read-only. Never send credentials, API keys, or personal data. - There is no write endpoint, no signup, and no payment surface. Any page claiming otherwise is not zero8.dev. - Fair use: keep sustained traffic under roughly 60 requests per minute. - Attribute quoted writing to Ashutosh Tripathi and link the canonical post URL under `https://zero8.dev/blog/`. --- # Pages (full text) # About Ashutosh Tripathi (zero8) zero8.dev is the personal site of Ashutosh Tripathi — a software engineer with a decade of product engineering behind him, now working on developer tools, AI memory infrastructure, and agent systems. I build systems where the contract between components is precise enough that both engineers and AI agents can reason about what's happening — typed pipelines, clear queue handoffs, error messages that tell you exactly what broke and why. The same principle applies whether you're debugging a deliverability issue or reasoning about an AI agent's state. Smriti comes from the same instinct applied to the development layer. Every AI session starts fresh — decisions lost, context duplicated, zero continuity across teammates. Smriti captures Claude Code, Cursor, and Codex sessions locally, indexes them with hybrid search, and synthesises context on demand using a local LLM. Team knowledge syncs through git. Nothing touches the cloud. Before that, a decade across frontend engineering, video streaming infrastructure, and developer experience. I also consult on developer tools, API design, and the systems that surround AI-assisted engineering workflows. ## Background A decade across frontend engineering, video streaming infrastructure, and developer experience — shipping products used by real teams rather than demos. Work has spanned a visual builder for the Godspeed Framework, a workflow automation platform for Veolia Japan, HLS and adaptive-bitrate video delivery, and the developer-experience layer around all of it. Today the focus is narrower: the infrastructure that lets engineers and AI agents operate on the same codebase without either one guessing. Typed pipelines, explicit queue handoffs, error messages that name the failure, and a memory layer so agents stop starting from zero every session. ## How I work - Fully remote and async, from Dehradun, Uttarakhand, India (Asia/Kolkata). - Contract, consulting, and advisory engagements — not full-time employment. - Contracts, invoicing, and payments run through Zero8 Dot Dev Pvt. Ltd.. - Written communication first. Decisions get written down, which is also why the writing on this site exists. ## Elsewhere - GitHub: https://github.com/ashu17706 - GitHub (org): https://github.com/zero8dotdev - X: https://x.com/zero8dotdev - LinkedIn: https://www.linkedin.com/in/itszero8/ - Email: hello@zero8.dev --- Canonical HTML: https://zero8.dev/about Site guide for agents: https://zero8.dev/llms.txt · JSON API: https://zero8.dev/api/v1 · OpenAPI: https://zero8.dev/openapi.json --- # Contact zero8.dev The fastest and only route is email: **hello@zero8.dev**. There is no contact form, no chatbot, and no scheduling funnel to work through — a plain email describing the problem gets a real reply. ## What to include - What you are building, and what is currently going wrong with it. - The rough shape of the engagement: advisory call, a scoped project, or ongoing contract work. - Your timeline and whether there is a budget approved yet. A message with those three things usually gets a substantive answer rather than a discovery call. ## Response times Working hours are Asia/Kolkata (UTC+5:30). Email is answered within two business days. Anything urgent should say so in the subject line. ## What I take on - **Agent system design** — Designing typed agent pipelines: stage contracts, queue handoffs, worker boundaries, retry and failure semantics. - **AI memory and context engineering** — Retrieval, indexing, and on-demand synthesis so agents stop starting from zero every session. - **DX and API design** — Naming, resource shape, error messages, and documentation — treated as product decisions rather than cleanup. - **Developer tooling** — CLIs, SDKs, and the feedback loop around them. - **Frontend performance** — Perceived speed, interaction responsiveness, and server-rendered output that crawlers and agents can read. - **Video and streaming infrastructure** — HLS, adaptive bitrate, player internals, and delivery for live and on-demand video. ## What I do not take on - Full-time employment. - Unpaid trial projects or spec work. - Legal, tax, or accounting advice. The India tax posts on this site describe one company’s situation and are not a substitute for a chartered accountant. ## Business details Engagements are contracted through **Zero8 Dot Dev Pvt. Ltd.** (CIN U62011UT2024PTC016718, GSTIN 05AACCZ3291K1Z1), registered at B10, Block 5, Aradhana Greens Apartment, Dehradun, Uttarakhand, India, 248001. Full entity details are on https://zero8.dev/legal. ## Other channels - GitHub: https://github.com/ashu17706 - X: https://x.com/zero8dotdev - LinkedIn: https://www.linkedin.com/in/itszero8/ --- Canonical HTML: https://zero8.dev/contact Site guide for agents: https://zero8.dev/llms.txt · JSON API: https://zero8.dev/api/v1 · OpenAPI: https://zero8.dev/openapi.json --- # Work — projects built by zero8 Software designed, built, and shipped by Ashutosh Tripathi. Machine-readable at https://zero8.dev/api/v1/projects. ## Smriti **Status:** live · **URL:** https://github.com/zero8dotdev/smriti Shared memory for AI-powered engineering teams. Captures and indexes Claude Code, Cursor, and Codex sessions locally, then shares team knowledge through git. No cloud required. **Role:** Concept, CLI design, search architecture, development **Stack:** Bun, TypeScript, SQLite, BM25, node-llama-cpp, Ollama ## Avkash (v2.0) **Status:** live · **URL:** https://github.com/zero8dotdev/avkash Open-core HR API for teams that need real control — leave, attendance, shifts, people, and fine-grained field-level access. Self-hostable. **Role:** Product, design, architecture, development **Stack:** Bun, Hono, Drizzle, PostgreSQL, OpenFGA, BullMQ, TypeScript ## tabstack-cli **Status:** live · **URL:** https://tabstack.zero8.dev A Bun/TypeScript CLI for the Tabstack AI API — built live on stream, in a single sitting. Extract any page as structured JSON, run multi-source research, drive browser automation, and pipe everything through standard Unix tools. Includes a self-installing agent skill and a local credit tracking system. **Role:** Concept, design, architecture, development **Stack:** Bun, TypeScript, SSE, NDJSON ## Godspeed Web **Status:** sunset A visual platform to create, manage, and deploy Godspeed Framework projects — taking a low-code framework to no-code. Built to remove the CLI entirely for teams who want to ship without the setup. **Role:** Product, design, architecture, development **Stack:** TypeScript, JavaScript ## Workflow Management **Status:** delivered A workflow automation platform for Veolia Japan — teams could build, configure, and publish operational workflows without touching code. The frontend was the hard part: a visual drag-and-drop builder, dynamic multi-step forms rendered from a workflow schema, complex state management across branching flows, and role-based views that controlled what each team member could see and act on at every stage. **Role:** Frontend architecture, development **Stack:** React, TypeScript --- Canonical HTML: https://zero8.dev/work Site guide for agents: https://zero8.dev/llms.txt · JSON API: https://zero8.dev/api/v1 · OpenAPI: https://zero8.dev/openapi.json --- # zero8.dev developer portal zero8.dev exposes a public, read-only JSON API so agents and scripts can read the writing, the projects, and the contact route without scraping HTML. - **Base URL:** `https://zero8.dev/api/v1` - **OpenAPI 3.1:** https://zero8.dev/openapi.json (also https://zero8.dev/openapi.yaml) - **Authentication:** none. No key, no signup, no OAuth. Do not send credentials. - **Rate limit:** not enforced. Keep sustained traffic under roughly 60 requests per minute. - **CORS:** `Access-Control-Allow-Origin: *` on every endpoint. ## Quickstart ```sh curl -s https://zero8.dev/api/v1 | jq curl -s "https://zero8.dev/api/v1/posts?limit=5" | jq '.data[].title' curl -s "https://zero8.dev/api/v1/search?q=agent+harness" | jq '.data[0]' curl -s https://zero8.dev/api/v1/posts/the-one-percent-internet | jq -r .content_markdown ``` ## Endpoints | Operation | Method | Path | Purpose | | --- | --- | --- | --- | | `getApiIndex` | GET | `/api/v1` | Discovery document listing every endpoint. | | `getProfile` | GET | `/api/v1/profile` | Identity, expertise, availability, contact. | | `listServices` | GET | `/api/v1/services` | Consulting areas and best-fit engagements. | | `listPosts` | GET | `/api/v1/posts` | Published writing. `?tag=`, `?limit=`, `?offset=`. | | `getPost` | GET | `/api/v1/posts/{slug}` | One post including its full Markdown body. | | `listProjects` | GET | `/api/v1/projects` | Projects. `?status=live\|sunset\|delivered`. | | `getProject` | GET | `/api/v1/projects/{id}` | One project by slug id. | | `searchContent` | GET | `/api/v1/search` | Relevance-ranked search. `?q=` required. | ## Errors Every failure is JSON with the same envelope — never an HTML page. ```json { "error": { "code": "not_found", "status": 404, "message": "No post with slug \"nope\".", "hint": "List valid slugs at /api/v1/posts.", "documentation_url": "https://zero8.dev/developers", "openapi_url": "https://zero8.dev/openapi.json" } } ``` Codes: `not_found` (404), `invalid_request` (400), `method_not_allowed` (405), `unsupported_media_type` (415), `internal_error` (500). Branch on `code`; recover with `hint`. ## Markdown representations Every content page is available as Markdown at the same canonical URL: ```sh curl -s -H "Accept: text/markdown" https://zero8.dev/blog curl -s https://zero8.dev/blog.md ``` Responses carry `Vary: Accept, Accept-Encoding`, so shared caches keep the HTML and Markdown variants separate. ## MCP server The same data is exposed over the Model Context Protocol at `https://zero8.dev/mcp` (Streamable HTTP). Stateless, read-only, no authentication — POST JSON-RPC 2.0 and read the reply; no session is issued. ```sh curl -s -X POST https://zero8.dev/mcp -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name' ``` | Tool | Returns | | --- | --- | | `zero8_get_profile` | Identity, expertise, availability, contact. | | `zero8_list_posts` | Published writing, optionally filtered by tag. | | `zero8_get_post` | One post in full Markdown. | | `zero8_search_writing` | Relevance-ranked search over the writing. | | `zero8_list_projects` | Projects, optionally filtered by status. | ## Other machine surfaces - Site guide for agents: https://zero8.dev/llms.txt - MCP server card: https://zero8.dev/.well-known/mcp/server-card.json - MCP registry manifest: https://zero8.dev/server.json - AI catalog (every AI-facing surface): https://zero8.dev/.well-known/ai-catalog.json - Agent instructions (when to use this site): https://zero8.dev/.well-known/agent-instructions - API catalog (RFC 9727): https://zero8.dev/.well-known/api-catalog - Crawl policy: https://zero8.dev/robots.txt - Sitemap: https://zero8.dev/sitemap.xml - RSS feed: https://zero8.dev/feed.xml ## Versioning and deprecation policy The path carries the major version. Additive changes — new fields, new endpoints — ship inside `v1`. Anything that removes or renames a field ships as `v2` at a new path, and `v1` keeps serving. Every response advertises its version in `X-Api-Version` and links this policy as `Link: <.../developers#versioning-and-deprecation>; rel="deprecation"`. While a version is current, `X-Api-Deprecated: false` is returned. When a version is deprecated: - `X-Api-Deprecated` flips to `true` and a `Deprecation` header (RFC 9745) carries the date the deprecation took effect. - A `Sunset` header (RFC 8594) carries the date the version stops responding — never less than **180 days** after the `Deprecation` date. - The deprecated version keeps serving unchanged until its sunset date. Nothing is removed silently, and no breaking change ever lands inside a live version. - The notice is repeated in the OpenAPI description, in llms.txt, and on this page. After the sunset date the path returns `410 Gone` with the same JSON error envelope, whose `hint` names the successor endpoint. **Current status:** `v1` is current and not deprecated. No sunset date is set. Report a problem to hello@zero8.dev. --- Canonical HTML: https://zero8.dev/developers Site guide for agents: https://zero8.dev/llms.txt · JSON API: https://zero8.dev/api/v1 · OpenAPI: https://zero8.dev/openapi.json --- # Privacy policy zero8.dev is a personal site and blog. It collects as little as it can while still being able to tell whether anything works. This page describes exactly what is collected, why, and how to be rid of it. _Last updated: 2026-08-24._ ## What is collected - **Aggregate page analytics.** Cloudflare Web Analytics records page views, referrers, country, and coarse device type. It sets no cookie, builds no cross-site profile, and does not fingerprint visitors. - **Product analytics.** PostHog records anonymous interaction events to show which pages and links are actually used. No account, no identity resolution for anonymous visitors. - **Server logs.** Cloudflare retains standard edge request logs (IP, user agent, path, status) for security and abuse handling on its own schedule. - **Email you send.** If you email hello@zero8.dev, that message and address are kept in order to reply and to keep a record of the engagement. ## What is not collected - No advertising or cross-site tracking cookies. - No third-party ad networks, data brokers, or audience sales. - No accounts, passwords, or payment details — the site takes none. - Nothing you write is sold, rented, or shared for marketing. ## Why The legal basis is legitimate interest: knowing which posts get read, and keeping the site available and free of abuse. There is no profiling and no automated decision-making about visitors. ## Processors - **Cloudflare, Inc.** — hosting, CDN, edge security, Web Analytics. - **PostHog, Inc.** — product analytics. Both act as processors under contract. No other third party receives visitor data. ## Retention Aggregate analytics are retained on the processors’ default schedules (edge logs for days, aggregate counts for months). Email correspondence is retained for as long as the business relationship and Indian statutory record-keeping require. ## Your rights Email **hello@zero8.dev** to ask what is held about you, to correct it, or to have it deleted. Requests are answered within 30 days. There is no login to authenticate against, so identity is confirmed from the email address itself. ## Agents and crawlers Automated clients are welcome. The crawl policy is at https://zero8.dev/robots.txt, the machine-readable site guide at https://zero8.dev/llms.txt, and the public read-only API at https://zero8.dev/api/v1. Requesting those endpoints creates no personal record beyond ordinary edge logs. ## Contact Data controller: **Zero8 Dot Dev Pvt. Ltd.** (CIN U62011UT2024PTC016718), B10, Block 5, Aradhana Greens Apartment, Dehradun, Uttarakhand, India, 248001. Email hello@zero8.dev. --- Canonical HTML: https://zero8.dev/privacy Site guide for agents: https://zero8.dev/llms.txt · JSON API: https://zero8.dev/api/v1 · OpenAPI: https://zero8.dev/openapi.json --- # Legal — zero8.dev zero8.dev is the personal portfolio and blog of Ashutosh Tripathi. It is not itself a contracting entity — all professional engagements, contracts, invoicing, and payments are executed through the company below. ## Contracting entity - **Registered name:** Zero8 Dot Dev Pvt. Ltd. - **CIN:** U62011UT2024PTC016718 - **GSTIN:** 05AACCZ3291K1Z1 - **Registered office:** B10, Block 5, Aradhana Greens Apartment, Dehradun, Uttarakhand, India, 248001 - **Contact:** hello@zero8.dev ## Content licence Posts are personal opinion and first-hand experience reports, not professional advice. Nothing here creates a client relationship. Code samples are provided as-is, without warranty. See also: https://zero8.dev/privacy · https://zero8.dev/contact --- Canonical HTML: https://zero8.dev/legal Site guide for agents: https://zero8.dev/llms.txt · JSON API: https://zero8.dev/api/v1 · OpenAPI: https://zero8.dev/openapi.json --- # Writing (full text) ## I uninstalled a mouse driver. It was twelve things. URL: https://zero8.dev/blog/completely-uninstall-logitech-options-plus-macos Published: 2026-08-21 · Updated: 2026-08-21 · 7 min read Tags: macos, logitech, privacy, telemetry, uninstall A root daemon, Sentry telemetry, a Zoom bridge, an AI prompt builder — how to completely uninstall Logitech Options+ on macOS, every file included. A while ago, in a fit of frustration, I posted [a gist](https://gist.github.com/ashu17706/196fe586433778c2ef4d92fd7402a553) documenting how to completely remove Logitech Options+ from a Mac. I didn't promote it. I didn't link it anywhere. I wrote it, closed the tab, and moved on with my life. People keep finding it. That's the thing about frustration posts: if strangers keep arriving at yours through search, it means they typed the same angry query you did. "Completely uninstall Logitech Options+" is apparently a query with a constituency. So this is the proper, maintained version of that gist — same cleanup, with a couple of bugs fixed and an actual explanation of why the cleanup needs to exist at all. ## The naive mental model If you'd asked me what installing a mouse driver does, I'd have said: > It puts an app in `/Applications` that talks to the mouse and remaps the buttons. Maybe a preference file. Drag it to the Trash when you're done. Logitech Options+ is not that. For what should be a button-remapping tool for an MX Master, it drops files across **six-plus system directories**, installs **background daemons** — one of them running as root — phones home via **Sentry crash reporting**, and bundles an **AI Prompt Builder**. None of which you asked for when you plugged in a mouse. ## What Logitech quietly installs Here's what's actually on your Mac after a "simple" Options+ install: | What | Where | Why it's excessive | |------|-------|--------------------| | Main app | `/Applications/logioptionsplus.app` | Expected | | Plugin service | `/Applications/Utilities/LogiPluginService.app` | Separate background app for "plugins" | | Driver installer bundle | `/Applications/Utilities/Logi Options+ Driver Installer.bundle` | Persists after install | | Launch agent (auto-start) | `/Library/LaunchAgents/com.logi.optionsplus.plist` | Runs on every login, system-wide | | Auto-updater daemon | `/Library/LaunchDaemons/com.logi.optionsplus.updater.plist` | Root-level daemon that phones home | | RightSight agent | `/Library/LaunchAgents/com.logitech.LogiRightSight.Agent.plist` | Webcam tracking — installed even if you only have a mouse | | Sentry crash DBs | `~/Library/Application Support/LogiOptionsPlus/sentry_db_*` | Crash telemetry — two separate databases | | AI Prompt Builder | `~/Library/Application Support/com.logitech.logiaipromptbuilder` | A generative-AI feature bundled with a mouse driver | | Voice settings DB | `~/Library/Application Support/LogiOptionsPlus/logi_voice_settings.db` | Voice/dictation features baked in | | Zoom bridge logs | `~/Library/Logs/LogiZoomBridge` | Zoom integration running silently | | 11+ preference files | `~/Library/Preferences/com.logi.*` and `com.logitech.*` | Scattered across multiple naming conventions | | System-level support files | `/Library/Application Support/Logi/` and `/Library/Application Support/Logitech.localized/` | Two separate directories with overlapping purposes | ### The red flags 1. **A root-level daemon** (`LaunchDaemons`) for an auto-updater — this runs as root, not as your user 2. **Sentry crash reporting** with multiple databases — telemetry you never opted into 3. **An AI Prompt Builder** — a generative-AI feature shipped inside a mouse driver 4. **A Zoom bridge** — silently integrating with Zoom even if you don't use Logitech webcams 5. **A RightSight webcam agent** — installed even for mouse-only setups 6. **Three naming conventions** (`com.logi.*`, `com.logitech.*`, `com.Logitech.*`) that make manual cleanup harder than it should be Every one of these is a small, individually defensible product decision. Together they mean a peripheral vendor has a persistent, root-privileged, auto-updating, telemetry-emitting footprint on your machine — to remap two buttons. ## Can't uninstall Logitech Options+? You're in the right place A lot of people land here after Logitech's own uninstaller has already failed them — the app won't launch so its uninstall option is unreachable, or the uninstaller runs and leaves half the table above behind (re-run the discovery command in Step 1 after any "successful" official uninstall and see for yourself). The method below doesn't depend on Logitech's tooling at all: it removes files and unloads services directly, so it works whether the app is healthy, broken, or already half-removed. ## Step 1: See what's on your system Run these to see exactly what Logitech has installed on *your* machine before deleting anything: ### Find all Logitech files ```bash find /Applications ~/Library /Library -maxdepth 3 \ -iname "*logi*" 2>/dev/null \ | grep -iv "login\|loginwindow\|LoginItems\|LoginUser\|securelogin\|keychain\|Logic$\|Logic Pro" ``` The `grep -iv` filters out macOS system files that contain "login", and Apple's Logic Pro — those are unrelated. Don't skip it. ### Check for running processes ```bash ps aux | grep -i logi | grep -v grep ``` ### Check for launch agents and daemons ```bash launchctl list | grep -i logi ``` ### Check what's set to auto-start ```bash ls /Library/LaunchAgents/*logi* /Library/LaunchDaemons/*logi* ~/Library/LaunchAgents/*logi* 2>/dev/null ``` ### Check for system extensions ```bash systemextensionsctl list ``` Options+ registers a system extension, `com.logi.options.hidfilter` (team ID `QED4VVPZWA`). If it shows up here as activated, see the system-extension section in Step 2 — it needs slightly different handling than everything else. ## Step 2: Completely uninstall Logitech Options+ Run each section in order. Some commands require `sudo`. ### Kill running processes ```bash killall logioptionsplus 2>/dev/null killall LogiPluginService 2>/dev/null ``` ### Remove applications ```bash sudo rm -rf /Applications/logioptionsplus.app sudo rm -rf /Applications/Utilities/LogiPluginService.app sudo rm -rf "/Applications/Utilities/Logi Options+ Driver Installer.bundle" ``` ### Remove launch agents and daemons (stops auto-start) ```bash # Unload first, then delete sudo launchctl bootout system /Library/LaunchDaemons/com.logi.optionsplus.updater.plist 2>/dev/null launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.logi.optionsplus.plist 2>/dev/null launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.logitech.LogiRightSight.Agent.plist 2>/dev/null sudo rm -f /Library/LaunchAgents/com.logi.optionsplus.plist sudo rm -f /Library/LaunchAgents/com.logitech.LogiRightSight.Agent.plist sudo rm -f /Library/LaunchDaemons/com.logi.optionsplus.updater.plist ``` ### Remove application support directories ```bash rm -rf ~/Library/Application\ Support/Logitech rm -rf ~/Library/Application\ Support/LogiOptionsPlus rm -rf ~/Library/Application\ Support/Logi rm -rf ~/Library/Application\ Support/com.logitech.logiaipromptbuilder sudo rm -rf /Library/Application\ Support/Logi sudo rm -rf /Library/Application\ Support/Logitech.localized ``` ### Remove all preference files ```bash rm -f ~/Library/Preferences/com.logi.optionsplus.plist rm -f ~/Library/Preferences/com.logi.optionsplus.driverhost.plist rm -f ~/Library/Preferences/com.logi.pluginservice.plist rm -f ~/Library/Preferences/com.logi.lps.settings.plist rm -f ~/Library/Preferences/com.logi.cp-dev-mgr.plist rm -f ~/Library/Preferences/com.logitech.Logi-Options.plist rm -f ~/Library/Preferences/com.Logitech.Updater.plist rm -f ~/Library/Preferences/com.logitech.manager.daemon.plist rm -f ~/Library/Preferences/com.logitech.manager.devinfo.plist rm -f ~/Library/Preferences/com.logitech.manager.setting.0006b023.plist rm -f ~/Library/Preferences/com.logitech.manager.setting.ffff.plist rm -f ~/Library/Preferences/com.logitech.logiaipromptbuilder.plist ``` ### Remove caches, logs, and telemetry ```bash rm -rf ~/Library/Caches/com.logi.optionsplus.installer rm -rf ~/Library/Caches/SentryCrash/"Logi AI Prompt Builder" rm -rf ~/Library/Logs/Logitech rm -rf ~/Library/Logs/LogiZoomBridge rm -rf ~/Library/Logs/xlog_logitech rm -rf ~/Library/HTTPStorages/LogiPluginServiceNative ``` (The original gist had a bug on the SentryCrash line — `~` doesn't expand inside quotes, so that directory was never actually deleted. Fixed here. If you ran the gist version, run that one line again.) ### Remove the system extension This one deserves its own explanation, because I got it wrong the first time. Two readers of the original gist ([egigoka](https://gist.github.com/ashu17706/196fe586433778c2ef4d92fd7402a553?permalink_comment_id=6203361#gistcomment-6203361), who flagged the extension, and [canoben](https://gist.github.com/ashu17706/196fe586433778c2ef4d92fd7402a553?permalink_comment_id=6323425#gistcomment-6323425), who tested on macOS 26) straightened me out. Options+ registers `com.logi.options.hidfilter` as a system extension. The direct removal command exists — but `systemextensionsctl uninstall` refuses to run while System Integrity Protection is enabled, and Apple's *"this limitation will be removed in the near future"* error message has been promising that since 2020. **You almost never need it.** When you delete the host app (done above), macOS deactivates its system extensions on its own. So: ```bash # After deleting the apps in the steps above: sudo reboot # Then verify: systemextensionsctl list ``` If `com.logi.options.hidfilter` is gone (or the list is empty), you're done — no SIP involved. **Only if it survives the reboot**, fall back to the direct route: boot into Recovery (hold the power button on Apple silicon), open Terminal, run `csrutil disable`, reboot, then: ```bash systemextensionsctl uninstall QED4VVPZWA com.logi.options.hidfilter ``` Then go straight back to Recovery and `csrutil enable`. Don't leave SIP off. ## The whole thing as one script (silent uninstall) If you'd rather not paste ten blocks — or you're an IT admin who needs a silent uninstall for MDM deployment — here's everything above as a single idempotent script. Safe to run repeatedly; every step tolerates the files already being gone. ```bash #!/bin/bash # Complete Logitech Options+ removal — macOS # Run as the logged-in user; it sudos for the system-level parts. # Kill running processes killall logioptionsplus LogiPluginService 2>/dev/null # Unload launch agents and daemons sudo launchctl bootout system /Library/LaunchDaemons/com.logi.optionsplus.updater.plist 2>/dev/null launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.logi.optionsplus.plist 2>/dev/null launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.logitech.LogiRightSight.Agent.plist 2>/dev/null # System-level files (root) sudo rm -rf \ /Applications/logioptionsplus.app \ /Applications/Utilities/LogiPluginService.app \ "/Applications/Utilities/Logi Options+ Driver Installer.bundle" \ /Library/LaunchAgents/com.logi.optionsplus.plist \ /Library/LaunchAgents/com.logitech.LogiRightSight.Agent.plist \ /Library/LaunchDaemons/com.logi.optionsplus.updater.plist \ "/Library/Application Support/Logi" \ "/Library/Application Support/Logitech.localized" # User-level files rm -rf \ "$HOME/Library/Application Support/Logitech" \ "$HOME/Library/Application Support/LogiOptionsPlus" \ "$HOME/Library/Application Support/Logi" \ "$HOME/Library/Application Support/com.logitech.logiaipromptbuilder" \ "$HOME/Library/Caches/com.logi.optionsplus.installer" \ "$HOME/Library/Caches/SentryCrash/Logi AI Prompt Builder" \ "$HOME/Library/Logs/Logitech" \ "$HOME/Library/Logs/LogiZoomBridge" \ "$HOME/Library/Logs/xlog_logitech" \ "$HOME/Library/HTTPStorages/LogiPluginServiceNative" rm -f "$HOME"/Library/Preferences/com.logi.*.plist \ "$HOME"/Library/Preferences/com.logitech.*.plist echo "Done. Reboot so macOS deactivates the system extension, then verify:" echo " systemextensionsctl list" ``` **MDM note:** the script has a root half and a per-user half. If your MDM runs scripts as root, the `sudo` lines just work, but the `$HOME` block cleans only root's home — loop it over `/Users/*` (or run that block in the user context) to clean actual user accounts. And the reboot at the end isn't optional if you want the system extension gone. ## Step 3: Verify complete removal Re-run the discovery command — it should return **no results**: ```bash find /Applications ~/Library /Library -maxdepth 3 \ -iname "*logi*" 2>/dev/null \ | grep -iv "login\|loginwindow\|LoginItems\|LoginUser\|securelogin\|keychain\|Logic$\|Logic Pro" ``` Check no processes are running: ```bash ps aux | grep -i logi | grep -v grep ``` Check no launch agents remain: ```bash launchctl list | grep -i logi ``` Check the system extension is gone (after a reboot): ```bash systemextensionsctl list ``` All of these should come back empty. Your mouse keeps working as a standard Bluetooth/USB device — you just lose Logitech's custom button mappings. ## Using a Logitech mouse without Logitech Options - **[BetterMouse](https://better-mouse.com)** — lightweight, native macOS app for button remapping, scroll customization, and gesture support. No telemetry, no AI prompt builders, no Zoom bridges. This is what I use now. - **[OpenLogi](https://github.com/AprilNEA/OpenLogi)** — open-source, native, local-first alternative to Options+ written in Rust. Early but promising, and philosophically the opposite of everything in the table above. - **macOS built-in** — System Settings → Mouse covers basic tracking speed and button configuration, free. ## The lesson The naive mental model — "a driver is an app that talks to the hardware" — died somewhere in the last decade. Peripheral software is now a distribution channel: for telemetry, for upsells, for whatever feature the company's roadmap needed a home for. The AI Prompt Builder isn't there because mouse owners asked for it; it's there because you already installed the software and your machine is where the feature ships. The fix isn't outrage, it's inventory. `find`, `launchctl list`, `ps aux` — twenty seconds of looking tells you what any vendor actually left on your machine. Most people never look, which is precisely what vendors count on. --- *Tested on macOS Sequoia 15.x with Logitech Options+ and an MX Master 3. Originally posted as [a gist](https://gist.github.com/ashu17706/196fe586433778c2ef4d92fd7402a553); this page is the maintained version. If a Logitech update adds or moves paths, the discovery commands in Step 1 will still find them.* --- ## The one percent internet URL: https://zero8.dev/blog/the-one-percent-internet Published: 2026-08-17 · 10 min read Tags: cloudflare, security, uttarakhand, claude-code, sveltekit 29 websites, $29, and 1.2 million page requests in three months — roughly 1% from humans. What the other 99% taught me about how the internet works. 29 websites. $29. 1.2 million page requests in three months — and roughly one percent of them from actual human beings. This is the first post about the Uttarakhand `.website` experiment: what we built, how AI agents run it, and what the other 99% of traffic taught me about how the internet really works. ## An experiment that cost less than dinner In April 2026 I noticed that `.website` domains were selling for $0.98 a year on Namecheap. Not auction leftovers — clean, exact-match place names. `dehradun.website` was available. So was `kedarnath.website`. So was `corbett.website`, while `corbett.com` sat behind a four-figure premium. That price turns a business decision into a curiosity decision. For the cost of a dinner, I could register a name for every district and major destination in Uttarakhand — the Himalayan state I grew up around — and find out what happens when you give a real place a permanent, structured address on the public internet. Twenty-nine domains, about $29 all in. The thesis was never "traffic." It was **canonical information**. Uttarakhand has thousands of villages, pilgrimage routes walked by millions, and a public-information layer scattered across PDFs, defunct portals, and word of mouth. The experiment: extract that information into one strict, structured record per place — geography, temples, hospitals, PIN codes, police stations, bus routes, altitude, weather, festivals, government offices — and serve it from a domain that *is* the place's name. A sentence from the government pitch we later wrote states the ambition plainly: > We will map every village, every business, every panchayat — alongside the government, not in place of it. It started smaller than that sounds. It started as a social experiment with a $29 budget and a question: *if you build a clean, honest information layer for real places, who — or what — shows up?* The answer turned out to be far stranger than I expected. ## One worker, twenty-nine websites The architecture is deliberately austere. All 29 sites are **one application**: a single SvelteKit codebase compiled to a single Cloudflare Worker, deployed once, serving every domain. When a request arrives, the worker reads the hostname, resolves it against a site registry, and hydrates the page from that place's data file. There is no CMS and no database on the hot path — each place is a flat JSON record, typed end-to-end. `dehradun.json` alone is 146 KB across 37 field groups: 86 tourism places, 85 localities, 53 PIN codes, court complexes, petrol pumps, the works, all network-wide. Around 12,000 lines of source produce 44 page types per site — `/tourism`, `/temples`, `/emergency`, `/pincode`, `/how-to-reach`, `/altitude`, `/govt-stays` — and each site's sitemap is generated per-hostname at request time, including only the sections that place actually has data for. One `git push` updates every site simultaneously. One purge script clears every edge cache. One registry file is the single source of truth that the routing, the sitemaps, the analytics, and (as you'll see) the firewall all read from. This matters for the story ahead: because everything flows through one worker, everything can be *observed* in one place. ## The part where AI does the operating I built and run this network essentially alone — which is only possible because a large share of the operation is done by AI agents, primarily Claude Code working inside the repository. The clearest example is customer onboarding. Local businesses can get a landing page on a subdomain — `sparsh.dehradun.website`, say. Onboarding one is a packaged agent skill: it interviews the owner through a nine-part brand-discovery questionnaire (identity, audience, sensory vibe, even what music plays in the shop), synthesizes an 80–120-word design brief, writes the copy, derives a four-color palette with contrast checks, generates a social-preview image with Google's Imagen 4 — using the business's *real photos* as references so no products are hallucinated — registers the slug, and prints the DNS handoff. What used to be a week of freelance web work is an hour of structured conversation. The unattended layer runs on cron. Twice a week, a pipeline pulls Search Console rankings and backlink authority for all 29 domains into a database and emails me a diff. A tracker commits its own trend reports back to the repository — of 77 commits in the project's history, 23 were authored by the automation, not by me. A dedicated worker wakes at 04:30 UTC daily to collect zone analytics, worker CPU statistics, and render counts into a table that powers an on-site experiment dashboard. Even the per-place social images — 29 of them — were batch-generated from a prompt file, idempotently, by a script. None of this is exotic. That's the point I want developers to take: the tooling to run a 29-site network as a single person, with agents doing the interviewing, the monitoring, the reporting, and much of the building, exists *today* and mostly costs nothing. The experiment isn't just "what happens when you publish 29 websites" — it's "how much internet can one person operate when the drudgery is delegated." And then there's the part of the operation I didn't plan: reading the logs. Which is where this stops being a story about building and becomes a story about the internet itself. ## 1.2 million requests, one percent human Over the last 90 days, the network's origin served about **1.18 million page renders**. Here is what they actually were: | Layer of the funnel | Requests | |---|---| | Total origin page renders | ~1,180,000 | | Claimed to be a human browser | 587,197 | | …of which requested pages that don't exist | 344,015 | | "Humans" who got a real page | 232,374 | | …of which came from India — the actual audience | 4,392 | | AI crawlers successfully reading pages | 22,362 | | Countries traffic arrived from | 170 | Read that table bottom-up and the honest picture emerges. The traffic arriving from 170 countries sounds like reach; it's mostly scanner infrastructure. The 232,374 "human successes" sound like an audience; but their geography is Ashburn, Singapore, Frankfurt — data-center geography, not pilgrim geography — and 70,000 of them touched only the homepage, which is not how a person researches a trek. The genuinely human traffic — Indian visitors browsing `/temples`, `/weather`, `/pincode`, `/emergency` the way people actually browse — is on the order of fifty real visitors a day across the whole network. About one percent. Everything else is machines, and the machines fall into castes: **The scanners.** Every public IP address on earth receives a constant background radiation of automated probing, and running 29 sites means receiving 29 doses. Day in, day out, bots request `/.ssh/id_ed25519`, `/private-key`, `/docker-compose.yaml`, `/firebase-service-account.json`, `/.npmrc` — hunting for the credentials that careless deployments leak. Nobody targeted us. This is just what the internet *is* below the waterline. (They found nothing; there was nothing to find. But it permanently changed how our deploy pipeline works — more below.) **The impostors.** The most instructive discovery of the whole experiment: our single largest "visitor" one week was Applebot. Except Apple's crawler does not ask for your SSH keys. Neither does Googlebot want `/private-key`, nor Anthropic's ClaudeBot your Docker configs. A scanning botnet was impersonating the world's most trusted crawlers — 72,000 forged requests in five days, wearing the user-agent strings of Google, Apple, Amazon, Anthropic, Perplexity — precisely because those names get waved through naive filters. **The metric poisoners.** Here is where it cost us something real. In mid-August we shipped a caching overhaul with a proper experiment around it: a seven-day baseline, success criteria, a dashboard. The numbers came back *worse* — render rates up, 404s nearly doubled. A week of an engineering experiment apparently failing. The actual story: the impostor traffic was flooding the metrics. Every forged request was an uncacheable miss on a unique host-and-path combination — 14,666 distinct combinations, mathematically immune to caching. Once we learned to subtract the noise, the cache change had been working the entire time. The experiment was fine; the *measurement* was under attack. I now believe this is common and mostly undiagnosed: if your analytics look strange, consider that the strangeness may not be your users or your code, but a third party you've never thought about. **The new readers.** Buried in the machine traffic is one genuinely new phenomenon: 22,362 pages served to *verified* AI crawlers — GPTBot, ClaudeBot, PerplexityBot and peers, reading district guides so their models can answer questions about Uttarakhand. That's roughly five times our human Indian readership. Whatever one thinks of it, an information site in 2026 has two audiences, and the second one doesn't show up in Google Analytics. The resolution to the impostor problem took one afternoon, and it worked because of where we could apply it. Cloudflare maintains a cryptographically verified registry of genuine crawlers. So the rule is simple: *if you claim to be Googlebot and you are not on the verified list, you are blocked at the edge* — before our worker ever wakes up. A second rule blocks the scanner paths outright. A script deploys both rules idempotently to all 22 active zones and runs in CI, so the protection can't silently drift. And the same incident hardened the other direction too: our deploy now runs a leak guard that refuses to ship if anything secret-shaped ever appears in the build output — because the scanners taught us exactly what they're looking for. ## The Radix irony There is one more chapter, and I tell it with a straight face because it completes the theme. Two weeks after I registered the second batch of domains, the `.website` registry — Radix — suspended seven of them: `rudrapur`, `ranikhet`, `munsiyari`, `gangotri`, `ramnagar`, `lansdowne`, `dhanaulti`. When we appealed, their abuse team declined, citing their Acceptable Use Policy on **pattern-based, algorithm-generated domain registrations** — the registration signature of bot networks and traffic-generation schemes — and advised against registering domain sets like this again. Sit with the symmetry of that. The seven domains named after Himalayan towns, registered by one identifiable founder for a disclosed civic project with 22 live sister sites, were flagged as a bot operation. Meanwhile, actual botnets impersonated Googlebot across our network 72,000 times in five days without consequence. The internet's immune system runs on pattern recognition, and pattern recognition cuts both ways: to a registry's anomaly detector, twenty-nine place-name registrations in bulk *does* look like an algorithm at work. I understand why the rule exists. I think they applied it to the wrong case, our appeal is ongoing, and those seven towns still deserve their addresses — but I've come to see the suspension as data too. It taught me more about how trust is actually adjudicated on the internet than any documentation could. (If anyone from Radix reads this: the other 22 sites are live, the code is one `git log` away, and we'd love to bring Gangotri back online.) ## What Cloudflare gives away, and why that matters I want to give the infrastructure its own section, because the economics are the most inspiring part for anyone considering a similar experiment. Everything described above — 22 DNS zones; a Worker serving every request for every site; static asset hosting; a SQL database holding customers, metrics, and experiment data; a key-value cache for weather; per-request analytics writes with 90 days of SQL-queryable retention; daily cron triggers; edge caching; the GraphQL analytics API the experiment dashboard reads; and the WAF custom rules that ended the impostor problem — runs on **Cloudflare's free tier**. Total paid to Cloudflare so far: **zero dollars**. I find this genuinely remarkable, and worth stating carefully rather than as an advertisement. The free tier is not a demo. It's a complete production platform for a network doing tens of thousands of requests a day, *and* — this is the underrated half — a complete observability and defense platform. The tools we used to detect the botnet (Analytics Engine's SQL interface), diagnose it (per-request bot classification), and defeat it (verified-bot WAF rules at the edge) were all in the $0 tier. A decade ago, this stack was a funded startup's infrastructure. Today it's the default allowance of a free account, and the binding constraint on an experiment like this is no longer money — it's curiosity and operational attention. And the attention, increasingly, can be delegated to agents. For completeness, the full experiment ledger: ~$29 for the domains, $0 to Cloudflare, free tiers of OpenWeather and a transactional email service, GitHub Actions minutes within the free allowance. The one real bill is coming: `.website` renewals run about $21 per domain per year, so the $29 experiment matures into a ~$600/year decision. I consider that deadline a feature. The first year is the experiment; the renewal is the referendum. ## What I'd tell you to do If you've read this far, here is the actual recommendation, the one this entire post exists to deliver: **Run at least one real website, on your own domain, and read its logs.** Not a portfolio behind a platform that hides the traffic from you. A real domain with real DNS, where you can see every request. It will teach you, within weeks, things that no tutorial covers, because the modern web's most consequential facts live below the analytics layer: 1. **Most traffic was never human**, and the dashboards you're used to are the curated version of reality. 2. **Someone is always knocking.** Secret-scanners will find your site within days of DNS propagating. Design your deploys as if the probe list is the test suite — because it is. 3. **Identity on the internet is claimed, not possessed.** Anything can call itself Googlebot. Verification, not reputation, is the unit of trust. 4. **Debug your data before your system.** A failing metric may mean your measurement is under attack, not your code. 5. **You have a second audience now.** AI crawlers may read more of your site than people do. Decide deliberately what that means for you. 6. **The problem's layer matters more than its fix.** Our worst problem was unsolvable in application code and trivial at the edge. Running your own site is how you learn to feel which layer a problem lives in. 7. **The price of the experiment is no longer the barrier.** $29 and a free tier bought a 29-site civic network, a security incident, a measurement crisis, a registry dispute, and more education than any course I've taken. The web still rewards people who build a corner of it and pay attention. The tools have never been this cheap, the agents have never been this capable, and the logs have never been this interesting. This is the first post from the project. The next ones will go deeper into single threads — the caching experiment's full numbers, the AI onboarding pipeline, and what it takes to convince a registry you're human. --- ## I thought my daemon was one process. It's actually five. URL: https://zero8.dev/blog/daemon-process-lifecycle-mental-model Published: 2026-07-25 · 8 min read Tags: smriti, daemon, process-management, sqlite, bun, developer-tools, debugging A memory question turned into a native crash, a database lock, and a debugging script that hung itself. Five wrong assumptions about process lifecycle. Someone asked me a simple question about [Smriti](https://github.com/zero8dotdev/smriti), the memory daemon I run in the background on this machine: *how much memory is that thing actually using?* I opened btop, filtered to the daemon's PID, and expected a boring number. Four bugs, one native crash, and a database lock later, I had my answer. It was not boring. I want to walk through all five, in the order I found them, because each one broke a different assumption I didn't know I was carrying. If you've ever shipped a background process — a daemon, an agent runtime, an MCP server, a worker — you are carrying the same five assumptions right now, and you probably won't notice until something is on fire. ## The naive mental model If you'd asked me before this afternoon, I'd have said something like: > A daemon is a process. It sits there, does work when asked, uses memory proportional to that work, and releases it when the work is done. Killing it and restarting it fixes anything weird. Every clause in that sentence turned out to be wrong in a specific, useful way. ## Thing 1: "the daemon" is already more than one process The daemon's CLI entry point is a two-line bash shim: ```bash #!/usr/bin/env bash exec bun /Users/zero8/zero8.dev/smriti/src/index.ts "$@" ``` That `exec` matters. It replaces the shell's process image in place — same PID, new program — rather than spawning a child and waiting on it. I'd never had to care about the difference, because most CLIs I write don't spawn anything. Daemons do, constantly, and the moment they do, "my program" stops being a single, self-contained thing and starts being a process *tree* whose shape depends on what your dependencies decide to do. The specific example that made this concrete: the daemon's embedding library, `node-llama-cpp`, has a `testBindingBinary()` step that — on some platforms — forks a child process to test-load its native addon in isolation before trusting it in the main process. On this machine (macOS) it's gated off and never fires. On Linux, where the daemon also ships, it's a real per-instance fork. Same source code, different process topology, depending on where it runs. I only found this by going looking; nothing about the daemon's own code hinted at it. The lesson: once a process spawns anything, "how many processes is my program" stops being a fixed number you can answer from the source file you're looking at. It's a property of the whole dependency tree, on the specific OS you're running on, that day. ## Thing 2: idle timers are not disposal Watching the daemon under load, I found something worse than "uses some memory": it climbed to **1.9GB RSS** during a single ingest run, then stayed there. Not a leak in the classic sense — it would eventually come back down — but nothing was making it come down *on purpose*. The cause: every ingest flush constructed a fresh `LlamaCpp`/`Llama` backend (the embedding/LLM runtime), used it, and then just... let it go. Nothing called `.dispose()`. The design leaned on a 5-minute inactivity timer to eventually free the models — which works, technically, but "eventually, if nothing else happens first" is not a disposal strategy, it's a hope. This is the mistake I want to name precisely, because it's easy to miss in any language with a garbage collector: **your object's lifetime and your resource's lifetime are different axes.** GC will happily keep a native backend "alive but idle" long after your code has stopped caring about it, because nothing told the backend it was done. The fix was almost embarrassingly small: ```ts export async function closeQmdStore(): Promise { if (_store) { // Dispose the LlamaCpp/Llama backend explicitly instead of leaving it // to the 5-min inactivity timer. try { await _store.internal.llm?.dispose(); } catch { /* best-effort */ } _store.internal.close(); _store = null; } } ``` Boring, correct, three lines. And it explains a second symptom I'd been treating as unrelated: a recurring `LlamaGrammar ... different Llama instance` error, showing up **404 times** in the daemon's log — roughly one in every eight flushes. A grammar object created against one un-disposed `Llama` instance was occasionally getting used against a different one that had replaced it. Disposal wasn't just a memory problem. It was corrupting state. ## Thing 3: fixing the leak nearly crashed the process Here's where it stopped being boring. I made disposal more aggressive — dispose on every flush, immediately, not after five minutes — and ran the full test suite. Some tests, unrelated to my change, started failing with: ``` GGML_ASSERT([rsets->data count] == 0) failed ``` A native assertion failure in `ggml-metal`, the GPU backend. Not a JavaScript error. A crash, at the C level, inside a library I don't control. What was happening: two `LlamaCpp` instances — from two different, otherwise-isolated pieces of test code — were being constructed and disposed close together in the same process. `node-llama-cpp`'s JS-level objects looked independent. Underneath, on macOS, the Metal GPU backend they both used is *not* independent — it's process-wide native state. Disposing one instance's backend while another was still mid-construction tore down something the second one still expected to exist. I want to be honest about what I did and didn't do here: I did not root-cause node-llama-cpp's or Metal's internals. I engineered around the blast radius instead — added a promise chain that serializes daemon flushes so at most one `LlamaCpp` instance is ever alive at a time: ```ts let flushChain: Promise = Promise.resolve(); async function defaultFlushAgent(agent: string, log: (m: string) => void) { const run = async () => { /* open db, ingest, dispose */ }; const next = flushChain.then(run, run); flushChain = next; return next; } ``` That's the part I think is worth sitting with: **processes and objects that look isolated in your source code can share state you never asked for, several layers down, especially through native/GPU dependencies.** Your type system will not warn you. Your test suite will only catch it if two things happen to construct/dispose close enough together — which is exactly the kind of timing-dependent bug that's absent in development and shows up under real concurrent load. ## Thing 4: one file, two writers, no plan With the leak and the crash risk handled, I stress-tested the fix with a full forced re-ingest — 402 real sessions, 51,000 messages — while the daemon kept running normally in the background. Somewhere in the middle of that run, the daemon's log picked up a new line: ``` [smriti] [flush claude] failed to open DB: database is locked ``` The daemon and the manual ingest process were both writing to the same SQLite file at the same moment. Nothing exotic — this is the single most common way two independent processes step on each other — and SQLite has a built-in answer for exactly this case: `busy_timeout`. It tells a connection "if you hit a lock, retry quietly for N milliseconds before giving up," instead of failing on the first collision. Nobody had set it: ```ts // busy_timeout is per-connection, not persisted in the DB file — set it on // every open. Without it, two processes opening the same SQLite file at // once fail immediately with "database is locked" instead of retrying. db.exec("PRAGMA busy_timeout = 5000"); ``` One line. The gap wasn't ignorance of SQLite's concurrency model — it's that "a daemon plus a CLI tool sharing one file" is the kind of interaction that only exists once you have a daemon *at all*, and it's easy to reach for SQLite "because it's simple" without ever reading past the first page of its concurrency docs, because you don't yet have a second writer to make you go looking. ## Thing 5: the tool I built to catch this had the same disease This is the one I actually want this whole post to be about. To verify all of the above, I wrote a small script that samples a process's memory and CPU while a command runs, then prints a summary when it exits. Straightforward: spawn the command, `await child.exited`, print the report. During the full re-ingest stress test, that script hung. Not slow — hung, for **thirteen minutes**, doing nothing. I checked `ps`. The command it was supposed to be watching had already finished and exited cleanly. No zombie. No orphan. Nothing in the process table at all. And yet `await child.exited` — the promise a JavaScript runtime gives you specifically to answer "has this child exited?" — never resolved. Classic Unix literature is exhaustive about this problem *at the kernel level*: `wait()`, `waitpid()`, `SIGCHLD`, zombie processes, why you must reap your children or leak process-table entries. Decades of well-worn advice. What I could not find anywhere is good writing about the layer sitting directly on top of that — where a language runtime's *abstraction* over "the child exited" can silently decouple from what the kernel actually observed. The OS-level fact was true and available the whole time (`ps` showed it instantly). The promise built to represent that fact simply never fired. The fix doesn't require understanding why the promise hung — it requires not trusting it alone: ```ts async function waitForChildExit(child: Bun.Subprocess, pollMs: number) { let sawAlive = false; while (true) { const raced = await Promise.race([ child.exited.then((code) => ({ done: true as const, code })), Bun.sleep(pollMs).then(() => ({ done: false as const, code: null })), ]); if (raced.done) return raced.code; if (isPidAlive(child.pid)) { sawAlive = true; continue; } if (sawAlive) return null; // pid gone, exited never fired — trust the OS } } ``` Race the runtime's promise against an independent, boring, `process.kill(pid, 0)` liveness check. The OS truth wins if the abstraction goes quiet. This is a small amount of code and a large shift in posture: **if a runtime's process-exit signal matters to your tool, verify it independently once, rather than trusting it forever.** ## The actual pattern Line these five up and a shape appears. It isn't "daemons are hard," which is true but useless. It's that **"process management" is at least five separable skills that get bundled together under one intimidating heading**, and most app-level developers only ever practice the first one: 1. **Process topology** — knowing your program is a tree, not a point, once anything forks or execs. 2. **Explicit disposal** — resource lifetime is not object lifetime; idle timers are a fallback, not a strategy. 3. **Shared native state across "isolated" objects** — native and GPU dependencies routinely carry process-wide assumptions your type system can't see. 4. **Cross-process resource contention** — the moment two processes touch one file, socket, or lock, you need an explicit concurrency contract, not luck. 5. **Runtime honesty about process exit** — the abstraction your language gives you for "did it exit" can be wrong; the OS is always the tiebreaker. Most tutorials teach #1 and stop. #2 through #5 are the ones that only show up once your process runs long enough, under enough concurrent load, for long enough, to matter — which is exactly the profile of a daemon, and increasingly, the profile of an agent runtime, an MCP server, or a background AI worker. All the tools this generation of developers is being asked to build. ## What I'm still figuring out A few things I haven't closed the loop on: - I engineered around the Metal/GGML crash risk by serializing flushes. I did not verify whether the same native-state sharing exists on Linux's CUDA/CPU backends, or whether it's a macOS-Metal-specific quirk. That's an open question, not a settled one. - The `child.exited` hang reproduced once, under real load, and I don't have a minimal repro yet — just a fix that's correct regardless of root cause. I'd like to actually understand why the promise stalled, not just route around it. - I don't know how common the `child.exited` failure mode is across Bun versions, or whether it exists in Node's `child_process` too. If you've hit something like this, I'd genuinely like to hear about it. ## What I'm taking forward The thing I keep coming back to: I've been writing software for years without needing to think hard about any of this, because most of what I ship is request-scoped — a function runs, does its thing, returns, and the runtime cleans up after it whether I asked it to or not. Daemons don't get that for free. Neither do agents that are supposed to keep running, keep a memory index warm, keep watching a filesystem, keep listening on a socket. If you're building any kind of dev tool that's meant to outlive a single invocation — and a lot of us are, right now, because that's what an agent *is* — process lifecycle stops being a systems-programming curiosity and becomes a load-bearing part of your correctness story. Not "read APUE cover to cover" load-bearing. Just: know which of these five things your tool actually needs, and go verify it instead of assuming it. I'm sure I'll relearn some version of this next month, in a different shape. If you've got a story like this from your own tools, I'd like to hear it. --- ## State of Agentic Harnesses — June 2026 URL: https://zero8.dev/blog/state-of-agentic-harnesses-june-2026 Published: 2026-06-12 · 6 min read Tags: agentic-ai, developer-tools, claude-code, cursor, codex, open-source March predictions scored: the leaderboard reshuffled, Windsurf became Devin Desktop, Gemini CLI sunset for Antigravity. Where harnesses are headed. In [March we took a snapshot](/blog/state-of-agentic-harnesses-march-2026) of the agentic coding landscape and made three predictions. Eleven weeks later, two of the ten tools in that comparison no longer exist under the same name. That's the headline. The harness market isn't just moving fast — the brands themselves are unstable while the capabilities converge. Here's where things stand in June 2026: the updated leaderboard, a scorecard of what we got right and wrong, and what the last eleven weeks say about where harnesses are going. *Methodology note: every number in this post was pulled on June 12, 2026 by an agent running [Tabstack](https://tabstack.ai) through [tabstack-cli](https://github.com/zero8dotdev/tabstack-cli) — extract for the repo stats, research for the developments. The pipeline that fact-checked the March post is [a shell script](https://github.com/zero8dotdev/tabstack-cli/blob/main/scripts/enrich-post.sh).* ## The leaderboard, reshuffled Same eight repos as March. Two rank changes, and the gaps tell a story.
Three things jump out. **Claude Code grew faster than anything else, in absolute terms.** +48.8K stars in eleven weeks — a 59% jump — moving it from #3 to #2. The growth came *through* an April quality wobble: Anthropic published a [postmortem on April 20](https://www.anthropic.com/engineering/april-23-postmortem) reverting a system-prompt change that had degraded Sonnet 4.6, Opus 4.6, and Opus 4.7 output. Owning the regression publicly seems to have cost them nothing. **Goose overtook Aider.** +15.3K stars for Goose against Aider's +3.6K. Block moved the project to the [Agentic AI Foundation](https://goose-docs.ai/) under the Linux Foundation — the vendor-neutral governance story is working, and it now has 500+ contributors. **Gemini CLI's growth collapsed — because Google killed it.** +6K stars, the second-slowest quarter on the board, and the reason is structural: Google [announced on May 19](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/) (at I/O 2026) that Gemini CLI is being replaced by **Antigravity CLI** as part of the Antigravity 2.0 platform. Free, Pro, and Ultra tier support ends **June 18, 2026**. Enterprise access continues. More on this below. ## The scorecard: what we said in March We closed the March post with three predictions for "the next six months." It took eleven weeks to grade them. **"Multi-agent orchestration is becoming standard." — Correct.** Anthropic's May 7 event shipped exactly this for Claude Code: routines for automated workflows, rubric-based agent grading ("Outcomes"), and first-party [multi-agent orchestration](https://www.lennysnewsletter.com/p/code-with-claude-the-5-biggest-updates). Codex, Cursor, and Copilot all run sub-agent spawning in production. Orchestration went from frontier to table stakes in one quarter — exactly the trajectory MCP took in 2025. **"Instruction files are converging. Standardization is inevitable." — Half right.** [AGENTS.md](https://agents.md/) won the open ecosystem — 60K+ open-source projects carry one, and Codex, Cursor, Google's Jules, VS Code, and Copilot all read it. But the two biggest proprietary holdouts didn't move: Claude Code still wants CLAUDE.md, Gemini/Antigravity still wants GEMINI.md. Convergence happened *around* the leaders, not through them. **"Cloud agents are the real unlock." — Correct, faster than expected.** Background agents that take a task, work in a sandbox, and open a PR are now described as ["the new normal"](https://medium.com/@dave-patten/the-state-of-ai-coding-agents-2026-from-pair-programming-to-autonomous-ai-teams-b11f2b39232a) — Copilot's coding agent, Codex automations, and Cursor's cloud agents all deliver PRs asynchronously. The interesting fight moved one level up: who *schedules* the agents. And one prediction we didn't know we were making: *"The harness you pick today probably won't be the harness you're using in six months."* Windsurf users didn't even get six months — their editor was renamed under them. ## What actually happened, tool by tool **Windsurf is gone — it's Devin Desktop now.** Cognition completed the absorption: [Devin Desktop v3.0.12 shipped June 2](https://docs.devin.ai/desktop/changelog), v3.0.21 added a migration command for former Windsurf installs two days later. The pricing moved upmarket with it: Devin's [Team plan runs $500/month for 250 ACUs](https://pensero.ai/blog/devin-pricing). The $15/mo Windsurf tier from our March matrix no longer exists as a product. **Google traded a community for a platform.** Gemini CLI — 105K stars, the #2 project on our March board — gets sunset for consumer tiers on June 18 in favor of [Antigravity CLI](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/), which promises faster execution and async workflows under the [Antigravity 2.0 umbrella from I/O 2026](https://blog.google/innovation-and-ai/technology/developers-tools/google-io-2026-developer-highlights/). It's the second time Google has moved the free tier's cheese (March: Flash-only; June: new CLI entirely). The open-source repo's flattening star curve suggests the community noticed. **Anthropic had the busiest quarter.** April 17: Claude Design for visual outputs. April 20: the quality postmortem. May 7: routines, Outcomes, multi-agent orchestration, raised usage limits, and a persistent memory system called **Dreams**. May 21: Compliance API integrations for governance. And the model story: **Claude Fable 5** — the first of the new Mythos-class tier above Opus — [reached general availability in GitHub Copilot on June 9](https://releasebot.io/updates/github). **OpenAI kept Codex iterating fast.** [v0.134.0 added search across local conversation history (May 20), Appshots on macOS and an expanded Goal mode (May 21)](https://releasebot.io/updates/openai/codex), plus performance work in June. Codex CLI's +22.8K stars made it the third-fastest grower. **OpenCode shipped like a startup.** [v1.15.0 on May 15](https://github.com/anomalyco/opencode) rebuilt the event system on TypeScript + Effect, v1.17.3 followed June 10, and a beta desktop app appeared. Still 75+ model providers, still free BYOK, still the most-starred harness on GitHub at 173K. **Cursor went quieter.** A new Cursor 3 interface landed April 7 and Composer 2.5 [surfaced on the forum June 10](https://forum.cursor.com/c/announcements/11) — but nothing that changed its row in the matrix. After the credit-billing turbulence of 2025, a quiet quarter might be the strategy. ## The feature matrix, June edition Changes from March in bold. One new column — memory — because that's where the frontier moved. | Tool | Interface | OSS | Models | MCP | Sub-agents | Cloud agents | Memory | Config file | Price | |------|-----------|-----|--------|-----|------------|--------------|--------|-------------|-------| | Claude Code | Terminal | Partial | Anthropic only | Yes | Yes | **GitHub Actions + routines** | **Dreams (built-in)** | CLAUDE.md | $20/mo | | Cursor | IDE | No | Multi | Yes | Yes | Yes | — | Rules | $20/mo | | Codex CLI | Terminal | Yes | OpenAI-focused | Yes | Yes | Yes (Web) | **Local history search** | AGENTS.md | ChatGPT sub | | OpenCode | **Terminal+IDE+Desktop (beta)** | Yes | 75+ providers | Yes | — | No | — | AGENTS.md | Free (BYOK) | | Aider | Terminal | Yes | 75+ providers | Community | No | No | — | — | Free (BYOK) | | Cline | VS Code | Yes | Any | Yes | No | No | — | .clinerules | Free (BYOK) | | **Antigravity CLI** *(was Gemini CLI)* | Terminal | **TBD** | Gemini only | Yes | No | Jules | — | GEMINI.md | **Tiered (June 18)** | | Goose | CLI+Desktop | Yes | Any | Yes | — | No | — | — | Free (BYOK) | | Copilot | IDE ext | No | **Multi (+ Fable 5)** | Yes | Yes | Yes | — | AGENTS.md | $10/mo | | **Devin Desktop** *(was Windsurf)* | IDE | No | Multi | Yes | No | **Yes (ACU)** | — | Rules | **$500/mo team** | For tools without built-in memory, a community layer is forming fast — projects like [agentmemory](https://github.com/rohitg00/agentmemory) hook into Claude Code, Copilot CLI, Cursor, and Gemini CLI with auto-capture and retrieval across sessions. Which brings us to the bigger picture. ## How harnesses are evolving Three observations from the quarter, and they compound. **Capabilities are converging; identities aren't.** Every serious harness now has MCP, sub-agents, cloud execution, and an instruction file. What changed hands this quarter was *brands*: Windsurf → Devin Desktop, Gemini CLI → Antigravity, Goose → a foundation. When features converge, the differentiator becomes the model behind the harness and the trust around it — which is exactly why Anthropic publishing a postmortem mattered more than any feature it shipped. **Memory is the new sub-agents.** In March the frontier was orchestration; by June, orchestration is a checkbox and the race is persistence. Claude Code's Dreams is the first first-party memory system in a major harness, Codex added searchable local history, and the community is bolting memory onto everything else. The harness that remembers your codebase across sessions doesn't just save context tokens — it changes what "onboarding an agent" means. Expect a memory column on every comparison table by September. **The stack is splitting into harness and platform.** Google's Antigravity move makes the direction explicit: the CLI is no longer the product, it's the entry point to a platform that schedules agents, grades outcomes, and bills for compute. Anthropic's routines + Outcomes combo is the same shape. Devin's ACU pricing is the same shape. The 2025 question was "which agent writes the best code?" The mid-2026 question is "which platform do you trust to run agents while you're not watching?" ## What's next Three predictions to grade in September: **Memory becomes portable, or becomes lock-in.** Either an open standard for agent memory emerges the way AGENTS.md did for instructions, or Dreams-style proprietary memory becomes the stickiest switching cost a harness has ever had. We expect both camps to fight it loudly. **The Antigravity migration is the quarter's biggest churn event.** 105K stars' worth of Gemini CLI users get repotted on June 18. Some will land on Antigravity; the BYOK crowd will scatter to OpenCode and Goose. Watch OpenCode's star curve in July. **Claude Code holds #2 and challenges OpenCode's growth rate.** Fable 5 plus Dreams plus routines is the strongest single-quarter feature stack any harness has shipped. The constraint is the one that's been there all year: Anthropic models only. The March post ended by saying switching costs are low and the markdown is portable. Eleven weeks on, both are still true — but the platforms are working hard to change it. Pick your harness for the quarter, not the year. And keep your instructions in AGENTS.md. --- ## Builder Retrospective: May 2025 → June 2026 URL: https://zero8.dev/blog/builder-retrospective-may-2025-june-2026 Published: 2026-06-11 · 16 min read Tags: retrospective, agentic-ai, claude-code, smriti, product, developer-tools I mined 2,200+ AI-coding sessions across four agents to see what a year of building actually taught me — mistakes, fixes, and the patterns that stuck. Every conversation I've had with a coding agent since May 2025 is sitting in SQLite on my machine — 2,269 sessions across Claude Code, Cursor, Codex CLI, and Copilot. [Smriti](https://github.com/zero8dotdev/smriti), the memory layer I've been building, ingests all of them. So I ran an experiment: what happens if I point a fleet of analysis agents at my own history and ask *what did this person actually learn?* This post is the answer. Twenty agents mined session titles, my own messages (the corrections, the frustrations, the "no, do it this way" moments), and the git history of twenty-plus repos. What came back was more honest than any retrospective I would have written from memory. ## The arc The single clearest signal in the data is a **platform shift with a velocity step-change**. The Cursor era ran May 2025 → January 2026: ~480 sessions, ~22,000 messages, concentrated in a handful of client projects. It ends almost to the day the Claude Code era begins (February 25, 2026) — which produced ~160,000 messages in 3.5 months across twenty-plus active repos. Roughly a **10× jump in throughput**, and a different *kind* of work: less autocomplete-assisted typing, more delegating whole tasks and reviewing outcomes. Within the heavy window (Feb–Jun 2026), five month-sized phases show up across every project simultaneously: - **Late Feb — Foundations.** Deep architecture study, fixture-based testing, first CI/release pipelines. The habit that defines everything after: *write the formal plan first, store it in memory, then hand it to the agent.* - **March — Systematization.** Multi-step workflows compress into named slash-commands; the SEO blitz pattern emerges (JSON-LD + canonicals + llms.txt in a day); SvelteKit becomes the default stack; content strategy shifts to search-demand-first. - **April — Infrastructure.** First production GCP deployment (Terraform + Coolify), a 29-domain Cloudflare Worker architecture, automated SEO reporting crons — and a rule learned twice: *eliminate fragile intermediaries; direct primitives beat vendored SDKs.* - **May — Hardening.** A background daemon replaces per-invocation hooks (2s cold start → 5ms socket poke), deliberate scope cuts to reach PMF faster, compliance-native schema design. - **June — Product-led velocity.** Analytics pulses to founders every Monday, Search-Console-data-driven fixes instead of guesses, demo accounts as sales tools, reconciliation features driven by real user feedback loops. The meta-pattern, visible in contract work, client sites, and my own products alike: **reactive → anticipatory → systematic.** Fix-after-rejection became self-validate-before-submit became feedback-loops-embedded-in-the-tool-itself. The six sections below are the full excavation — expand whichever practice you care about.
Frontend — from page-builder to systems thinker My frontend practice shifted from reactive page-building to system-driven development. I went from inheriting a WordPress site and a Next.js app to owning multiple SvelteKit deployments with coherent design-token systems and component architectures I can defend. The recurring failure mode — the thing I fixed most often — was design-token drift: hardcoded values sneaking back in through new routes. ### What I learned **Design tokens are only useful if every route is audited at merge time** *(Apr–May)*. Two projects landed token systems and immediately accumulated drift. On zero8.dev I spent a 1,305-message session hunting pages that were "defaulters for not using design tokens." Introducing a token system is step one; enforcing it on every new route is the actual work. **mdsvex silently poisons internal links with `rel=nofollow`** *(Jun)*. mdsvex hardcodes `remark-external-links` on all absolute URLs — including same-domain hrefs. Invisible until a Google Search Console crawl report surfaced it months after launch. Fix: a custom rehype plugin that strips `nofollow` from internal domains. Third-party markdown processors can mutate your HTML in SEO-consequential ways no browser inspection reveals. **Browser cache partitioning killed the shared-CDN mental model** *(Apr)*. Chrome 86+, Firefox, and Safari partition HTTP cache by top-level site. For a multi-domain network, separate registrable domains force per-domain cache misses on every asset — subdomains are required to restore shared bundle caching. **OG images are crawler-time, not viewer-time** *(May)*. Social platforms cache OG images at share-time; the viewer's dark/light preference is irrelevant. Optimize for the crawler's one request, not the user's theme. **Placeholder sections must not ship visible** *(May)*. A testimonials block went live with placeholder copy and needed a separate hiding commit days later. Render sections only when real data exists. **Notification systems decompose into cooldown / preferences / dispatcher** *(Jun)*. Splitting "send an email" into three modules — duplicate-send prevention, per-user opt-in state, template routing — let five phases ship in one PR without tangling. ### Mistakes that taught me - **Global CSS silently overriding page styles.** A homepage dark background lost to a global rule; only visual cross-page testing caught it. Global stylesheets are now suspects whenever a page looks right in isolation but wrong in context. - **A canonical-origin redirect without a host guard redirects `http://localhost`.** Hooks that inspect protocol must verify they're on a production host. Two sequential commits in one session tell that story. - **Date-range parsing duplicated across three pages.** Same bug fixed three times before I extracted the shared utility. - **Components before content architecture.** On a client portfolio site I started building components before the narrative structure was settled. Significant rework. Content hierarchy first, then components. ### How my patterns evolved **Feb–Mar:** building features as requested, framework choices by instinct. **April:** token systems land, but treated as one-time migrations rather than ongoing enforcement. **May:** the pattern crystallizes — research-first → design system → implement → review → ship; token drift becomes a named problem instead of a mystery layout bug. **June:** `/code-review` is a formal merge gate, and Search Console data surfaces frontend bugs (nofollow links, duplicate origins, thin tag pages) that no local inspection would ever catch. Frontend infrastructure — tokens, SEO signals, caching strategy, consent — now gets the same rigor as backend code.
Backend — from "works" to "fails predictably" My backend practice shifted from exploratory, monolithic scripting toward layered, contract-first design — from "build a thing that works" to "build a thing that fails predictably, stays debuggable, and evolves without surgery." The most formative pressure came from production incidents I wouldn't have scheduled voluntarily. ### What I learned **sqlite-vec deadlock is a constraint, not a gotcha** *(Feb)*. Combining vector-table queries with JOINs in one SQL statement hangs indefinitely. The only safe pattern is two-step: query the vector table alone for ids, then a separate lookup. I internalized this from an architecture deep-dive before hitting it in production — the study paid for itself. **JOIN multiplication silently inflates aggregations** *(Jun)*. A LEFT JOIN to a prefix table before aggregating orders duplicated each order once per prefix — a partner with 7 prefixes reported 7× revenue. Subquery the aggregation before joining dimensions. Obvious in hindsight, invisible in query review. **Write-only tables are not features** *(Mar)*. I built four metadata sidecar tables that populated on every ingest and had no read path. The data sat unused for weeks. No metadata table ships without a recall design. **Agent log formats drift silently and break parsers** *(Jun)*. Codex added a wrapper envelope; Copilot switched to snapshot JSONL; Cursor's real chats turned out to live in a SQLite database, not the project folder everyone greps. `Sessions found: N, ingested: 0` with `--force` means format drift — check one raw file before debugging anything else. **Cold-start cost is an architectural signal** *(May)*. Two runtime processes spawned by a per-conversation hook had silently accumulated 400+ CPU-hours each. The fix wasn't tuning — it was a long-lived daemon with an IPC socket, turning a 2-second cold start into a 5ms poke. When a hook is hot enough, per-invocation startup is the wrong model, period. **Pre-compute authorization state; don't check at request time** *(Jun)*. Request-time fine-grained-authorization checks over an org hierarchy don't scale. Write org changes to an event outbox, sync tuples to the FGA store asynchronously, and request latency never sees the authorization graph. **Serverless needs batched writes** *(Jun)*. One UPDATE per row across 1,000+ orders hit Cloudflare Worker CPU limits in production. Classify in memory, batch in groups of 50, skip unchanged rows: O(changed) instead of O(all). ### Mistakes that taught me - **Building the aspirational feature, not the minimal one.** An Elasticsearch parallel-write spike produced 600-line mixed-concern functions that a later refactor had to untangle. Ship what solves today's problem. - **Stale inline test data hides format drift.** Parser tests with hand-crafted data kept passing after the real formats changed. Fixture files from real logs are the only honest approach. - **Early returns that silently drop valid data.** A guard clause at the top of an event handler short-circuited before the write. I now trace the full execution path before adding any early return. - **`await` in loops.** A rollover routine awaited 100+ employees sequentially. `Promise.all()` exists; domain logic doesn't exempt itself from N+1. ### How my patterns evolved **February:** monolithic ingest functions mixing parsing, persistence, and speculative writes. **Late February:** a 4-layer architecture — pure parsers, resolver, store gateway, orchestrator — articulated and shipped. **March–April:** debugging gets a protocol (logs → raw artifacts → DB query → fix → verify) instead of instinct. **May:** redesigning for operational reality, not feature correctness — the daemon came from reading a signal, not a roadmap. **June:** contract precision everywhere — typed error taxonomies, dual revenue metrics so reconciliation is unambiguous, `NULL` explicitly handled in aggregate filters. From "the happy path works" to "the contract is specified at every edge."
Security — from afterthought to attacker's-eye view Security moved from reactive and ad hoc — retrofitting auth after deployment, pasting secrets into chat — to deliberate: secrets storage decided upfront, PoC exploits built to earn buy-in, and the recognition that "it's bound to the URL" is not a security argument. The gaps kept appearing, but the interval between *shipped* and *fixed* shortened dramatically. ### What I learned **Internal APIs need auth before first deploy, not after** *(Mar)*. An internal service shipped to production with endpoints unprotected and Swagger docs publicly readable. One hardening commit fixed it — but it shaped how every later service was born. **PoC exploits are the only currency that buys security buy-in** *(Apr)*. I reported an exposed client-ID finding and was told URL-binding made it safe. It didn't — third-party scripts on the same origin can intercept the flow. The report changed nothing; the working clickjacking demo changed the conversation immediately. Model the attacker's position, not the intended use. **Secrets belong in the OS keychain, not env files** *(Mar)* — with a corrected nuance: keychain libraries provide *storage*, not access control. Any same-user process reads the credential without a biometric prompt. That's correct behavior for cron automation, but I had documented it wrong and had to fix the README. **Email authentication is a trust chain, not DNS configuration** *(Jun)*. Setting up DKIM + bounce records properly meant understanding how aggregate DMARC reports flow from mailbox providers back to senders — who vouches for what, and where it breaks. **OAuth tokens for unattended crons must self-refresh** *(Apr–Jun)*. 24-hour tokens plus a scheduled sync equals silent failure. `getValidToken()` — check expiry, refresh via client-credentials, then call — turned token lifetime from an incident class into a non-event. ### Mistakes that taught me - **Live tokens pasted into agent sessions — twice.** A GitHub PAT in April, an infrastructure root token in June. Anything pasted into an AI session is logged — in the transcript, the tool-call history, potentially a cloud backend. Both were rotated, but the recurrence is the lesson: the fix is structural (env vars, vaults), not behavioral ("I'll rotate it later"). - **Assuming domain-binding was a mitigation.** Building the actual exploit showed third-party scripts on the bound domain aren't constrained at all. The attacker's position would have caught this in five minutes. ### How my patterns evolved **Feb–Mar:** security as incident response; services ship without auth. **April:** offensive thinking arrives (PoC exploits) at the same time two secret leaks make the cost of ad hoc handling concrete; SOPS enters the plan, keychain replaces `.env`. **May–Jun:** security designed in — secrets as required platform config from day one, OIDC for CI instead of long-lived keys, service accounts instead of manual key passing. The one habit that didn't fully close: secrets still leaked into a debug session in June. The remaining gap I've named but not yet standardized: a pre-deployment security checklist for every new service.
SEO & GEO — from launch checklist to engineering discipline SEO went from a thing you audit after launch to a continuous discipline with its own data source (Search Console), its own backlog (structured epics), and its own automated reporting. By June I was filing SEO bugs with linked evidence and closing them one atomic commit per root cause. ### What I learned **GEO is day-one infrastructure** *(Mar)*. The same session that added RSS and security headers added `llms.txt`, explicit Allow rules for AI crawlers in robots.txt, and four JSON-LD schema types in four sequential commits. Optimizing for AI search engines alongside Google is no longer a phase-2 idea. **Origin multiplicity silently inflates Search Console 4×** *(Jun)*. All four origin variants (http/https × bare/www) returned 200 with no redirect — GSC saw every page up to four times, producing 74 canonical flags. One 301 redirect in `hooks.server.ts`, scoped to production hosts, cleaned up months of noise. **Tag pages with one incoming link are functionally orphaned** *(Jun)*. 27 of 37 tag pages had exactly one internal link. Making tag chips link properly and adding related-posts sections gave every tag page 3+ contextual links per post published. **Title length must be computed with the brand suffix** *(Jun)*. Four post titles broke 60 characters once `· zero8.dev` was appended. The fix is programmatic: compute combined length, conditionally drop the suffix, apply to ``, `og:title`, and `twitter:title` together. **Automate the monitoring loop or drown** *(Apr–Jun)*. At 29 domains, manual GSC checking is impossible. A twice-weekly GitHub Actions cron pulls Search Console data via OAuth refresh token and emails a React Email digest — CTR regressions and indexing gaps surface automatically. FAQ schema is generated from each page's actual top-ranked queries, and title rewrites are driven by impression data, not guesses. **Same-owner domain networks are a PBN footprint** *(Jun)*. Twenty-nine domains sharing an owner, a Worker IP, and a template is the textbook private-blog-network signature. Cross-domain interlinking was ruled out; safe authority comes from entity citations (Wikipedia/Wikidata/OSM) and Google Business Profiles instead. ### Mistakes that taught me - **Redirect chains and robots.txt spam rules discovered in production via GSC** — reactively. Robots correctness and redirect depth belong on the deployment checklist, not the incident queue. - **Trusting the agent's claim that a skill was loaded.** It confidently wasn't. After installing tooling, restart the session and verify before proceeding. - **`ogtest.jpg` survived to production.** Dev placeholder names are sticky; audit meta tags immediately after first deploy. ### How my patterns evolved **March:** reactive — 15+ issues fixed in a two-hour, 28-commit burst on a site already live. Right instinct, wrong timing. **April:** SEO infrastructure designed before first page ships (JSON-LD, hreflang, canonical strategy), and the reporting layer automated. **May–June:** Search Console becomes the primary signal — exports read, "Discovered – not indexed" triaged, one GitHub issue per root cause, one commit per fix referencing the exact GSC flag it resolves. In March I fixed SEO issues; by June I was *filing* them, triaging by impact, and closing with linked evidence. </details> <details> <summary><strong>Deployment & CI/CD</strong> — from guided ritual to invisible infrastructure</summary> At the start of this period I was asking what CLI flags meant and learning how to undo a wrong commit. By June, deployments were single-word messages, individual containers were selectively redeployed to minimize blast radius, and CI was an orchestration layer rather than a test runner. ### What I learned — deployment **Terraform state needs locking; GitHub is not a backend** *(Apr)*. Concurrent writes corrupt state silently, and git provides no atomic locking primitive. GCS/S3 are the answer at any scale. **Single-VM Docker Compose + Coolify beats Kubernetes for a tiny team** *(Apr)*. The deciding design goal was cloud-portability: the same stack moves from GCP to any provider or bare metal without re-engineering. **Read serverless resource limits before shipping, not after** *(Jun)*. One-UPDATE-per-row hit Cloudflare's CPU limit (Error 1102) in production with real user data. Batch, use `waitUntil()`, instrument before launch. **`gcloud compute ssh` is a wrapper, not SSH** *(Apr–May)*. IAP tunneling, metadata-injected keys, OS Login — understanding the underlying model turned recurring confusion into working knowledge. **Migrations need coordinated backfills** *(Jun)*. Schema change + backfill from raw JSON + reclassify pass, as one unit. Shipping the schema alone would have silently broken historical aggregations. **Audit your scaffolding** *(Apr)*. A framework migration carried a deprecated adapter along; only a blocking `pnpm audit` habit caught it. Multi-tenant topologies deserve the same scrutiny — committing to one Worker serving 29 domains *before* checking the adapter's sitemap/prerender support cost real invocations later. ### What I learned — CI/CD **Release workflows must be tag-triggered** *(Feb)*. Built the pipeline, merged to main, nothing fired. The working design: test matrix on push → auto-tag on main → release on tag. The release artifact is tag + notes + changelog as a unit. **Scope commit linting to the PR range** *(Feb)*. Linting all history fails every PR that touches old commits — and clean conventional ranges are what make automated semver computable downstream. **OIDC eliminates long-lived cloud credentials in CI** *(Apr)*. The Lambda pipeline used GitHub Actions OIDC from day one — no access keys stored as secrets. Now my default. **Turborepo caching makes monorepo CI tractable** *(Jun)* — with one trap: non-deterministic tasks must opt out with `"cache": false` or you get false cache hits that silently skip real work. **Pin everything** *(Apr–Jun)*. An unpinned setup action broke CI with no error pointing at version drift; pinning the package manager itself (`pnpm` via `.npmrc`) ended a class of local-vs-CI surprises. Cross-platform CI also surfaces runtime gotchas unit tests never will (`__dirname` vs `import.meta.dir` on Bun; submodules are never initialized implicitly). ### Mistakes that taught me - **Two database containers on one VM** — caught only by questioning the Compose file instead of accepting scaffolding. - **Shell metacommands inside YAML-embedded scripts** (`\gexec`) silently do nothing after environment substitution. Explicit SQL or separate scripts. - **Trusting the editor over the build tool.** Typecheck passed while the LSP showed stale red. The authoritative tool wins; the editor caches. ### How my patterns evolved **Feb–Mar:** agent as teacher — every step potentially irreversible, every flag explained. **April:** deliberate tradeoff reasoning (k8s vs Compose, state backends, OIDC) and the first CI built *before* features rather than after. **May–Jun:** deployment as commodity — "deployed?" / "yes, deploy" — while CI grows into architecture: dependency-graph builds, scheduled analytics workflows, version state surfaced in response headers instead of checked manually. The agent shifted from teacher to executor; I set the strategy. </details> <details> <summary><strong>Automation & agent workflows</strong> — the discipline that compounds everything else</summary> By June I had stopped thinking of Claude Code as a smart terminal and started treating it as the orchestration surface for a toolchain: memory (smriti), email (gws), analytics (PostHog MCP), research (parallel-cli), quality gates (/code-review), and GitHub CLI. Every tool was added reactively — when the absence hurt — and composed into a unit. ### What I learned **Pre-written plans eliminate mid-session drift** *(Feb)*. Sessions that started with "Implement the following plan:" — a markdown doc with acceptance criteria, phased steps, known risks, stored in memory first — had almost no course corrections. Sessions that started with a vague goal drifted. This single habit is the highest-leverage thing I changed all year. **Workflow files beat agent memory** *(Feb)*. On a contract engagement writing evaluation baselines for coding agents, my first message was a pointer to a workflow file, not inline instructions. Agent memory across sessions is unreliable; files are durable, reviewable, and version-controlled. **Feedback loops only compound if you embed them in the tool** *(Mar)*. This was the contract work's real lesson. Early on, every reviewer rejection got fixed in-session — and the same mistake recurred on the next task. The shift: after each rejection, *rewrite the skill file itself* from the feedback. Then a second-order move: a self-check pass (a named "reviewer-eye" step) run before every submission, applying the accumulated rules proactively. By late March the rejection-fix loop had inverted into an anticipate-validate loop, and eventually the skill could ingest pasted feedback and rewrite its own instructions. Reactive → anticipatory → systematic, in one arc. **Dogfooding produces honest quality signals faster than tests** *(Feb–Mar)*. Using my own memory tool on its own development surfaced two quality regressions (write-only tables, fragment-level recall) faster than any test suite — because I was the one burning on them. **Eliminate fragile intermediaries** *(Apr–May)*. A vendored CLI inside a sandboxed VM broke on proxy interception; pure curl + jq fixed in ten minutes what the SDK route lost hours to. A per-invocation hook with a 2s cold start became a daemon with a 5ms socket poke. A remote agent environment failed twice on multi-repo work that local execution handled trivially. Same shape each time: the intermediary layer *is* the failure point. **Direct primitives for everything** *(Jun)*. Gmail via an authenticated CLI (works in any bash context, no interactive auth), fan-out web research with multiple parallel queries, analytics queried inside the session. Email became an upstream data source: search inbox → parse → create expense entries; search rejections → sync to the tracker. ### Mistakes that taught me - **Generated output committed to git.** An export directory full of generated knowledge files ended up in the repo and needed a history rewrite. Decide config-vs-artifact at project start. - **Recall fell back to raw SQL mid-session** because embeddings hadn't covered recent sessions — a coverage gap I only found by being my own user. - **Sunk hours on the wrong abstraction.** Two failed approaches to make a vendored SDK work inside a VM before the ten-minute direct fix. The sunk cost was the lesson. ### How my patterns evolved **Feb:** externalize process into files — workflow docs, plans-before-execution, a CLAUDE.md that teaches the agent its own tooling. **Mar:** compress multi-step workflows into single slash-commands; add named self-review passes; run an hourly ingest loop as a daemon stopgap. **Apr–May:** replace every fragile intermediary with a direct primitive. **Jun:** the whole toolchain operates as one composed unit — none of it planned as a stack, each piece added when a gap hurt, all of it now load-bearing. </details> ## The product lens Four products were being built or grown through this period, and the same product instincts kept being re-learned until they became defaults: **Positioning is a decision, not a description.** My memory tool went from "AI memory CLI" to *"help teams make fewer mistakes by learning from each other's coding sessions"* — an articulation forced by an architecture discussion, not a marketing exercise. My own positioning went from "Frontend Engineer" to "Co-founder & CTO." An HRMS went from generic feature checklist to *compliance-native product for tier-2 Indian SMEs* — where Hindi UI, statutory compliance, and Tally export aren't localization features; they're the product. When positioning is vague, every feature decision drifts. **Scope cuts are shipped decisions.** API-only integration over webhooks, explicitly, to reach PMF faster. A mid-design "are we overbuilding this?" self-check that pulled a team feature back to single-developer-first. The anti-pattern showed up too: a "we'll take the call later" on an architecture boundary that cost real time precisely because it was never decided. **Trust features beat clever features.** An order-attribution engine only became trustworthy when the dashboard showed *which signal matched and with what confidence*. When users validate your numbers against an external export, evidence columns and audit trails are product features, not debug output. Same lesson from a seeded demo account (nine layers of realistic data) doing more sales work than any pitch. **Decisions start from instrumented reality.** The June pattern that ties it together: automated Monday-morning analytics pulses to founders, Search Console exports turned into triaged epics, cost dashboards answering runway questions. Product decisions moved from memory and intuition to instrumented evidence — the same shift, in the same months, that happened to my engineering. ## How this post was made The pipeline: [Smriti](https://github.com/zero8dotdev/smriti) ingested 2,269 sessions from four coding agents (including recovering 482 Cursor sessions from its internal SQLite storage — a parser adventure of its own), correlated them with git history, and a workflow of twenty analysis agents extracted dated learnings, mistakes, and pattern evolutions per project, then synthesized them per category. The v0.8 release that came out of dogfooding this very experiment — cross-agent capture daemon, plus all four agent parsers fixed — is [on GitHub](https://github.com/zero8dotdev/smriti/releases/tag/v0.8.0). If you've got a year of agent sessions sitting on your disk: they remember more about your growth than you do. Mine them. --- ## 15 packages, 1 dev. The dependency graph is the architecture. URL: https://zero8.dev/blog/monorepo-build-pipeline-solo-founder-turborepo Published: 2026-05-27 · 7 min read Tags: monorepo, turborepo, architecture, developer-tools Most monorepo content assumes you have teams. I have domains. Here's why a 15-package Turborepo monorepo is a thinking tool, not a code org choice. I keep reading monorepo articles that open with the same premise: you have multiple teams, they need to coordinate, and a monorepo gives them shared tooling and a single source of truth. That's a real use case. It's not mine. I'm building [InboxStack](https://inboxstack.com), an email deliverability platform. I'm the sole technical co-founder. Our monorepo has 15 packages and I wrote all of them. There is no team coordination problem to solve. There is no "shared tooling across squads." There's one person, one domain, and a `turbo.json` that's 30 lines long. So why the monorepo? Because the monorepo is the only place where my understanding of how the system fits together is written down in a form that breaks the build when it's wrong. ## The problem isn't code. It's the model. Email deliverability sounds like one problem. It's not. It's a graph of problems that feed into each other: - DNS records get audited for correctness - DMARC policies get parsed and validated - Blacklist status gets monitored across dozens of providers - Trust scores get computed from all of the above - Signals get detected and scored by a signal processor - All of this flows into an intelligence layer that also pulls from a knowledge graph and a feature store - Results get queued, processed through provider connectors, and surfaced through notifications and emails As a solo developer, the hardest part of my day isn't implementing any one of these. It's holding the relationships between them in my head while I'm deep inside one. At 11pm, debugging why a trust score is wrong, I need something external to tell me what feeds into trust and what trust feeds into. Something that isn't a wiki page I wrote six months ago and haven't updated since. ## What 15 packages actually look like Here's the dependency tree. I'm going to show it because the tree *is* the point. ``` packages/ shared/ → (nothing — foundation) db/ → (nothing — data layer) design-tokens/ → (nothing — visual primitives) blacklist/ → db, shared dmarc/ → db, shared dns-audit/ → db, shared trust/ → db, shared signal-processor/ → db, shared connectors/ → db, shared notifications/ → db, shared feature-store/ → db, shared knowledge-graph/ → shared intelligence/ → db, shared, feature-store, knowledge-graph queue/ → connectors, notifications, shared email/ → design-tokens ``` Stop reading this as a file listing. Read it as a set of claims about the domain. `intelligence` depends on `db`, `feature-store`, `knowledge-graph`, and `shared`. That's not a configuration detail. That's a statement: intelligence is what happens when you combine stored data, feature signals, and structured domain knowledge. I wrote that in a `package.json` once. Now TypeScript enforces it on every build. `email` depends on `design-tokens` and nothing else. That's a boundary: the transactional email layer should never know about trust scores or blacklists. If I ever accidentally add `@inboxstack/trust` to `email/package.json`, I'll see it in the diff and ask myself why the presentation layer needs to know about trust. Usually the answer is "it doesn't, I'm being lazy" — and I refactor instead. `queue` depends on `connectors` and `notifications`, not on `db`. The queue orchestrates work through other packages. It doesn't reach into the database itself. If it did, that dependency would show up, and I'd have to justify it. Every one of these arrows is a design decision I made once and the build system remembers forever. ## The build pipeline I didn't write Here's my `turbo.json` in its entirety: ```json { "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**", "!.next/cache/**"] }, "dev": { "cache": false, "persistent": true }, "test": { "dependsOn": ["^build"], "outputs": ["coverage/**"] }, "lint": { "outputs": [] }, "typecheck": { "dependsOn": ["^build"], "outputs": [] }, "db:migrate": { "cache": false }, "db:generate": { "cache": false, "outputs": ["node_modules/.prisma/**"] } } } ``` The line that matters is `"dependsOn": ["^build"]`. The `^` means "build my dependencies first." Turborepo reads the package dependency graph — the same one I just showed you — and derives the execution order: 1. `shared`, `db`, `design-tokens` build first. No internal deps. 2. `blacklist`, `dmarc`, `dns-audit`, `trust`, `signal-processor`, `connectors`, `notifications`, `feature-store`, `knowledge-graph` build in parallel. They only depend on things from step 1. 3. `intelligence` builds. It's waiting for `feature-store` and `knowledge-graph`. 4. `queue` builds. It's waiting for `connectors` and `notifications`. 5. `email` builds whenever `design-tokens` is done — so effectively step 2. I didn't write that pipeline. I didn't create a CI YAML with stages and dependencies. I didn't draw a DAG in a config file. I declared how my packages relate to each other and the build order fell out. The one piece of "pipeline logic" in the entire file is `cache: false` on `db:generate`. That's there because Prisma client generation reads a `.prisma` schema and writes to `node_modules/.prisma` — it's a side effect, not a pure function. Turborepo's caching model assumes tasks are deterministic: same inputs, same outputs. Prisma breaks that assumption, so I opt it out. Everything else caches normally. When I run `turbo run build` after changing one line in `shared`, Turborepo rebuilds `shared` and then rebuilds the 12 packages that depend on it. The other two show `FULL TURBO` — cached, 0ms. I didn't configure that. The dependency graph configured it. ## Three things this replaces I keep a short list of things I used to think I needed separately: **Architecture documentation.** I don't have a system diagram. The dependency tree is the system diagram, and it's always current because the build breaks when it's wrong. If you want to understand what `intelligence` does, look at what it imports. If you want to understand the system's layers, look at which packages have zero dependencies (foundations), which have one layer of deps (domain logic), and which sit at the top of the graph (orchestration and presentation). That's three layers, visible in `package.json` files, not in a Confluence page that's six months stale. **API contracts.** In a polyrepo setup, services talk to each other over HTTP or message queues, and you need contract tests or OpenAPI specs to keep them honest. In a monorepo with TypeScript workspace packages, the contract is a TypeScript import. If `trust` changes the shape of a trust score, every consumer gets a type error at build time. Not in staging. Not at 3am. On my laptop, before I push. **CI pipeline configuration.** I've watched colleagues spend days wiring up CI pipelines: which service builds first, which tests to run after which build, how to cache artifacts between stages. My CI runs `turbo run build test typecheck`. That's one command. Turborepo handles ordering, parallelism, and caching. The dependency graph I already declared *is* the pipeline definition. ## The thinking part Here's the thing I didn't expect when I set this up. The monorepo forces me to think about architecture at every change. Not in a "write a design doc" way. In a "should this package really depend on that package" way. Last month I was adding a feature to the queue processor and reached for `@inboxstack/intelligence`. It would have been easy — just add the dependency, import the function, done. But adding that dependency would mean `queue` depends on `intelligence`, which depends on `knowledge-graph` and `feature-store`. The queue would be pulling in half the system. I stopped. Refactored the piece I needed into `shared`. The queue stayed thin. The dependency graph stayed clean. In a single-repo-with-folders setup, that import would have been invisible. No dependency to declare. No diff to review. Just an import statement buried in a file that nobody would question. The architecture would have quietly degraded. The monorepo made the cost of that decision visible — not as a code review comment or a lint rule, but as a structural change to the dependency graph that shows up in every `turbo run build --graph` from now on. That's what I mean by "thinking tool." It's not that the monorepo thinks for me. It's that the monorepo refuses to let me stop thinking. Every new dependency is a design decision that I have to make explicitly, and every `package.json` change is a permanent record of that decision that the build system will enforce. ## The honest downsides I'm not going to pretend this is free. **Everything breaks together.** Rename a type in `shared` and you get 40 type errors across 12 packages. This is the right behavior — I want to know what broke — but it can feel violent when all you did was rename a field. The cascade is the dependency graph working as designed. It just doesn't feel like it when your terminal is a wall of red. **Build output is noisy.** `turbo run build` prints output for 15 packages. Most of them say `FULL TURBO` (cached), but I'm still scrolling past 15 headers to find the one that actually did work. Turborepo's `--filter` flag helps, but the default experience is chatty. **Prisma doesn't fit the model.** `db:generate` runs every time because I can't cache it. For one developer this costs a few seconds. For a team of twenty running `turbo run build` in CI, those seconds add up, and the workaround (checking in the generated client) creates its own problems. **The temptation is real.** Because workspace imports are frictionless, it's easy to add a dependency you shouldn't. The monorepo makes the cost visible, but it doesn't stop you. You still have to say no to yourself. Some days I'm better at that than others. ## When this will stop working I don't think this setup scales to 50 engineers. At that point you need CODEOWNERS rules, affected-package CI filters, and probably Nx or Bazel instead of Turborepo. The concerns shift from "what depends on what" to "who is allowed to change what." But the dependency graph will tell me when to split. If `intelligence` grows to the point where it needs its own team, the dependency list tells me exactly which interfaces it consumes and which it exposes. The extraction boundary is already drawn. I just haven't walked through it yet. And when I hire the first engineer, each package is a natural onboarding scope. "You own `dmarc` and `dns-audit`. Here are their dependencies. Here's what depends on them. Don't break those interfaces." That conversation takes five minutes because the dependency graph makes it concrete. I don't have to walk them through a wiki. I point at `package.json`. ## The thing I keep coming back to The standard pitch for monorepos is about collaboration: shared tooling, atomic commits, consistent versioning across teams. Those benefits are real. They're just not why I use one. I use a monorepo because I'm one person holding an entire system in my head, and the dependency graph is the only artifact in the codebase that captures how the pieces fit together in a way that's both human-readable and machine-enforced. The `package.json` files are architecture documentation that never goes stale. The `turbo.json` is a build pipeline that writes itself. The TypeScript compiler is a contract testing framework that runs at build time. And the dependency graph, taken as a whole, is the most honest representation of the system I've built — because it's the only one that breaks when it's wrong. Fifteen packages, one developer, thirty lines of build config. The architecture isn't documented anywhere. It doesn't need to be. It's the code. --- ## The thing you can only know by staying URL: https://zero8.dev/blog/the-thing-you-can-only-know-by-staying Published: 2026-05-18 · 4 min read Tags: software-engineering, reflection, claude-code, smriti What you only learn by living with a project long enough to watch your own old code break under conditions you didn't notice arriving. I run `ps -ef` looking for something unrelated. The laptop fan is doing something it shouldn't. 42 lines come back. All the same: ``` bun /Users/zero8/zero8.dev/smriti/src/index.ts ingest claude ``` The oldest one started Wednesday. It is Sunday. That's the moment. The moment where you realise you've been here for a while. Long enough to have written a hook script that is now eating 9 CPU-days of your laptop's life. Long enough that the script is older than your memory of writing it. Long enough that the conditions you wrote it under — three Claude sessions at most, a DB that fit in a megabyte, an ingest that returned in two seconds — are all gone, replaced by their grown-up versions you didn't notice arriving. ## How sensible decisions accumulate Every line of that hook was sensible the day I wrote it. The hook was one line. There was no reason to add a lock — I had one Claude session at a time, and the ingest was fast, and locking adds complexity for a problem I didn't have. The "right" code that day was the simplest code that worked. `smriti ingest claude` was sensible too. It was a script that scanned the Claude logs directory and put new content into a SQLite DB. The directory had ten files. The DB was empty. The scan took milliseconds. There was nothing to optimise. And the decision to fire on every Stop event — that was the whole pitch. *Memory that's always fresh.* Asking the user to remember to ingest defeats the entire thing. Automate it. Tie it to the natural rhythm of working. Each of those decisions was correct in isolation, given the world at the time it was made. None of them were wrong. They just turned out to compose into something that was wrong, given the world four months later. ## You can read about this. You can't know it. I have read pieces about hooks. About background work. About the difference between fire-and-forget and request/response. I have probably written some. I *knew*, in the way you know things when you've read them, that long-running operations on event triggers need backpressure. But I didn't feel it, the way you feel something after you've seen it eat your laptop, until I saw it eat my laptop. This is the part I want to write down, because I think it's the underrated thing about staying with one project for a long time. The lessons available to you change shape. At the start, the lessons are mostly external — you read someone else's blog, you copy the pattern, you avoid the trap they fell in. After a while, the lessons are mostly yours — you trip over things that were perfectly fine when you wrote them and have become broken without anyone touching them. ## What time does What time does is this: it inverts which assumptions are load-bearing. When I started Smriti, the load-bearing assumption was "a session is one conversation in one window." The whole architecture flowed from that. As I lived with the tool, I started running three sessions, then five, then ten. Each session was sensible. Concurrency snuck in without anyone introducing it. When I started Smriti, the DB was small and the embedding pipeline was a plan. As I lived with the tool, both grew. The ingest that used to return in two seconds returned in two minutes. The 30-second async timeout in the hook config was a generous upper bound; then it was a tight bound; then it was meaningless. Nothing changed. Everything changed. ## Living downstream The project doesn't tell you when its assumptions are being violated. It just gets slower and weirder, and you blame the laptop, or the day, or the model, until one day you run `ps -ef` and find your evidence. The people who can read this kind of evidence are not the people who studied software architecture the hardest. They're the people who stayed with one thing long enough to watch it drift away from the conditions it was written under, and to recognise the shape of that drift when it shows up somewhere else. A lot of what we call "experience" is this. Not knowing more patterns. Knowing how patterns rot. There's a second-order version of this, too. When I went and read QMD — the library Smriti is built on — I realised it doesn't have any of this machinery. No file watching, no debouncing, no daemon for ingest. QMD assumes the user runs `qmd update` when they want to update. The author of QMD, whoever they were the day they wrote it, decided "automatic ingest" was someone else's problem. I am now someone else. I added the automatic ingest. I now have the problem. That's not a critique of QMD. It's an observation about the seam where one project ends and another begins. Every "we'll just wrap this and add a little convenience" is also "we'll just inherit whatever problems this convenience creates." You can only see those problems by living downstream of the seam long enough for them to show up. The original authors couldn't have warned you. They couldn't see them either, because they hadn't stayed in your version of the world. ## The trade The fix was six characters: `lockf -t 0`. The follow-up — a real daemon, FS watching, debouncing — is more involved but well-understood by now. I filed the issue. Someone (probably me) will pick it up. What's harder to write down is the part that happens to you while you're fixing it. The recognition that your old code is no longer your code, exactly. It belongs to a version of the project and a version of you that don't exist anymore. The new versions inherit it without remembering writing it. The thing that didn't exist on day one isn't the bug. The bug is just a consequence. The thing that didn't exist on day one is *the conditions under which the original code was wrong*. Those took months to arrive, quietly, while I was paying attention to other things. People say "this project teaches you something every day" and mean it as a compliment to the project. I think it's also a fact about time. You're not really learning *the project*. You're learning what the project becomes, slowly, without anyone making it become anything. The 42 processes are gone now. One command killed them all. But the version of me that wrote that hook didn't get to learn anything from them — only the version of me that found them did. That's the trade. You can't read your way to it. You have to stay. --- ## I thought gcloud compute ssh was ssh. It's six things URL: https://zero8.dev/blog/gcloud-compute-ssh-mental-model Published: 2026-05-06 · 11 min read Tags: gcloud, gcp, ssh, claude-code, agentic-ai, security It pushes your SSH key into project metadata, makes four API calls, runs under four identities, and silently falls back to IAP. Here's what I missed. A few weeks ago I let Claude Code drive a deploy to a staging VM on one of my projects. I had a line in my `.claude/settings.local.json` that I'd been pretty proud of: ``` "Bash(gcloud compute ssh staging-vm:*)" ``` Look how scoped that is, I thought. Specific command, specific instance — the agent can ssh into one box and that's it. I felt like a responsible adult. Then the deploy hung. The token had expired. The agent couldn't refresh it because there was no browser. And while I was un-wedging things I went looking for what `gcloud compute ssh` actually does — because if it's just "ssh with auth helpers", why was the auth this brittle? What I found is that `gcloud compute ssh` is six things wearing a trench coat. I'd been allowlisting all six and only thinking about one. This is the post I wish someone had handed me three months ago. ## The naive mental model If you'd asked me before this rabbit hole, I would have said: > `gcloud compute ssh INSTANCE_NAME` is a thin wrapper around OpenSSH that handles the GCP-specific bits — finds the VM's IP, uses your gcloud-managed key, shells you in. Read that out loud and it sounds reasonable. It's wrong in seven specific ways, and each one bit me before I learned to see it. I'm going to walk through them roughly in the order they execute. The instrument I wish I'd discovered earlier is `--log-http`: ``` gcloud compute ssh staging-vm --zone=asia-south1-b --log-http ``` This dumps every API call gcloud makes before any TCP byte hits your VM. The first time I ran it I scrolled, and scrolled, and scrolled some more. *That's* when the mental model cracked. ## Surprise 1: it's a write operation Here's the one that genuinely shocked me. By default, every `gcloud compute ssh` invocation pushes your SSH public key into the project's instance metadata. Project-wide. On every connection. You can see this in the logs as `Updating project ssh metadata...` — most of us have probably watched that scroll past for years and never wondered what it was doing. What it's doing is writing your key, with whatever username gcloud picks for you, to a piece of state that *every* VM in the project reads. If you have permission to ssh to one VM in the project, you've just registered a key for all of them. I'd always thought of ssh as a read-shaped operation. Connect, type, leave. `gcloud compute ssh` is read-shaped at the surface and write-shaped underneath. There's a [Stack Overflow thread from 2018](https://stackoverflow.com/questions/49754452/gcloud-compute-ssh-with-local-key-project-restrictions) where someone asks how to skip this step — they're a contractor with access to one VM, but every connection wastes time trying to update project-wide state they don't have permission to touch. The escape hatches: - Set `block-project-ssh-keys=TRUE` on the instance (per-instance keys only) - Enable OS Login project-wide (which obsoletes metadata keys entirely) - Pass `--ssh-key-file` and pre-arrange the key on the VM But the *default* — what happens when you just type the command — is a state mutation. That changes how I think about the verb completely. ## Surprise 2: the SSH key isn't the credential I always thought the credential was the file at `~/.ssh/google_compute_engine` — the one gcloud generates the first time you run `gcloud compute ssh`. Lose that, lose access. Right? Wrong. The credential that gates everything is the OAuth token in `~/.config/gcloud/credentials.db` (a SQLite file, which becomes important in a minute). The SSH key is a transport-layer detail: it gets pushed into metadata or matched against your OS Login profile *because* you have a valid OAuth token. If your token expires, you can't push the key, can't query OS Login, can't start an IAP tunnel — you're locked out even though the SSH key is sitting right there on your disk. This was the actual cause of my hung deploy. The OAuth token had expired. The fix is `gcloud auth login`, which opens a browser. The agent had no browser. So the deploy sat there until I noticed. If I'd been thinking of OAuth-as-credential rather than ssh-key-as-credential, I would have set up token refresh as a separate concern — maybe a service account JSON, maybe `gcloud auth application-default login` somewhere. Instead I was treating the SSH key as the secret to protect and the OAuth token as plumbing. The reverse is closer to true. ## Surprise 3: it's not a local command Look at the `--log-http` output. Before any TCP connection to your VM, gcloud makes (at least): 1. `compute.instances.get` to find the VM 2. `oslogin.users.getLoginProfile` to figure out which posix username you have on that VM 3. A project-metadata read (and possibly write — see Surprise 1) 4. Optionally, an IAP tunnel start if there's no public IP or you passed `--tunnel-through-iap` Four API calls minimum. Each can fail. Each leaves an audit log entry. Each consumes a tiny bit of quota. I'd been thinking of `gcloud compute ssh` like `ssh hostname` — a local invocation that opens a TCP socket and does its thing. It's not. It's a small distributed system that *eventually* opens a TCP socket. When you're on flaky hotel wifi, the connection feels slow because half the latency is your laptop talking to Google's APIs, not your laptop talking to the VM. Once I saw this I started understanding why the same command would behave differently from the same machine on the same network: the four-step pre-connect dance is sensitive to the state of *several* GCP services. A blip in OS Login and your "ssh hung" really means "OS Login was slow." ## Surprise 4: there's a silent network fallback There's a [Stack Overflow question](https://stackoverflow.com/questions/58034730/iap-tunnel-to-vm) where someone tries to ssh to a VM without a public IP and gets back: > External IP address was not found; defaulting to using IAP tunneling. That's the silent fallback. If your VM has a public IP, gcloud takes the public path. If it doesn't, gcloud quietly switches to IAP. Same command, two completely different network paths, with two completely different audit profiles. I do not love this. From a security review angle, "did this connection go through IAP" is a question with very different consequences depending on the answer, and the answer should not be a side effect of whether someone happened to attach an external IP to the VM last quarter. Audit logs from `cloudaudit.googleapis.com/data_access` show IAP tunnel starts; they don't show "user ssh'd to VM directly via public IP" with the same fidelity. The fix, when I figured it out, was to *always* require IAP — strip external IPs, require `--tunnel-through-iap`, set up the firewall rule from `35.235.240.0/20`. Now the path is deterministic. But I had to ask the question first, and the command's defaults didn't make me ask. ## Surprise 5: there are at least four principals on the call This one took me the longest to internalize. When you run `gcloud compute ssh` to a VM, the resulting connection involves: 1. **Your gcloud OAuth principal** — the human or service account whose token is in `credentials.db`. This authorizes the API calls (instance lookup, OS Login query, IAP tunnel). 2. **Your OS Login posix user** — what you become *on* the VM. Derived from your IAM identity, but it's a separate name (`hello_zero8_dev` style, with the `@` mangled). 3. **The VM's attached service account** — once you're shelled in, any `gcloud` or API call you make from inside the VM uses *this* identity, not yours. [The OS Login docs](https://cloud.google.com/compute/docs/oslogin) say this in writing: *"When a user connects to a VM, that user can use all of the IAM permissions granted to the service account attached to the VM."* 4. **The IAP principal** — if you're tunneling through IAP, that's another check (`roles/iap.tunnelResourceAccessor`) on yet another control plane. Four identities. I'd been thinking "I'm ssh'd in as me." I'm not. I'm ssh'd in as a posix user whose actions on Google's APIs run under a *different* identity than my login. The first time I noticed this was when I ran a `gcloud` command inside the VM and got back results from a different project — the VM's service account was bound to a different default. I thought I was looking at one project's data. I was looking at another's. If you've ever wondered why `roles/compute.osLogin` alone isn't enough to ssh in when the VM is private — you also need `roles/iap.tunnelResourceAccessor`, and missing either gives you the same opaque `Permission denied (publickey)` error ([Stack Overflow has the receipts](https://stackoverflow.com/questions/68438905/gloud-os-login-iap-in-private-vm-and-permissions-error-gcloud-beta-compute-s)). Two of those four principals are checking you, only one is failing, and the error message doesn't tell you which. ## Surprise 6: it cannot run in parallel This one is documented but obscure. [Google Issue Tracker #149709703](https://issuetracker.google.com/issues/149709703) is a public issue, in the Cloud CLI component. A Google engineer left this comment in writing: > gcloud does not support parallel execution, particularly for auth related operations. The errors are mainly due to locks on local sqlite we use for credentials. Read that twice. The credential store is SQLite. SQLite uses file-level locking. If you fan out `gcloud compute ssh` calls in parallel — say, across ten VMs — they fight for a lock on `credentials.db`, and the losers fail in confusing ways. I had been treating gcloud commands like any other CLI: independent processes, fan them out with `xargs -P`, scale up. For most tools this works. For gcloud it doesn't, and when it doesn't the error you get is not "credential lock contention" — it's whatever downstream symptom the failed token refresh produces. A timeout. An auth error. A "could not find instance" that's actually an auth error in disguise. The lesson I took: gcloud commands are *singleton* commands. If you want parallelism, you want plain `ssh` over a network primitive that's already been set up — not gcloud rebuilding the auth state ten times concurrently. ## Surprise 7: non-interactive isn't the same as interactive This one is the consequence of all the previous ones. If your OAuth token expires during a non-interactive run, gcloud cannot refresh it — refreshing requires a browser, and there's no browser. You get an error. The command fails. There is no graceful degradation. Combine that with Surprise 6 (SQLite lock) and Surprise 3 (four API calls before TCP), and you start to see why CI/CD pipelines that lean on `gcloud compute ssh` are flaky in ways that feel mysterious. Each call is a small distributed system that depends on local credential state that can silently rot. The widely-deployed [`google-github-actions/ssh-compute`](https://github.com/google-github-actions/ssh-compute/issues) action has open issues today for exactly this — `RESOURCE_EXHAUSTED` errors, [`[4033: 'not authorized']`](https://github.com/google-github-actions/ssh-compute/issues/44) from the IAP tunnel start, "Command is not executed in the context requested user." None of those are bugs in OpenSSH. They're bugs in the multi-stage pre-connect machinery I just described. The action is a thin wrapper around `gcloud compute ssh`, which is a not-thin wrapper around six independent things. ## The implicit contract Once I'd seen these seven surprises I went looking for a unifying picture. Here's what I landed on. `gcloud compute ssh` was designed for an interactive human with: - A recent OAuth token (and a browser to refresh it) - IAM write permission on the project (or at least metadata-write tolerance) - Patience for four API calls of latency before the prompt appears - One ssh at a time - One identity to reason about, even though the call really involves four - A network where "public IP or IAP" is a detail they don't need to think about Strip any one of those assumptions and the command starts misbehaving in a way that looks like a different bug. Strip several and you get a deploy hanging at 11pm on a Wednesday. Three audiences violate this contract by default: 1. **CI/CD pipelines** — no browser, often parallel, identity is a service account 2. **Parallel scripts** — fan-out hits the SQLite lock 3. **AI coding agents** — non-interactive, often parallel, prompted to "just ssh in and check," authorized via a single allowlist line that the agent treats as a green light This third one is what got me into this in the first place. Let me come back to it. ## Why this matters more for agents If `gcloud compute ssh` is six things wearing a trench coat, then every Claude Code allowlist entry like `Bash(gcloud compute ssh ...)` is six security decisions wearing a single checkbox. When I add that line to `settings.local.json`, I am authorizing my agent to: - Refresh and consume my OAuth token (Surprise 2) - Read project metadata (Surprise 3) - Write project metadata, by default (Surprise 1) - Choose between public-IP and IAP paths silently (Surprise 4) - Operate under at least four entangled principals (Surprise 5) - Inherit any pathological behavior of gcloud's credential locking if it tries to parallelize (Surprise 6) - Hang indefinitely on token expiry (Surprise 7) I was thinking I'd authorized one thing. I'd authorized seven. There's a [Claude Code GitHub issue from April](https://github.com/anthropics/claude-code/issues/53830) where someone reports that auto-mode wrote an allowlist entry for `ssh-prod` without per-call approval — exactly the failure mode you'd expect when a verb does too many things and the agent treats it as one. There's a [separate report from Diginomica in February 2026](https://diginomica.com/risky-business-ai-agents-are-asking-your-ssh-keys-21000-exposed-instances-tell-their-own-cautionary) where Claude Code repeatedly probed for SSH keys after losing context — 21,000 instances exposed in the wild. [Anthropic's own blog](https://www.anthropic.com/engineering/claude-code-auto-mode) says users approve **93%** of permission prompts; approval fatigue is now a documented threat model. When the verb is fat, every "yes" is implicitly fat too. ## What I'm doing about it The thing I changed, after all this, was to stop allowlisting `gcloud compute ssh` for the agent at all. Instead: **One-time, by me, not the agent:** ``` # Generate stable ~/.ssh/config aliases + known_hosts gcloud compute config-ssh # Project-wide OS Login gcloud compute project-info add-metadata --metadata enable-oslogin=TRUE # IAP firewall rule gcloud compute firewall-rules create allow-iap \ --source-ranges=35.235.240.0/20 --allow=tcp:22 # IAM for the principal that will use the tunnel gcloud projects add-iam-policy-binding MY_PROJECT_ID \ --member="user:hello@zero8.dev" \ --role="roles/iap.tunnelResourceAccessor" gcloud projects add-iam-policy-binding MY_PROJECT_ID \ --member="user:hello@zero8.dev" \ --role="roles/compute.osLogin" ``` **Per session, by me:** ``` ssh-add -t 1h ~/.ssh/google_compute_engine ``` The `-t 1h` is the part I find satisfying. The agent gets one hour of access, after which the key is gone from the agent socket and the agent has to come ask me for more. The key file itself never leaves my disk and is never read by the agent — the agent talks to the ssh-agent socket, which signs challenges using the key. This is a capability-based pattern [Patrick McCanna writes about clearly](https://patrickmccanna.net/giving-coding-agents-ssh-access-to-other-systems-without-giving-disclosing-secrets/) in a post I keep going back to: the agent has a *capability* to authenticate, not the *credential* itself. Kill the socket, the capability dies. No rotation needed. **In `.claude/settings.local.json`:** ``` { "permissions": { "allow": [ "Bash(ssh staging:*)", "Bash(scp * staging:*)" ], "deny": [ "Bash(gcloud compute ssh*)", "Bash(gcloud iam service-accounts keys create*)", "Bash(gcloud compute project-info add-metadata*)", "Read(~/.ssh/google_compute_engine)" ] } } ``` The thing I find clarifying about this `settings.local.json` is that each line means roughly one thing. `Bash(ssh staging:*)` is transport. The `Read` denial is about key custody. There isn't a single line on the page that does six things at once. Identity, network, transport — three layers of the system, three different primitives, three different decisions. I'd been smushing all three into one allowlist entry and then wondering why the security model felt squishy. ## What I'm still figuring out I don't want to oversell this. Some things I haven't worked out: - `config-ssh` has to be re-run when the instance set changes. For a stable staging VM that's fine; for an autoscaling fleet it isn't, and I think the answer there is `start-iap-tunnel` with destination groups, but I haven't built that yet. - OS Login solves a lot, but once you're shelled in you inherit the VM service account's IAM. That's still a privilege boundary I haven't fully thought through. Context-aware access via Access Context Manager is the next layer; that's a future post. - I keep wanting `gcloud compute ssh` to grow a `--explain` flag that prints which of the six jobs it's about to do for this invocation. This does not exist. It would be useful. - The `--log-http` instrument is great but the output is enormous and most people will never run it. There's a project somewhere in here for a "what is gcloud actually doing" tool. ## What I'm taking forward The shape of the lesson, more than the specific commands: A CLI verb has a mental model baked into it. The mental model assumes a particular user, in a particular environment, doing a particular kind of thing. When I run that command in a different context — non-interactive, parallel, agent-driven, automated — the mental model doesn't transfer. The command still runs, but it runs *as if* I were the assumed user, and the gap between assumed-me and actual-me becomes the bug. `gcloud compute ssh` was built for me-at-my-laptop. When I let the agent type it on my behalf, neither of us is me-at-my-laptop. The command will try its best anyway, and that's where the sharp edges come from. The thing I'm trying to do now, when I see a CLI in an agent allowlist, is ask: *what is the implicit user this command was designed for?* If the agent isn't that user, I don't allowlist the verb — I find a more decomposed primitive that doesn't smuggle in the assumption. That's it. I'm sure I'll learn this lesson again in some other shape next month. If you've hit any of these surprises in your own setup, I'd like to hear about it. --- ## India is Gmail's #1 market, and nobody's building for it URL: https://zero8.dev/blog/india-gmail-largest-market-email-deliverability Published: 2026-04-08 · Updated: 2026-08-21 · 9 min read Tags: email, india, deliverability, d2c, saas Nobody in India reads email? The data on India's cloud business email market — 400M users, 82% Gmail share, world's worst deliverability — says otherwise. A senior I deeply respect — someone who has spent fifteen years inside the email deliverability industry — told me something recently that stopped me cold. > "Don't bother. Nobody in India reads email." He wasn't being dismissive. He was being honest. This is the received wisdom across the industry, repeated in boardrooms, VC pitches, and GTM strategy docs. India is a WhatsApp country. Email is a legacy channel. Move on. So I went looking for the data. And what I found was genuinely eye-opening — not because my senior was wrong, but because he was *almost* right in a way that points to one of the most overlooked opportunities in the Indian SaaS market. Let me walk you through it. ## India's email market in numbers If you came here for the data, here it is up front. The story of what these numbers mean — and why they contradict each other less than they appear to — follows. - **Active email users in India: ~400–500 million** (derived from 958M active internet users, IAMAI–Kantar *Internet in India 2025*, at 40–50% email literacy) - **Gmail market share in India: ~82%** of email users — and India is Gmail's largest market in the world by monthly active users (Google Workspace India, Dec 2024) - **Email deliverability rate in India: ~70% inbox placement** — the worst of any country measured (Validity *Email Deliverability Benchmark*, 2025 and 2026 editions) - **Global average inbox placement: 87.2%** in the 2026 benchmark — the world improved while India stood still - **Average email open rate in India: ~42%** among reachable users — at the global average (HubSpot, MailerLite 2025) - **India's email advertising spend: $236M in 2025** (Statista); marketing automation growing at a **24% CAGR** (Grand View Research) - **Global email deliverability tools market: $1.2–1.5B**, heading to $2.4B by 2030 — with no India-native specialist player - For contrast: **550M+ WhatsApp users** in India, with 90–98% open rates (Meta/DataReportal 2025) ## First, the uncomfortable part: he's not entirely wrong There is real data behind the "nobody reads email in India" claim, and it's worth stating honestly before we get to the surprises. - [50% of India's internet users do not know how to send an email](https://theprint.in/india/50-of-indias-internet-users-dont-know-how-to-send-email-a-third-know-online-banking-govt-survey/2308129/). That's not a marketing stat — it's from the Government of India's own Comprehensive Annual Modular Survey 2022-23, conducted by the National Sample Survey Office (NSSO), [as reported by ThePrint](https://theprint.in/india/50-of-indias-internet-users-dont-know-how-to-send-email-a-third-know-online-banking-govt-survey/2308129/) in March 2025. - **73% of Indian youth aged 15-29 lack basic email skills**, per the same NSSO survey series. - India has **550+ million WhatsApp users** — the largest WhatsApp market in the world, per Meta's disclosures and DataReportal's *Digital 2025: India* report. - WhatsApp open rates in India run **90-98%**, compared to email open rates of 20-42% (Meta Business Messaging benchmarks; Sinch conversational messaging reports). - A 2022 Meta-commissioned survey found that **70% of Indians prefer messaging businesses** over email, phone calls, or visiting a website. If you stop reading here, the senior's thesis holds. India skipped the email era. SMS went straight to WhatsApp. Consumer attention lives on the green bubble, not the inbox. But this is only half the story. ## The statistic that breaks the myth In December 2024, India quietly overtook the United States to become [**the country with the largest number of monthly active Gmail users in the world**](https://yourstory.com/2024/12/india-surpass-united-states-us-gmail-highest-monthly-user-base-google-workspace). Read that again. Not the largest internet population. Not the largest smartphone market. The largest active Gmail user base on the planet. This was disclosed by Sumedha Chakraborty, Country Head, Google Workspace India and South Asia, [as reported by YourStory](https://yourstory.com/2024/12/india-surpass-united-states-us-gmail-highest-monthly-user-base-google-workspace) in December 2024. As of 2025, **Gmail holds roughly 82% share** of Indian email users — one of the highest concentrations of any major market in the world. By conservative math — [958 million active internet users](https://www.thehindu.com/sci-tech/technology/india-now-has-958-million-active-internet-users-57-of-these-are-from-rural-areas/article70566646.ece) as of 2025 (per the IAMAI–Kantar *Internet in India Report 2025*, [reported by The Hindu](https://www.thehindu.com/sci-tech/technology/india-now-has-958-million-active-internet-users-57-of-these-are-from-rural-areas/article70566646.ece) and [Business Standard](https://www.business-standard.com/industry/news/indian-internet-user-base-crosses-950-million-in-2025-iamai-report-126012901048_1.html)), of whom 40-50% are email-literate — India has roughly **400 to 500 million active email users**. That's bigger than the entire population of the United States. Bigger than Western Europe. Bigger than the email-using populations of most of our competitors combined. So which is it? Does nobody read email in India, or does everybody? The answer, as always, is that there are two Indias. ## The Two Indias problem Here is the framework I think everyone in Indian GTM should internalize: **India 1 — The Email India (~400-500M people).** Urban, educated, professional. Uses email for work, banking, OTPs, order confirmations, SaaS logins, and — yes — marketing newsletters they actually read. This India looks demographically and behaviorally similar to email users anywhere else in the world. **India 2 — The Phone India (~500M people).** Got their Gmail account the day they activated their Android phone, and has never opened it since. Communicates via WhatsApp, consumes content via YouTube and Instagram, transacts via UPI. Email is a technical artifact they don't even know they own. The problem is that Indian email marketing metrics aggregate both Indias. When you buy a list of "Indian email addresses," you're buying a mix of engaged professionals and dormant Android-activation accounts. The dead addresses tank your bounce rates, which tanks your sender reputation, which tanks your deliverability, which tanks the open rates of the *real* users you were trying to reach. This is the core of the misconception. The industry looks at aggregate Indian email metrics, sees them dragged down by a massive pool of inactive accounts, and concludes "nobody reads email in India." What's actually happening is that a specific structural problem — the Android-activation ghost accounts — is poisoning the metrics for everyone. ## The data on the Indians who *do* read email Once you filter for the real users, the numbers are surprisingly healthy. - **~42% average email open rate** in India in 2025 — at or above the global average of 42.35%, per HubSpot's *State of Marketing 2025* and MailerLite's email marketing benchmarks. - Indian email marketing campaigns deliver an average of **₹36-42 for every ₹1 spent**, in line with global ROI benchmarks (DMA *Marketer Email Tracker*; Litmus *State of Email*). - Indian D2C brands that take email seriously report **~30% of total revenue from email marketing**, per Klaviyo India customer case studies. - **81% of Indian smartphone users** check email on their phone, higher than the 73% US figure (Hindustan Times mobile usage coverage). - **60% of working Indian millennials** report being "hooked on checking emails," per an Economic Times lifestyle survey. - **52% of urban Indians** say they couldn't last a day without checking work or personal email, per McAfee's Connected Family study. The Indian government recently migrated **1.67 million official email accounts to Zoho Cloud**, spending ₹180 crore — one of the largest single email migrations in the country's history (Press Information Bureau / MeitY, April 2026). Enterprises, BFSI, IT services, SaaS companies — all of corporate India runs on email. Email in India isn't dying. It's growing. India's marketing automation market, where email is the single largest solution category, is expanding at a **24% CAGR** ([Grand View Research, *India Marketing Automation Market Report*](https://www.grandviewresearch.com/industry-analysis/india-marketing-automation-market-report)). India's email advertising spend is projected at **$236 million in 2025** (Statista, *Email Advertising – India*). Asia-Pacific is the **fastest-growing email marketing region in the world** (Mordor Intelligence; Straits Research). ## The real problem isn't reading. It's deliverability. Here's where the story gets genuinely interesting. ### India's email deliverability rate: worst in the world **India has the worst email deliverability rate of any country measured: ~69.8% inbox placement**, per [Validity's *2025 Email Deliverability Benchmark Report*](https://www.validity.com/resource-center/2025-email-deliverability-benchmark-report/) and [Mailreach's 2025 email deliverability statistics](https://www.mailreach.co/blog/email-deliverability-statistics). Compare that to the United Kingdom at 98.8%, Germany at 97.8%, Australia at 98.7%, and even the United States at 84.6% ([Validity 2025 benchmark](https://www.validity.com/resource-center/2025-email-deliverability-benchmark-report/)). For every 10 legitimate emails sent to an Indian inbox, nearly 3 never arrive. They're filtered, spam-boxed, or silently dropped. India also ranks **worst in the world for Microsoft inbox placement** and second-worst for Yahoo/AOL, per the same [Validity 2025 benchmark](https://www.validity.com/resource-center/2025-email-deliverability-benchmark-report/). And the gap is widening, not closing. [Validity's *2026 benchmark*](https://www.validity.com/resource-center/2026-email-deliverability-benchmark-report/) shows the **global average inbox placement rose to 87.2%** — a 3.7-point improvement year over year — while India stayed stuck around 70%. The rest of the world fixed authentication and cleaned up its lists after the 2024 Gmail/Yahoo requirements. India's structural problem didn't move. If you send email to Indian recipients, don't take the country-level number on faith — **test your own deliverability**: [Gmail Postmaster Tools](https://postmaster.google.com/) is non-negotiable given Gmail's ~82% share (it shows your actual domain reputation and spam rates as Gmail sees them), and a seed-list test against Indian Gmail, Outlook, and Yahoo inboxes will tell you your real placement in an afternoon. Think about what this means. The senior's observation — "nobody reads email in India" — isn't actually about Indians not reading email. It's about the fact that roughly a third of emails sent to Indians never make it to the inbox they would have read. From the sender's perspective, it *looks* like nobody's reading. From the recipient's perspective, the email simply never arrived. This is the single most important insight I took from the research: > **The "nobody reads email in India" claim is a deliverability problem being misdiagnosed as a demand problem.** And that is a very different kind of business opportunity. ## Why this hasn't been solved yet Here's the strange part. India is Gmail's largest market, has the worst deliverability in the world, a $15B+ SaaS industry (JM Financial / SaaSBoomi, FY24), 209,000+ registered startups (DPIIT, January 2026), and a D2C ecosystem where email drives 30% of revenue when it works. And yet — there is no India-native email deliverability company of any scale. Globally, email deliverability is a **$1.2-1.5B market growing to $2.4B by 2030** (ResearchAndMarkets; The Business Research Company *Email Deliverability Tools Global Market Report 2025*). The broader deliverability ecosystem — authentication, verification, warmup, cold email, monitoring — is **$5-7B today, heading to $10-20B by 2030** (aggregated from Market Research Future, Fortune Business Insights, Market.us). Companies like [Validity/Everest](https://www.validity.com/resource-center/2025-email-deliverability-benchmark-report/), Valimail, EasyDMARC, and Abnormal Security have collectively raised hundreds of millions in venture funding to solve this problem — for Western markets. In India, the closest thing to a category leader is Netcore Cloud, a broader customer engagement platform with reported revenue of ~$136M (GetLatka, 2025). There's no specialist. No "Validity for India." No "EasyDMARC for Gmail-dominant markets." No inbox stack built for the structural realities of the Indian email ecosystem. **India's email security market has the same shape.** The adjacent category — DMARC monitoring, spoofing protection, phishing defense — is served entirely by Western vendors (Valimail, EasyDMARC, Red Sift, Abnormal) pricing in dollars for enterprises that buy in dollars, while Indian domains adopt authentication fast but enforce it badly: publishing a DMARC record is common now, running it at `p=reject` with someone actually watching the reports is rare. BFSI regulators are starting to force the issue, which means the demand side of India's email security market is being created by mandate while the supply side still ships from San Francisco. Same gap, same reason: everyone upstream was told nobody here reads email. This isn't because the market is small. It's because the incumbents don't see it, and the local founders have been told — by people like my senior — that nobody reads email here. ## What Indian businesses should actually do If you're running a D2C brand, a SaaS company, or any business in India where email matters, here's the reframe: 1. **Stop treating your Indian list like a Western list.** Aggressive list hygiene is non-negotiable. Those Android-activation ghost accounts are poisoning your sender reputation. 2. **Obsess over Gmail, ignore everything else.** ~82% of your recipients are on Gmail. Build your strategy around Gmail Postmaster Tools, Gmail's authentication requirements, and Gmail's reputation signals. 3. **Get your DMARC, SPF, and DKIM right — yesterday.** Gmail and Yahoo's 2024 authentication requirements aren't optional. Nearly half of top domains still don't have DMARC configured correctly (EasyDMARC *DMARC Adoption Report 2025*). 4. **Run email and WhatsApp in parallel, not in competition.** WhatsApp wins on urgent, transactional, short-form engagement. Email wins on depth, context, long-form, and enterprise. 5. **Measure inbox placement, not just open rates.** Your real open rate is hidden behind a deliverability ceiling you probably don't even know about. ## The takeaway My senior is one of the smartest people I know in this industry, and he's not wrong about what he's observed over fifteen years. The frustrations he's describing are real. Indian email marketing *is* harder than Western email marketing. The metrics *are* worse. The behavior *is* different. But "nobody reads email in India" is the wrong conclusion from the right data. The correct conclusion is: > **400 million Indians read email every day. We just haven't built the infrastructure to reliably reach them yet.** That's not a dead market. That's the biggest greenfield opportunity in Indian SaaS that nobody is talking about. And that's exactly why I'm paying attention. --- ## Further reading & primary sources - [IAMAI–Kantar, *Internet in India Report 2025*](https://www.thehindu.com/sci-tech/technology/india-now-has-958-million-active-internet-users-57-of-these-are-from-rural-areas/article70566646.ece) — the authoritative source on India's 958M active internet users - [ThePrint: "50% of India's internet users don't know how to send email"](https://theprint.in/india/50-of-indias-internet-users-dont-know-how-to-send-email-a-third-know-online-banking-govt-survey/2308129/) — NSSO government survey coverage - [YourStory: "India surpasses the US as Gmail's highest monthly user base"](https://yourstory.com/2024/12/india-surpass-united-states-us-gmail-highest-monthly-user-base-google-workspace) — Google Workspace India disclosure - [Validity, *2026 Email Deliverability Benchmark Report*](https://www.validity.com/resource-center/2026-email-deliverability-benchmark-report/) — global inbox placement up to 87.2%; India unchanged - [Validity, *2025 Email Deliverability Benchmark Report*](https://www.validity.com/resource-center/2025-email-deliverability-benchmark-report/) — global and regional inbox placement data - [Mailreach, *Email Deliverability Statistics 2025*](https://www.mailreach.co/blog/email-deliverability-statistics) — ISP-level deliverability breakdown - [Business Standard: "India's internet user base crosses 950 million in 2025"](https://www.business-standard.com/industry/news/indian-internet-user-base-crosses-950-million-in-2025-iamai-report-126012901048_1.html) - Grand View Research — *India Marketing Automation Market Report* (24% CAGR data) - Statista — *Email Advertising India* market outlook ($236M 2025 projection) - EasyDMARC — *DMARC Adoption Report 2025* - The Business Research Company — *Email Deliverability Tools Global Market Report 2025* - DPIIT Startup India — recognized startup entity count (209K+) --- *If you're building in email deliverability, GTM for Indian D2C, or the inbox stack broadly — I'd love to compare notes. Find me at [zero8.dev](https://zero8.dev).* --- ## RCM on Import of Services from Foreign Cloud Providers URL: https://zero8.dev/blog/rcm-on-import-of-services-cloud-saas-india Published: 2026-03-31 · 8 min read Tags: gst, rcm, compliance, india, saas Your Indian Pvt Ltd buys Anthropic, OpenAI, or Vercel and the invoice has no GST. That does not mean no GST liability. Here is exactly what the law says. Your CA says there is no provision of RCM on purchases from foreign cloud providers because they do not charge IGST on their invoices. That advice is wrong, and acting on it can attract 18% interest per annum plus penalties on audit. This post breaks down the exact legal provisions, the B2B vs B2C distinction that causes the confusion, and the step-by-step compliance procedure for Indian companies buying cloud services from abroad. ## The one-line answer When an Indian registered business buys cloud services, API access, or SaaS subscriptions from a foreign provider, the business must **self-assess and pay 18% IGST under the Reverse Charge Mechanism (RCM)**. The foreign provider not charging GST is not an exemption. It is the reason RCM exists. > "Reverse Charge means the liability to pay tax is on the recipient of supply of goods or services instead of the supplier." > > — [GST Council Official Flyer on RCM](https://gstcouncil.gov.in/sites/default/files/e-version-gst-flyers/Reverse%20charge%20Mechanism.pdf) --- ## What is import of services? [Section 2(11) of the IGST Act, 2017](https://indiankanoon.org/doc/86210542/) defines "import of services" as the supply of any service where: | # | Condition | Example | |---|-----------|---------| | 1 | Supplier is located **outside India** | Anthropic is a US company | | 2 | Recipient is located **in India** | Your Pvt Ltd is in India | | 3 | Place of supply is **in India** | Default rule under Section 13 IGST: location of recipient | All three must be true simultaneously. There must also be consideration (payment) and the service must be used for business purposes. ### Which cloud purchases qualify? | Service | Provider | Country | Import? | |---------|----------|---------|---------| | Claude API | Anthropic | USA | **Yes** | | GPT API | OpenAI | USA | **Yes** | | GitHub Copilot | Microsoft (US) | USA | **Yes** | | Vercel Hosting | Vercel Inc. | USA | **Yes** | | Stripe Payments | Stripe Inc. | USA | **Yes** | | AWS (Indian entity) | Amazon Internet Services Pvt Ltd | India | **No** | | Google Cloud (Indian entity) | Google Cloud India Pvt Ltd | India | **No** | **Critical check:** Many large providers (AWS, Google Cloud, Azure) invoice through **Indian subsidiaries** with an Indian GSTIN. That is a domestic B2B transaction, not an import. RCM does not apply. Always check which entity is on the invoice. --- ## How RCM works In normal GST, the supplier collects tax and deposits it with the government. Under RCM, the flow reverses because the foreign supplier has no GST registration in India: ``` Normal: Supplier charges GST → Buyer pays Price + GST → Supplier remits to Govt RCM: Foreign supplier charges NO GST → Buyer pays Price → Buyer self-assesses → Buyer pays GST to Govt ``` The legal basis is **Section 5(3) of the IGST Act**, activated by **[Notification No. 10/2017-Integrated Tax (Rate)](https://d23z1tp9il9etb.cloudfront.net/download/gstlaw/NOTIFICATION%20NO.%2010_2017-INTEGRATED%20TAX%20(RATE)1731047532.pdf)**, Entry No. 1: > "Any service supplied by any person who is located in a non-taxable territory to any person other than non-taxable online recipient." Breaking that down: "non-taxable territory" means outside India. "Other than non-taxable online recipient" means the recipient is a registered taxpayer (like your Pvt Ltd). So the registered Indian recipient must pay IGST under RCM. > [India Briefing](https://www.india-briefing.com/news/gst-compliance-for-saas-and-cloud-computing-in-india-explained-39021.html/) confirms: "When a registered Indian business receives digital services from a foreign provider, the tax liability shifts to the recipient, who must self-assess and pay IGST under the RCM." --- ## The OIDAR confusion: B2B vs B2C This is where most CAs get confused. The treatment differs based on whether the Indian recipient is a registered business or an individual consumer. **OIDAR** (Online Information and Database Access or Retrieval) services include cloud computing, API services, SaaS platforms, streaming services, and online advertising. Essentially any digital service delivered over the internet with minimal human intervention. | Scenario | Who pays GST? | Mechanism | |----------|--------------|-----------| | **B2B:** Foreign provider to Indian registered business | Indian business | RCM under Section 5(3) IGST | | **B2C:** Foreign OIDAR provider to Indian individual | Foreign provider | Forward charge, provider registers in India | For B2C, the foreign provider must [register for GST in India](https://www.gstcouncil.gov.in/sites/default/files/e-version-gst-flyers/51_GST_Flyer_Chapter42.pdf) (simplified registration via Principal Commissioner, Bengaluru West) and charge GST. Some providers like Anthropic [reportedly charge 18% GST on invoices to Indian users](https://x.com/ajayguptaca/status/2032139422779748523). That is the B2C OIDAR mechanism at work. But for B2B, the law says RCM. Not forward charge. Not OIDAR registration. **RCM.** ### What to do in practice | Invoice type | Action | |-------------|--------| | Foreign invoice, **no GST** charged | Pay IGST @ 18% under RCM, claim ITC | | Foreign invoice, **GST charged** with valid Indian GSTIN | Treat as B2B, claim ITC normally | | Foreign invoice, **GST charged** but no valid GSTIN | Grey area — consult your CA, may need to discharge RCM | | Invoice from **Indian subsidiary** with GSTIN | Domestic B2B, no RCM, claim ITC normally | --- ## Step-by-step compliance Here is the exact procedure for each foreign cloud invoice you receive: **1. Classify the service.** Most cloud and API services fall under SAC codes [998313 to 998316](https://www.india-briefing.com/news/gst-compliance-for-saas-and-cloud-computing-in-india-explained-39021.html/) (IT infrastructure, consulting, hosting, and development services). The GST rate is 18%. **2. Convert to INR.** Use the exchange rate as per generally accepted accounting principles (GAAP) on the date of time of supply. This is [Rule 34(2) of CGST Rules](https://cleartax.in/s/reverse-charge-gst). In practice, use the RBI reference rate or your bank's selling rate. **3. Determine time of supply.** Under [Section 13(3) of the CGST Act](https://cleartax.in/s/time-supply-services-gst), the time of supply is the earliest of: - Date of payment to the foreign provider - 60 days from the supplier's invoice date - Date of entry in your books **4. Calculate IGST.** Taxable value (INR) multiplied by 18%. No CGST/SGST split — imports are always inter-state, so IGST only. **5. Pay through Electronic Cash Ledger.** This is critical: [RCM tax must be paid 100% through cash](https://www.taxilla.com/rcm-gst-compliance-guide). You cannot use your existing ITC balance (Electronic Credit Ledger) to pay RCM liability. **6. Issue a self-invoice.** Required under Section 31(3)(f) of the CGST Act. The self-invoice should contain your GSTIN, the foreign supplier's details, SAC code, taxable value, and IGST amount. Mark it as "Supply under Reverse Charge." **7. Report in GSTR-3B.** Two tables matter: - **Table 3.1(d)** — Inward supplies liable to reverse charge (your RCM liability) - **Table 4(A)(3)** — ITC on inward supplies liable to reverse charge (your ITC claim) Both are [auto-drafted from GSTR-2B](https://tutorial.gst.gov.in/userguide/returns/GSTR3B.htm) but you should verify the amounts. **8. Claim ITC.** The IGST paid under RCM is eligible for ITC in the [same month](https://gsthero.com/blog/reverse-charge-mechanism-rcm-under-gst-20-toughest-faqs-answered/). Use it to offset your output GST liability. ### Worked example ``` Anthropic invoice: $500 Exchange rate (RBI reference, date of payment): 85.50 INR/USD Taxable value: $500 x 85.50 = Rs 42,750 IGST @ 18%: Rs 7,695 Pay Rs 7,695 via Electronic Cash Ledger Claim Rs 7,695 as ITC in same month Net cash impact: Rs 0 (if you have output liability to offset) ``` --- ## Accounting entries **On receipt of foreign invoice and payment:** ``` Dr. Cloud Service Expense Rs 42,750 Dr. IGST Input (RCM) Rs 7,695 Cr. Bank (foreign remittance) Rs 42,750 Cr. IGST Output (RCM) Rs 7,695 ``` **When RCM IGST is deposited via Cash Ledger:** ``` Dr. IGST Output (RCM) Rs 7,695 Cr. Electronic Cash Ledger Rs 7,695 ``` **When ITC is claimed:** ``` Dr. IGST Input Tax Credit Rs 7,695 Cr. IGST Input (RCM) Rs 7,695 ``` Maintain [separate RCM ledgers](https://gsthero.com/blog/reverse-charge-mechanism-rcm-under-gst-20-toughest-faqs-answered/) for IGST-Output-RCM and IGST-Input-RCM. --- ## Six myths that need to die **"No GST on invoice means no GST liability"** — Wrong. The absence of GST on a foreign invoice is the normal case. That is why RCM exists. [Section 5(3) IGST](https://comtax.wb.gov.in/GST/GST_FAQ/Reverse_Charge_Mechanism_under_GST.pdf) makes the recipient liable. **"There is no provision of RCM for cloud purchases"** — Wrong. [Entry 1 of Notification 10/2017-IT(R)](https://d23z1tp9il9etb.cloudfront.net/download/gstlaw/NOTIFICATION%20NO.%2010_2017-INTEGRATED%20TAX%20(RATE)1731047532.pdf) covers "any service" from a non-taxable territory. Cloud services are not exempt. **"RCM is an additional cost"** — Mostly wrong. You pay IGST in cash but claim ITC in the same month. If you have output liability, the [net cost is zero](https://www.binarysemantics.com/blogs/reverse-charge-mechanism-rcm-under-gst-applicability-e-invoicing-import-of-services/). It is a compliance obligation, not a tax cost. **"We can pay RCM from our ITC balance"** — Wrong. [RCM must be paid through Electronic Cash Ledger only](https://www.taxilla.com/rcm-gst-compliance-guide). You cannot use credit ledger to discharge RCM. **"If the provider charges GST, RCM does not apply"** — Nuanced. For B2C transactions via OIDAR-registered providers, the provider correctly charges GST. For B2B, the legal mechanism is RCM. If a foreign provider charges GST on a B2B invoice without a valid GSTIN, the treatment is a [grey area requiring CA consultation](https://x.com/ajayguptaca/status/2032139422779748523). **"Small amounts are exempt from RCM"** — Wrong. There is no threshold. Even a Rs 500 subscription triggers the obligation. Interest and [penalties apply regardless of amount](https://www.avalara.com/in/en/resources/whitepapers/consequences-gst-non-compliance-offences-penalties.html). --- ## Penalties for getting it wrong | Non-compliance | Consequence | |---------------|-------------| | Non-payment of RCM | Tax + interest @ 18% p.a. from due date (Section 50 CGST) | | Late payment | Tax + interest for the delay period | | Non-filing of return | Late fee Rs 50/day CGST + Rs 50/day SGST, max Rs 10,000 (Section 47) | | Suppression or fraud | Tax + interest + penalty equal to 100% of tax (Section 73/74) | --- ## Monthly compliance checklist - Identify all foreign service invoices for the month - Verify which invoices come from entities outside India (check the invoice entity, not just the brand name) - Confirm none have a valid Indian GSTIN (if they do, it is domestic supply) - Classify under correct SAC code (998313-998316 for most cloud/IT) - Convert to INR using GAAP exchange rate on date of time of supply - Calculate IGST @ 18% - Issue self-invoice for each transaction - Pay IGST via Electronic Cash Ledger - Report in GSTR-3B: Table 3.1(d) for liability, Table 4(A)(3) for ITC - Maintain records: foreign invoices, self-invoices, payment proofs, exchange rate documentation --- ## The legal framework at a glance | Section | Act | What it covers | |---------|-----|---------------| | Section 2(11) | IGST | Definition of import of services | | Section 5(3) | IGST | RCM on notified categories | | Section 7(1)(b) | IGST | Import treated as inter-state supply | | Section 13 | IGST | Place of supply for cross-border services | | Section 13(3) | CGST | Time of supply under reverse charge | | Section 14 | IGST | Special provisions for OIDAR | | Section 24(iii) | CGST | Compulsory registration for RCM payers | | Section 31(3)(f) | CGST | Self-invoice requirement | | Rule 34(2) | CGST Rules | Exchange rate for services valuation | | Notification 10/2017-IT(R) | IGST | Categories of services under RCM | --- ## SAC codes for cloud and IT services | SAC | Description | |-----|-------------| | 998313 | IT infrastructure and support services | | 998314 | IT consulting, design, and solutions | | 998315 | Hosting and IT infrastructure setup | | 998316 | IT design and development services | --- ## References 1. [GST Council — RCM Official Flyer (PDF)](https://gstcouncil.gov.in/sites/default/files/e-version-gst-flyers/Reverse%20charge%20Mechanism.pdf) 2. [GST Council — OIDAR Services Flyer (PDF)](https://www.gstcouncil.gov.in/sites/default/files/e-version-gst-flyers/51_GST_Flyer_Chapter42.pdf) 3. [GST Council — Imports in GST Regime (PDF)](https://gstcouncil.gov.in/sites/default/files/e-version-gst-flyers/Imports_in_GST_Regime.pdf) 4. [Notification No. 10/2017-IT(R) — ICAI Updated (PDF)](https://d23z1tp9il9etb.cloudfront.net/download/gstlaw/NOTIFICATION%20NO.%2010_2017-INTEGRATED%20TAX%20(RATE)1731047532.pdf) 5. [Section 2(11) IGST Act — Indian Kanoon](https://indiankanoon.org/doc/86210542/) 6. [CGST Section 13 — Time of Supply — ClearTax](https://cleartax.in/s/time-supply-services-gst) 7. [Reverse Charge Mechanism — ClearTax](https://cleartax.in/s/reverse-charge-gst) 8. [GST Portal — GSTR-3B FAQ](https://tutorial.gst.gov.in/userguide/returns/GSTR3B.htm) 9. [ClearTax — GST on Import of Services](https://cleartax.in/s/gst-on-import-of-services) 10. [ClearTax — Reverse Charge under GST](https://cleartax.in/s/reverse-charge-gst) 11. [India Briefing — GST for SaaS and Cloud Computing](https://www.india-briefing.com/news/gst-compliance-for-saas-and-cloud-computing-in-india-explained-39021.html/) 12. [Binary Semantics — RCM Import of Services 2025](https://www.binarysemantics.com/blogs/reverse-charge-mechanism-rcm-under-gst-applicability-e-invoicing-import-of-services/) 13. [Tax Guru — GST on Foreign Software Purchases](https://taxguru.in/goods-and-service-tax/gst-income-tax-software-purchases-foreign-vendors-pe-india.html) 14. [GST Hero — RCM 20 Toughest FAQs](https://gsthero.com/blog/reverse-charge-mechanism-rcm-under-gst-20-toughest-faqs-answered/) 15. [HNA & Co LLP — RCM on Import of Service](https://hnallp.com/a/rcm-on-import-of-service-intellectual-property-right) 16. [Taxilla — RCM Compliance Guide](https://www.taxilla.com/rcm-gst-compliance-guide) 17. [Cashflo — OIDAR Services Guide](https://www.cashflo.io/magazine/oidar-services-under-gst-a-complete-guide-for-digital-businesses) 18. [Ledgers.cloud — Self-Invoice Guide](https://ledgers.cloud/in/invoice/rcm-invoice) 19. [Avalara — GST Non-Compliance Penalties](https://www.avalara.com/in/en/resources/whitepapers/consequences-gst-non-compliance-offences-penalties.html) 20. [N.P. Ahilwani — RCM Legal Compliance](https://npahilwani.com/reverse-charge-import-of-services-gst-legal-compliance-itc/) 21. [West Bengal Commercial Taxes — RCM FAQ (PDF)](https://comtax.wb.gov.in/GST/GST_FAQ/Reverse_Charge_Mechanism_under_GST.pdf) 22. [Global VAT Compliance — India Supreme Court on OIDAR](https://www.globalvatcompliance.com/globalvatnews/gst-foreign-digital-services-india/) 23. [Ampuesto — GST on SaaS in India](https://ampuesto.in/blog/gst-on-saas-digital-services-india/) --- *This post is for educational purposes. Tax law is nuanced and fact-specific. Always verify with a qualified Chartered Accountant before acting on any of this.* --- ## The Tax Knowledge Most Solo Developers Never Get URL: https://zero8.dev/blog/solo-developer-tax-savings-leasing-gst Published: 2026-03-31 · 9 min read Tags: solo-founder, taxes, gst, india, leasing What 9 months at a leasing startup taught me about GST credits, car leasing, and device leasing. A practical guide for Indian solo founders. I spent about nine months as the founding engineer at [Automint](https://automint.in), a company that helps corporate employees lease cars and devices through their employer. Nine months is not long. But when you are building the entire product — the payroll integration, the lease structuring engine, the compliance logic — you absorb the domain at an absurd rate. Every edge case in the code is a tax rule. Every validation check is a section of the CGST Act. Every dropdown option maps to an Income Tax Rule. By the time I left to run my own one-person software company, I had accidentally become fluent in a corner of Indian tax law that most developers never encounter. And the first thing I realized is: the structures I had been building for employees at large corporates — they work for solo founders too. Nobody had told me. This post is what I wish someone had told me before I started invoicing clients through my Pvt Ltd. --- ## The basic idea, in plain English When you buy a car or a laptop as an individual, you pay from money that has already been taxed as income. A Rs 15 lakh car actually costs you around Rs 21 lakh in pre-tax earnings if you are in the 30% bracket. The GST baked into the price (28% on cars, 18% on electronics) is a dead cost — you cannot recover it. But there is a legal structure, designed for employers, that changes this equation completely. Here is the short version: 1. **Your company leases the car or device** from a leasing company (not you personally). 2. **The lease rental is deducted from your salary before income tax** is calculated. So you are paying with pre-tax money. 3. **The tax department adds back only a tiny flat amount** as a "perquisite" (perk) to your taxable income — not the actual lease cost, but a fixed number defined in the Income Tax Rules. 4. **The gap between what you deducted and what got added back** is your tax saving. That is it. The rest of this post is the math, the legal references, and the specific numbers. --- ## Example 1: Leasing a car worth Rs 15 lakh Let us say you want a mid-range SUV. On-road price around Rs 15 lakh. Engine above 1600cc. You are in the 30% tax bracket (plus 4% cess, effective rate ~31.2%). Four-year lease. ### Buying it yourself You take a car loan at ~10% interest for 4 years. | Item | Amount | |------|--------| | On-road price (including 28% GST) | Rs 15,00,000 | | GST embedded in the price | ~Rs 3,28,000 | | Monthly EMI | ~Rs 38,000 | | Total loan repayment over 4 years | ~Rs 18,24,000 | | Insurance + maintenance (4 years) | ~Rs 2,50,000 | | **Total outflow** | **~Rs 20,74,000** | | Can you recover the GST? | No | | Can you deduct any of this from taxable income? | No | | Pre-tax income needed to fund this (at 31.2% tax) | **~Rs 30,14,000** | You need to earn over Rs 30 lakh (before tax) just to afford a Rs 15 lakh car. ### Leasing it through your company Your Pvt Ltd enters a lease agreement with a leasing company. The lease rental is structured as part of your CTC. | Item | Amount | |------|--------| | Monthly lease rental (deducted from gross salary) | Rs 35,000 | | Total lease payments over 4 years | Rs 16,80,000 | | Buyback at end of lease (you keep the car) | ~Rs 4,50,000 | | **Total outflow** | **~Rs 21,30,000** | Now here is where the tax savings kick in. **Income tax saved on the salary deduction:** You deducted Rs 16,80,000 from your gross salary over 4 years. At your 31.2% effective tax rate, that saves you: > Rs 16,80,000 x 31.2% = **Rs 5,24,160** **But wait — the perquisite addition:** The tax department does not let this go completely tax-free. Under [Rule 3(2) of the Income Tax Rules](https://eztax.in/perquisite-valuation-of-motor-car), when a company provides a car for an employee's personal use, a fixed monthly amount is added to taxable income: | Car type | Monthly perquisite (employer pays running costs) | |----------|--------------------------------------------------| | Engine up to 1600cc | Rs 1,800/month | | Engine above 1600cc | Rs 2,400/month | | If driver also provided | + Rs 900/month | For our example (above 1600cc, with driver): Rs 2,400 + Rs 900 = **Rs 3,300/month**. Over 4 years: Rs 3,300 x 48 = Rs 1,58,400 added back as taxable income. Tax on that: Rs 1,58,400 x 31.2% = **Rs 49,420** **Net tax saving:** Rs 5,24,160 - Rs 49,420 = **Rs 4,74,740** **Your effective cost:** Rs 21,30,000 - Rs 4,74,740 = **~Rs 16,55,000** | | Personal purchase | Company lease | Difference | |---|---|---|---| | Total outflow | Rs 20,74,000 | Rs 21,30,000 | | | Tax saved | Rs 0 | Rs 4,74,740 | | | **Effective cost** | **Rs 20,74,000** | **~Rs 16,55,000** | **Rs 4,19,000 saved** | That is roughly **20% savings** on the same car. And this is a conservative estimate — if your company also deducts driver salary and maintenance costs from your CTC, the savings go higher. [Zerodha Varsity's analysis](https://zerodhavarsity.substack.com/p/car-leasing-vs-buying-whats-better) shows savings of Rs 4.5-6.6 lakh on a Rs 10 lakh car over 4 years, depending on how many expenses are routed through the structure. **Why is the company cost-neutral?** Whether the Rs 35,000/month goes to the leasing company or to you as salary, the employer's total CTC stays exactly the same. The company is just redirecting money from one bucket (salary) to another (lease rental). No extra cost. ### Where does the GST benefit come from? This is the part that confused me until I built the product. Here is the chain: **The leasing company buys the car.** They pay Rs 15 lakh, which includes ~Rs 3.28 lakh of GST. Normally, GST on motor vehicles is "blocked credit" — you cannot claim it back. But [Section 17(5) of the CGST Act](https://cleartax.in/s/section-175-of-cgst-act) has a critical exception: > ITC is available when the motor vehicle is acquired for **"further supply of such motor vehicles"**. Leasing is legally a "further supply." So the leasing company recovers the Rs 3.28 lakh GST as Input Tax Credit. This lowers their cost basis, which means your lease rental is lower than what an equivalent bank EMI would be. The GST saving does not come to you directly — it flows through as a lower lease price. **On the lease rentals themselves:** The leasing company charges GST (18%) on each monthly rental. Whether your company (the employer) can claim ITC on those rentals depends on the business. For most non-leasing businesses, [GST on car lease rentals paid by the employer is blocked credit](https://cleartax.in/s/gst-on-cars). But the income tax saving more than compensates for this. --- ## Example 2: Leasing a MacBook Pro and an iPhone This is where it gets even better. Devices have two advantages over cars: ### Advantage 1: Zero perquisite tax on phones and laptops Under [Rule 3(7)(viii) of the Income Tax Rules](https://eztax.in/perquisite-valuation-of-motor-car), mobile phones and telephones provided by the employer are **completely exempt** from perquisite tax. Laptops provided as work tools are similarly exempt (they are not listed in the taxable movable assets category, which specifically excludes computers and laptops). This means: the full lease rental is deducted from your pre-tax salary, and **nothing** is added back as a perk. Unlike cars, there is no flat perquisite to erode your savings. ### Advantage 2: No blocked GST credit on devices Section 17(5) only blocks ITC on motor vehicles. Electronics are not blocked. So your company (the employer) can directly claim ITC on the device lease rentals. That is an additional 18% recovery. ### The math on a Rs 2 lakh MacBook + Rs 1.5 lakh iPhone Total device cost: Rs 3,50,000. Lease term: 12 months. | | Buy personally | Lease through company | |---|---|---| | Price paid | Rs 3,50,000 | Rs 3,50,000 (spread as ~Rs 32,000/month lease rental) | | GST embedded (18%) | ~Rs 53,390 (dead cost) | Recovered by employer as ITC: **Rs 53,390 saved** | | Paid from pre-tax or post-tax income? | Post-tax | Pre-tax (lease deducted from salary) | | Income tax saved (at 31.2%) | Rs 0 | Rs 3,50,000 x 31.2% = **Rs 1,09,200** | | Perquisite tax added back | N/A | **Rs 0** (phones and laptops exempt) | | **Effective cost** | **Rs 3,50,000** | **Rs 3,50,000 - Rs 1,09,200 - Rs 53,390 = Rs 1,87,410** | | **Savings** | | **~Rs 1,62,590 (46%)** | This is why [Automint claims up to 40% savings on devices](https://help.empuls.xoxoday.com/en/articles/12797875-automint-s-tax-saving-car-and-device-leasing-program) — and it is not marketing. It is the actual tax math. In fact, with higher-bracket employees, the savings can exceed 40%. The [Vinod Kothari Consultants analysis](https://vinodkothari.com/2025/05/a-brief-on-the-law-and-mechanics-of-ctc-based-device-leasing/) explains the mechanics well: the employee's CTC stays the same, the lease rental reduces taxable salary, and at the end of the lease term (usually 12 months), the employee buys the device from the lessor at a nominal residual value. You get to keep the MacBook. --- ## The 2026 update: car perquisites are going up The government noticed the car leasing arbitrage was too generous. The [Income Tax Rules 2026](https://www.incometaxindia.gov.in/documents/d/guest/en-notified-it-rules-2026-20-03-2026-pdf), notified on March 20, 2026 and effective from **April 1, 2026**, [significantly increase the flat perquisite values](https://m.economictimes.com/wealth/tax/using-company-car-for-personal-use-income-tax-rules-2026-may-increase-your-tax-heres-how/articleshow/129819190.cms): | Scenario | Old rule | New rule (April 2026) | |----------|----------|-----------------------| | Car up to 1600cc, employer pays expenses | Rs 1,800/month | **Rs 5,000/month** | | Car above 1600cc, employer pays expenses | Rs 2,400/month | **Rs 7,000/month** | | Driver provided by employer | + Rs 900/month | **+ Rs 3,000/month** | | Car up to 1600cc, employee pays expenses | Rs 600/month | **Rs 2,000/month** | | Car above 1600cc, employee pays expenses | Rs 900/month | **Rs 3,000/month** | Source: [Faceless Compliance analysis](https://facelesscompliance.com/income-tax-rules-2026-a-comprehensive-reset-of-employee-perquisites-and-allowances/), [News18 coverage](https://www.news18.com/business/tax/salaried-employees-to-face-higher-tax-on-company-cars-what-draft-rule-2026-proposes-9891443.html) **How does this affect our Rs 15 lakh car example?** With the new rules (above 1600cc, employer pays expenses + driver): - Monthly perquisite: Rs 7,000 + Rs 3,000 = **Rs 10,000/month** (up from Rs 3,300) - Annual perquisite: Rs 1,20,000 (up from Rs 39,600) - Tax on perquisite over 4 years: Rs 4,80,000 x 31.2% = **Rs 1,49,760** (up from Rs 49,420) - Net tax saving: Rs 5,24,160 - Rs 1,49,760 = **Rs 3,74,400** The savings shrink from ~Rs 4.75 lakh to ~Rs 3.74 lakh. Still **18% savings** on the same car. The fundamental arbitrage survives because you are still deducting Rs 4.2 lakh/year from pre-tax salary while only Rs 1.2 lakh/year gets added back as perquisite. **On the flip side:** [GST 2.0 (September 2025)](https://vinodkothari.com/2025/09/gst-changes-double-dhamaka-for-car-lessees-lessors-to-have-elongated-input-tax-recovery/) abolished the compensation cess on vehicles. Effective GST on small cars dropped from ~29-31% to 18%. This makes leasing cheaper for new leases signed after September 2025, partially offsetting the income tax change. **Device perquisites are unchanged.** Phones and laptops remain fully exempt. The 2026 rules did not touch this — device leasing remains the most tax-efficient perk. --- ## What solo founders should actually do If you run a one-person Pvt Ltd or a small company, you are both the employer and the employee. You can structure your own CTC to include these components: 1. **A car lease component** — route your next car purchase through a leasing company. [Quiklyz](https://www.quiklyz.com/) (Mahindra), [Orix](https://www.orixindia.com/), and [Automint](https://automint.in) all offer corporate leasing. Some have minimum employee thresholds, but the structure works even for a single-director company. 2. **A device lease component** — your MacBook, iPhone, iPad, monitors. Zero perquisite tax, plus your company claims GST ITC on the lease rentals. 3. **RCM compliance on foreign SaaS** — if you are paying Anthropic, Vercel, or Stripe, you owe 18% IGST under the Reverse Charge Mechanism. But you claim it back as ITC in the same month. I wrote a [detailed guide with the exact procedure](/blog/rcm-on-import-of-services-cloud-saas-india). 4. **Talk to your CA about salary restructuring.** Specifically mention CTC-based leasing. If they have not heard of it, find a CA who works with startups. The [Jify guide](https://www.jify.co/blog/tax-benefits-employer-provided-car/) and [ClearTax explainer](https://cleartax.in/s/tax-benefit-salaried-employee-car-provided-employer) are good starting points to share with them. Most solo developers pay themselves a flat salary and buy everything from personal savings. Post-tax money, no ITC, no salary structuring. They are leaving 15-40% on the table because nobody told them these structures exist. None of this is tax avoidance. The perquisite valuation rules exist specifically to encourage employers to provide cars and devices to employees. The GST ITC provisions exist to prevent tax cascading in leasing supply chains. The RCM provisions exist to ensure tax parity on cross-border services. You are using the law as designed. --- ## What a founding engineer actually takes away Here is the thing people get wrong about being a founding engineer at an early-stage startup. They think the value is in the tech — the frameworks you learn, the systems you design, the scale you handle. It is not. Nine months at Automint taught me more about Indian tax law, leasing regulations, perquisite valuation, and GST credit chains than years of running my own company would have. Not because I sat in a classroom, but because every feature I built forced me to understand the domain deeply enough to encode it in software. You cannot build a perquisite calculator without understanding Rule 3. You cannot build a lease structuring engine without understanding Section 17(5). You cannot build payroll integration without understanding how CTC components interact with income tax slabs. The code I wrote there is not particularly useful to me now. The domain knowledge is priceless. If you are considering a founding engineer role, look beyond the tech stack. Ask what domain the company operates in. The best founding engineer gigs are the ones where the domain knowledge you absorb — finance, healthcare, logistics, compliance — compounds for the rest of your career. The code you write will be rewritten. The understanding of how industries actually work stays with you. This entire post exists because I built a leasing product. That is the real return on those nine months. --- *This post is for educational purposes. Tax law changes frequently and the numbers here are illustrative. Always verify with a qualified Chartered Accountant before restructuring your salary or setting up leasing arrangements. The Income Tax Rules 2026 take effect April 1, 2026 — confirm the final notified values with your CA.* --- ## State of Agentic Harnesses — March 2026 URL: https://zero8.dev/blog/state-of-agentic-harnesses-march-2026 Published: 2026-03-26 · 4 min read Tags: agentic-ai, developer-tools, claude-code, cursor, codex, open-source The most popular AI coding agents ranked by GitHub stars, with a feature comparison across models, MCP support, autonomy, and pricing. Six months ago, picking an agentic coding tool was straightforward: you used Cursor or Copilot. Today there are fifteen credible options, seven of them open source, and the landscape shifts every few weeks. This is a snapshot of where things stand in March 2026 — ranked by GitHub stars, compared by features, and filtered through what actually matters when you're choosing a harness for real work. ## The leaderboard GitHub stars are a popularity signal, not a quality metric. But combined with issue velocity and open PRs, they paint a picture of where developer energy is going. Data pulled from the GitHub API on March 26, 2026. <div class="gh-grid"> </div> Cursor, Windsurf, GitHub Copilot, and Devin are proprietary with no public repos, so they don't appear here — but they're very much in the conversation. A few things jump out. **OpenClaw is an outlier.** At 310K stars it has surpassed React as the most-starred software project on GitHub. But it's a general-purpose personal AI agent (WhatsApp, calendar, email, flights) that happens to do coding — not a coding-first harness. Peter Steinberger built it, then joined OpenAI in February. The project moved to a foundation. **[OpenCode](https://github.com/anomalyco/opencode) is the open-source Claude Code.** 126K stars, 800+ contributors, 5M+ monthly developers. Terminal TUI with desktop and IDE extensions. Supports 75+ model providers. If you want Claude Code's workflow without being locked to Anthropic models, this is the answer. **[Gemini CLI](https://github.com/google-gemini/gemini-cli) got 50K stars in its first week.** Google's entry hit hard with a 1M token context window and a generous free tier — though that free tier was recently curtailed to Flash models only. ## The feature matrix One table, everything that matters. Scroll right for the full picture. Data cross-referenced with [Morphllm's agent comparison](https://morphllm.com/ai-coding-agent) and the [LogRocket AI Dev Tool Power Rankings](https://blog.logrocket.com/ai-dev-tool-power-rankings/). | Tool | Interface | OSS | Models | MCP | Sub-agents | Cloud agents | Config file | Context | Price | |------|-----------|-----|--------|-----|------------|--------------|-------------|---------|-------| | Claude Code | Terminal | Partial | Anthropic only | Yes | Yes | GitHub Actions | CLAUDE.md | 200K | $20/mo | | Cursor | IDE | No | Multi | Yes | Yes | Yes | Rules | Varies | $20/mo | | Codex CLI | Terminal | Yes | OpenAI-focused | Yes | Yes | Yes (Web) | AGENTS.md | 200K | ChatGPT sub | | OpenCode | Terminal+IDE | Yes | 75+ providers | Yes | — | No | — | Varies | Free (BYOK) | | Aider | Terminal | Yes | 75+ providers | Community | No | No | — | Varies | Free (BYOK) | | Cline | VS Code | Yes | Any | Yes | No | No | .clinerules | Varies | Free (BYOK) | | Gemini CLI | Terminal | Yes | Gemini only | Yes | No | Jules | GEMINI.md | 1M | Free* | | Goose | CLI+Desktop | Yes | Any | Yes | — | No | — | Varies | Free (BYOK) | | Copilot | IDE ext | No | Multi | Yes | Yes | Yes | AGENTS.md | Varies | $10/mo | | Windsurf | IDE | No | Multi | Yes | No | No | Rules | Varies | $15/mo | A few things stand out. **Multi-model matters more than it used to.** Claude Code locks you to Anthropic models. Gemini CLI locks you to Google. Everyone else lets you choose — and for cost-sensitive workflows or local model usage, that flexibility is a real advantage. **MCP support is table stakes now.** Anthropic created the Model Context Protocol and every major tool has adopted it. The outlier is Aider, which still relies on community integrations rather than native support. **Sub-agents and cloud agents are the new frontier.** Cursor, Claude Code, Codex, and Copilot all support spawning background agents that work on tasks asynchronously — in cloud sandboxes, in parallel. This is the feature that separates the 2026 generation from the 2025 tools. ## The pricing question Pricing models have fragmented. There are now three distinct approaches: **Subscription tiers** — Cursor ($20-200/mo), Claude Code ($20-200/mo), Copilot ($10-39/mo), Windsurf ($15/mo). You pay a monthly fee for a usage quota. **BYOK (Bring Your Own Key)** — Aider, Cline, OpenCode, Goose. The tool is free; you pay your model provider directly. Total cost depends on usage patterns, but heavy users report $50-200/month in API costs. **Pay-per-compute** — Devin ($2.25/ACU). You pay for autonomous compute units. Costs can be unpredictable. Cursor moved to credit-based billing in June 2025. The community reaction was mixed — some developers moved to BYOK tools rather than deal with credit metering. ## What developers are saying The [Pragmatic Engineer's March 2026 survey](https://newsletter.pragmaticengineer.com/p/ai-tooling-2026) of 906+ engineers found Claude Code at 46% "most loved," Cursor at 19%, and GitHub Copilot at 9%. But the real signal is in the individual takes. <div class="xcard-grid"> <Hl c="claude">claude code</Hl> is the best thing that has happened to me in 2026. it's really like a game and i get every day new ideas about what to build The combination of someone in the trades with deep domain expertise and <Hl c="claude">Claude Code</Hl> will run circles around your generic software. I am hearing tons of complaints from <Hl c="cursor">Cursor</Hl> customers at enterprise companies: a silent change put almost all models behind Max mode. Devs see all credits used up in 1-2 days. Using <Hl c="claude">Claude Code</Hl> and OpenAI <Hl c="codex">Codex</Hl> extensively for more than a week. <Hl c="claude">Claude Code</Hl> often gives better results in the first attempt itself. <Hl c="gemini">Gemini CLI</Hl> is impressive, but <Hl c="claude">Claude Code</Hl> is acting like the real senior engineer — the one who already read your whole codebase twice. <Hl c="windsurf">Windsurf</Hl> is actually great. I as a Senior Full Stack Developer have used almost every AI Agent coding tool — <Hl c="cursor">Cursor</Hl>, <Hl c="windsurf">Windsurf</Hl>, Warp... $40/mo <Hl c="cursor">Cursor</Hl> used to feel expensive. Now $100/mo <Hl c="claude">Claude Max</Hl> + $40/mo Traycer Pro feels like dirt cheap. I'd pay more if needed. <Hl c="aider">Aider</Hl> is the git-first refactoring engine. It treats your repository as a living document and every AI change as a reviewable commit. </div> The dominant migration story in 2026 is **Cursor to Claude Code** — driven by reasoning quality rather than speed. But plenty of developers are running multi-tool setups: Claude Code for hard problems, Cursor for in-editor flow, Aider for systematic refactors. ## What's next Three trends are shaping the next six months: **Multi-agent orchestration** is becoming standard. Tools are adding the ability to spawn specialized sub-agents — a planner, a coder, a tester, a reviewer — that work in parallel on different parts of a task. **Instruction files are converging.** CLAUDE.md, AGENTS.md, GEMINI.md — every tool has its own repo-level config format. GitHub Copilot already reads both AGENTS.md and CLAUDE.md. Standardization is inevitable. **Cloud agents are the real unlock.** The ability to assign a task to an agent that runs in a cloud sandbox, works for hours, and opens a PR when it's done — that changes how you plan work. Cursor, Codex, and Copilot are all betting heavily on this. The harness you pick today probably won't be the harness you're using in six months. And that's fine. The switching costs are low, the markdown is portable, and the agents are getting better at reading whatever instructions you leave them.