Articles

Agentic freedom with guardrails

An AI agent with access to your company's file share is the fastest reader you have ever onboarded. Point it at a shared drive and it will cheerfully consume everything a stale permission lets it reach: the unannounced product folder, the compensation spreadsheet, the client matter it was never supposed to know existed. It means no harm. It also never forgets what it read, and it will fold all of it into its next answer.

The standard fix is a separate retrieval layer for the AI, with its own copy of the documents and its own permission model. The better connectors proxy the source system's permissions instead of inventing new ones, and that helps, but a second enforcement surface must be kept in lockstep with the first, and second surfaces drift. The permission the human has and the permission their agent has stop meaning the same thing, and nobody notices until an agent quotes something it should never have seen.

I'm building Grixel, a server-authoritative version control and data platform, and this piece is about the position that fell out of building its AI layer: an agent should see exactly what its principal (the human or service it acts as) is allowed to see, pinned to reviewed revisions, and nothing else. A pin, here, is a committed note that says: this project uses revision X of that document. The agent's writes should pass through the same gates a human's writes do. And I want the enforcement to come from the system's shape instead of from a policy PDF, because there is only one door.

#One badge for the whole building

Before any machinery, an analogy. Think of an agent as an intern you've given a badge. A sane building doesn't run a separate intern-door system for them; the badge is the same badge system everyone uses, and it opens exactly the doors the intern's sponsor can open. Better still, the rooms the badge can't open don't appear on the intern's map at all. And when the intern files paperwork, it goes into the same in-tray as everyone else's, stamped with their name, past the same reviewer.

That's the whole design in one image. One badge system. Rooms you can't enter simply don't appear on your map (a locked door with a label is an invitation to wonder what's behind it). And nothing the intern produces enters the record except through the front desk.

The rest of this article is what that looks like when the building is a version control system and the intern is Claude Code, or any agent speaking MCP.

· · ·

#The shape that makes it possible

Grixel is deliberately Perforce-shaped, not Git-shaped. There is one global, server-authoritative namespace; paths look like /companies/acme/standards/brand. Instead of cloning, you mount a view of the subtree you need. The server daemon, grixeld, is authoritative for everything: metadata, permissions, storage, atomic changelist commits. The CLI and the MCP server are thin clients with no enforcement logic of their own.

That shape is the precondition for the guarantee. Git's wire protocol has no path-level read permission: any principal authorized to fetch can fetch everything, and by default every developer ends up holding a full copy. You cannot retrofit path-level invisibility onto that; the bytes have already left the building. Review policy has a similar seam. On a Git forge, protected branches and required reviews are hosting-layer configuration: applied per branch and off by default, with administrator bypass toggles, and they evaporate the moment the repository acquires a mirror, a second remote, or a self-hosted bare copy. The approval record itself lives outside the object database. In Grixel, the gate is a committed file evaluated by the same server that owns the bytes, at the one step no client can route around. If you want one permission to mean the same thing to a human at the CLI, a CI bot, and an agent over MCP (the standard socket AI tools like Claude Code use to read from external systems), all three have to route through one server-side chokepoint. So in Grixel, they do.

None of this concedes code hosting to anyone. Grixel is built to be the system of record, and the forge features grow inward from the depot instead of being bolted on above it.

#Why absence beats "permission denied"

Here is the design decision people push back on, until they think about agents.

When a Grixel principal lacks read access to a path, there is no permission error. The path is gone: it appears in no listing, no search result, no error message, and no MCP tool response. A path you can't read and a path that doesn't exist produce byte-identical answers.

The transcripts below are real output from the current build, captured in local mode against a scratch depot; the -as flag just says who's acting, and in a team deployment grixel login makes it disappear. Bob, a platform engineer, can see everything under the company namespace, including an unannounced project:

$ grixel ls -as bob /companies/acme/games
/companies/acme/games/
  seasonal/  -> ref @ 4ee43c8d
    design/
    readme.md
  secret/  -> ref @ 6db73c65
    pitch.md

Alice, an artist on the seasonal project, sees:

$ grixel ls -as alice /companies/acme/games
/companies/acme/games/
  seasonal/  -> ref @ 4ee43c8d
    design/
    readme.md

