Mac Anderson

Part 16: Operating the workforce

The agent asks, a rule decides

Authority is a decision at the moment of use, written down by the team that owns the system.

Mac Anderson5 min read1,095 words3 sources cited
View markdown

An agent needs to push a fix to a release branch at 2 a.m. Someone decided, at some point, whether that is fine. Where is that decision? If it is "the token has write scope," then the decision was made once, for every branch, for every task, by whoever created the token. When a team adds an agent, where does its authority get set, and who sets it?

A request, not a standing grant#

The alternative to a broad credential is a request at the moment of use. When a task needs a system, a scope, or an action, the agent asks for it. The request carries four facts, and each one is data your system already has:

  • Who started the task. The initiator from part 14.
  • Which agent is asking. Its identity.
  • What it wants. The tool and the action: github.push.
  • What the action would reach. The resource: acme/billing, branch release/2026.09.

This is the prompt parsing of part 3 applied to authority. The request is structured input. A rule is a deterministic function of it. No model decides whether the agent may act, in the same way no model decides which files to retrieve.

Three answers#

The team that owns the system writes the rule, and a rule has three possible answers.

AnswerWhat happensExample
AllowedThe action proceeds inside the same call. The record keeps the rule id.Read a public repository
DeniedThe action does not reach the code that would have done it. The agent gets a reason it can act on.Push to main
Routed to a personThe run waits. A named person answers. The run resumes with the answer in the record.Push to a release branch

A rule that allows everything is still a rule. Someone wrote it, someone can read it, and someone can change it. That is the difference between an allowed request and a standing grant: the first has an author and a row.

# rules/release-branch.yaml          owner: release engineering
id: release-branch
when:
  action: github.push
  resource.branch: "release/*"
effect: route
route_to: role:release-owner
timeout: 4h
on_timeout: deny

# rules/no-push-main.yaml
id: no-push-main
when: { action: github.push, resource.branch: main }
effect: deny
reason: "Push to a branch and open a pull request."

Write the denial for the agent#

A denied request is input to the next step, so write the reason as an instruction. "Request denied by rule no-push-main. Push to a branch and open a pull request." An agent that receives that sentence opens a pull request. An agent that receives 403 retries, then tries another path. Part 2 made the same point about tools: what the interface returns decides what the model does next.20

The default is a decision too#

Every rule set has a request that matches nothing. Decide what happens then, and say it on the same page as the rules. "When no rule matches, deny" and "when no rule matches, identity alone answers" are both defensible. An unstated default is the one to avoid, because the first person to learn it will be learning it during a review.

Where the credential lives#

For a connection the platform mediates, the agent does not need the connection credential at all. The platform holds it, checks the request, and makes the call on the agent's behalf. The agent still has its own identity and whatever reaches its context, so this is not a promise about leaks. It is a statement about custody: the secret for that connection stays in one place, and each use of it is a row.

Evidence · Per-request decisions are the established pattern

NIST's zero trust architecture places a policy decision point and a policy enforcement point between every subject and every resource. Access is granted per request, under dynamic policy, and limited to what the task needs.22 OWASP's guidance for excessive agency recommends the same controls for LLM applications: limit the permissions an extension holds, execute actions in the context of the specific user, and require human approval for high-impact actions.23 The three answers above are those recommendations in one mechanism.

NIST SP 800-207 · OWASP, Top 10 for LLM applications
Counterweight · Routing has a cost

Every routed request makes a person the latency. Route too much and approvals become a reflex, which is worse than a written rule. Review the routed requests monthly. A request the same person approves every time, without changes, is a candidate for an allow rule with a narrower condition. A request that is often rejected is a candidate for a deny rule with a better reason.

Where Oxagen fits

In Oxagen a decision rule names a capability, a condition, and one effect: allow, deny, or require approval. For governed calls, the kernel checks the rule after identity and entitlement and before the handler runs, so a denied action does not reach the code that would have done it. A routed request pauses the run. The person answers from the Access page, and the run resumes with the answer in the record. For mediated connections, Oxagen uses the connection credential on the agent's behalf and the agent does not receive it. When a workspace has no rules, identity alone answers requests, and the Access page says so.

Do this week
  1. List the ten write actions your agents take most. Pull them from the logs of part 14. For each, write the answer you want: allowed, denied, or routed, and to whom.
  2. Write the three rules that matter most as files. Use the shape above. Give each an id, an owner, and a reason.
  3. State the default. One sentence at the top of the rules directory that says what happens when nothing matches.
  4. Rewrite one denial message. Pick the denial your agents hit most and make its text the next step.
  5. Route one action to a named person. Set a timeout and an on-timeout answer. Run it once in staging and time how long the run waits.
Measure it
  • requests_by_answer: allowed, denied, and routed, per agent, per week. A sudden rise in denials after a deploy is a tooling change nobody told security about.
  • time_to_answer_p50 and p95: how long routed requests wait. This is the latency you are adding to time_to_green from part 13.
  • unmatched_requests: requests answered by the default. Each one is a rule nobody has written yet.
  • retries_after_denial: calls an agent makes after a denial before it changes course. A good reason brings this toward 0.
Takeaway

Do not hand your agents the keys. Let the agent ask at the moment of use, let a rule the owning team wrote answer allowed, denied, or routed to a person, and keep the answer.

Cite this

Anderson, M. (2026). The agent asks, a rule decides. In Engineering Deterministic AI Coding Agents (2nd ed., Part 16). Oxagen Inc. https://macanderson.com/manual/the-agent-asks-a-rule-decides

BibTeX
@incollection{anderson2026theagentasks,
  author    = {Anderson, Mac},
  title     = {The agent asks, a rule decides},
  booktitle = {Engineering Deterministic AI Coding Agents},
  edition   = {Second},
  chapter   = {16},
  publisher = {Oxagen Inc.},
  address   = {Los Angeles, CA},
  year      = {2026},
  url       = {https://macanderson.com/manual/the-agent-asks-a-rule-decides}
}

Updates by email

Get the next edition of the field manual and new research when it is published.

No spam. Unsubscribe any time. Read the privacy note.