I’m opening capacity for select fractional architecture, AI systems governance, and modernization lead roles in defense, GovTech, and regulated environments.

A few relevant projects from recent work:

Production LLM Pipelines: Helped build Amazon Bedrock document extraction on VA’s vets-api: raw response persistence, Datadog observability, and auditable form generation through federal security review.

Constrained Agent Tooling: Built tertulia-mcp to give coding agents bounded access (read-only introspection, test execution, scratch databases) while keeping production unreachable by construction.

Parity-Tested Modernization: Redesigned authorization queries and legacy frameworks at the Census Bureau by running legacy Stripes and new Spring Boot implementations side-by-side in CI, certified against every production permission signature with zero divergence.

High-Reliability Operations: Led platform engineering across 15,000 IoT devices at TSA.

Former Presidential Innovation Fellow. Author of apollo (Maven Central); creator of tertulia.social. Favorably adjudicated Public Trust (DHS/TSA, VA, USCIS). Arlington, VA / remote.

Typical engagements:

Architecture & Security Blueprints (2–3 week sprint): Fixed-scope evaluation of data boundaries, AI pipelines, or legacy refactor strategies before teams write application code.

Fractional Architect / Tech Lead (5–15 hrs/week): Ongoing technical direction, CI/CD parity gates, model governance, and core architectural scaffolding.

Local & Enclave AI Integration: Architecture and compliance design for deploying models into air-gapped or strictly regulated enclaves.

If your team is modernizing legacy infrastructure or deploying production AI, reach out: [email protected]

The software behind online community platforms encodes a governance model that represents a strikingly narrow set of governance possibilities focused on moderators and administrators. When online communities desire other forms of government, such as ones that take many members’ opinions into account or that distribute power in non-trivial ways, communities must resort to laborious manual effort. In this paper, we present PolicyKit, a software infrastructure that empowers online community members to concisely author a wide range of governance procedures and automatically carry out those procedures on their home platforms.

Untangling the Hairball: What to Do When You Can't Feed the LLM Your Code

What can you do if you're not allowed to use LLMs at work — or you can use them, but you can't feed them CUI (Controlled Unclassified Information), which includes your code?

You can build better harnesses.

In essence, questions and metaphors can be generic even when the codebase isn't. "How do I boot a Spring context inside JShell?" "How do I render the SQL a Hibernate Criteria would run, without executing it?" "How do I structure a test that proves two implementations of the same function return identical results?" There is no CUI in any of those questions. They're the kinds of generic questions we used to ask on StackOverflow. You can ask for a generic framework, then plug in your contextual specifics.

For example —

One of my clients has a Java hairball: a two-decade-old application whose behavior is unspecified because it is the accumulation of features and bugfixes applied one on top of another. Fix one issue and another appears somewhere else. It had no tests, because it was written in the pre-testing era, and every change carried the low-grade dread of not knowing what you'd broken.

Two practices changed that, and an LLM helped build both, without ever providing it with the client's code.

First, the standing rule: I never ship new code without tests. My client's application used to have none. It now has a suite wired into CI that fails if behavior drifts. The valuable pattern here is the parity harness. When you refactor something critical, you don't just test the new code: you run old and new side by side and assert the results are deep-equal. That harness design is a generic problem, and generic problems are exactly what you can take to an LLM.

Second, cycle time. Coming from Rails, I missed the console. Type an expression, see the answer. Java's traditional loop is write, compile, deploy to Tomcat, then poke an HTTP endpoint and hope the logs tell you something. So we built one: JShell, plus a startup script that boots the application context and loads every service. Now I run .\bin\console.bat and I'm inside the live application. Want to know what query the search engine will actually generate? Call the criteria builder and see the rendered SQL instantly, with the parameter bindings, no debugger required. Every piece of that console — the context bootstrapping, the transaction wrapper, the SQL-rendering trick that reaches into Hibernate's internals — came from generic questions with generic answers.

The console is the lab where tests are born. I can experiment interactively, against our real system, until I understand its behavior, compare old to new behavior, to give me confidence I haven't broken anything, then harden the experiment into a JUnit test. Confidence is the name of the game, and we have it now.

