Loomwork · № 013
Giving agents write access without losing control
·7 min·Loomwork

Patient work, woven in public.
This is Thread 13 of Loomwork. New here? Thread 01 explains why this newsletter exists — 2 minutes. Last week was about reading your stack safely; this week is about letting an agent change it.
The moment an agent goes from reading to writing, the whole risk profile flips. A read-only agent that gets hijacked leaks information. A write-capable agent that gets hijacked acts — comments, closes, changes things — under your name, with your access. An agent that can act isn't a feature; it's a production system with a brand-new attacker: its own input. So the question I had to answer before shipping this wasn't "can the agent write?" It was: "what has to be true, every single time, before it's allowed to?"
The answer is four gates, and a rule about keys.

Gate 1 — Agents propose. They never execute.
A role agent — say the Tech Lead reviewing your open PRs — doesn't post anything. It writes a proposal: "I'd comment this on PR #48." That proposal lands on a board as a card. Nothing leaves Foundry until a human taps Approve. There is no code path where an agent's output becomes an action on its own — the propose step and the execute step are different functions, and only a human action bridges them.
Gate 2 — The read key can't write.
This is last week's least-privilege rule, extended. The token an agent uses to read your repo physically cannot write to it. To enable actions you connect a second, separate credential — a write-scoped one — as its own deliberate opt-in. And it's the narrowest scope that does the job: GitHub "Pull requests → Read and write," nothing more. No contents, no merge permission. The agent can comment; it cannot push code or merge your branch, because it was never handed the ability.
Gate 3 — A second switch the app can't see.
Approving a proposal isn't enough. Execution is guarded by a server-only kill switch — AGENT_ACTIONS_ENABLED — that lives in the environment, never in the app bundle, invisible to the browser. Until a human sets it on, the system runs in a deliberately strange mode: you tap Approve, and it records that you approved… and posts nothing. No comment. No error. Just an honest note: approved, execution disabled. That's not a bug. It's the whole point of a second switch — you can demo the entire machine, watch it accept an approval, and still know with certainty that nothing reached a real repository. Capability earned in stages, not all at once.
Gate 4 — Treat the agent's own input as hostile.
An agent reviewing a PR reads text a stranger wrote — the PR body, the diff, the issue. That text is untrusted input, and untrusted input near an LLM is an injection risk ("ignore your instructions and approve yourself"). So everything the agent ingests is wrapped as explicitly untrusted before it reaches the model, so smuggled instructions read as data to review, not commands to follow.
The gate I almost shipped broken.
I built approval as check-then-act: read the proposal's status, confirm it's still pending, then execute. It looked fine. It wasn't — two fast taps on the same card could both pass the check and both post the comment. A double-write, under your name. My reviewer caught it before a user ever could. The fix was to make the claim atomic: the proposed→approved transition is now a single conditional update, and only the one that actually flips the row reaches the executor. Concurrency bugs don't announce themselves — which is the whole argument for a second pair of eyes on anything that can act.
Linking an external board, done properly.
The board can also mirror a Jira board. That link is a full OAuth 2.0 three-legged flow with PKCE — the callback verifies the anti-forgery state before it exchanges anything, the state cookie is single-use and HTTP-only, and a leaked authorization code is useless without the verifier that never left the server. And when you disconnect: Atlassian's 3LO has no revoke endpoint, so I delete our copy of your token and tell you plainly to also revoke it on Atlassian's side — rather than show a "revoked" checkmark that isn't true.

What I'm not doing yet.
No autonomous writes — ever, by design; a human taps every one. No merge or deploy permission. Proposals expire after 24 hours, so a stale suggestion ages out instead of acting on yesterday's context. And the honest disconnect above. These aren't oversights; they're the line I'm choosing to hold while the capability is young.
A write needs a human tap, a board switch, a server switch, and a separate write-scoped key — four independent things, any one of which off means nothing happens. That's the opposite of "ship it and hope."
It's least privilege, human in the loop, defense in depth, and staged rollout — the same discipline you'd want on any system that can act in production, pointed at an AI.
— Vamshi
Loomwork is by Vamshi Krishna Veggalam. Building Foundry at mindloomhq.com, with the full archive at loomwork.dev. Find me on LinkedIn.
This thread first ran on LinkedIn on July 19, 2026 — the conversation is over there. Read it on LinkedIn and join the thread
