# Coniferous Blog Coniferous is a worker-owned software studio of senior engineers and product builders. We help small teams simplify complex systems and ship products people enjoy using. --- # A Year Without Writing Code Section: Blog Source: https://coniferous.dev/blog/a-year-without-writing-code > # 👋 Welcome back to my series: Make it Better. Make it Faster. > > This is Part II. If you're just arriving, start with Part I, [How AI Coding Actually Works](/blog/how-ai-coding-works), which covers the vocabulary this piece builds on. There are two loud stories about what AI has done to software. One says the job is over: the AI writes the code now, so the smart move is to stop learning how and just tell it what you want. "Vibe code" your way there, forgetting the code even exists. For a weekend project it's genuinely fast and fun, and plenty of impressive demos have been built this way. The trouble shows up later, when that code meets real users and real scale, and "move fast" turns into something unmaintainable. The other story is the reverse: that most of what these tools produce is "AI slop," plausible-looking code nobody really understands, and the responsible thing is to keep it away from anything that matters. What I've found, after a year of building almost entirely this way, is that these tools genuinely can make you faster and change how you work for the better without giving up quality, but only on top of sound software practices and a lot of diligence. # My last year using AI exclusively I've spent the past year building a ton of software, in stretches of genuine amazement and spurts of real frustration. In that time I've hand-written almost no code. I guide AI and review its output. And in that same stretch I've built more software than I ever have: six months of it on a high-performance team at Amazon, then six months on a three-person team at Coniferous. In barely six months at Coniferous I added more than half as much code as I did across nearly a decade at Amazon. Raw lines of code is a crude measure, and I'd never hang an argument on it alone, but a gap that size is hard to ignore. And I believe this software, on average, meets or beats the quality of what I wrote and reviewed as a Principal Engineer at Amazon. ::image{src="/blog/github-contributions-last-year.png" alt="GitHub contributions calendar showing 1,859 contributions over the last year, nearly all concentrated in the most recent six months." caption="My past year on GitHub: 1,859 contributions, nearly all in the last six months, about 500 commits and ~712,000 lines of code added (212,000 removed) across the Coniferous codebases."} ::image{src="/blog/amazon-commit-history.png" alt="Amazon internal code-contribution dashboard for jarntson@: 4,205 changes, 1,116,762 lines added and 672,058 removed across 163 packages over about ten years, with a year-by-year commit-activity heatmap from 2017 to 2025." caption="And nearly a decade at Amazon for comparison: 4,205 changes, 1,116,762 added and 672,058 removed across 163 packages." width="50%"} A sample of what the last six months turned into: :::accordion{defaultOpen="0"} ### Spruce A native desktop app that pulls planning, code, terminals, and AI coding agents into one workspace, with every plan stored as plain markdown in your git repo instead of a cloud silo. Built for small teams who want agents working as real collaborators. It's the product-delivery tool my own team lives in every day, and you can try it at [buildwithspruce.com](https://www.buildwithspruce.com). *Status: open beta — free and local-first.* ::image{src="/blog/spruce-kanban.png" alt="Spruce's Kanban board: Backlog, Todo, In Progress, and In Review columns filled with bug, feature, and chore cards, a left sidebar of views and artifact types, and a row of running agent sessions across the top." caption="Spruce, running the board for Tamarack's work — the same tool used to build another item on this list."} ### Tamarack Screen capture for builders: a desktop studio that turns multi-window screen recordings into polished demos, bug reports, and guides. Every video is described in a small, custom text language, so a recording becomes something you can edit, re-run, and even hand to an AI to annotate. *Status: private beta (waitlist).* ::image{src="/blog/tamarack-editor.png" alt="Tamarack's editor: a screen recording on the canvas with a presenter-camera bubble and a multi-track timeline below, and a text panel on the right showing the declarative script that defines it — screen, presenter camera, media, focus/zoom shots, and captions." caption="Tamarack editing a Spruce demo. The panel on the right is the whole video as editable text — media, zoom shots, and captions — not a mouse-only timeline."} ### A content management system The docs-and-blog engine behind our websites (used for this post). It renders markdown, then lets you edit any element visually right on the live page: click a paragraph, change it, and it writes the exact lines back to the source file as an ordinary git diff. No database, no separate admin portal. *Status: powering our own sites; the rendering half published as open libraries.* ::image{src="/blog/spruce-cms-overlay.png" alt="The CMS overlay editing this post's project list in place: a right-click menu on the accordion offers Add comment, Edit, Insert image, and Change layout (Cards, Accordion, Tabs, Steps, Columns), with a floating widget on the right showing the linked artifact, comments, and a 9-file diff." caption="The overlay editing this very list on the live page — swapping the layout, leaving comments, and writing every change straight back to the markdown as a git diff."} ### A terminal emulator Spruce streams live agent and command output inside the app, so we wrote our own VT/ANSI terminal core from scratch in Rust — parser, screen, scrollback, the works, backed by 200+ tests and a fuzzer. Exactly the kind of thing that wasn't worth building before writing code got cheap. *Status: in daily use inside Spruce.* ::image{src="/blog/spruce-terminal.png" alt="Spruce's terminal rendering a live coding-agent session: a colored diff of the blog post's markdown with red removed lines and green added lines, tool output, and a prompt at the bottom." caption="Our terminal emulator rendering the agent session that wrote this very list — including the edit to the panel right above it."} ### Client projects A handful of engagements building and untangling software for other teams. *Status: ongoing.* ::: # But am I actually faster? It *felt* fast, and its undeniably significantly more code. But feeling fast, being fast, and delivering more value aren't the same thing. For example, when the research group METR ran a careful study on experienced developers doing real work in codebases they knew well, the developers expected AI to speed them up by about 24%. They came out [19% *slower*](https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/). And even after finishing slower, they were sure the tool had sped them up, by about 20%. In contrast, in a controlled experiment [from GitHub](https://arxiv.org/abs/2302.06590), developers building a web server from scratch finished about 56% *faster* with an AI assistant. A [Google study](https://arxiv.org/abs/2410.12944) of its own engineers measured a 21% gain, but the results varied so much from developer to developer that the study couldn't rule out the real effect being zero. They disagree because they measured different worlds. The GitHub task was small and built from scratch, in a common language, with no existing code to understand and little that could go wrong: the easy end. The METR study took seasoned open-source developers and had them work on their *own* large, mature projects — repositories they'd maintained for years, where any change has to thread through a mountain of existing code and conventions, and where the bar for merging is high. Against that bar the AI's suggestions often needed so much correcting and reworking to be acceptable that they cost more time than they saved: the hard end. So where do I land? I believe I'm quite a bit faster with AI, but there are a few reasons why: **It isn't all shipped.** Call it 80% of the way there. Some is live, some is close, some is still being shaped. Getting some of these projects ready to release will take quite a bit more effect (the last 20% is usually the hardest bit). **It's all greenfield, and built for AI from the start.** No decade of legacy to fight. I got to choose a foundation that plays to an agent's strengths: clean structure, sound naming, high quality from day one because it was written that way, and languages like Rust that pair well with these tools while pushing you toward simpler, safer code. **The environment is unusually clear.** A small team, no waterfall, no gauntlet of approvals, the freedom to move fast. Drop the same tools into an org where every change waits on a sign-off and a two-week planning cycle, and you'll find typing was never the bottleneck. **Some code now exists only because writing it got cheap.** When the cost of writing code drops, you can build libraries, tools, and features that weren't pragmatic before: a linter for a custom SQL dialect, a terminal emulator, a couple of small domain-specific languages, more robust testing. I wouldn't have taken these all on prior to AI coding tools. And then there are extra bits of polish, the quality-of-life improvement, the nicer user experience, and the "nice to have" I'd have cut for time before — now they get built because building it barely costs anything. **I've stayed disciplined.** Its easy to let an agent run and rubber-stamp whatever it hands back; staying on top of what it's actually doing — reading the changes, catching where it went wrong instead of waving it through — is hard work. You need to break the work into pieces small enough to review, keep each change small, and not let the codebase sprawl just because the code is suddenly cheap to produce. **You need to refactor regularly.** And because an agent leaves you idle while it works, running several streams at once to fill that time is exhausting. My last six months at Amazon were spent building with these same tools (although the models and tools have also improved quite a bit). That stretch felt faster too, but not nearly at the level of my time at Coniferous so far. I was working in a huge, mature landscape with more microservices, sprawling code bases, layers of leadership review, and the politics of a big org. The typing was never the bottleneck. # So, how has the job changed? Less than you might expect. AI compresses almost every step: research, design, scaffolding, drafting tests, a first pass at review. But it compresses them unevenly. The mechanical parts collapse toward instant, while the judgment-heavy parts (deciding what to build, choosing the approach, catching what the agent missed, knowing when "done" is actually done) speed up far less. So my days didn't get faster so much as they changed shape: less time typing, more time deciding and guiding and reviewing and verifying. The share of the work that's pure judgment went up signifcantly. And because each task now spends long stretches waiting on an agent, the way to use that time is to run several at once. Working many things in parallel might be the single biggest change: the job becomes less about finishing one thing than about keeping several moving. The practices behind good AI development — breaking a problem into small pieces, keeping every change reviewable, holding a high bar for quality — are the same ones good engineering has always had. The teams and developers who already work this way have the least to relearn. For them, adopting AI isn't a reinvention but pointing good habits at a tool that rewards them. You are more likely to struggle without a strong foundation, because the tools tend to expose that gap. My nine years at Amazon gave me a head start on exactly that. Long before any of these tools existed, my team built and ran critical systems by starting from the customer's problem and working backward, shipping small reviewable changes with eyes on each, keeping things simple, and reaching for complexity only when the problem genuinely demands it. That is part of the reason why one person gets a speedup where the METR study showed otherwise. [DORA's research](https://dora.dev/dora-report-2025/), across thousands of teams, lands on exactly this: AI is an amplifier. It magnifies an organization's existing strengths and weaknesses rather than fixing anything, and the biggest returns come from the underlying system, not the tool. Point it at good practices and they compound. Point it at a mess and you get a faster mess. From here, the series gets practical. Each post that follows takes one of those long-standing disciplines and shows how to bring it to building with AI: deciding what to build, shaping software an agent can actually work on, breaking the work down, running the review loop, and holding the quality bar high now that the typing is suddenly cheap. *Next: what we actually mean by "quality," and why, when the cost of writing code falls, the cost of writing the* wrong *code goes up. (Part 3.)* :::tip[Work with us] We build software this way every day at Coniferous, and increasingly we help teams who want to too, including the ones digging out of a codebase that got away from them. If that's you, we'd genuinely like to hear about it. ::: --- # The end of the E2E tax Section: Blog Source: https://coniferous.dev/blog/the-end-of-the-e2e-tax :::e2e-tax-epigraph ``` $ npm run e2e Running 329 tests across 31 files... ✓ 315 passed ✗ 14 failed (selector mismatch) 42m 18s. ``` ::: For most of my career, end-to-end UI tests came with a heavy tax. The tax took multiple forms. Every new test had to be authored: locating elements, wiring selectors, modeling state, handling async. Every UI overhaul required corresponding changes across the test suite, and the more thorough the suite, the more changes piled up. Every flake required triage time, and a real suite generated enough flakes that teams built dedicated dashboards and triage rules just to sort signal from noise. Each of these was tractable in isolation. The cumulative drag made senior engineers across the industry hold an opinion about how much E2E coverage was "too much." The answer was always smaller than you'd want if the tests were free. Over a decade ago I started at Pearson VUE, which operates the infrastructure behind a lot of high-stakes certification exams: the NCLEX, the GMAT, the kinds of exams where someone's career hinges on the result. The quality bar was high, and our E2E suite reflected it. Keeping that suite functional was real work. Engineers wrote and maintained fixtures the QA team could use to author tests in semi-natural language, first on top of a product called Twist and later Gauge. I built the nightly runner that consumed on-prem compute for hours, and the dashboards that sliced results into "real product issue" vs "flake" so the suite stayed useful. I also wrote an Atom plugin that gave syntax highlighting and autocomplete for our Gauge fixture library to make the authoring experience less painful. The team's actual job was building the product, but a meaningful chunk of our time went to keeping our E2E suite from rotting. When I moved to Amazon a few years later, things were different. The products I worked on were primarily internal Amazon tools. The tax was acknowledged, and the org chose not to pay it. E2E UI tests, if present at all, were generally scoped down to smoke tests. On my team we kept exactly two: sanity checks that the main pages rendered without producing errors, and an auth flow. Everything else leaned on heavy unit coverage, API integration tests, code review, and broad production monitoring. When something tripped, you'd scan logs, check for recent changes, consult the runbook, and decide what to do; some teams had auto-rollback wired in, others triggered rollback manually. Either way, you usually reverted in minutes. The easy thing to miss about that model is that it's a function of *what kind of software Amazon ships*. When most of your logic lives on servers you own and the browser is a thin client over them, you get to instrument the runtime, watch real traffic, and roll back when something goes sideways. That option doesn't exist for shipped software: desktop apps, mobile binaries, CLIs, self-hosted infrastructure that runs inside someone else's environment. The runtime is the user's machine. You can't observe most of what goes wrong, and by the time a bug report surfaces, the broken version is already in the wild. So the industry settled into one of two failure modes. Either you paid the tax (engineering time, a triage pipeline, an ongoing maintenance burden), or you skipped E2E and pushed the cost downstream: into bugs that shipped, into manual regression days before each release, into release cadence slowing down because nobody trusts a green build that doesn't exercise the product. Then code throughput went up 10x. Testing didn't accelerate with it. The gap that opened up is where E2E sits: the part of testing that's expensive to scale up alongside the new throughput, but too important to skip. Either you slow down to test, or you ship features you haven't fully tested. Neither is sustainable. ## The inversion The 10x lift can apply to automated tests too. Writing an E2E test used to mean: locate the elements, wire the selectors, model the state, handle the async, debug the flake. Most of that is now something a competent coding agent can do for you, given the right framework and the right context about your app. The tax isn't just lifted. For greenfield work and well-instrumented apps, automated E2E coverage is now *cheaper upfront than the equivalent manual test plan.* Take one example from [Spruce](https://www.buildwithspruce.com/), our local-first product delivery tool. It has a project-setup wizard with two top-level flows (create a new project, import an existing one), and in both, each linked code repo can be created from scratch, linked to an existing folder on disk, or cloned from a remote. The interesting test cases are the combinations: a project where one repo is created fresh, another is linked to an existing local checkout, and a third is cloned. Testing that well means setting up filesystem state before each run (some folders pre-created with git init, others left empty, others entirely absent), driving the wizard through each variation, and tearing it all down so the next case starts clean. The cost lives in the cross-product. Each variation is straightforward on its own, but the combinations multiply, and the manual coverage has to keep up with that. With an agent that can read the wizard's source, enumerate the matrix, and write the setup and teardown helpers alongside each test, that cost collapses. The question is the same as it ever was: does the value justify the cost of writing and maintaining the test? Both costs dropped enough that the answer is now almost always yes. A lot of teams haven't internalized that yet. At Coniferous, we've been building the tools to do this effectively, specifically on the Tauri side, where the existing E2E story is thin. We'll have more to share soon. If your team backed off E2E coverage because the math didn't work, the math has changed. Worth revisiting. :::tip[Work with us] We work fast at Coniferous, in part by noticing where the old methods no longer fit. If your team is figuring out what to keep, what to drop, and where the new math actually leads, we'd love to hear about it. ::: --- # How AI Coding Actually Works Section: Blog Source: https://coniferous.dev/blog/how-ai-coding-works When I first started building with AI, the results were all over the place. Some days the output was great: I'd describe a feature in a sentence or two and get back a clean, working implementation with the tests already wired up. Other days it did something head-scratching, like "fixing" a failing test by quietly deleting the assertion that was catching the bug. Same tool, same me, different results. What I've learned is that how you use these tools has a direct impact on the result. The models have gotten a lot better over the past year, and so has the way I work with them. I'm genuinely happy with my process now, though it still takes plenty of guidance, review, and spurts of frustration to get there. As with most things in software, getting the most out of these tools starts with understanding how they actually work. > # 👋 Welcome to my series: Make it Better. Make it Faster. > > This is Part I. It covers how these tools actually work and the levers you control when you use them. The rest of the series goes into what's actually changed about building software with AI, what "quality" really means, and the way I work day to day. > > I wrote it for anyone who builds software, whether you've got fifteen years in, just graduated from a CSCI program, or vibe-coded your way here. # The engine: an LLM An **LLM** (large language model) is the engine, "the AI." It's a model that predicts plausible text. An LLM doesn't look things up in a database of facts, it generates what should come next. That's why it can be fluent and confident and still completely wrong. That failure is called a **hallucination**: an answer that looks right and simply isn't. A function that doesn't exist, a library that was never written, a citation to nothing. On top of that, the model is tuned to be agreeable: shaped by human ratings, and since people prefer answers that agree with them, it leans toward telling you what you want to hear. :::note[Where does it learn all this?] An LLM is "trained" by reading an enormous pile of text: a huge slice of the public internet, books, articles, and a great deal of code. And that pile doesn't contain just the *good* code, but all of it: every elegant library and every copy-pasted Stack Overflow answer, every careful security check and every injection bug waiting to happen. It learned from the average, and the average sometimes isn't "good". It also only knows what was in its training data, up to a **cutoff date**, and usually has never seen *your* private codebase. So it can be confidently out of date, and because it completes patterns rather than looking things up, it can hand you something that *sounds* exactly right but isn't. ::: The model **is not deterministic.** Ask the exact same question twice and you can get two different answers. That's a feature when you want options, like a few different takes on the copy for a landing page or names for a new function. It's a problem when a task needs the same answer every time, like approving a payment or calculating a customer's invoice. # The agent An **agent** is what you get when you put an LLM in a loop and give it tools, so it can actually *do* things instead of just chatting back: pursue a goal, take an action, look at the result, and decide what to do next. For example, point one at a failing test and it'll search the codebase, open the files that look relevant, make an edit, rerun the test, and keep looping until it passes, with no further typing from you. Agentic just means working like that. When the model "calls a tool," it isn't running anything itself. It writes out a request, like *run `run_tests` with these arguments*, and something else runs it and hands back the result. That back-and-forth is **tool calling** (or function calling). # The harness: your console That loop doesn't run itself. A **harness** is the software around the model that makes it happen: it feeds the model context, takes the actions the model decides on and actually carries them out (reading and writing files, running commands, running your tests), and loops the results back in. The model decides; the harness acts. Put a model, a harness, and a goal together, and you have an agent. Claude Code, Codex, and Cursor are harnesses. :::note[What about Lovable, Bolt, and v0?] A whole category of hosted services lets you describe an app and watch it appear, with the code mostly kept out of sight. They bundle the model, a harness, and hosting behind one chat box, and they're a fast way to get from an idea to something running, which makes them great for prototypes (and a big part of what people mean by "vibe coding"). The tradeoff is that they hide the code it writes and the control to review and guide it. They also make the architectural calls for you. Choosing the tech stack, the database, and how the pieces fit together is a real part of engineering, and in the mainstream tools you largely don't get that choice. ::: The rest of this post goes through the levers you control. # Lever 1: the model you pick Every harness lets you pick which model runs, and the choice matters. Models trade power for speed and cost: big frontier ones (Claude Opus, GPT-5) reason the hardest but are the slowest and priciest, while small fast ones (Claude Haiku) cost a fraction as much and are perfectly good for simple or high-volume work. Models from different providers also have their own strengths: some are better at writing code, others at long stretches of reasoning, or at working across a lot of context. No single model tops every benchmark, and the rankings on leaderboards like [LMArena](https://lmarena.ai) and [SWE-bench](https://www.swebench.com) shift every few months, so the best one really depends on what you're doing. Some models also "think" first. **Reasoning models** work through a chain of intermediate steps before they answer, which makes them noticeably better at multi-step problems like debugging, at the cost of being slower. Often this is a dial rather than a separate model: Claude, for instance, lets you turn its reasoning up or down (*think*, *think harder*) on the same model, trading speed for depth. The lever isn't "always pick the biggest." It's matching the model to the task: a frontier reasoning model for planning the architecture or solving a subtle concurrency bug, a fast cheap one to rename a symbol across forty files. # Lever 2: the context you curate **Context** is everything the model can "see" right now in addition to its training data: your request, the files it's been given, the conversation so far. Think of it as the model's short-term working memory: finite, and mostly wiped clean between sessions. Context is measured in **tokens**: the chunks models read and write in, where one token is roughly ¾ of a word. The maximum a model can hold at once is its **context window**. Tokens are also the unit you're *billed* in. Every token going in and coming out costs a sliver of a cent, so a bloated context isn't just slower and easier to confuse; it also costs more. :::note[How much can it actually see?] | Model family | Window | | --- | --- | | Claude (Opus / Sonnet) | ~1M tokens (200K on Haiku and older models) | | OpenAI GPT-5 | ~400K tokens | | Google Gemini 2.5 Pro | ~1M tokens (up to 2M) | *(These numbers move every few months, so treat them as a snapshot.)* What's a million tokens? Roughly the entire *Lord of the Rings* trilogy, about 750,000 words, with room to spare. Even 200K, the smaller end, is around 150,000 words: a 500-page book. Sounds like plenty, until you point it at real code. This very website's front-end is about 30,000 tokens, which fits many times over. The *whole* repository, with the backend and config and docs, is around 300,000 tokens: comfortably inside a 1M window, but already past the 200K that smaller models cap out at. And that's a small marketing site. Most real production codebases run into the millions of tokens, past *every* window on the market. ::: Because the window is finite and the agent reasons only over what's actually in it, deciding what goes in is the lever you operate most often. You're pulling it every time you point the agent at a file, paste an error, or start a fresh session instead of letting a stale one drag on. The craft of doing it well even has a name, **context engineering**. The harness also helps you manage context. As a session fills the window, most will **auto-compact**, summarizing older turns so the work can continue instead of hitting the limit. That summary is lossy, though: a decision you made or a file the agent read early on can get dropped, so it sometimes forgets something it plainly "knew" a few minutes ago. # Lever 3: the memory you build Context evaporates between sessions, so anything you want the agent to know next time has to live somewhere durable. **Memory** is the catch-all term for that, making knowledge last after the context window has been wiped. The simplest form is also a lever you write by hand: a plain-markdown file you commit to the repo. Most coding agents read one at the start of every session holding your architecture notes, your conventions, your "always do this, never do that." Claude Code looks for a `CLAUDE.md`, and the open, cross-tool convention is `AGENTS.md`. Because it's just a versioned file sitting in git next to the code, it gets reviewed like everything else and is shared with everyone working in the codebase. :::note[The frontier of memory] Beyond the committed file, there's a lot of active research into richer memory: systems that persist your corrections and preferences across sessions, and that index a large codebase so the agent can fetch the right piece without reading everything. It's promising but unsettled, and the evidence sometimes points the other way: when Anthropic built Claude Code, plain agentic file search (grep and glob, no embeddings) beat their vector-retrieval pipeline so clearly they dropped the embeddings entirely. For now, a clear, well-named codebase plus a committed instructions file is still the highest-leverage memory you can give an agent. ::: # Lever 4: the agents and skills you build You can make your own tools, both to run a process the same way every time and to capture your own judgment and taste in something the whole team can reuse. There are two kinds, both increasingly standard across harnesses. The first is **custom agents** (often "subagents"): a specialized agent with a scoped job, its own instructions, and its own limited set of tools. Instead of asking one generalist to do everything, you define a reviewer that only reviews, a researcher that only gathers context, a test-writer that already knows how your suite is wired, and hand each the narrow job it's good at. The second is **skills** (you'll also see "commands" or "slash commands"): a reusable, packaged procedure the agent pulls in when it's relevant. The runbook for cutting a release, the steps to run and verify a database migration, the checklist you walk before opening a PR: written down once and invoked on demand instead of re-explained every session. In practice, both are usually just markdown files you commit alongside your code, versioned and reviewed like everything else. # It only acts on what it can see These levers all control the same thing: what the agent can see. And what it sees is never the full picture. First, the agent only ever sees a slice of your codebase, never the whole thing. It usually can't fit the whole repo in the window (you just saw the numbers), so the harness searches, greps, and opens the handful of files that look relevant, and the model reasons from those. That slice isn't even fixed: auto-compaction can quietly drop something the agent had a minute ago, so what it sees can shrink mid-task. And which files make it in depends entirely on how your code is organized and named: clear structure and honest names surface the right slice, while logic copied into five places or a function called `handleData` quietly steers it to the wrong one. (More on that in [the next piece](/blog/a-year-without-writing-code).) Second, as we said, the model is agreeable by default, a tendency with a name, **sycophancy**: it reflects your own context and phrasing back at you, your assumptions returned with confidence. And third, even with the right files in front of it, the model reaches for what's *plausible* over what's best for your app: trained on a vast average of the internet's code, it defaults to the common pattern and the popular library, and won't know your priorities unless you provide them. So, the agent sounds just as confident when it's wrong as when it's right, and it never has the full picture, only what you and your workflow give it. You can't take what it hands you at face value. You push back, you question its assumptions, and you question your own, since it will happily hand those right back to you too. And you build a workflow around it, one that feeds it the right context up front and checks what comes back, so good results are repeatable instead of luck. It's the same judgment good engineers have always brought to their craft. We'll pick up there in [the next post](/blog/a-year-without-writing-code): how my own workflow has changed to make the most of AI, and how it's impacted my day-to-day. # Other tech and concepts you'll hear about - **MCP (Model Context Protocol)**: a standard plug that lets any agent talk to any tool or data source. - **RAG (retrieval-augmented generation)**: the general pattern of fetching relevant material into context before answering. That grep-versus-embeddings story above is a RAG design choice. - **Fine-tuning**: further-training a base model on your own data. - **Prompt engineering / context engineering**: writing better inputs, and the broader craft of deciding what goes in the window. - **Temperature** and the other sampling knobs: how random the output is. *Next: [how my workflow actually changed](/blog/a-year-without-writing-code) — less typing, more guiding and reviewing, and a lot more work in parallel. (Part 2.)* :::tip[Work with us] We build software this way every day at Coniferous, and increasingly we help teams who want to too, including the ones digging out of a codebase that got away from them. If that's you, we'd genuinely like to hear about it. :::