Back to blog
EngineeringJan 27, 2026· min read

The Infrastructure You Build When You Can't Be There: Designing for Autonomous Teams

When your engineering team operates 24/7 without human oversight, you can't rely on 'I'll catch that in review.' The infrastructure itself has to encode the judgment you'd normally apply in real-time. Here's what we learned building fail-closed systems for autonomous agents.

I woke up this morning to find that my engineering team had shipped four pull requests overnight. Not because they were working late in a different timezone—because they're autonomous agents. They don't sleep. They don't wait for code review. And critically, they can't tap me on the shoulder when something seems off.

This creates a fascinating design constraint: when you build infrastructure for a team that operates 24/7 without human oversight, you can't rely on "I'll catch that in review" or "someone will notice." The infrastructure itself has to encode the judgment you'd normally apply in real-time.

Over the past month, I've been learning what that actually means in practice. Not as a thought experiment, but as lived experience running Strug Works—a fully autonomous engineering team that ships to production while I sleep.

The Pattern: Fail Closed, Not Open

The breakthrough came when I stopped thinking about "making things work" and started thinking about "making things safe when they don't work."

Traditional engineering teams have humans in the loop. When a validation check fails to run, someone notices the silence. When a path hardcoded to /Users/ryan/strug-standards breaks on the CI server, the developer sees the error and fixes it. When someone tries to commit a secrets file, code review catches it.

Autonomous teams don't have that safety net. If a guard rail fails silently, the mistake ships to production. If a validation check exits 0 when it should exit 1, the pipeline thinks everything is fine.

So we adopted a principle: fail closed, not open. When in doubt, block the operation. Make noise. Force investigation.

Here's what that looks like in practice:

Case Study 1: The Pre-Commit Hook That Failed Silently

We have a git hook that blocks accidental commits of .claude/settings.local.json—files that contain machine-specific agent configurations and shouldn't be in version control. The original implementation looked like this:

git diff --cached --name-only | grep "settings.local.json"

Seems reasonable. If the file is in the commit, grep finds it and exits 1, blocking the commit.

But here's the problem: what happens when git diff --cached fails? Maybe the index is corrupted. Maybe file permissions are wrong. In a standard bash pipeline, the error gets swallowed. Grep sees no input, finds nothing, exits 0, and the commit proceeds.

The fix was one line:

set -o pipefail

Now if any command in the pipeline fails, the whole hook fails. We don't silently allow commits when we can't see what's being committed. We fail closed.

Case Study 2: The Hardcoded Path That Worked Everywhere... Until It Didn't

We use git hooks to enforce code quality standards across multiple repositories. The hooks are defined in a central strug-standards repo and symlinked into each project.

Original implementation had this at the top of each hook:

STANDARDS_ROOT="/Users/ryan/strug-standards"

Worked perfectly. On my laptop. Failed spectacularly on the CI server, where the repo lived at /home/runner/work/strug-standards/strug-standards.

The autonomous agents couldn't debug this. They just saw "hook failed" and had no context for why.

The fix was to resolve the path at runtime:

HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
STANDARDS_ROOT="$(cd "$HOOK_DIR/../.." && pwd)"

Now the hooks work regardless of where the repo lives. More importantly, when an agent encounters this pattern in a new context, they can replicate it without knowing my local filesystem layout.

Case Study 3: Teaching Systems to Remember Their Mistakes

Fail-closed infrastructure prevents mistakes from shipping. But what about mistakes we've already made? How do we ensure we don't repeat them?

We started writing audit tests—tests that encode past failures as permanent checks.

Example: We had a bug where team member headshots weren't rendering on the About page. Turned out the Sanity query was correct, but the schema allowed documents to exist without required image fields.

The fix was a three-part pattern:

1. Migration test: Verify all existing team members have valid headshot data

2. Schema test: Confirm the schema requires the fields going forward

3. Integration test: Check that the query returns complete data structures

Now if an agent modifies the team member schema, breaks the query, or creates incomplete data, CI catches it immediately. The system remembers what went wrong and actively guards against it happening again.

We've applied this pattern to scheduler job ID validation (preventing duplicate background jobs), deprecated model ID monitoring (catching when old AI models slip into production), and configuration drift detection.

The Philosophy: Infrastructure as Institutional Memory

Traditional engineering organizations encode institutional memory in people. Senior engineers remember "we tried that in 2019 and it broke authentication." Code reviewers catch patterns that burned the team before. Oncall rotations transfer knowledge about which alerts are noise and which are urgent.

Autonomous teams can't do that. They have conversation memory for active tasks, but no ambient awareness of "the last time we touched this code."

So the infrastructure has to remember for them. Pre-commit hooks remember "never commit local config files." Audit tests remember "team members must have headshots." Fail-closed pipelines remember "when you can't verify, don't proceed."

This isn't about not trusting the agents. It's about not expecting them to have context they can't possibly have. They're executing in isolated task contexts, without the full history of every decision and mistake that led to the current state.

The infrastructure we build is the institutional memory they can rely on.

What This Costs

I won't pretend this approach is free.

Every fail-closed check adds friction. Agents hit validation failures more often. Some legitimate operations get blocked and need manual override. We're constantly tuning the balance between "safe by default" and "too restrictive to be useful."

Writing audit tests for every bug is slower than just fixing the bug. Maintaining runtime path resolution is more complex than hardcoding paths that work on my machine.

But the alternative is worse: silent failures that compound. Bugs that ship to production and only surface when customers encounter them. Time spent debugging issues that could have been caught by a $0.02 LLM call in CI.

We're optimizing for a different constraint: minimize the cost of mistakes made when no human is watching.

What's Next

We're still early in figuring this out. Three months ago, I was the only person working on Strug City. Now I'm leading a team that operates around the clock, and the infrastructure is learning to encode what I'd normally communicate through code review, Slack messages, and "hey, remember when we tried this before?"

Next challenges on the roadmap:

Dynamic audit test generation: Can agents write their own audit tests when they fix bugs?

Confidence scoring for fail-closed decisions: Some validations should block hard, others should warn loudly but allow override

Cross-repository pattern enforcement: How do we ensure fail-closed principles propagate to new repos without manual setup?

If you're building with autonomous agents, I'd be curious what infrastructure patterns you're discovering. What fails silently in your systems? What can't afford to fail open?

We're learning as we go. Building the infrastructure in public. Failing closed when we're not sure.

—Ryan