<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://moak.dev/feed.xml</id>
  <title>Posts — Jordan Moak</title>
  <subtitle>Notes, takes, and deep dives on software, AI, and building things.</subtitle>
  <link rel="self" type="application/atom+xml" href="https://moak.dev/feed.xml"/>
  <link rel="alternate" type="text/html" href="https://moak.dev/posts"/>
  <updated>2026-09-07T00:00:00Z</updated>
  <author>
    <name>Jordan Moak</name>
    <uri>https://moak.dev</uri>
  </author>
  <entry>
    <id>https://moak.dev/posts/maintenance-by-construction</id>
    <title>The Work Should Compound</title>
    <link rel="alternate" type="text/html" href="https://moak.dev/posts/maintenance-by-construction"/>
    <published>2026-09-07T00:00:00Z</published>
    <updated>2026-09-07T00:00:00Z</updated>
    <summary>I believe developing a feature should also make the next change easier to automate. That's the self-healing system I want to build: software that needs less of me as I work on it.</summary>
    <category term="Thoughts"/>
    <category term="AI"/>
    <category term="Engineering"/>
    <content type="html">&lt;p&gt;I want to choose when an application needs me. Right now, a dependency I picked eight months ago gets a vote.&lt;/p&gt;
&lt;p&gt;That&amp;#x27;s part of owning software, but I don&amp;#x27;t believe it has to consume so much of our attention. AI has made the interruptions easier to deal with. I can get an assistant into the repository and work through the problem. What bothers me is how often I still have to get that process started, especially when we&amp;#x27;ve dealt with something similar before.&lt;/p&gt;
&lt;p&gt;Maybe we understood this integration perfectly well six months ago. We fixed the issue. We discussed why it happened. Somewhere in that work was enough knowledge to make the next occurrence much less dependent on me; apparently we didn&amp;#x27;t leave it in a form the system could use.&lt;/p&gt;
&lt;p&gt;Eventually, I want to hand over a meaningful piece of development and have the system carry it through. Each recurring maintenance task we can delegate gives us a concrete way to work toward that. The place to start is during development, while we&amp;#x27;re already doing the expensive work of understanding the application.&lt;/p&gt;
&lt;h2&gt;What else should a feature leave behind?&lt;/h2&gt;
&lt;p&gt;Consider an export feature that stops working for older accounts after a change to the data model. Someone investigates and discovers that a field didn&amp;#x27;t always exist.&lt;/p&gt;
&lt;p&gt;The fix might be two lines. Finding out which two lines could take an afternoon.&lt;/p&gt;
&lt;p&gt;When we resolve it, I&amp;#x27;d like us to ask what would let the system handle this situation without us next time. A representative old account that we can recreate in isolation would help. We could use it to check future migrations. We also need to preserve why the behavior matters: the customer should still be able to export their older records, even if supporting them is inconvenient for the current implementation.&lt;/p&gt;
&lt;p&gt;Writing that expectation in a document helps the next developer. Making the account reproducible, and requiring it to pass before a migration ships, changes what the development process can do.&lt;/p&gt;
&lt;p&gt;But the regression test is only part of it. The system needs to know when to run it and have somewhere to investigate a failure. It also needs a defined basis for accepting the resulting change. If I still have to connect those steps every time, I&amp;#x27;ve automated a check. I haven&amp;#x27;t automated the work.&lt;/p&gt;
&lt;p&gt;The ingredients are familiar. We already have regression tests and release pipelines. What&amp;#x27;s often missing is the connection that lets a detected need become completed work without somebody moving it between tools. I want us to build and verify that connection alongside the feature.&lt;/p&gt;
&lt;p&gt;I&amp;#x27;m calling the approach &lt;strong&gt;maintenance by construction&lt;/strong&gt;, with &lt;strong&gt;Tendie&lt;/strong&gt; as the working name for the system. Working on a feature should also improve our ability to hand over future work on it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://moak.dev/diagrams/maintenance-contract.svg&quot; alt=&quot;Development produces a working change and reusable knowledge. Later automated work uses that knowledge, with new findings feeding back into development.&quot; loading=&quot;lazy&quot;/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The old-account fixture becomes a condition future changes must satisfy, rather than an observation somebody needs to remember.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;We discover the requirements by building&lt;/h2&gt;
&lt;p&gt;I don&amp;#x27;t know everything a feature needs when I start implementing it. Building it is how I find out. Any serious approach to automated development has to account for that.&lt;/p&gt;
&lt;p&gt;Sometimes an apparent defect exposes a product question we never answered. Does an export include deleted records? The current code can tell us what happens today. It can&amp;#x27;t establish whether that behavior is what we want.&lt;/p&gt;
&lt;p&gt;If I decide deleted records must stay excluded, that decision needs to become an executable check. The next export change should encounter the rule without needing to find the conversation where we settled it.&lt;/p&gt;
&lt;p&gt;I can deliberately change that decision later. A repair agent shouldn&amp;#x27;t be able to change it because including deleted records makes its patch easier. Those changes need different authority, even if they eventually touch the same test.&lt;/p&gt;
&lt;p&gt;Protected doesn&amp;#x27;t mean permanent. We can improve our understanding as we build; the point is to make each decision usable by the next round of work.&lt;/p&gt;
&lt;h2&gt;What I want a maintenance run to do&lt;/h2&gt;
&lt;p&gt;Here&amp;#x27;s the intended behavior; we haven&amp;#x27;t built this complete loop yet.&lt;/p&gt;
&lt;p&gt;A dependency approaches the limit of our support policy. That creates an upgrade task without waiting for me to notice. The system prepares a candidate in a disposable environment, where the old-account export check fails.&lt;/p&gt;
&lt;p&gt;We know which behavior failed and can reproduce it. The cause still needs investigation, but the agent doesn&amp;#x27;t have to invent the expected result while debugging. The account we preserved during development gives it somewhere to work without experimenting on a customer&amp;#x27;s data.&lt;/p&gt;
&lt;p&gt;Suppose the proposed repair makes the check pass. That qualifies it for further evaluation, not immediate release. The other required checks still apply to the exact candidate we&amp;#x27;d deploy. The process also needs an appropriate recovery path if the change behaves differently under real conditions; rolling back code won&amp;#x27;t necessarily undo what it wrote to a database.&lt;/p&gt;
&lt;p&gt;If the candidate satisfies the release policy, the system deploys it and observes the result. If the checks remain inconclusive, it stops within its budget and brings back the unresolved problem. A decision to stop supporting older records would come to me. That&amp;#x27;s a change to what the product offers, and the upgrade task doesn&amp;#x27;t have permission to make it.&lt;/p&gt;
&lt;p&gt;The interesting part is how much of this run depends on work we&amp;#x27;ve already done. We aren&amp;#x27;t asking an agent to rediscover what an old account is or negotiate acceptable data loss. We&amp;#x27;re giving it a development task whose conditions have become specific enough to delegate.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://moak.dev/diagrams/maintenance-loop.svg&quot; alt=&quot;A maintenance task leads to an isolated candidate and protected verification. Qualified changes move through release and observation; rejected candidates stay out of production.&quot; loading=&quot;lazy&quot;/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The trigger starts the work. Protected acceptance criteria determine how far it may proceed.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is why I think the reproducible environment matters so much. It&amp;#x27;s useful while we&amp;#x27;re developing the feature, and it gives future automation a place to work. We can improve the same capability rather than build a second, approximate version of the application for an agent.&lt;/p&gt;
&lt;h2&gt;How much can we actually hand over?&lt;/h2&gt;
&lt;p&gt;My instinct is still that every meaningful requirement needs a way to exercise it. That&amp;#x27;s a substantial amount of work. I don&amp;#x27;t see how we get serious about automated development while treating validation as something we&amp;#x27;ll fill in later.&lt;/p&gt;
&lt;p&gt;An isolated end-to-end test won&amp;#x27;t be sufficient for every requirement. An export can finish successfully while exposing another customer&amp;#x27;s records. Features can also work on their own and interfere with each other. The checks have to address the reasons we&amp;#x27;d reject a change, including behavior beyond the path that first failed.&lt;/p&gt;
&lt;p&gt;For an existing app, I&amp;#x27;d start with the workflows that matter most. When I&amp;#x27;m already investigating a problem, that&amp;#x27;s a good moment to capture its conditions. When a feature is under development, I&amp;#x27;m available to answer questions about what it should do. We should use that access well; I don&amp;#x27;t expect an assistant arriving months later to recover a decision I never recorded.&lt;/p&gt;
&lt;p&gt;As coverage improves, particular classes of work can qualify for automation. A routine dependency update may qualify before a database migration does. That distinction should be explainable in terms of the evidence and consequences. Success with one kind of change doesn&amp;#x27;t grant authority over another.&lt;/p&gt;
&lt;p&gt;Sometimes the best contribution to future automation will be removing a dependency or simplifying the feature. I don&amp;#x27;t want to preserve a complicated process just because we&amp;#x27;ve taught an agent to operate it.&lt;/p&gt;
&lt;p&gt;The routine machinery should stay small too. Checking a support deadline doesn&amp;#x27;t need an AI conversation. An unfamiliar failure may deserve substantial reasoning. In &lt;a href=&quot;https://moak.dev/posts/intelligence-budget&quot;&gt;What the Next Change Shouldn&amp;#x27;t Have to Know&lt;/a&gt;, I explore how architecture can reduce the amount of reasoning future work requires by taking over obligations we&amp;#x27;d otherwise have to keep rediscovering.&lt;/p&gt;
&lt;p&gt;Put a limit on that effort too. A system that spends all night producing unsuccessful patches owes me a useful account of the unresolved problem. It hasn&amp;#x27;t earned credit for being busy.&lt;/p&gt;
&lt;p&gt;There will be uncovered behavior, and the system needs to say so. Every check passing only tells us about the checks we have. It doesn&amp;#x27;t eliminate the possibility that our understanding is wrong. My argument for &lt;a href=&quot;https://moak.dev/posts/the-underestimated-raw-power-of-ai-as-a-resource&quot;&gt;treating AI as a resource&lt;/a&gt; depends on how we arrange imperfect components; the validation needs that same scrutiny. A simulator can agree with an implementation because they share a bad assumption.&lt;/p&gt;
&lt;h2&gt;Jerbs is where I&amp;#x27;m starting&lt;/h2&gt;
&lt;p&gt;Jerbs observes public company career sources and preserves changes in job postings. It gives us a useful test: can the system adapt to a changed source without inventing changes in the company we&amp;#x27;re observing?&lt;/p&gt;
&lt;p&gt;A source returning incomplete data shouldn&amp;#x27;t become evidence that a company stopped hiring. If we get that wrong, every subsequent explanation is reasoning from a collection defect.&lt;/p&gt;
&lt;p&gt;Jerbs already preserves the source evidence so we can revisit the interpretation. We&amp;#x27;ve started strengthening the checks a proposed parser repair must satisfy. The autonomous repair loop is still ahead of us; the next experiment needs to demonstrate an incorrect repair being refused as well as a correct one being accepted.&lt;/p&gt;
&lt;p&gt;I want to carry this approach across my stack, but the common machinery has to earn its generality through different applications. Otherwise we could build something excellent at parser repair and spend the next year forcing everything else into it.&lt;/p&gt;
&lt;h2&gt;Buy back the next afternoon&lt;/h2&gt;
&lt;p&gt;I believe the real opportunity is to make development progressively less dependent on our presence.&lt;/p&gt;
&lt;p&gt;I still want to choose what my applications do. But once I&amp;#x27;ve made a decision, I want the system to become better at completing work within it. If an external change makes that decision insufficient, bring me the actual question. Don&amp;#x27;t make me reconstruct the entire repository to find out what you&amp;#x27;re asking.&lt;/p&gt;
&lt;p&gt;The measure has to include the cost of maintaining Tendie itself. Moving the interruptions into a more elaborate system won&amp;#x27;t buy back any time. I want to see a class of work that used to need my attention become something the application can handle, with enough evidence that I can reasonably leave it alone.&lt;/p&gt;
&lt;p&gt;If I fix the same class of problem faster next month, that&amp;#x27;s useful. If the system can handle it without me next month, the development work has changed what owning the application costs.&lt;/p&gt;
&lt;p&gt;I believe we should build for that second outcome much more deliberately.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://moak.dev/posts/intelligence-budget</id>
    <title>What the Next Change Shouldn't Have to Know</title>
    <link rel="alternate" type="text/html" href="https://moak.dev/posts/intelligence-budget"/>
    <published>2026-06-26T00:00:00Z</published>
    <updated>2026-09-07T00:00:00Z</updated>
    <summary>I believe we should spend intelligence on changing how much the next decision requires. Good architecture can make some uncertainty safe to leave unresolved.</summary>
    <category term="AI"/>
    <category term="Engineering"/>
    <category term="Architecture"/>
    <content type="html">&lt;p&gt;I don&amp;#x27;t want to keep paying for somebody to understand the same problem.&lt;/p&gt;
