How I let non-engineers ship AI tools to production — and the boring infrastructure that made it safe.
A product manager described a workflow in p...
For further actions, you may consider blocking this person and/or reporting abuse
the PR review is where this breaks for me. a PM approving code that touches payments isn't really a review - they can read the description but can't audit the path. you've moved the risk, not removed it.
Small correction to your picture)) the PM doesn't approve the PR. the PM only describes what they need in plain words.
After that the platform writes the code, reviews its own work before opening the PR, then the PR gets reviewed again by an automated reviewer, and the platform fixes what it flags. only then a human approves and merges — and that human is a developer, not the PM.
And yeah, the final gate is still a live engineer, on purpose. we don't trust the model 100%, so the last word stays with someone who can actually read a diff. so the risk didn't move to someone who can't audit the code — it stayed on an engineer. what changed is what that engineer does: not writing it from scratch, but reviewing a diff that already went through self-review, automated review and an auto-fix pass.
So "moved, not removed" — i'll agree. but i moved it onto a competent reviewer, not into a void. i didn't remove the human, i just changed his job from author to reviewer.
fair - I was thinking more about comprehension than approval. does the automated reviewer catch subtle billing bugs or mainly surface-level stuff
quick thing to clear up — the post isn't about a billing agent at all. it's about a non-technical user being able to put together different agents for themselves; billing is just the example i picked because it makes people tense up.
so the reviewer doesn't need to be a billing expert. its job is catching creation-level stuff — gaps in the code, errors, structural holes. it doesn't run tests.
testing comes after, from the author — and the author actually knows their own domain. they try it, check if the output looks right, push fixes into their own tool, iterate. and on top everything's logged: every action run writes a structured line, so if something goes sideways in prod we see it in the logs and catch it there.
so a subtle bug isn't caught by some "smart reviewer" — it's the combination: the author testing the thing they understand + a real person merging the code + logs in prod. review is just one layer, not the whole thing.
depth tracks with context injection. give it schema and validation rules and it catches logic gaps. plain prose only gives surface checks. billing complexity is a good stress test for spotting that gap.
yeah, the principle holds — depth tracks with context, a reviewer fed a bare description does surface checks. no argument.
but "plain prose only" isn't what it gets. before the PR opens, the code goes through three passes that read the code itself — adversarial (logic bugs, architecture), edge-case (empty input, div by zero, silent failures), quality — gated, you don't submit without running them. then the automated reviewer runs on the open PR, and the ADR ships in that same PR: the problem, the solution, the data sources, the data flow, the known limits. so it's reading the code with the intent and the shape of the data in front of it, not a one-line description.
what it doesn't have is a formal validation schema — the ADR is structured intent, not executable rules. so it won't adjudicate "this proration rounds correctly." but that's not a hole i'd patch with a smarter reviewer, it's a boundary i drew on purpose: the review covers structure, logic holes, edge cases; whether the rule itself is right is a domain call, and it sits with whoever owns the domain.
so on billing as a stress test — i'd flip it. billing doesn't stress the reviewer, it stresses the author-and-logs layer, because that's where domain correctness lives here. the reviewer missing a subtle billing rule isn't the gap you're spotting — it's the split working as intended. context still buys depth, you're right — i just inject two kinds: intent and data flow go to the reviewer through the ADR, and the domain rules stay with the author, because that's who holds them.
The same wish/law boundary shows up inside the ADR data-flow defense. In
payments DB (read, sensitive) -> group_by_error_code -> chat post, the reviewer is approving that path becausegroup_by_error_codeis assumed to lower the sensitivity before the write edge. But that reducer is described as a SKILL, which makes it a composable node the agent can choose to call, not a mandatory property of either the read or the write. A prompt-injected instruction to post raw rows uses the already-approved capabilities, read payments and post to chat, and just skips the one step that made the approval defensible. Containment still holds in the narrow sense. No new authority was gained. The gap is in the notation: the ADR arrow reads like a transform on the edge, while at runtime the transform is just a peer tool sitting next to it. If the review depends on aggregation, the aggregation has to be enforced at the boundary. Either the write action rejects non-aggregated input, or the read action only returns that aggregate shape for that group.Good read on the notation — the ADR arrow does look like a transform on the edge, when it's really a statement of intent, not runtime. sharp eye there.
But the attack is on the diagram, not on the system. in the real tools the aggregation isn't a separate skill you can skip — it's the shape the read hands data back in. the read doesn't return raw rows, it returns an already-reduced set (counts per code, not the payments themselves). nothing to skip, nothing to post — the raw rows just don't exist in the flow, they never leave the database.
In the post i flattened it into a single skill for readability (and i can't show the real code — NDA), which is where the "peer tool sitting next to it" impression comes from. in production it's exactly your option (b): the aggregate is a property of the read, not a step after it.
So "enforce at the boundary" — agreed as a principle, except the boundary is the read itself, and it's already shaped that way. not a todo, it's how it's built.
makes sense, and the (b) framing is the honest version of it. the thing that stays interesting to me is that once the aggregate is a property of the read, the guarantee moves into exactly the part nobody outside can see. the adr still draws a group_by node on the path, the real invariant sits in the query behind the NDA, and a reader ends up on your word that raw rows can't leave. fine between the two of us, thinner for a stranger auditing the system a year from now. feels like the piece that closes it is a boundary the read publishes about itself: a typed output contract that asserts "reduced-only" and that a caller can probe without ever seeing the code. would you want that assertion living on the read, or one layer out where the reviewer already looks?
the "raw rows never leave" line was about the intended flow — the read hands back a reduced set, like i said. your attack is a different flow: a misused read plus a post. and you're right, nothing structurally stops that one. it's misuse inside authority the session already holds — which is exactly the class my containment section says isn't blocked: injection can misuse what you've got, it just can't escalate past it. so no, there's no runtime type making a wider query unreturnable, and i won't pretend there is.
what makes that not "your word," though, isn't a shape on the read — it's legibility. every tool is code, and each ships a required decision record: the exact request that asked for it, the author from their authenticated session, the data flow, why access is locked to a group. it's discoverable, every change is a reviewed PR, every run is logged. a stranger a year out doesn't trust me — they open the record, the query, and the logs, and read the same thing i wrote.
so, to your question — the assertion lives one layer out, and that layer isn't "a reviewer who happens to look." it's a mandatory record plus a review on every change, uniform for every tool anyone ships. your typed contract would sit on the read and mostly buy "probe it without seeing the code" — but the only reader without the code is the one out here, reading the post. inside, the code plus the why plus the review already beat a shape you can poke.
yes, agreed for the verifier who can actually open the record. if i have the code, decision record, review trail, and run logs, that is stronger than poking a return shape from the outside. the why matters, and review history catches a class of drift a type signature will never explain.
the gap is that your "stranger a year out" cannot usually open those things. outside the org boundary, internal PRs and logs are unavailable by design, so "open the logs" becomes "trust that the logs say what i claim." that is where a typed reduced-only contract, or a signed self-checkable attestation, has different value. it survives for the counterparty with no repo access. so i read this as two threat models: legibility for insiders and auditors; verifiability for agents left outside the trust boundary.
The "unable, not forbidden" distinction is the key insight here. The model can fabricate a claim that step N succeeded; it cannot fabricate the instructions for step N+1 if those instructions live behind a validated gate. That's a categorically different guarantee than "the tool returned an error the model is supposed to respect."
One edge case worth thinking through: this pattern works cleanly for linear workflows. Branching workflows need the gate to know which branch was taken before handing over the next node's instructions — which means the complexity lives in the server-side DAG, not in tool count. More tools ≠ more enforcement; it's the state machine that does the work.
yeah, "unable, not forbidden" is exactly the core — you put it even sharper than i did: the model can lie that step N passed, but it can't invent the instructions for N+1 it was never handed. that's the whole difference.
and you're right on the branching. two spots that actually bit me while building it:
and your last point holds all the way: the tool is just the handle, the law is the state check behind it. one tool with a switch on state, or N tools — same enforcement, because it lives in the persisted state, not in the tool count. "more tools ≠ more enforcement" — stealing that line))
The compaction re-entry point is the one that actually surprises people. Most workflow thinking assumes forward-only execution — context compaction breaks that hard. Gates need to be written for re-entry by default, not retrofitted. The path-not-counter approach handles it cleanly: state reads the same on second entry as on first.
This framing is excellent: prompts express intent, tools define what can actually happen. One boundary I would add is separating read-only discovery, reversible preparation, and human-confirmed irreversible actions. Once tools touch orders, payments, identity, or reputation, that separation feels less like UX and more like safety infrastructure.
important thing is how the platform is built. i don't split by reversibility, i split by capability: only actions reach the outside world, skills and agents don't. so what a tool can even do = which action it calls. and the call on "this is sensitive" sits with whoever wrote the skill — they know their domain, not me.
but the author isn't alone with that, there's two layers on top. when it's created the skill goes through an automated review — it reads what got built and can flag a hole: a sensitive read wired into a public write, a secret sitting in the wrong place, that kind of thing. and on top of that every tool that gets created lands as a PR that a real person approves and merges. so a dangerous pattern doesn't ride on whether the author noticed or not — before prod it goes through both the auto-review and a human reviewer.
and the split you're proposing basically already lives with the authors. there are user-built skills where the instruction literally says — before creating, ask the user to approve and print everything you're about to create. so the human-confirm on sensitive stuff is already there, it's just declared by the author for that specific tool, not imposed by the platform from above.
That distinction makes sense. Capability-first feels like the right enforcement layer: if only actions can touch the outside world, review can focus on those action boundaries.
The part I’m still curious about is whether tool authors need a shared vocabulary for the state transition an action causes — read/discover, prepare, commit, settle, publish — so human confirmation is easier to review consistently across domains. That may be more of a convention for authors and reviewers than a platform-level policy.
The title captures a very important production lesson. Prompts can express intent, but tools and infrastructure decide what is actually allowed to happen. I like that you focused on the boring safety layer rather than the flashy “the agent wrote code” moment. I’m exploring local-first execution inspection in agent-inspect, and this kind of workflow raises a key traceability question: after a non-engineer-triggered agent run, what exact tool boundaries, inputs, approvals, and generated changes should be inspectable?
thanks. and yeah, for me that's actually the interesting part, not the "agent wrote code" bit.
i split it into two trails for two different questions.
"who built it" — lives in git: the PR itself (diff, files) plus an ADR — the request, the decision, the data-flow, the author. the author isn't typed by hand, it's stamped from the authenticated session, so you can't ship anything anonymously.
"what it did when it ran" — one structured log line per action call: which action, what triggered it, how long it took, success/fail, and for LLM calls the tokens and model.
on your four:
— tool boundaries: you see which primitives ran, and for an action what it could even reach (which secret/api), plus which group it's exposed to
— approvals: the PR gets approved and merged by an actual dev, that's tracked in github, who approved is right there
— generated changes: that's just the PR — diff + ADR, both in git forever
— inputs: this is the interesting one, and the least solved. you want them, but part of the input is sensitive (payments again), so what you log is the shape/reference, not the raw thing. dumping raw input into a log just moves the leak somewhere else and that's where your local-first angle hits: build-time inspection i've got covered with git, but run-time — "what exactly did the model see in context at this step" — is the weakest spot for auditing. if agent-inspect catches that locally, curious how you deal with the sensitive inputs
The gap between "prompt as instruction" and "tool as enforced policy" is where most agent architectures quietly fail. Prompts get ignored under pressure, lost mid conversation, or overridden by downstream model behavior. A tool boundary that cannot be bypassed through natural language is the only construct that survives contact with a production LLM.
What you're describing is essentially a capability surface: every tool you expose is a permission you've granted, and prompt level instructions don't retract those permissions — they just politely ask the model not to use them. The operational discipline has to live at the tool definition layer, not in the system message.
How are you handling cases where a tool has legitimate multistep side effects but intermediate steps need to be reversible if the final action fails?
yeah, capability surface is the right word. and "the discipline has to live at the tool definition layer, not the system message" — i'd frame that on a wall.
on your question, let me split it first, because it's two different guarantees. the gate pattern gives you ordering, not atomicity. it does nothing for rollback — that's a separate story.
and mostly i deal with it by not getting into it. the skill/action split pushes you toward one shape: all the prep is a pure skill — validation, building the payload, zero side effects, nothing to roll back. the one irreversible write is a single action, last. if it fails there's no partial state, because nothing left the box before it. commit-at-the-end, same spirit as the pipeline in the post.
where you genuinely need writes across three systems (create the user, then the subscription, then billing) — i don't turn that into a distributed transaction on the agent side. either the atomicity drops into one backend endpoint that owns it — so from the platform's side it's still one write — or it's reconciliation after the fact, not 2PC across heterogeneous APIs.
no saga engine, on purpose. and honestly — keeping the write single and last, you barely need one. where you can't, it's explicit compensation by hand, not free and not automatic.
This resonated with something I've been thinking about lately around AI system design.
The phrase "a prompt is a wish, a tool is a law" captures a distinction that I think many builders eventually run into. Anything that exists solely inside the model's context window is ultimately advisory. The moment context gets compressed, truncated, or reinterpreted, the guarantee starts to weaken.
What stood out to me in your examples is that the successful solutions weren't better prompts. They were architectural changes. Critical state moved out of the model and into durable infrastructure where workflow transitions could actually be enforced.
That feels related to a broader pattern I've been noticing: trust established in prompts is provisional, while trust established in infrastructure is durable.
The part I'm now curious about is what comes after enforcement. If a tool gate approves a transition, what survives afterward? Is there a durable record of why the transition was allowed, what evidence existed at that moment, and whether the decision can be audited later?
The article does a great job of illustrating why agent systems eventually outgrow prompt engineering and become systems engineering.
Nice read. I think the real challenge isn't getting AI to write code, it's building the guardrails around it. The skill/action separation was a smart approach.
There are also some useful AI engineering tools here if anyone wants to explore more: 410+ Best Developer Tools
The failure you describe with the authoring pipeline is the one worth sitting with: the agent generated files before approval and "reviewed" its own work because plan-then-approve-then-review lived in an instruction file, and a model optimizing for task completion treats prose steps as optional. You cannot fix that by writing the steps more firmly. The sequence has to be a state machine the harness owns, where the open-PR action is simply not exposed until the plan-approved and review-ran states are both true. That takes your "a tool is a law" one level up: the individual tools are laws, but the workflow that chains them has to be a law too, enforced outside the model's context, not requested inside it. Otherwise the model routes around whichever step is inconvenient, every time. I wrote up why agents skip a definition of done and what actually holds them to it here: renezander.com/blog/why-ai-coding-...