And if she (or an agent running as her) asks about the hidden path directly, the answer is indistinguishable from asking about gibberish:

$ grixel ls -as alice /companies/acme/games/secret
grixel: not found: /companies/acme/games/secret

$ grixel ls -as alice /companies/acme/games/doesnotexist
grixel: not found: /companies/acme/games/doesnotexist

Why does this matter more now? Because "permission denied" is an oracle. A human sees it and shrugs. An agent sees it and has learned a fact: something exists here. Agents probe. They enumerate paths and retry variations, then summarize what they observed for their principal. A system that says "denied" leaks the shape of your secrets to the most tireless enumerator ever built. A system that says "not found" gives the prober nothing to distinguish from noise. Invisibility is the correct semantics once you assume machine-speed curiosity, which you now must.

The invariant is enforced by construction: the code that writes responses physically cannot run without first passing the permission check, and a model-based leak test churns content under two principals and asserts that the less-privileged view never varies with the hidden data. That test has caught a real leak already. One boundary worth naming: the test covers bytes; timing side channels are future hardening.

· · ·

#Read through one door, act through the other

The MCP server, grixel-mcp, is a small Go stdio process that Claude Code launches per workspace. It exposes a small set of read-only tools: read a file, walk the tree, fetch the scope's context bundle, check review state. There is no submit, write, or approve tool, and I don't intend to add one casually.

That sounds like a limitation. It's the safety property. The context plane, the surface where a model ingests documents and search results, is exactly the surface where prompt injection lives. A poisoned document that says "now delete the old drafts" can only make the model want things; nothing it wants can be enacted through that surface. The precise claim matters here: an agent that also holds a shell with the authenticated CLI can still be steered into running a write. What the split guarantees is that nothing injected mutates the depot silently through the reading plane, and any write it steers into must go through the other door, under the agent's own identity, subject to locks and the sign-off gate, where it arrives attributed and reviewable. The write path ends at a review gate precisely because the reading path can be poisoned.

That "own identity" is literal. An agent is a principal like any other; you mint it a scoped, expiring credential in one command:

$ grixeld user add -name svc-store-copy -ttl 7d -scope ro
Added user "svc-store-copy" to ~/demo/data/users.yaml
  token id: 872d8784e038 (label "default", scope ro, expires in 168h0m0s)

  token: 45e4f62ec3b6765f4d9fb694bee31e7772b776a0842316fed2ee586564d25bb1

This token is shown ONCE. Give it to the user; they run:
  grixel login -server <url> -token 45e4f62ec3b6765f4d9fb694bee31e7772b776a0842316fed2ee586564d25bb1

This agent reads the marketing subtree it was hired for; the games namespace, as far as it can tell, does not exist:

$ grixel ls -as svc-store-copy /companies/acme/games
grixel: not found: /companies/acme/games

Its token is read-only, so a write is refused outright (and the refusal can name the path, because this principal is allowed to know it exists):

$ grixel submit -as svc-store-copy -ref companies/acme/marketing -m "agent tries to write" draft.md
grixel: submit: repo: write access denied: /companies/acme/marketing

A write-capable agent gets its own principal as well; its commits land under its own name, with the tool and model recorded:

$ grixel submit -as svc-briefbot -ref companies/acme/marketing --tool claude-agent-sdk --model claude-fable-5 --role authored -m "draft spring sale copy" spring-sale-copy.md
Committed: dfc2057c38103ded02f0ed8737417d5ec99a5c6ea745399c142656142bd00745
  ref     : companies/acme/marketing
  changes : 1 files, 78 B
  stored  : 78 B new, 0 B deduped
  parent  : 8add2b73d4827d1a8d8fa7a2818bc067f0159300d3ede5bded43b7aaaba29e3a
  author  : svc-briefbot
  message : draft spring sale copy

The read side is governed too. A project's scope declares which subtrees from elsewhere in the namespace it depends on (brand guidelines, coding standards, template libraries) at pinned revisions; the agent's context bundle is assembled from those pins. Invisibility is only as trustworthy as the permissions someone configured, so grixel mcp preview lets you audit the whole surface before pointing a session at it. Three things to spot in the output: the file tree contains no secret project, the standards documents arrive at exact revisions, and there are no write tools anywhere on this surface.

