Skip to main content

Command Palette

Search for a command to run...

Off-Label Git: A Secure Data Store for an AI Agent Running Real Storefronts

Updated
9 min readView as Markdown
Off-Label Git: A Secure Data Store for an AI Agent Running Real Storefronts

In my last post about The Construct, I mentioned building a Claude Code skill to onboard new services into the deployment pipeline. That worked well enough that I kept pulling on the thread: if an AI agent can be trusted to write deployment configs, what else can it be trusted to run day-to-day? The answer I landed on was a small storefront operation I run out of the same homelab (mostly 3D-printed and laser-cut products), and that raised a question the deployment skill never had to answer, because Ansible playbooks don't need write access to real, ongoing storefront data.

The question was: how much access does an agent actually need, and how do you make sure it never gets more than that, while still keeping the data it does need right at its fingertips, not buried behind a request I have to broker by hand every time?


The Setup: Several Storefronts, One Assistant

I run a handful of small storefronts side by side: different product lines (3D-printed goods, laser-cut goods, a couple of others), different workflows, different data. Each one now has a dedicated Cowork skill: read a cost ledger, draft a listing, close out an issue, tell me what's pending. That's genuinely useful, right up until you think about what it implies. An assistant that can write to live, ongoing storefront data needs somewhere to write to, and "somewhere" is exactly the kind of vague scope that turns into a real problem the first time a skill misfires.

I'd already gone through this exercise once, with the Zone-Based Firewall from a couple posts back: don't give broad trust when a narrow boundary does the same job. Same principle, one layer up the stack. This time the thing needing a boundary wasn't a Zigbee bulb, it was an agent with write access to a git repo.


Repurposing a Repo: Not for Code, for Context

Worth naming the obvious oddity here: a git repo is built for source code, not cost ledgers and listing copy. That's a non-traditional use of the tool, and I went with it anyway, because the properties that make a repo good for code turn out to matter just as much for storefront data: versioned history, plain-text files that diff cleanly, a folder structure that means something on its own.

The part that sold me wasn't the versioning. It's that the same file works twice. A markdown file sitting in a repo is presentable to me (I can open the folder and read it like a document), and it's just as legible to the agent reading it back for context. One data store, two audiences, no translation layer in between.

That second audience is the real shift. One of my skills even standardizes its draft files on a plain README.md instead of a custom filename, purely because it renders natively the moment the folder is opened, presentable to a human glancing at the repo, and parsed the same way by the agent working inside it. A repo stopped being just where code lives and became the shared surface where I hand data to my agent team and get it back in a form I can actually read.


Why Cowork, Not Code

I could have wired this up as a Claude Code integration instead. It's closer to how The Construct's onboarding skill runs. I deliberately didn't. Cowork's whole shape is built around a single local MCP connection per session; there's no path for it to reach past that connection unless I go build one myself. Claude Code assumes a broader kind of trust by design: it expects to operate across a whole codebase with shell access, and I'd have had to manually claw all of that back down to get the same guarantee Cowork gives me for free.

Choosing Cowork wasn't about which tool was more convenient. It was picking the tool whose default shape already matched the boundary I wanted, instead of building a boundary on top of a tool that assumes it doesn't need one.


The Hard Boundary: One Org, Nothing Else

The lazy version of this is pointing an agent at "my Gitea" and calling it done. I didn't do that. There's a local MCP server dedicated to this entire arrangement, and it authenticates as a dedicated Git account whose own permissions are limited to exactly one org and that org's repos: not my account, not my other projects, one org.

The distinction matters. Cowork can read and write inside that org's repos all day. It has no path to anything outside it: no other orgs, no other repos, no way for a misfiring skill to reach past the fence, because there's nothing on the other side of the fence for it to reach, and no account credential that would let it try.

This is the same shape as the Guest Zone from the firewall post: access to exactly what's inside the boundary, enforced twice over, by the MCP server's own scope and by the account it authenticates as, not by good behavior.


The Soft Boundary: One Repo Per Storefront