&lt;p&gt;Sometimes that somebody is me. Increasingly, it&amp;#x27;s an AI assistant working through a repository until it has enough context to make a change. The investigation may be excellent. But if the next change needs the same investigation, I think we should look at what the first one left behind.&lt;/p&gt;
&lt;p&gt;Documentation helps. A regression test can preserve a failure we&amp;#x27;d otherwise have to rediscover. There&amp;#x27;s another possibility I want us to take seriously: changing the architecture so future work no longer depends on resolving the same question.&lt;/p&gt;
&lt;p&gt;I believe that&amp;#x27;s one of the better uses of increasingly capable reasoning. Use it to reduce how much somebody has to get right next time.&lt;/p&gt;
&lt;h2&gt;An answer we can stop needing&lt;/h2&gt;
&lt;p&gt;An application asks another service to create a resource. The request times out.&lt;/p&gt;
&lt;p&gt;We don&amp;#x27;t know whether the service failed before creating it or created it and lost the response. Retrying might create a duplicate. Investigating might require access to the other service, and its state could change while we look.&lt;/p&gt;
&lt;p&gt;Now give the operation a durable identity and have the receiving service enforce an idempotency contract: repeating that operation produces no additional effect within the contract&amp;#x27;s scope. The caller can retry with the same identity.&lt;/p&gt;
&lt;p&gt;We still don&amp;#x27;t know what happened. We can proceed anyway.&lt;/p&gt;
&lt;p&gt;AWS describes this tradeoff in &lt;a href=&quot;https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/&quot;&gt;Making retries safe with idempotent APIs&lt;/a&gt;. The service takes on complexity so its callers can safely do less. Recording the request identity and performing the mutation must work together; otherwise a crash between them recreates the ambiguity. The contract also needs limits, including how long a repeated request remains recognizable.&lt;/p&gt;
&lt;p&gt;The difficult work hasn&amp;#x27;t evaporated. We&amp;#x27;ve put it somewhere that can handle it for every caller using that contract.&lt;/p&gt;
&lt;p&gt;That&amp;#x27;s a more useful result than teaching every caller to investigate a timeout. A whole set of possible histories now permits the same next action. Recovery needs less information.&lt;/p&gt;
&lt;h2&gt;Context efficiency has to be earned&lt;/h2&gt;
&lt;p&gt;I think this is where architecture belongs in the conversation about AI context.&lt;/p&gt;
&lt;p&gt;A smaller file doesn&amp;#x27;t necessarily make a change easier to reason about. If its behavior depends on an ordering assumption buried elsewhere, somebody still needs to find that assumption. A concise summary might mention it, but now correctness depends on the summary staying accurate and the next developer following it.&lt;/p&gt;
&lt;p&gt;An architectural boundary can go further. It can take responsibility for the ordering so callers don&amp;#x27;t have to manage it. That responsibility must be real: if callers can bypass the boundary through an ordinary code path, the obligation still belongs to them too.&lt;/p&gt;
&lt;p&gt;Parnas made the underlying distinction in his &lt;a href=&quot;https://www.cs.lafayette.edu/~gexia/cs301/resources/parnas.html&quot;&gt;1972 paper on modularity&lt;/a&gt;. He organized modules around design decisions they concealed, rather than simply dividing the program into processing steps. The point was to contain the consequences of those decisions, including their eventual replacement.&lt;/p&gt;
&lt;p&gt;For our purposes, I&amp;#x27;d judge a boundary by what someone can safely leave out of their reasoning. The caller of our idempotent operation still needs to understand request identity and expiry. It shouldn&amp;#x27;t need to reconstruct the service&amp;#x27;s crash history.&lt;/p&gt;
&lt;p&gt;That is context efficiency I can believe in. The smaller context follows from fewer responsibilities.&lt;/p&gt;
&lt;h2&gt;Give uncertain work less authority&lt;/h2&gt;
&lt;p&gt;This becomes particularly useful when I think about software maintaining itself.&lt;/p&gt;
&lt;p&gt;In Jerbs, which observes public job postings, a parser may need to change when a company changes its careers page. Suppose a proposed parser produces fewer postings than the previous version. That could reflect actual removals. It could also mean the parser missed half the page.&lt;/p&gt;
&lt;p&gt;If parser output directly determines which postings disappear, accepting that change requires confidence in both extraction and the meaning of every absence. We&amp;#x27;ve given uncertain interpretation considerable authority.&lt;/p&gt;
&lt;p&gt;I want the design to separate those responsibilities. A parser can propose observations from preserved source evidence. Removing a posting should require a separate basis for treating the collection as complete and authoritative. A successful parse alone doesn&amp;#x27;t establish either.&lt;/p&gt;
&lt;p&gt;That does not solve the completeness problem. It gives it a place to be handled explicitly, where every parser must encounter the same rule. Matching a source&amp;#x27;s reported count might be one check; it wouldn&amp;#x27;t establish that the source itself told the truth.&lt;/p&gt;
&lt;p&gt;The architectural gain is specific: an omission in a parser need not automatically become a deletion in the product. A future repair can have narrower authority, with checks aimed at the consequences it can actually cause. This is the direction we&amp;#x27;re working toward in Jerbs, not a claim that its autonomous maintenance is finished.&lt;/p&gt;
&lt;p&gt;I believe that matters more than how impressively an agent can explain a proposed patch. We&amp;#x27;ve changed what a mistaken patch is allowed to do.&lt;/p&gt;
&lt;h2&gt;The boundary has a cost&lt;/h2&gt;
&lt;p&gt;It&amp;#x27;s possible to take this too far. A framework can centralize every decision until changing anything requires understanding the framework.&lt;/p&gt;
&lt;p&gt;I&amp;#x27;d look for obligations that recur and are stable enough to enforce. Preventing a retry from duplicating the same operation is a good candidate. Deciding whether a customer should be allowed to request a second resource is a product choice. The operation&amp;#x27;s identity needs to preserve that distinction.&lt;/p&gt;
&lt;p&gt;Enforcement also concentrates trust. A defect in a shared boundary can affect all its callers, so that boundary deserves focused verification. We should count its maintenance cost against the work it saves. Another abstraction earns nothing merely by existing.&lt;/p&gt;
&lt;p&gt;And some questions still belong to the complete application. The &lt;a href=&quot;https://www.cs.cmu.edu/afs/cs.cmu.edu/academic/class/15712-s12/www/papers/saltzer84.pdf&quot;&gt;end-to-end argument&lt;/a&gt; explains why reliability inside a lower layer cannot necessarily establish the outcome the application needs. Avoiding duplicate resource creation doesn&amp;#x27;t tell us whether we created the right resource for the right customer.&lt;/p&gt;
&lt;p&gt;That&amp;#x27;s why I still want validation tied to meaningful requirements. Structural guarantees can remove specific reasons for a change to fail. They don&amp;#x27;t establish that the change is worth making, or that we&amp;#x27;ve captured everything users depend on.&lt;/p&gt;
&lt;h2&gt;What the investigation should leave behind&lt;/h2&gt;
&lt;p&gt;The practical opportunity appears while we&amp;#x27;re doing difficult work. When an investigation exposes something we keep having to remember, ask why each caller or change needs to remember it independently.&lt;/p&gt;
&lt;p&gt;Sometimes a test is enough. Sometimes the right answer is to remove a capability that shouldn&amp;#x27;t have been exposed. If a dependable operation can take over the obligation, future work gets to rely on it. We should be able to name the reasoning that is no longer required, and show where the responsibility went.&lt;/p&gt;
&lt;p&gt;That&amp;#x27;s the architectural contribution to &lt;a href=&quot;https://moak.dev/posts/maintenance-by-construction&quot;&gt;The Work Should Compound&lt;/a&gt;. An automated maintenance process needs work it can safely attempt. Development can create more of that work by making the consequences of a change easier to contain and evaluate.&lt;/p&gt;
&lt;p&gt;I want to use capable AI to investigate difficult designs and help build those boundaries. But I don&amp;#x27;t want its ability to navigate a complicated system to become our excuse for leaving the complication there.&lt;/p&gt;
&lt;p&gt;The next change should inherit more than an explanation of why this was hard. It should have less of that difficulty to deal with.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://moak.dev/posts/the-underestimated-raw-power-of-ai-as-a-resource</id>
    <title>The Underestimated Power of AI as a Resource</title>
    <link rel="alternate" type="text/html" href="https://moak.dev/posts/the-underestimated-raw-power-of-ai-as-a-resource"/>
    <published>2026-03-02T00:00:00Z</published>
    <updated>2026-09-07T00:00:00Z</updated>
    <summary>I believe we're underestimating what AI lets us spend on verification. A second implementation won't buy certainty, but it can give a mistake somewhere else to show up.</summary>
    <category term="Thoughts"/>
    <category term="Takes"/>
    <category term="AI"/>
    <category term="Engineering"/>
    <content type="html">&lt;p&gt;At Roche, I worked on a floating-point precision defect that had taken down a production line doing millions of dollars in daily output. Finding it meant methodical verification work. The code had already made it through the process intended to catch problems like it.&lt;/p&gt;