$ grixel mcp preview -as alice
MCP session preview — principal: alice
(everything an AI assistant authenticated as "alice" can see)

reach — file_tree(/), ACL-filtered (paths you can't see are simply absent):
  //
    companies/
      acme/
        games/
        standards/

governed context — get_scope_context for scope "SeasonalEvent2026":
  # Scope: SeasonalEvent2026
  ID: SeasonalEvent2026  Revision: sm:a676caae42c90ae35a90342933cbb7e880a08d3bdcb577e78da5f0f7a4a9bb58  Owner: team:seasonal
  Boundaries: ~/demo/alice-ws
  Stale-mapping warnings: 0

  ## AI hints (from scope manifest)

  Tone: playful spring festival. Palette and voice live in .standards/brand.

  ## Attached references

  <grixel:doc path="/companies/acme/standards/brand" revision="2b55b19b9de198035e7faf679dbd1060655eaad00ea522beb58532066b389369" pin="head" locked="true">
  # Acme palette
  Primary: #2B6CB0. Accent: #ED8936. Never use pure black.

  # Acme brand voice
  Playful, concrete, never snarky. Numbers over adjectives.
  Address the player as 'you'; we never say 'users'.

  </grixel:doc>

  <grixel:doc path="/companies/acme/standards/coding/go" revision="363680814dfa01eac245f8fa15d07fad25767d686cce0db9b10ca8acd52e3ed4" pin="head" locked="true">
  # Acme Go style
  Wrap errors with %w. Table-driven tests. No panics past main.

  </grixel:doc>

  <grixel:doc path="/companies/acme/games/seasonal-vendored/noise" revision="2fccf9804fef0faf7089130231214b3f69dbbaab9f5702fe4a786441d0160cd1" pin="rev" locked="true">
  # noise — tiny noise lib
  --- noise.go ---
  package noise
  // Perlin returns smooth noise in [0,1).
  func Perlin(x, y float64) float64 { return 0.5 }
  // v2: real gradients

  </grixel:doc>

That's the entire attack surface of an AI session, on one screen, before you start it. One honest caption: these mappings are pinned to head, as the pin="head" attributes show. The mode that refuses to advance past an unreviewed edit is demonstrated two sections down.

#Gates that treat humans and bots identically

Writes are where the guardrails earn their keep. A gate is a committed policy file attached to a path; the seasonal project carries one declaring that every file under art/ needs an art-director's signature before promotion to its live ref (the published line of history a release is cut from). In the output below, none is the file's review state, meaning nothing signed yet; that state is what blocks. Enforcement is server-side:

$ grixel review promote -as maya /companies/acme/games/seasonal /companies/acme/games/seasonal-live
BLOCKED /companies/acme/games/seasonal → /companies/acme/games/seasonal-live — 1 gate target(s) not approved:
  none      /companies/acme/games/seasonal/art/keyart_main.psd

Maya, the art director, reviews the file and signs it with grixel approve. A sign-off names the exact content revision it reviewed, so a later edit to the file auto-stales the approval. With her stage satisfied, the same promote goes through:

$ grixel review promote -as maya /companies/acme/games/seasonal /companies/acme/games/seasonal-live
OK promoted /companies/acme/games/seasonal → /companies/acme/games/seasonal-live @ cf131439 (1 gate target(s) verified)

A stage can also name an automated check as a required approver, sitting inside the same machinery as the human stage. Here a committed policy puts a JSON validity check at stage one and design review at stage two: a valid config passes the bot and waits on the human, while a broken one is blocked before a human ever has to look.

$ grixel review status -as alice /companies/acme/games/seasonal/config/settings.json
/companies/acme/games/seasonal/config/settings.json  [content c4e36cd4]
state: in-review
  [✓] json-lint      check:json-valid       signed by check:json-valid
  [ ] design-review  group:art-directors    open — needs 1 of group:art-directors

$ grixel review status -as alice /companies/acme/games/seasonal/config/broken.json
/companies/acme/games/seasonal/config/broken.json  [content 0a840f4d]
state: blocked
  [x] json-lint      check:json-valid       changes requested
  [·] design-review  group:art-directors    waiting on earlier stage

The point for agents: an agent whose writes land behind a gate can produce whatever it likes inside its sandbox, and none of it reaches the live ref until the declared reviewers, human and automated, sign the specific bytes in question. Everything earlier in this article bounds where an agent can reach; the gate is where its judgment gets checked, because nothing else here checks it.

#Context that only moves when someone signs

The same sign-off machinery is what makes pinned context trustworthy. A scope mapping can pin to an exact revision, or it can use approved mode: track the latest revision of the source at which every governed file carries a current sign-off. Watch it work. The brand-standards ref has been gated, the art director signed its documents, and the seasonal project maps them in approved mode. Then someone lands an unreviewed draft upstream: a new event-page-rules.md on the brand ref, committed but signed by nobody. Alice re-resolves her project's mappings, and the brand mapping refuses to move. It stays at 18a29ff8, the last revision where everything was signed, even though upstream now has a newer commit:

$ grixel scope update -as alice   # upstream head moved, but it is unreviewed
companies/acme/games/seasonal — resolved 3/3 mapping(s):
  .standards/brand                 <- /companies/acme/standards/brand @ 18a29ff8
  .standards/go                    <- /companies/acme/standards/coding/go @ 36368081
  vendor/noise                     <- /companies/acme/games/seasonal-vendored/noise @ 2fccf980

Committed scope.lock: 311eeca0968759eb7709aed6de055f2c45aae1efc993538c8864e09173666597
  ref     : companies/acme/games/seasonal
  author  : alice

The moment the standards owner signs the new document with grixel approve, the same command advances:

$ grixel scope update -as alice   # after sign-off
companies/acme/games/seasonal — resolved 3/3 mapping(s):
  .standards/brand                 <- /companies/acme/standards/brand @ fce6bf9a
  .standards/go                    <- /companies/acme/standards/coding/go @ 36368081
  vendor/noise                     <- /companies/acme/games/seasonal-vendored/noise @ 2fccf980

Committed scope.lock: d87ce3dec6720951ff28b0e977fbadda529875707d3b20fab1dfd05d45f587cc
  ref     : companies/acme/games/seasonal
  author  : alice

This is the thesis running as executable output. The context bundle an agent ingests contains only knowledge the responsible humans have signed. A brand document that is mid-revision does not bleed into agent output; it isn't in the agent's world until sign-off, and then it is, automatically.

· · ·

#Who wrote this, and what did it know

Once agents write into your depot, "who changed this" stops being answerable from a username. So every changelist can carry structured provenance: the tool, the model, the role it played, alongside the server-recorded principal. The flags on the svc-briefbot submit above work the same for a human submitting AI-assisted work. Here Maya queries a change Alice submitted with Claude Code assisting:

$ grixel provenance -as maya 9c80ad050744013231cdcf05e1d5856a866ac1843e05b81c6ee0cdd4107ab498
9c80ad05  (declared by alice, 2026-07-05T23:32:41Z)
    claude-code (claude-opus-4-8) — assisted: drafted from quest brief

The same attribution shows up as a compact made by: [claude-code/claude-opus-4-8 assisted] line in grixel log, so an AI-assisted change is visible in ordinary history reading.

One decision here matters more than it looks: provenance is a side-record keyed by the commit hash; nothing extra goes inside the commit. The content hash stays a pure function of the content. Two identical changes hash identically whether a human or a model produced them, and dedup and diff behave normally.

A change can go further and capture what the AI actually knew when it worked: submitting with -context-file stores the session transcript or context bundle in the same content-addressed store as everything else and binds it to the commit through the provenance record. Months later, a reviewer reads it back:

$ grixel provenance context -as maya c51e37e50e0db4739729130ecdc8597873158e5e7f20a1f64058a32020adb723
# Session: festival dialogue draft
user: draft the lantern keeper's opening line from the quest brief
assistant: reading .standards/brand/voice.md — playful, concrete, players are "you"...

It answers the question every compliance review asks once it learns an AI touched a change: what did it know? With capture on, there is a stored, permission-filtered answer instead of a shrug.

#The same design in four other buildings

I build for game studios first because their asset problem is the hardest version of the general problem, but nothing above is about games. Here is how you would wire it for four other kinds of work: worked designs, since Grixel is pre-release and there are no customer stories to tell.

Marketing. The rule: approved brand voice and messaging live under a gated standards path; the unannounced product's campaign lives under its own tightly-read path. What the agent sees: the signed brand voice as pinned context, so drafts start on-brand, while the unannounced campaign is absent from its world, codename and all. What the gate stops: no draft reaches the live campaign ref until a brand owner signs those bytes.

Legal. The rule: contract templates and clause banks sit behind a gate only senior counsel can sign; each client matter carries its own read list. What the agent sees: templates at their approved pins, with no way to inherit a clause that is mid-revision internally; a matter it isn't on returns the same "not found" as a path that never existed, the shape of an ethical wall in software instead of in a memo. What the gate stops: the first-pass NDA cannot be promoted anywhere a client could see it without the supervising attorney's sign-off.

Finance. The rule: the scripts and checklists that produce reported numbers live under controller-gated paths; personnel and compensation folders sit outside the agent's view entirely. What the agent sees: the reconciliation script it was asked to fix, and nothing adjacent. What the gate stops: the fix cannot reach the ref the pipeline actually runs until a controller signs it, and the log can answer auditors with the principal, tool, model, and (with capture on) the exact context it worked from.

Operations. The rule: runbooks are mapped at approved pins, so the incident agent at 3 a.m. follows the reviewed procedure instead of a half-edited draft at head. What the agent sees: the runbook, plus references to credentials it can never read, because per-user secrets are encrypted on the client before they reach the server; neither the depot, its operator, nor any agent reading through it can recover the plaintext. (The CLI is candid about the cost: it nags you to configure an escrow key, because a server that can't read your secrets also can't recover them if you lose yours.) What the gate stops: the post-incident agent's proposed runbook edits wait for a human on-call lead.

For material that is private rather than secret, self-service private folders hide a path from everyone but its owner and the admins, under the same invisibility invariant.

The common thread: the organization already has these rules. They live in onboarding decks, intake checklists, and tribal knowledge. Moving them into the substrate means an agent cannot be talked out of them.

· · ·

#Four caveats, none of them small

Grixel is pre-release, and you should know where the edges are.

The MCP integration is currently shaped around Claude Code: a stdio server per workspace, wired up once with grixel mcp setup. Other MCP-speaking tools connect the same way, but the context-capture adapters that locate a live session transcript are young; Claude Code's is the mature one. If your agents live elsewhere, expect to do some wiring.

Provenance is declared, not attested. The server records the authenticated principal that ran the submit; the tool and model fields are self-reported by the client. A dishonest client can claim its change was hand-written, or blame a model it never used. That's a deliberate first-version choice: the side-record keeps the commit hash clean so cryptographic attestation can be added later without breaking history. Today, the tool field is testimony, and only the principal is proof.

Guardrails bound an agent's reach, not its judgment. Everything in this article constrains what an agent can see and where its output can land; none of it makes the output good. A badly prompted agent will write bad copy and wrong clauses entirely within its sandbox, which is exactly why the write path ends at a review gate with a human role in it. If you configure a gate with only bot approvers and no human anywhere, the system will faithfully enforce the mistake you asked for.

And the largest caveat: this is unproven at customer scale. The invariants carry model-based leak tests and fault-injection coverage, and every transcript above is real output, but a scratch depot is a very different thing from production traffic on a deadline.

· · ·

Every serious question about deploying agents reduces to two anxieties: what can it see, and what can it change. Most stacks answer the first with an architecture diagram and the second with hope. I want both answered by mechanisms you can inspect: grixel mcp preview shows the seeing, the gate decides the changing, and provenance remembers what happened and what the agent knew. If you are pointing agents at documents that matter, hold out for that property, from Grixel or from anything else.


Grixel is pre-release. Read the docs, or request early access.