The first thing I open at work is no longer a code editor. It is a bot in our internal Slack called Anvil. That sentence sounds like a victory lap for automation, so I should correct the impression immediately: attaching a bot did not change my day. The useful change came later, when we built a harness that made work started by someone else meet the same bar as work I started myself.

The old routine was familiar. QA would ask, “If I change this label, what else will move?” I would stop what I was doing, unload the context in my head, open the repository, trace the relevant code, reply, and then reconstruct my original task. A small question could consume half a day. QA waited for an answer; I waited to recover my focus. Neither side was doing the work it had planned.

Now QA sends Anvil a ticket number and a short request. The bot reads the ticket and the code, classifies the impact, and handles a low-risk change through to a merge request. A broader change produces an approval request before implementation. Around lunch I review the MRs that accumulated. The important difference is not raw speed. It is that a change initiated from Slack goes through the same project rules as a change initiated from my terminal.

The figures below come from one internal retrospective that I supplied for this article. They are not a product benchmark, and they do not predict what another organization will achieve. Public technical claims link to the official documentation for Claude Code, ESLint, and TypeScript. The name Anvil, the workflow, and the outcome figures are first-party operating notes rather than vendor claims.

Why I wrote this

Delegation does not happen when a message reaches a bot. If I still have to reread every line of the result, the input box changed but the work did not. I can call it delegated only when there is a credible reason to expect the same result after the task leaves my hands. That reason turned out to be the project rules, not a particularly clever model.

At first I tried making the prompt more complete. That quickly became absurd. Rules such as “a billing month is 28 days,” “the minimum order is KRW 50,000,” and “reject a record when its currency unit is invalid” do not belong in every chat message. Some already existed in the codebase, but not in a form that every new task would discover in the same order.

We collected policy files, risk levels, validation commands, and handoff conditions in one operating definition. Slack and the terminal became two entrances to the same process. Anvil is not a separate intelligence layer; it is a thin interface that invokes the coding agent we already use in a non-interactive mode. The official Claude Code headless documentation describes claude -p as a way to run one query and exit, which is the public mechanism behind this kind of wrapper.

The distinction matters because the interface is easy to copy. The operating definition is the work. A beautiful Slack message that launches an agent without the same repository context, permission policy, and finishing checks gives non-developers a faster path to inconsistent output.

The work I used as the test case

I used one QA ticket as the reference flow. The request first has to identify the repository and relevant files. The agent then determines whether the change touches policy or a core module. A small change can proceed to implementation and checks. A broad one waits for the owner before code changes begin. In every path, a person retains the final decision about product intent and release.

A single flow from a Slack request through impact analysis, code change, quality gates, merge request, and final human judgment

The Slack path is deliberately plain:

[QA request in Slack]
→ Anvil
→ non-interactive coding-agent run
→ project rules and risk classification
→ code change
→ lint · typecheck · test
→ merge request or owner approval request

Slack is only the interface. The project definition, validation commands, and blocking rules are the harness. Anthropic’s configuration debugging guide makes a similar distinction: project instructions tell Claude Code how a project works, while permissions and hooks enforce limits. Writing “never run a dangerous command” in prose is not the same as preventing that command from running.

That separation also kept the workflow usable outside Slack. When I start a change in the terminal, the same definition applies. A subagent does not have to inherit the whole history of a chat to know the month-length rule or which checks must pass. It reads the shared operating material and encounters the same gates.

What I compared in practice

The first comparison was not model against model. It was a request that merely asked for checks versus a request whose response could not finish until checks passed. Our gate runs as the agent tries to stop. Fixable lint issues are handled with ESLint --fix; remaining lint errors, tsc --noEmit, and the relevant tests must all pass before the result can leave the workflow. The Claude Code hooks guide documents lifecycle events and Stop hooks, which can run deterministic commands at the completion boundary.

Dangerous actions are divided into two layers. Commands such as rm -rf, DROP TABLE, or creating an .env file are blocked because execution itself carries unacceptable risk in this path. Actions such as git push --force or bypassing checks with --no-verify may have a legitimate exceptional use, but they require a person to approve and leave a record. One is a guardrail around the furnace; the other is a warning sign that asks for a qualified operator.

Minor changes proceed autonomously, moderate changes receive review after implementation, and policy or core changes require approval first

Impact tier Typical examples Route
MINOR Copy changes, simple mappings Implement → quality gates → MR
MODERATE Component structure, style refactoring Implement → quality gates → owner review
MAJOR Policy constants, validation rules, core modules Owner approval → implement → MR review

Putting a bottleneck at MAJOR allowed us to open MINOR and MODERATE more widely. Sending every change through at the same speed is not automation; it is the absence of validation. Requiring advance approval for every typo has the opposite problem. The bot would automate waiting.