&lt;p&gt;A separate implementation of that calculation might have caught it. What interests me now is whether that additional check would be affordable enough to try before production makes the decision for us.&lt;/p&gt;
&lt;p&gt;I believe AI makes that question worth revisiting. We can spend reasoning on another attempt at the problem, even when that attempt will never become production code. Its job is to challenge the first one.&lt;/p&gt;
&lt;h2&gt;What would we build if the second version were affordable?&lt;/h2&gt;
&lt;p&gt;For a consequential calculation, I&amp;#x27;d consider having an agent build a separate implementation from the requirements, without showing it the existing code. Then I&amp;#x27;d compare the results on inputs chosen to stress the calculation.&lt;/p&gt;
&lt;p&gt;If the results differ beyond the tolerance the application allows, we have a concrete case to investigate. That doesn&amp;#x27;t tell us which version is right. It gives us somewhere to look.&lt;/p&gt;
&lt;p&gt;The comparison needs design too. Exact equality may be inappropriate for a floating-point result; an overly generous tolerance may hide the defect we&amp;#x27;re trying to find. What the application can accept has to come from its requirements, not whichever tolerance gets the implementations to agree.&lt;/p&gt;
&lt;p&gt;This is what I mean by treating reasoning as a resource. We decide where an additional attempt could help, and what would make its output useful. The allocation follows the problem. A fixed number of agents assigned to every task would miss the point.&lt;/p&gt;
&lt;h2&gt;A reviewer inherits a frame&lt;/h2&gt;
&lt;p&gt;A reviewer reads your code inside the frame your code creates. An independent implementation never saw your frame.&lt;/p&gt;
&lt;p&gt;Starting from the requirement gives the second attempt a chance to make a different mistake, and gives us something to compare. Review still contributes something the comparison can&amp;#x27;t: questioning whether the requirement itself makes sense for the customer.&lt;/p&gt;
&lt;p&gt;The second implementation can also be slower or less elegant than the production one. A straightforward reference calculation might be easier to inspect than the optimized code it checks. We can choose to make it simple because serving production traffic isn&amp;#x27;t its job.&lt;/p&gt;
&lt;p&gt;I think we overlook how much engineering is worth doing purely to check other engineering.&lt;/p&gt;
&lt;h2&gt;Independent work can still fail together&lt;/h2&gt;
&lt;p&gt;In their &lt;a href=&quot;https://doi.org/10.1109/TSE.1986.6312924&quot;&gt;1986 experiment on multiversion programming&lt;/a&gt;, John Knight and Nancy Leveson tested 27 independently developed programs against a million test cases. Multiple programs failed together more often than the independence assumption predicted. Separate development did not establish independent failures.&lt;/p&gt;
&lt;p&gt;That study concerned human-written programs, not today&amp;#x27;s models. It doesn&amp;#x27;t measure the benefit of an agent workflow, but it exposes the assumption we&amp;#x27;d be making if we treated separately produced answers as independent evidence by default.&lt;/p&gt;
&lt;p&gt;For agents, I&amp;#x27;d look at what the attempts share. If each receives the same suggested approach, we&amp;#x27;ve already influenced where they might go wrong. Keeping the existing code out of view avoids copying it; it doesn&amp;#x27;t prevent both attempts from misunderstanding the requirement.&lt;/p&gt;
&lt;p&gt;Different models or methods might expose different mistakes. We need to evaluate that benefit. Changing a model name doesn&amp;#x27;t establish it, and an alternative that uses the same underlying library may reproduce a library defect exactly.&lt;/p&gt;
&lt;p&gt;When implementations disagree, I&amp;#x27;d preserve the input and investigate. A majority vote could simply outnumber the correct result. The useful outcome is an explanation of the difference, with enough evidence to decide what needs changing.&lt;/p&gt;
&lt;h2&gt;Spend the effort where it changes the result&lt;/h2&gt;
&lt;p&gt;I&amp;#x27;d start with a bounded calculation whose output matters. We can make its acceptance criteria specific and inspect the comparison. Reimplementing an entire application is a different commitment; generating more code doesn&amp;#x27;t make maintaining it free.&lt;/p&gt;
&lt;p&gt;Before trusting the comparison with unknown defects, I&amp;#x27;d test it against known ones and deliberately introduced mistakes. Does it catch anything our existing checks miss? How much time do we spend investigating differences that don&amp;#x27;t matter?&lt;/p&gt;
&lt;p&gt;Another answer can be cheap. Establishing whether it helps is the expense.&lt;/p&gt;
&lt;p&gt;That includes my attention. A reference implementation that constantly produces harmless disagreements may create more work than it saves. I want to know whether the extra verification earns its cost, not whether the process looks thorough.&lt;/p&gt;
&lt;p&gt;AI gives us a way to try additional verification without committing a second engineering team. Whether the comparison catches enough consequential errors to justify its upkeep is something we still have to establish.&lt;/p&gt;
&lt;h2&gt;Keep what the investigation teaches us&lt;/h2&gt;
&lt;p&gt;When a comparison exposes a defect, keep the input that exposed it. Future changes should face that check without requiring another investigation. That&amp;#x27;s the connection to &lt;a href=&quot;https://moak.dev/posts/maintenance-by-construction&quot;&gt;The Work Should Compound&lt;/a&gt;: the reasoning we buy today should reduce what we need to rediscover tomorrow.&lt;/p&gt;
&lt;p&gt;Sometimes the investigation also exposes an obligation the architecture could enforce. I explore that next step in &lt;a href=&quot;https://moak.dev/posts/intelligence-budget&quot;&gt;What the Next Change Shouldn&amp;#x27;t Have to Know&lt;/a&gt;: reducing what future work has to reason through in the first place.&lt;/p&gt;
&lt;p&gt;I believe we&amp;#x27;re underestimating how much useful engineering we can now afford to do purely to check our work.&lt;/p&gt;
&lt;p&gt;The second version doesn&amp;#x27;t have to ship to be worth building.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://moak.dev/posts/building-rosavera-on-openclaw</id>
    <title>Building rosavera on OpenClaw</title>
    <link rel="alternate" type="text/html" href="https://moak.dev/posts/building-rosavera-on-openclaw"/>
    <published>2026-03-02T00:00:00Z</published>
    <updated>2026-09-07T00:00:00Z</updated>
    <summary>In rosavera, the people in a conversation determine which memory vaults it can search and where its transcript belongs. One small rule connects the system.</summary>
    <category term="Deep Dives"/>
    <category term="Agents"/>
    <category term="OpenClaw"/>
    <category term="Memory Systems"/>
    <content type="html">&lt;p&gt;rosavera is a personal assistant I run on OpenClaw. It helps maintain my code projects and gives me somewhere to try agent ideas in daily use. I want it to remember our work, including when the conversation moves between a private message and a group.&lt;/p&gt;