So, in conclusion: even if you can't share the hairball, you can generate better tools to give you the confidence to untangle it.

I advise engineering teams on AI systems architecture, security boundaries, and high-reliability modernization. See my current advisory availability and engagement models.

Another interesting problem: say we use MCP. It looks like OAuth can get the user's identity to the server. What's the standard way — the zero-trust way 😏 — for the server to decide whether this user is authorized to invoke a given capability? Or even whether the server should admit to them that the capability exists?

On CUI, Agentic Engineering, and Designing Governance Boundaries

I work mostly on systems in regulated environments where agents, today, simply cannot be connected to them. It's a problem of information policy: we can't allow third-party companies access to government data, or even its code. We've found workarounds, of course: we describe generic problem "shapes" to the model, then fill in the particulars ourselves. The problem is that the shapes aren't always perfect. The model reasons well about the generic case and can't see the specifics that break it, so when our particulars don't fit, the debugging lands back on a human — and by hand, that probably costs more time than if the LLM could simply have gotten it right the first time, because it knew the system we're actually tasked with developing and maintaining.

Thinking carefully about MCP, though, I'm not sure its use necessarily, or always, violates the constraints we're under.

In the strictest sense, an MCP server exposes a few levers that can be pulled to accomplish some tasks within our system, and the model is allowed to pull them. What the model can't do is access our code. It might be useful if you can ask the agent, "Can you tell me if user XYZ has access to resource ABC?", and you've built an interface that lets the model answer that question without ever seeing your database, or a single line of code. The tool layer moves the knowledge out of the model's context and into the tool implementation. The model doesn't need to know how permissions work — only that it may ask.

The question, of course, remains: what if the prompt, or its answer, still contains CUI? But that's a risk-tolerance-adjustable design surface, not a wall. Tool inputs and outputs are where we can apply some level of access minimization. We can return booleans instead of records, or identifiers instead of names. And now every question the model asks is a discrete, reviewable event. A smaller CUI footprint isn't the same as having none at all. But a small, enumerated, logged surface is something we can write policy around.

And that's kind of the point of all this: there's a specific set of policy changes it will take to get to our agentic future, and some level of risk tolerance. But that tolerance depends on having the right infrastructure in place first, both at the level of the LLM interface itself, and at the level of access the application grants the LLM.

The path forward that doesn't involve local LLMs still seems to be authorized models inside institutionally accepted boundaries — Bedrock in GovCloud, or an equivalent accredited endpoint. Reaching that boundary is itself a modernization project. I'm already being asked to work towards it for my clients: on-prem Jenkins to GitHub Actions, on-prem Oracle toward RDS. The infrastructure is the prerequisite for the tools we know we'll eventually want. Ok, we want them today, we just can't have them yet.

So, in the long run, I think this pattern could be useful in government software engineering. tertulia-mcp exists as a proof-of-concept: it exists to solve problems I have in an environment I control, against a codebase and data I'm allowed to share (my own, and nearly all of it public). One day it won't just be Tertulia.

I advise engineering teams on AI systems architecture, security boundaries, and high-reliability modernization. See my current advisory availability and engagement models.

:eyes:

Roughly the nail on the head of why I can't use Scala at work:

[W]hat killed Haskell is the parochialism, the inability to address the needs of the Enterprise... If we’re going to survive in the end, we have to address problems that everybody has. Otherwise someone else will address those problems. - Alexander Granin, memeing Robert C. Martin

I'd disagree with the characterization as parochialism, though. I've met enough talented functional programmers to know that it isn't really an ego thing. It's more about what excites a certain kind of person paid to code when they wake up in the morning.

Early in my career I got caught up in languages and frameworks, like lots of developers. But something happened that may not happen to every programmer, but certainly changed my mind about my work: I started working with non-profits, educational institutions, and the federal government, on software adjacent to problems whose outcomes I cared about. I've been fortunate enough to find work where the mission matters, and to keep drawing from the well of important problems.

But this colors my experience, as a developer, and as an open source user and contributor.

