Free Tier Development: What One Engineer Can Build

A first-person case study in building llama-manager with free-tier AI tools
The hidden cost nobody puts in the estimate
Every project I start begins with a task that never appears in a sprint plan: the preflight check.
Can I create an account on this platform? Will the payment go through, or will checkout fail with an error that explains nothing? If it works today, will it still work next month? And if it stops, what do I lose: just the subscription, or also the code, the history, and the week I spent teaching the tool how my project works?
Engineers in the US and EU rarely run this check, because the answer is almost always yes. I run it every time, because the answer is sometimes no, and "sometimes" is the problem. A tool that is reliably unavailable is easy to plan around. A tool that works on Tuesday and disappears on Friday is a dependency with a hidden failure rate, and I'm the one who finds out what the rate is.
I'm a Russian software engineer, and I won't spend this essay on why the walls exist. Some come from compliance policies, some from payment networks, some from a vendor's fraud model that dislikes my IP range, and some are just a country dropdown that doesn't include mine. The engineering effect is the same in every case, and the engineering is what I want to write about.
One thing plainly, because it sets the tone for everything below: I am not asking for sympathy. I am demonstrating what can still be built despite constraints.
The demonstration is llama-manager, a terminal tool that finds, installs, configures, and launches llama.cpp and the models it runs. It is about 2,700 lines of TypeScript in a single file, has one runtime dependency, compiles to a single executable, and was built using only free-tier AI coding tools: no coding agents, no multi-file autonomous workflows, one source file at a time. The README has a section called "Free Tier Development." It's a few sentences long. This essay is the long version.
The argument is narrower than "AI is great" and narrower than "access is unfair." AI has opened a tier of software development where one engineer can build something valuable at almost no cash cost. That tier is not equally available to everyone. And the habits you're forced into when you can't assume availability turn out to be good engineering for anyone who ships with AI in the loop.
The invisible walls
"Constraints" is too abstract to be useful, so here is the itemized version: the layers I deal with, from most visible to least. Versions of these walls exist in many places, and the exact combination varies by country, bank, and year.
The signup wall. Some platforms don't list my country as supported. Others do, then require a phone verification that never arrives or an address field that rejects my region. The failure rarely says why. You get a generic error, a silent loop back to the form, or an account that appears to work and doesn't.
The payment wall. "Free" turns out to be a fragile property. Many tools want a card on file even for a free tier, and cards issued by Russian banks mostly don't work for foreign subscriptions. The question "what does it cost?" quietly becomes "can I buy it at all?" A price on a pricing page tells me nothing if the checkout is closed to me.
The stability wall. Even when signup and payment succeed, an account is only as stable as the fraud model watching it. A sign-in from an unexpected country, a billing address that doesn't match an IP, a device the system hasn't seen: any of these can lower a trust score nobody shows you. The failure mode isn't denial. It's silent revocation, usually with no meaningful way to appeal.
The continuity wall. Every hosted tool is a standing permission to keep using it. Permissions get revoked for everyone (vendors change pricing, retire models, deprecate APIs), but mine are more likely to be revoked, and for reasons unrelated to anything I did. So I treat every SaaS dependency the way a distributed-systems engineer treats a network call: it will eventually fail, which means the failure path has to be designed before it's needed.
The assumption wall. The industry's default path assumes a particular user, one with a card, a supported country, and accounts on a handful of specific platforms. Quick-starts say "just sign up." Templates wire in hosted services. Tutorials assume the author's stack. When the assumption fails you're off-road: no guide, no template, no answer on Stack Overflow that starts from your situation. A lot of what we call developer experience is a pile of assumptions about who you are.
The time wall. Everything above converts into hours: checking whether a tool works from here before committing to it, comparing alternatives, setting up fallbacks, migrating when something disappears. I call this the access tax. It's paid in the scarcest currency an engineer has, attention, and it appears in no estimate and on no invoice. It also compounds, because time spent fighting for access is time not spent building the thing that would reduce the need for it.
Workarounds exist. I've mostly stopped building on them. A workaround is a dependency with an unknown owner and an unknown expiry date, and I already carry enough of those.
What I do instead is design as if the network were hostile. Chaos-engineering teams inject failures like these into their systems on purpose. Mine arrive built in, and I've learned to plan for them: assume any tool can vanish, assume any account can be lost, and keep everything valuable somewhere I control. That last clause is most of the methodology.
Free tier development as an economic model
Software has always had a cost structure, even when nobody wrote it down. A rough version:
cost ≈ deciding + producing + verifying + maintaining + access
The last term rarely appears on a spreadsheet. It's the access tax from the previous section.
For most of the industry's history, producing (turning decisions into working code) was the dominant term, because it was the part that scaled with headcount. Teams exist largely to parallelize it, and parallelizing has a price. A team of n people has n(n−1)/2 communication paths, as Brooks observed in The Mythical Man-Month, and Conway's law makes the architecture mirror the org chart. Meetings, tickets, handoffs, and review queues are the interest we pay for parallelizing the work.
AI tools attack the producing term more directly than anything before them. In the vocabulary of Brooks's "No Silver Bullet," they are the strongest assault yet on accidental complexity, the work imposed by tools, syntax, and boilerplate rather than by the problem. What they leave alone is essential complexity: deciding what to build, what it must never do, and how you'll know it's broken. The marginal cost of the two-thousandth line approaches zero. The cost of knowing it's the right two thousand lines doesn't fall at anything like the same rate.
Two consequences follow. A single engineer carries no coordination overhead and no Conway constraint, so the architecture can follow the problem instead of an org chart, and one person with a fast assistant covers more ground than the old arithmetic allowed. And when the price of producing falls, the set of projects worth doing expands. Glue, managers, converters, the small utilities everyone needs and nobody funds: things that never justified a team's time now justify an evening. It's the Jevons pattern applied to software. Cheaper code creates demand for more code, and for more judgment about which code to write.
I use free-tier development to mean something specific: building real software using only the zero-cost tier of AI assistants. These tiers have hard limits: a smaller working context, usage quotas, chat-shaped workflows, no agents wandering your repository. A short while ago that would have made them toys. It doesn't anymore. The free tier has crossed the viability threshold, and that is an economic event rather than a convenience, because the cash cost of an additional feature can now be zero.
The cost didn't vanish, though. It changed currency. In a paid agentic workflow you spend money to relax constraints. In a free-tier workflow you pay with structure: the specification, the boundaries, and the verification have to come from you, because the tool won't supply them. A rough comparison, typical rather than universal:
| Paid agentic workflow | Free-tier workflow | |
|---|---|---|
| Cash cost | Subscription or usage fees | $0 |
| What the model sees | Often the whole repo, explored by an agent | One file, plus whatever I paste in |
| Who navigates the codebase | The agent | Me |
| Who runs and verifies the output | Agent and human | Me |
| Where project memory lives | Split between tool and repo | The repo, by necessity |
| If access disappears tomorrow | Workflow stalls | Workflow degrades, project survives |
The last row is the one I care about. A workflow built under scarcity is portable by construction, because everything of value had to live in the repository instead of a tool's session history. If access disappears, I lose a collaborator, not the project.
Two cautions, because it would be dishonest to sell this as a free lunch. First, leaderboards rank models by capability and almost none rank them by availability: to whom, from where, and for how long. For an engineer in my position, availability decides which capability I actually get. Second, a free tier is somebody else's marketing budget. I'm happy to spend it. I'm not willing to depend on it. A subsidy is a loan, not an asset, and the terms can change overnight.
What AI changes about the job
For a long time, typing was the visible part of the job, and visible work gets mistaken for the work. Take the typing away and what remains is what the job always was: understanding the problem, deciding what the system should and shouldn't do, and being accountable for whether it does.
Every line of code carries a decision. When you don't make a decision explicitly, the assistant makes it silently, in whichever direction its training data leans. So the engineer's work shifts from writing code to making the consequential decisions explicit before the code exists (boundaries, data formats, behavior on bad input, what must never happen) and then reviewing everything else with suspicion. If a model can write the function, the function was never the valuable part. The valuable part is knowing it should exist, what it must refuse to do, and how you'll notice when it's wrong.
That doesn't make the engineer a manager of robots. The assistant is closer to a very fast, very literal collaborator with uneven judgment: excellent on well-worn patterns, confidently wrong at the edges. Working with it well is a craft, and the craft is mostly editorial.
What does this replace in a traditional workflow? Parts of it, not the whole. Walking down the classic pipeline:
| Stage | With one engineer and an assistant |
|---|---|
| Requirements | Unchanged. Still mine, and still the hardest part. |
| Architecture | Mine. The assistant is a sparring partner for trade-offs, not the decider. |
| First implementation | Mostly delegated, against a specification I wrote. |
| Review | The assistant does a first pass ("how does this fail?"); final judgment is mine. |
| Docs | The assistant drafts from code; I correct what matters. |
| Release | One build command. |
| Ownership | Unchanged. Nobody else is on call. |
A single engineer with an assistant can now absorb a large share of the implementation work that once justified extra hands on a small project. Only the implementation.
There's also a new failure mode worth naming. Code that is plausible, compiles, and is subtly wrong arrives faster than it used to, and so does code I didn't write and don't fully understand. Writing code got cheap. Believing code didn't. Verification becomes the bottleneck, and it's a skill that can't be delegated to the thing being verified.
The experiment: building llama-manager
The problem. Running llama.cpp locally takes more manual steps than it should. You find the right release, read what changed, download the build that matches your OS and GPU backend, find a model in a compatible format, wire it into the engine's configuration, and only then start inference. Many local-LLM front ends sidestep this by bundling or hardcoding one llama.cpp version, which goes stale quickly, because upstream can ship several builds in a single day.
The thesis. Separate the UI from the engine. Treat the engine as a replaceable artifact, and make finding, installing, configuring, and launching it one workflow.
The rules. I chose the free tier on purpose: free-tier AI coding tools only, one source file at a time, no coding agents, no multi-file autonomous workflows. The goal was a functional prototype under those rules. Not a record or a benchmark, just an answer to the question "what is the floor?"
What it does. It's a terminal app built on a single UI library (@opentui/core), with a prompt at the bottom and a slash-command dropdown. /releases lists llama.cpp releases, shows the notes, filters a release's build artifacts, and installs one into ./engines. /models searches Hugging Face for GGUF models (sortable by trending, likes, downloads, creation date, or last update), groups the files into quantizations, and downloads the one you pick, pulling in the multimodal projector file when the repo has one and registering the result in models.ini. /installed manages what you've downloaded. /settings edits the manager's config.ini and the engine's models.ini. /status shows the engine version, server logs, and DRAM/VRAM usage. /start and /stop run the server in the background and end it.
The interesting part isn't the feature list. It's a handful of decisions.
The engine is an artifact, not a dependency. Each release unpacks into its own folder under ./engines, and config.ini selects one with a single line:
[*]
engine-path = engines/llama-b11438-bin-win-cuda-13.4-x64
Rolling back a bad release means editing that line. Nothing patches the engine, and nothing is bundled with it.
--help is the schema. The settings screen doesn't hardcode llama.cpp's flags. It runs the selected engine's own --help, parses the layout (option columns, group headings, defaults, environment variables), and builds the catalog from that, so options added in a new release show up without a manager update. It also copes with the entry point itself changing: it looks for llama-server and for a unified llama binary with a serve subcommand. This is a bet that help-text layout changes more slowly than the flags it describes. The bet can lose. Hardcoding would have lost faster.
The seams are public protocols. Everything the manager talks to is documented and widely known: the GitHub Releases API, Hugging Face's model API, HTTP range requests, INI files, process arguments. No private SDKs, no proprietary service, no account required. Optional tokens (GITHUB_TOKEN, HF_TOKEN) lift GitHub's anonymous rate limit and unlock gated Hugging Face repos; they aren't prerequisites. I built the tool I'd want if I couldn't assume accounts, because I can't.
The config files stay yours. models.ini is the preset file llama.cpp's server reads directly. The manager edits it with a line-based editor that preserves comments, ordering, alignment, and line endings, and writes through a temp file and a rename. Delete the manager and everything still works: a folder of engines, a folder of models, and a config file llama.cpp already understands. That is the whole anti-lock-in strategy: no format of my own to be trapped in.
[*]
ctx-size = 32768
n-gpu-layers = 99
[Qwen3-4B-Q4_K_M]
model = models/Qwen3-4B-Q4_K_M.gguf
A dependency budget of one. The only runtime dependency is the terminal UI library. Everything else is Node built-ins, including readers for ZIP and tar archives written against node:zlib and node:fs, so engine installs behave identically on Windows, macOS, and Linux with no external unzip or tar. The code sticks to "erasable" TypeScript so it can also run on plain Node.js, which keeps the choice of Bun as runtime reversible.
Hand-writing archive readers is exactly what security people tell you not to do, so the design carries the risk. The extractor validates every path and link before it writes anything into the install directory, rejects entries that would escape it, and refuses formats it doesn't handle (ZIP64, encrypted entries) with an explicit error instead of guessing.
Downloads are treated as hostile. Engine archives are checked against the SHA-256 digest GitHub publishes for release assets, and model files against the hash in Hugging Face's LFS metadata. Downloads stream into .part files and resume through range requests, so a flaky connection costs minutes instead of gigabytes. Cancel a job and the partial file stays; run it again and it picks up where it stopped.
The manager is a control surface, not a parent. /start launches the server in the background and deliberately doesn't tie its lifetime to the manager's. The PID and log go to ./logs, and the manager can exit while the server keeps running.
One executable.
bun build --compile --format=esm --bytecode ./index.ts --outfile dist/llama-manager
That produces a single self-contained binary. The runtime travels inside it, so it isn't as small as a Go or Rust build would be, but it's one file with nothing to install: no Node, no Python environment, no Docker. A tool whose job is to remove setup friction shouldn't ship with setup friction.
What it is, and isn't. The manifest says 0.0.2, and it means it. This is a prototype: about 2,700 lines and 105 KB in one file, every box on the README checklist ticked, and nothing yet that has survived contact with a lot of users. A single file isn't the architecture I'd recommend to a team. It's the shape the experiment's rules produced.
Lessons learned
None of this is exotic, but together it adds up to a working method.
1. Context is the new RAM. Engineers who grew up inside kilobytes learned to design data structures because memory forced them to. The AI-assisted equivalent is the working context: what the model can see, hold, and use well. The constraint isn't only window size. It's also rehydration cost, everything you must re-explain at the start of each session because a free-tier assistant doesn't carry your project in its head. Bigger windows help, but in practice long contexts degrade unevenly, so a larger window mostly moves the cliff instead of removing it. The discipline is the one memory limits always taught: minimize what has to be held to make one correct change. The next four lessons are applications of that.
2. The repo is the memory, not the chat. The README holds the goal, the constraints, and a checklist of what's done. It's the first thing any assistant sees in any session, and it's written for a stranger, which is exactly what a fresh chat window is. Because state lives in files, I can swap assistants without losing the project. That's also the answer to the access problem on the development side: the tool can vanish, the context can't.
3. Design for locality. The single file is organized into labeled sections (configuration, types, helpers, INI handling, GitHub API, Hugging Face API, archive extraction, downloads, engine discovery), so most changes touch one of them. Types double as compressed context: a few dozen lines of interfaces (Release, Asset, HfModel, Quant, OptionInfo) describe the shape of the data in less space than any prose could. The working set for a change becomes the section being edited plus the interfaces it touches.
4. Put the seams on public protocols. When the assistant sees one file, anything crossing a boundary has to be describable without showing the other side. Public, text-based interfaces make that free: CLI flags, REST endpoints, INI files, HTTP headers. The model already knows them from training, so I don't spend context teaching them. A private abstraction costs tokens to explain every session. A public protocol was explained in advance by someone else.
5. Count dependencies like milliseconds. Every dependency is API surface that the model must know (or invent), that I must review, and that the next session must re-explain. Models are also least reliable where libraries are youngest, so adopting a young dependency is a decision about which code I'll have to verify by hand. I accepted exactly one, the terminal UI library, which is pre-1.0 by its own version number, and kept everything else on the standard library.
6. Verification is the job. With no agent to run anything, I am the runtime. The README checklist reads like a build order (list releases, read the notes, list artifacts, filter them, download and install), each step small enough to run and see before the next prompt. Two habits carried the weight. Treat model output as untrusted input, the same stance the extractor takes toward archives: validate before you write. And ask for failure modes explicitly, because code that says "ZIP64 archives are not supported yet" beats code that quietly mishandles them. My verification loop was mostly manual, which is the project's largest gap. A real test suite is the obvious next investment.
7. Scarcity improves specs. When every request counts, you stop typing vague instructions and start writing acceptance criteria. A line in the README like "We do NOT kill engine backend if we close manager" is a prompt for the assistant and a test I can run by hand. Quotas turned into a rhythm: ask once, carefully, then use the cooldown to read what came back.
What this doesn't prove. One prototype is a sample of one, in a favorable domain: a tool that glues together well-documented interfaces, which is close to ideal terrain for AI assistance. It doesn't show that free tiers scale to distributed systems, regulated environments, or years of maintenance. Agentic tools would probably have been faster, and splitting this file into modules is exactly the mechanical refactor I'd gladly hand to one. I'm making no claim about speed (the evidence on AI productivity is mixed and context-dependent), only about the floor: the minimum toolset for shipping something real is lower than most people assume.
Conclusion: constraints change where innovation happens
Constraints don't eliminate innovation. They change where it happens. Under these conditions the creative work moved from the keyboard to the whiteboard: fewer clever lines, more deliberate boundaries, less typing and more deciding what the system is allowed to be.
The economics point the same direction. The cost of producing software is collapsing, the cost of access is not collapsing evenly, and the free tier is where the two curves currently meet: a place where an engineer can ship something real without paying anyone, provided they pay in structure, discipline, and a process that doesn't depend on any single tool staying available.
That proviso isn't unique to my situation. Vendors change pricing, retire models, tighten quotas, suspend accounts, and have outages. The difference is that I've had to rehearse. Repo-as-memory, public protocols, a dependency budget, and verification as a habit came out of a constrained environment, and they're also what makes any AI-assisted workflow resilient. If your productivity collapses when one SaaS account becomes unavailable, you didn't adopt AI. You adopted a dependency.
I'm still not asking for sympathy. If you want to do something with this essay, run the experiment yourself: pick a tool you wish existed, set a rule such as free tier only and one file at a time, and see how far you get. Then read my code, break it, and tell me what's wrong with it: github.com/8clever/llama-manager.
The walls are real. So is the working code.