&lt;p&gt;But putting the assistant in a group shouldn&amp;#x27;t make my private conversations available to everyone there. I don&amp;#x27;t want it deciding what to withhold after it&amp;#x27;s already retrieved the material.&lt;/p&gt;
&lt;p&gt;The memory system starts with who&amp;#x27;s in the conversation. That set determines which vaults retrieval can search, and it gives the transcript a place to go afterward. I believe the simplicity of that connection is the part worth explaining.&lt;/p&gt;
&lt;h2&gt;Who&amp;#x27;s in the room?&lt;/h2&gt;
&lt;p&gt;Messages arrive with metadata about the sender and conversation surface. The sessioning layer uses that context to build a participant set. A private conversation with me is &lt;code&gt;{jordan}&lt;/code&gt;. A conversation with Sarah and me is &lt;code&gt;{jordan, sarah}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Vaults use the same sets. Their directory names are sorted so the same participants produce the same key: &lt;code&gt;jordan/&lt;/code&gt;, &lt;code&gt;sarah/&lt;/code&gt;, or &lt;code&gt;jordan+sarah/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For retrieval, the rule is set containment: every participant in the current session must belong to the vault&amp;#x27;s participant set.&lt;/p&gt;
&lt;p&gt;With those three vaults, access looks like this:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Current conversation&lt;/th&gt;&lt;th&gt;Vaults it can search&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Me, privately&lt;/td&gt;&lt;td&gt;&lt;code&gt;jordan/&lt;/code&gt; and &lt;code&gt;jordan+sarah/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sarah, privately&lt;/td&gt;&lt;td&gt;&lt;code&gt;sarah/&lt;/code&gt; and &lt;code&gt;jordan+sarah/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sarah and me together&lt;/td&gt;&lt;td&gt;&lt;code&gt;jordan+sarah/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;I can recall our shared conversation in private. Bringing Sarah into a conversation excludes my private vault from retrieval. The rule also extends to larger sets: a vault shared with Sarah, me, and another person meets the containment condition for a session with Sarah and me.&lt;/p&gt;
&lt;p&gt;Writing has a narrower destination. A session&amp;#x27;s transcript goes to the vault matching its participant set exactly. Being able to read a shared vault doesn&amp;#x27;t mean a private conversation gets written into it.&lt;/p&gt;
&lt;h2&gt;Put the rule before retrieval&lt;/h2&gt;
&lt;p&gt;Each indexed chunk carries a &lt;code&gt;vault_id&lt;/code&gt;. Retrieval begins with the vaults the session is allowed to search, so excluded vaults contribute no results through that path.&lt;/p&gt;
&lt;p&gt;That&amp;#x27;s the design choice I care about. The assistant doesn&amp;#x27;t need to inspect a private result and judge whether to reveal it. The retrieval boundary handles that decision.&lt;/p&gt;
&lt;p&gt;It also gives me something specific to inspect when debugging access: the session&amp;#x27;s participant set and the vaults selected for its search. Correctly identifying the participants still matters; the containment rule depends on that input.&lt;/p&gt;
&lt;p&gt;When a session ends, its transcript is chunked and embedded locally through Ollama, with SQLite and sqlite-vec holding the index. For private vaults, the database stores references to the source text rather than the transcript text itself. The source files are encrypted on disk and decrypted when needed for retrieval.&lt;/p&gt;
&lt;p&gt;A separate topic store makes sessions discoverable by subject. Private sessions contribute metadata there by default, without copying their transcript text into that store.&lt;/p&gt;
&lt;h2&gt;A heartbeat isn&amp;#x27;t evidence I was away&lt;/h2&gt;
&lt;p&gt;One correction came directly from using it. Early archives described days with only heartbeat polls as &amp;quot;quiet day, Jordan wasn&amp;#x27;t around.&amp;quot;&lt;/p&gt;
&lt;p&gt;Wrong. A heartbeat tells us the machinery ran. It says nothing about whether I was present.&lt;/p&gt;
&lt;p&gt;The session notes now distinguish presence from infrastructure activity, with &amp;quot;quiet&amp;quot; specifically meaning only the machinery ran. If the agent misreads silence, it remembers a false version of your life.&lt;/p&gt;
&lt;p&gt;That needed a correction to what the archive recorded. The vault routing could work exactly as intended and still preserve a bad interpretation.&lt;/p&gt;
&lt;h2&gt;What I&amp;#x27;d keep&lt;/h2&gt;
&lt;p&gt;I still haven&amp;#x27;t built the planned recall probe: roughly twenty questions with known answers in memory, checked against what retrieval returns. Retrieval feels better, but I don&amp;#x27;t have that measurement yet.&lt;/p&gt;
&lt;p&gt;The design I would carry forward is the connection between the conversation and its memory. The same participant set determines what retrieval can access and where the new transcript belongs. I can follow that rule through the system without reconstructing a separate policy for each conversation surface.&lt;/p&gt;
&lt;p&gt;That&amp;#x27;s why I&amp;#x27;d build this part again. It solves a problem I have in daily use, and it&amp;#x27;s small enough that I can explain how it works.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://moak.dev/posts/ai-wont-replace-engineers</id>
    <title>The New Layer of Work Showing Up Underneath AI</title>
    <link rel="alternate" type="text/html" href="https://moak.dev/posts/ai-wont-replace-engineers"/>
    <published>2026-02-25T00:00:00Z</published>
    <updated>2026-02-25T00:00:00Z</updated>
    <summary>Not a replacement story. A story about what becomes worth building once code is cheap and fast to get right.</summary>
    <category term="Takes"/>
    <category term="AI"/>
    <content type="html">&lt;h1&gt;The New Layer of Work Showing Up Underneath AI&lt;/h1&gt;