My customers don't care about Cats Effect, or ZIO, or Kyo. Heck, I don't think they know the difference between Java and JavaScript, let alone http4s and Spring Boot. Three effect libraries, but it takes weeks to get auth running on http4s because nothing like Devise exists (well, until I'd shipped Apollo).

And so I agree that what's killing functional programming is its abstractness from concrete problems. Certainly there are snobs in FP, but I don't think the majority of FP programmers are snobs, merely that functional languages scratch a very specific itch for certain kinds of programmers. Especially programmers who hate bugs, and spaghetti.

The Rust vs. Scala Native conversation too appears similarly: talented folks doing crazy cool stuff, then no ecosystem is built around it, so nobody uses except a group of people you could probably fit around a conference table.

I've been using Scala off and on for around eight years or so. I like the language, and I hope it succeeds. But I'm also a realist, and I'm not sure the people who make Scala great are the same kind of people who can ever make Scala popular.

https://gist.github.com/graninas/22ab535d2913311e47a742c70f1d2f2b

There's a whole universe of great things happening in the Scala ecosystem I just don't see often, or really even think about, because I live so far up the stack, right alongside my customers. This window into Cats Effect on Scala Metals is pretty inspiring. I can't easily cross-compile Tertulia due to Doobie (though JDBC should be replaceable with Skunk), plus some of my other fun features, like processing Anki stats and rendering genuinely good social media previews.

Apollo 0.2.0: invitations, and TIL

Apollo 0.2.0 is on Maven Central 🎉

There are two new features:

The first is invitation-gated registration à la DeviseInvitable, which exists because while toying with the multi-user branch I decided that an invitation system would be the right way to limit who's allowed to register. It's now part of Apollo, not Tertulia. Flip it on and registration requires a code — members can mint invitations (or anyone can, if you want a waitlist), the invitee gets an invite link to their e-mail, and following it allows you to register via the existing registration form. No template changes, invite codes ride their own short-lived cookie, and the user-facing messages travel through the existing flash middleware. Codes default to expiring after a week, but this is adjustable.

The second is more humbling. I announced 0.1.0 in the http4s Discord. Justin Reardon looked at my API and gently pointed out that since every service returns EitherT[F, Throwable, _] , over IO results in two error channels (IO has its own error channel, plus Either's Left) carrying the same type (Throwable). In my ignorance of this, and because the type checker didn't suggest to me this was an issue, when I wrote the original code I ended up splitting failures between them by the happy accident of liftFing my service code into the shape of an EitherT in some places, where in other places I'd simply wrap an Either-returning block of code in an EitherT . Depending on the lift, some errors are catchable, and some sail past every case Left in the codebase and hit the server-level error handler. (liftF lifts the success path only. The one that includes errors is .attemptT. Oops.)

0.2.0 fixes this. There's now a proper AuthError enum — EmailTaken, ResetNotFound, and friends — on the typed left, and infrastructure failures stay in F's own channel. I figure, essentially, that some errors are recoverable, and some aren't: the ones that are I want to match on, so they can be handled correctly in the UI, and the unrecoverable errors; e.g. the database is unavailable; just need to log correctly. The HTTP routes also no longer import java.sql to sniff SQLStates for error handling, which should have been a smell, but I'm a n00b.

It's a breaking release — every service trait changed — but that's what 0.x is for, and better now than after anyone besides me depends on it. Thank you for your code review, Justin. I know LLMs will one day probably be able to one-shot a lot of this code, and maybe it's silly to think the Scala/Http4s community needs this module, but it's useful to me!

At any rate:

mvn"io.github.joshuakfarrar::apollo-http4s:0.2.0"
mvn"io.github.joshuakfarrar::apollo-doobie:0.2.0"

https://github.com/joshuakfarrar/apollo — and mill init joshuakfarrar/apollo.g8 still gets you a working login box in one sitting.

🎉

Summer reading list —

Never Split the Difference: Negotiating As If Your Life Depended On It―Unlock Your Persuasion Potential in Professional and Personal Life by Chris Voss with Tahl Raz

Difficult Conversations: How to Discuss What Matters Most by by Douglas Stone, Bruce Patton, and Sheila Heen of the Harvard Negotiation Project