The tier is decided from project-specific paths and policy keywords. It is not a universal security classifier. A text file may be harmless in one repository and control customer pricing in another. The useful part is not the labels themselves but the explicit connection between expected impact and the moment at which a person enters the flow.

Where it usually breaks

Typing code took less of my time, but the work did not disappear. In my rough recollection, implementation moved from about 60% of my effort to almost zero, and writing tests moved from about 20% to almost zero. Explicit policy and requirements rose to roughly 60%, review to about 30%, and merge and release work to around 10%. The figures are personal estimates, not timesheet measurements, so the before and after columns are not intended to sum neatly to 100.

A personal estimate showing time moving away from implementation and test writing toward policy, specifications, review, merge, and release

I still type. The keyboard now captures user paths, input-validation rules, error messages, and failure cases rather than most of the implementation. A one-line request such as “improve login” becomes a work note that says where the journey starts, which values arrive, what happens when validation fails, and what the user sees next. That document is both the specification presented to the agent and the review surface for the person who accepts the result.

The new workflow has broken in mundane ways. On one screen with several error modals, the bot could not confidently decide which repository to change. The run stopped at the first step. We added a fallback that asks the user to choose among candidate repositories, and the workflow could continue. I had treated error recovery as a code problem; it was also an interface problem for the person who had initiated the job.

Ambiguous policy caused more failures than a weak model. If a domain rule remains in someone’s head as “obviously we do it this way,” an agent cannot read it. Repeated failures pushed me to edit the policy documents and harness before reaching for another code change. A model’s inability to read my mind is not a bug. It is the boundary around the system.

Passing tests did not mean the feature was complete

I went back through one completed change and scored two different things. Technical completeness was 85%; specification coverage was 62.5%. All 151 Jest tests passed, and all six modal states were present. Yet a line-by-line comparison with 40 specification items found only 25 in the product. The missing 15 involved category policy, ownership scope, and search filters—business rules that lint, types, and existing tests had no basis to infer.

One internal retrospective comparing 85 percent technical completeness with 62.5 percent specification coverage, 151 passing tests, six modal states, and 25 of 40 requirements reflected

The odd part was that the agent had not completely missed the gaps. Its report listed category, owner display, and save confirmation as unfinished. It found and recorded the missing work, then stopped without connecting those observations to the definition of done. A technical gate could not catch that failure because the code was valid and the tests it knew about were green.

We began adding a second layer for behavior and specification checks. It finds which policies a change touches, runs the related acceptance criteria, and warns when the document and result diverge. It does not yet run across every project. It also cannot discover every unwritten rule. The modest current goal is to connect “what must be true before this is done” to executable checks so unfinished items cannot live only in a report.

What I would do next time

I would not start by building the bot. I would first ask whether two people starting the same task encounter the same rules and the same checks. If not, a Slack bot is a faster entrance to the existing confusion.

I would keep three boundaries. First, dangerous commands belong in an execution control, not only in a prompt. Second, human approval should move earlier as impact rises. Third, test status and requirement status need separate scores. Technical gates answer whether the code is broken. Acceptance checks answer whether we built the thing we meant to build.

People remain in the workflow. A change involving money, customer data, or core policy gets approval from someone with the relevant experience. Recovery from a failed automation and the final release decision also stay with a named owner. The goal is not to remove the human. It is to let machines handle repeated verification while humans occupy the points where judgment changes the outcome.

Checklist before you copy this

  • Write down three policies that your team repeatedly explains from memory.
  • Separate autonomous changes, review-required actions, and blocked commands at the command level.
  • Run lint, type checks, and tests as enforced completion gates rather than optional prompt requests.
  • Route copy and mapping changes differently from component, policy, validation, and core-module work.
  • Track specification coverage separately from the number of tests that pass.
  • Give the initiating user a recovery path for choosing a repository or next action when classification fails.
  • Name one person who owns final merge and any external deployment.

You do not need an internal Slack bot to begin. Applying one project definition and the same hooks in the terminal provides much of the value. Pick one question that repeats several times a day, allow only a low-risk change to reach an MR, and watch the reopened-MR rate and missing-requirement count before celebrating hours supposedly saved.

Put the operating boundary on paper

This internal retrospective is one operating case, not proof that the same controls have been tested or will work in another repository. Use the worksheet to name the affected paths, risk tier, approval point, and checks before an agent starts a change. Download the AI agent change-risk worksheet.

Sources I checked

My day changed not because I stopped writing code, but because I became more precise about what needed to be made. Inside a clear boundary the model can move freely; outside it a person decides. Harness engineering felt less like making a model stronger and more like making the limits of delegation visible.

References and reporting

Public pages used for reported facts, official documentation, policy background, product details, and claims that may change.