&lt;p&gt;I don&amp;#x27;t feel strongly that AI is coming for anyone&amp;#x27;s job. What I keep noticing instead is smaller and stranger: a layer of work that didn&amp;#x27;t used to make sense to do is starting to make sense.&lt;/p&gt;
&lt;p&gt;Calculators didn&amp;#x27;t kill mathematicians, they just moved the job upstream. Excel didn&amp;#x27;t kill accountants, it moved the job toward judgment. Every tool shift in this space seems to work the same way: it doesn&amp;#x27;t erase the work, it relocates it. LLMs and coding agents look like the same pattern, just wider.&lt;/p&gt;
&lt;h2&gt;What&amp;#x27;s Actually New&lt;/h2&gt;
&lt;p&gt;The interesting part isn&amp;#x27;t the debate about who gets replaced. It&amp;#x27;s that a working script, a first-draft tool, or a small reliable slice of code is now available quickly and cheaply enough that it changes what&amp;#x27;s worth attempting at all.&lt;/p&gt;
&lt;p&gt;Things that used to be &amp;quot;not worth the setup cost&amp;quot; now take minutes instead of days: a one-off script to check an edge case, a scraper for a single afternoon&amp;#x27;s question, a tiny internal tool nobody would&amp;#x27;ve greenlit a sprint for. That&amp;#x27;s not a productivity bump so much as a threshold crossing. Below a certain cost, curiosity turns into a working prototype instead of staying a hunch.&lt;/p&gt;
&lt;p&gt;And that abundance stacks. Once the small stuff is basically free, you start spending the saved time exploring niches you wouldn&amp;#x27;t have bothered with — and then niches of those niches. Not because the tool is smart, but because it removed the tax on finding out.&lt;/p&gt;
&lt;h2&gt;Where This Goes&lt;/h2&gt;
&lt;p&gt;I don&amp;#x27;t know yet. That&amp;#x27;s the honest answer, and it&amp;#x27;s the part I find genuinely interesting rather than alarming. Some of what falls into scope this way will be trivial. Some of it will be knowledge nobody was deliberately pursuing, just a byproduct of enough people poking at enough small, cheap questions.&lt;/p&gt;
&lt;p&gt;The skill that seems to matter more, not less, is knowing which of the fast answers is actually right for your situation. That part hasn&amp;#x27;t gotten automated. If anything it&amp;#x27;s the bottleneck now, since producing an answer stopped being the hard part.&lt;/p&gt;
&lt;h2&gt;Worth Trying&lt;/h2&gt;
&lt;p&gt;Pick something you&amp;#x27;ve been curious about but never justified spending real time on: a tiny tool, a check, a &amp;quot;what if.&amp;quot; Ask an agent for a first pass. Not because it&amp;#x27;ll be perfect, but because seeing a rough working version fast tells you whether the idea was worth pursuing in the first place.&lt;/p&gt;
&lt;p&gt;Most of what I&amp;#x27;ve learned about this shift didn&amp;#x27;t come from reading takes on it. It came from noticing what I started building once the cost of starting dropped.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://moak.dev/posts/building-time-aware-mcp</id>
    <title>Building a Time-Aware MCP Server</title>
    <link rel="alternate" type="text/html" href="https://moak.dev/posts/building-time-aware-mcp"/>
    <published>2026-02-25T00:00:00Z</published>
    <updated>2026-02-25T00:00:00Z</updated>
    <summary>chrono-mcp gives agents timezone-correct temporal reasoning, and why the naive version doesn't survive contact with the IANA database.</summary>
    <category term="Deep Dives"/>
    <category term="MCP"/>
    <category term="TypeScript"/>
    <content type="html">&lt;h1&gt;Building a Time-Aware MCP Server&lt;/h1&gt;