Inside the org, the repos split by storefront, and each skill is written to operate on its own repo only. A couple of the storefronts do legitimately overlap (shared source designs, overlapping audiences between the 3D-printed and laser-cut lines), and the skills know about that overlap and flag it when relevant. They just don't act across the boundary to resolve it themselves.

Worth being precise about what kind of boundary that is. The org scope is enforced twice: once by the MCP server's own configuration, and again by the Git account the MCP server authenticates as, whose permissions are limited to that org and its repos regardless of what the MCP config says. Neither layer has a setting that lets a skill see outside it. The repo-per-storefront split is a different kind of thing entirely: it's enforced by how the skills are written, a convention, not a permission either layer checks. It's the difference between a locked door and an agreement not to open one. Both matter. Only one of them is unbreakable.


Solving the Binary Problem: Git LFS

Scoping access solved half the problem. The other half was that a git repo without LFS falls over the moment you commit real production files, and 3D and laser production files are not small: print files for one, cut files for the other.

LFS is enabled at the org level, with storage redirected server-side to a separate, larger pool built for it. Each repo's .gitattributes tracks the binary types it actually has reason to hold:

*.stl filter=lfs diff=lfs merge=lfs -text
*.3mf filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text

Not every repo needs the same rules: the 3D-printed line tracks .stl/.3mf, the laser line tracks its own cut-file formats, and one of my repos is text and copy only with no binary asset to track at all. That's not an oversight. Enable LFS where the problem exists, skip it where it doesn't.

What this actually bought me is the thing I was missing before: a product's real print or cut files now live in the same folder as the listing copy describing them, under the same commit history, instead of two systems that only stayed in sync because I remembered to update both by hand. That's a genuine long-term storage solution for production files, with the diffable, text-based organization a git repo gives you for free.


Where the Agent Still Isn't Trusted

None of this means Cowork writes the binaries itself. The MCP's file-write call takes a plain string. Push raw binary bytes through a string parameter and you risk silent corruption. And a corrupted production file is a worse outcome than no file at all.

So the skills are explicit about the limit: draft the text, reference real filenames once I've given them, and leave the actual binary commit to me, by hand, the way I'd do it anyway. It's a deliberate seam, not a gap nobody's gotten to yet.

Giving an agent full context on a storefront's assets (real filenames, real folder structure, what's committed versus what's still a placeholder) doesn't require giving it a write path into a format it can silently break. It gets to know everything. It doesn't get to touch the one part of the pipeline where a mistake is expensive.


The Whole Picture


Everything in Phase 1 is where the agent actually lives: one connection, one gate, no path outward if the answer is no. Phase 2 is the explicit read/write line running to every storefront repo, none skipped, none reached by any other route. Phase 3 is the split that matters most: text goes straight from the agent into the repo, binaries get routed to LFS and committed by hand, and one storefront never even enters that second path because it has no binaries to begin with.


The Result

Several storefronts, one scoped org, LFS handling the 3D and laser production files a plain git repo was never going to manage gracefully, and an assistant that can act across all of it without holding a key to anything else. The hard boundary is the org scope: that one's enforced whether the skills behave or not. The soft boundaries (repo-per-storefront, text-only writes) are discipline, documented in the skills themselves, doing the same job a stricter permission model would, without needing one.

Same lesson as the firewall, just applied to an agent instead of a network: figure out the smallest boundary that still gets the job done, then build it so nothing has to remember to respect it.

The Blueprint

Part 5 of 5

This is the Blueprint. A professional-grade smart home isn't bought; it's engineered. This series documents the transition from consumer gadgets to a hardened, local-first infrastructure. From UDM SE network segmentation and Alpine Linux bastions to automated CI/CD pipelines, these are the architectural standards of the Digital Homestead.

Start from the beginning

Five Unbreakable Rules for a Smart Home

The Digital Homestead · Home Automation Most smart home setups fail not because of bad hardware, but because they neglect the fundamentals. After years of designing and refining my own system, I've se