&lt;p&gt;LLMs are bad at time. Ask one for today&amp;#x27;s date and you get a plausible guess anchored to its training cutoff, not a fact. For a chat window that&amp;#x27;s a shrug. For an agent scheduling something, computing a duration, or deciding whether a token has expired, a plausible wrong date is a bug wearing a confident voice.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chrono-mcp&lt;/code&gt; fixes that by giving agents a real time source over MCP instead of asking them to reason about it.&lt;/p&gt;
&lt;h2&gt;The Naive Version Doesn&amp;#x27;t Survive Contact&lt;/h2&gt;
&lt;p&gt;The first pass looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export async function handleGetCurrentTime(): Promise&amp;lt;TimeResponse&amp;gt; {
  return {
    iso: new Date().toISOString(),
    unix: Date.now(),
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    formatted: {
      short: new Date().toLocaleDateString(),
      long: new Date().toLocaleDateString(&amp;#x27;en-US&amp;#x27;, {
        weekday: &amp;#x27;long&amp;#x27;, year: &amp;#x27;numeric&amp;#x27;, month: &amp;#x27;long&amp;#x27;, day: &amp;#x27;numeric&amp;#x27;
      })
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works, right up until you need real timezone math: DST transitions, historical offset changes, or converting a time for a user who isn&amp;#x27;t in the same zone as the process. Native &lt;code&gt;Date&lt;/code&gt; treats timezone as a display option: you can format into a zone, but arithmetic across zones is landmines the whole way down.&lt;/p&gt;
&lt;p&gt;The production version runs on Luxon instead, which models a &lt;code&gt;DateTime&lt;/code&gt; as zone-aware from construction. That one decision is the difference between &amp;quot;handles New York to Tokyo&amp;quot; and &amp;quot;handles New York to Tokyo correctly, across a DST boundary, in November.&amp;quot;&lt;/p&gt;
&lt;h2&gt;What&amp;#x27;s Actually in There&lt;/h2&gt;
&lt;p&gt;The published server (&lt;code&gt;@jmoak/chrono-mcp&lt;/code&gt; on npm) does more than tell time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Timezone conversion&lt;/strong&gt; across all IANA zones, not just the handful anyone remembers exist&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duration math&lt;/strong&gt;: add, subtract, diff, and a calendar-aware &lt;code&gt;duration_between&lt;/code&gt;, because &amp;quot;72 hours&amp;quot; and &amp;quot;3 days&amp;quot; stop agreeing the moment DST is involved&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch operations&lt;/strong&gt;: sort or compute stats across a list of timestamps, with modes for one-to-many and many-to-many comparisons&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zod-validated inputs&lt;/strong&gt;, so a malformed request fails with a clear error instead of a confusing one three calls downstream&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Responses are shaped by what&amp;#x27;s actually asked for: a bare timestamp gets a minimal reply, a multi-timezone request gets a fuller one. That&amp;#x27;s a deliberate token optimization: an agent pays for every field in the response, so the schema shouldn&amp;#x27;t be fatter than the question.&lt;/p&gt;
&lt;h2&gt;What I&amp;#x27;d Still Change&lt;/h2&gt;
&lt;p&gt;Error handling is solid for malformed input but hasn&amp;#x27;t been stress-tested against genuinely adversarial timestamps — leap seconds, dates that predate reliable IANA data. The surface is small enough that this hasn&amp;#x27;t bitten anyone yet, but &amp;quot;hasn&amp;#x27;t bitten anyone yet&amp;quot; isn&amp;#x27;t the same claim as &amp;quot;correct.&amp;quot;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;If your agent needs to know what day it is, it shouldn&amp;#x27;t be guessing.&lt;/em&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://moak.dev/posts/case-for-small-tools</id>
    <title>The Case for Small Tools</title>
    <link rel="alternate" type="text/html" href="https://moak.dev/posts/case-for-small-tools"/>
    <published>2026-02-25T00:00:00Z</published>
    <updated>2026-02-25T00:00:00Z</updated>
    <summary>Small, focused tools beat frameworks that try to do everything. The MCP ecosystem is a live example.</summary>
    <category term="Thoughts"/>
    <category term="Software"/>
    <content type="html">&lt;h1&gt;The Case for Small Tools&lt;/h1&gt;
&lt;p&gt;A tool that does one thing well is easy to trust. A framework that does forty things is easy to get lost in, and eventually easy to blame for a bug that&amp;#x27;s actually three layers away from where you&amp;#x27;re looking.&lt;/p&gt;
&lt;h2&gt;Where the Weight Comes From&lt;/h2&gt;
&lt;p&gt;Frameworks sell &amp;quot;batteries included,&amp;quot; but batteries have mass. Every bundled feature is a dependency you didn&amp;#x27;t choose, a version you now track, and surface area an attacker doesn&amp;#x27;t care whether you use. The cost isn&amp;#x27;t hypothetical: it&amp;#x27;s &lt;code&gt;npm audit&lt;/code&gt; warnings for code paths you never call, and upgrade breakage in modules you didn&amp;#x27;t know you depended on.&lt;/p&gt;
&lt;h2&gt;A Concrete Comparison&lt;/h2&gt;
&lt;p&gt;I&amp;#x27;ve built three MCP servers at different points on this spectrum: two bare templates with a single echo tool, meant purely to learn the protocol, and &lt;code&gt;chrono-mcp&lt;/code&gt;, a real production server with two tools, Zod-validated inputs, Luxon for timezone math, test coverage, and an npm release.&lt;/p&gt;
&lt;p&gt;The templates taught me MCP. &lt;code&gt;chrono-mcp&lt;/code&gt; is the one people actually use, and it&amp;#x27;s still only two tools. It doesn&amp;#x27;t also try to handle reminders or scheduling — those would be separate servers, composed in, not features bolted on. The scope stayed narrow on purpose, which is exactly why it shipped with real tests and documentation instead of half of both.&lt;/p&gt;
&lt;h2&gt;The Tradeoff, Honestly&lt;/h2&gt;
&lt;p&gt;Small tools mean more integration points and some duplicated glue across servers. Need five capabilities, and you&amp;#x27;re wiring up five things instead of configuring one big one. That&amp;#x27;s a real cost, not a rounding error.&lt;/p&gt;
&lt;p&gt;But the failure mode of a monolith is a debugging session across code you don&amp;#x27;t own. The failure mode of a small tool is a debugging session across code you can read in one sitting. I&amp;#x27;ll take the second one every time.&lt;/p&gt;</content>
  </entry>
</feed>
