{"id":6680,"date":"2026-07-27T14:24:34","date_gmt":"2026-07-27T14:24:34","guid":{"rendered":"https:\/\/dialnexa.com\/blogs\/?p=6680"},"modified":"2026-07-27T14:48:48","modified_gmt":"2026-07-27T14:48:48","slug":"ai-agent-graduated-agency","status":"publish","type":"post","link":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/","title":{"rendered":"How to Give AI Agency Without Giving It Reckless Power"},"content":{"rendered":"<nav class=\"alfred-series-nav\" style=\"margin:0 0 2.25rem;padding:1.25rem 1.5rem;border:1px solid #e0e7ff;border-left:4px solid #6366f1;border-radius:10px;background:#f8f7ff;\">\n<p style=\"margin:0 0 .6rem;font-size:.78rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#4f46e5;\">Building Alfred \u00b7 a six part series<\/p>\n<ol style=\"margin:0;padding-left:1.25rem;font-size:.95rem;line-height:1.75;\">\n<li><a href=\"https:\/\/dialnexa.com\/blogs\/company-second-brain-architecture\/\">Why a second brain is a system, not a chat box<\/a><\/li>\n<li><a href=\"https:\/\/dialnexa.com\/blogs\/grounding-ai-agent-company-data\/\">Grounding: accessing company reality safely<\/a><\/li>\n<li><a href=\"https:\/\/dialnexa.com\/blogs\/company-memory-governance-postgres\/\">Memory as a governance problem<\/a><\/li>\n<li><strong>Graduated agency<\/strong> <span style=\"color:#6b7280;font-size:.85rem;\">\u00b7 you are here<\/span><\/li>\n<li><a href=\"https:\/\/dialnexa.com\/blogs\/production-ai-agent-reliability\/\">Production reliability<\/a><\/li>\n<li><a href=\"https:\/\/dialnexa.com\/blogs\/ai-agent-learning-loop\/\">The learning loop<\/a><\/li>\n<\/ol>\n<\/nav>\n<p><em>Building Alfred, Part 4 of 6. <a href=\"https:\/\/dialnexa.com\/blogs\/company-second-brain-architecture\/\">Parts 1 to 3 covered architecture, grounding and memory<\/a>. This one is about letting an agent do things.<\/em><\/p>\n<p>Alfred investigates an incident, finds the likely defect, and identifies the exact code path involved.<\/p>\n<p>At that moment the interesting question is no longer whether the AI is intelligent. It is whether the AI should be allowed to do anything about what it found.<\/p>\n<p>Send the customer email? Change a setting? Edit the repository? Merge the fix?<\/p>\n<p>Agent discussions tend to split into two unhelpful camps. One builds assistants that explain everything and change nothing. The other treats removing human control as the goal.<\/p>\n<p>We chose a third thing: give the agent enough power to move work forward, and put the strongest boundary exactly where impact becomes hard to reverse.<\/p>\n<figure class=\"alfred-diagram\" style=\"margin:2.5rem 0;\">\n<svg viewBox=\"0 0 760 300\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:100%;height:auto;display:block;font-family:Inter,system-ui,sans-serif;\" role=\"img\" aria-label=\"A ladder of six agency levels from observe to review, with the approval boundary marked between prepare and approve\">\n<rect x=\"1\" y=\"1\" width=\"758\" height=\"298\" rx=\"12\" fill=\"#f8f7ff\" stroke=\"#e0e7ff\"\/>\n<g font-family=\"Inter,system-ui,sans-serif\">\n<rect x=\"30\" y=\"212\" width=\"106\" height=\"52\" rx=\"8\" fill=\"#ffffff\" stroke=\"#c7d2fe\"\/>\n<text x=\"83\" y=\"234\" text-anchor=\"middle\" fill=\"#1f2937\" font-size=\"13\" font-weight=\"600\">Observe<\/text>\n<text x=\"83\" y=\"251\" text-anchor=\"middle\" fill=\"#6b7280\" font-size=\"10.5\">read only<\/text>\n<rect x=\"150\" y=\"180\" width=\"106\" height=\"84\" rx=\"8\" fill=\"#ffffff\" stroke=\"#c7d2fe\"\/>\n<text x=\"203\" y=\"203\" text-anchor=\"middle\" fill=\"#1f2937\" font-size=\"13\" font-weight=\"600\">Recommend<\/text>\n<text x=\"203\" y=\"220\" text-anchor=\"middle\" fill=\"#6b7280\" font-size=\"10.5\">human acts<\/text>\n<rect x=\"270\" y=\"148\" width=\"106\" height=\"116\" rx=\"8\" fill=\"#ffffff\" stroke=\"#c7d2fe\"\/>\n<text x=\"323\" y=\"171\" text-anchor=\"middle\" fill=\"#1f2937\" font-size=\"13\" font-weight=\"600\">Prepare<\/text>\n<text x=\"323\" y=\"188\" text-anchor=\"middle\" fill=\"#6b7280\" font-size=\"10.5\">draft, worktree<\/text>\n<rect x=\"390\" y=\"116\" width=\"106\" height=\"148\" rx=\"8\" fill=\"#ffffff\" stroke=\"#c7d2fe\"\/>\n<text x=\"443\" y=\"139\" text-anchor=\"middle\" fill=\"#1f2937\" font-size=\"13\" font-weight=\"600\">Validate<\/text>\n<text x=\"443\" y=\"156\" text-anchor=\"middle\" fill=\"#6b7280\" font-size=\"10.5\">argv allowlist<\/text>\n<rect x=\"510\" y=\"84\" width=\"106\" height=\"180\" rx=\"8\" fill=\"#4f46e5\"\/>\n<text x=\"563\" y=\"107\" text-anchor=\"middle\" fill=\"#ffffff\" font-size=\"13\" font-weight=\"700\">Approve<\/text>\n<text x=\"563\" y=\"124\" text-anchor=\"middle\" fill=\"#c7d2fe\" font-size=\"10.5\">human, explicit<\/text>\n<rect x=\"630\" y=\"52\" width=\"106\" height=\"212\" rx=\"8\" fill=\"#ffffff\" stroke=\"#c7d2fe\"\/>\n<text x=\"683\" y=\"75\" text-anchor=\"middle\" fill=\"#1f2937\" font-size=\"13\" font-weight=\"600\">Review<\/text>\n<text x=\"683\" y=\"92\" text-anchor=\"middle\" fill=\"#6b7280\" font-size=\"10.5\">attributable<\/text>\n<\/g>\n<line x1=\"500\" y1=\"34\" x2=\"500\" y2=\"278\" stroke=\"#ef4444\" stroke-width=\"2\" stroke-dasharray=\"6 4\"\/>\n<text x=\"504\" y=\"30\" fill=\"#ef4444\" font-size=\"11.5\" font-weight=\"700\">approval boundary<\/text>\n<text x=\"30\" y=\"30\" fill=\"#6b7280\" font-size=\"11.5\">reversible, low friction<\/text>\n<text x=\"30\" y=\"288\" fill=\"#6b7280\" font-size=\"11\">Capability checked at dispatch on every call, not only when tools are advertised<\/text>\n<\/svg><figcaption style=\"margin-top:.75rem;font-size:.9rem;color:#6b7280;text-align:center;\">Six rungs of agency. Human attention concentrates where actions stop being reversible.<\/figcaption><\/figure>\n<h2>The ladder<\/h2>\n<p>Reading a call record is not the same as changing it. Drafting an email is not sending it. Preparing a change in an isolated branch is not merging it.<\/p>\n<p>Six rungs, and Alfred implements all of them:<\/p>\n<ol>\n<li><strong>Observe.<\/strong> Retrieve narrow, permission-scoped evidence. Read only wherever possible.<\/li>\n<li><strong>Recommend.<\/strong> Explain findings, state uncertainty, suggest a step. The human acts.<\/li>\n<li><strong>Prepare.<\/strong> Produce a complete draft, plan or isolated change. Reviewable, not final.<\/li>\n<li><strong>Validate.<\/strong> Run organisation-configured checks and report the result honestly.<\/li>\n<li><strong>Approve.<\/strong> A human authorises at the point of impact.<\/li>\n<li><strong>Review.<\/strong> The outcome stays visible, attributable and reversible where possible.<\/li>\n<\/ol>\n<p>The value of the ladder is that capability can grow without collapsing every decision into one permission called &#8220;agent access.&#8221;<\/p>\n<h2>Permissions have to exist outside the prompt<\/h2>\n<p>A prompt can tell a model not to do something. That is useful. It is not a boundary.<\/p>\n<p>Alfred has three roles and ten capabilities in one dict. Exactly one capability currently gates tools:<\/p>\n<pre class=\"wp-block-code\"><code>\"manage_repository_code\": frozenset({\"admin\", \"tech\"}),<\/code><\/pre>\n<p>and it covers eleven tools:<\/p>\n<pre class=\"wp-block-code\"><code>TOOL_CAPABILITY_REQUIREMENTS: dict[str, str] = {}\n\ndef _register_repository_write_tools() -&gt; None:\n    for name in (\n        START_CODING_WORKSPACE_TOOL_NAME, READ_CODING_WORKSPACE_FILE_TOOL_NAME,\n        EDIT_CODING_WORKSPACE_TOOL_NAME, INSPECT_CODING_WORKSPACE_TOOL_NAME,\n        VALIDATE_CODING_WORKSPACE_TOOL_NAME, WAIT_CODING_WORKSPACE_CHECKS_TOOL_NAME,\n        READ_CODING_WORKSPACE_CI_LOGS_TOOL_NAME, PUBLISH_CODING_WORKSPACE_TOOL_NAME,\n        CREATE_PULL_REQUEST_TOOL_NAME, UPDATE_PULL_REQUEST_TOOL_NAME,\n        CLOSE_PULL_REQUEST_TOOL_NAME,\n    ):\n        TOOL_CAPABILITY_REQUIREMENTS[name] = REPOSITORY_WRITE_CAPABILITY<\/code><\/pre>\n<p>Two separate mechanisms enforce it, and the distinction matters.<\/p>\n<p><strong>Advertisement<\/strong> is per request. <code>ToolRegistry.schemas()<\/code> decides what the model is even told exists:<\/p>\n<pre class=\"wp-block-code\"><code>if self.code_tool is not None:\n    repo_names = self.code_tool.repo_names()\n    schemas.extend(code_tool_schemas(repo_names))\n    if has_capability(user_roles, REPOSITORY_WRITE_CAPABILITY) or approval_enabled:\n        schemas.extend(coding_workspace_schemas(repo_names))\n        schemas.append(create_pull_request_schema(repo_names))<\/code><\/pre>\n<p>Repository enums come from live GitHub discovery. The Gmail <code>account<\/code> enum contains only the caller&#8217;s linked addresses. Inside a scheduled run, the repeat-admin tools are withheld entirely so, in the words of the comment, &#8220;a scheduled task cannot silently breed more scheduled tasks.&#8221;<\/p>\n<p><strong>Execution<\/strong> re-checks at dispatch, per tool call:<\/p>\n<pre class=\"wp-block-code\"><code>if not context.has_capability(REPOSITORY_WRITE_CAPABILITY):\n    return ToolExecutionResult(\n        content=\"Only users with the admin or tech role can write code or create PRs.\",\n        is_error=True,\n    )<\/code><\/pre>\n<p>Not advertising a tool is a UX optimisation. Refusing it at dispatch is the boundary. If those two ever disagree, the dispatch check wins, which is the correct direction for them to fail.<\/p>\n<h2>Four human-in-the-loop mechanisms<\/h2>\n<h3><code>ask_user<\/code> pauses the turn<\/h3>\n<p>A well-formed <code>ask_user<\/code> called on its own ends the turn without emitting a <code>tool_result<\/code>. The question is persisted. When the user answers, the answer is inserted <strong>as<\/strong> the tool result and the same turn resumes.<\/p>\n<p>That is more subtle than it sounds. The alternative, treating the answer as a new user message, loses the model&#8217;s in-progress reasoning and forces it to rediscover where it was. Calling <code>ask_user<\/code> alongside other tools, or malformed, gets a specific misuse result rather than a generic error, because the model needs to learn the protocol, not just that it failed.<\/p>\n<h3>The coding plan gate<\/h3>\n<p>This is the strongest control in the system. Before any repository mutation, the model must call <code>request_coding_plan_approval<\/code>, alone, and wait.<\/p>\n<pre class=\"wp-block-code\"><code>def coding_plan_gate_error(self, action: str) -&gt; str | None:\n    if not self.coding_plan_required or self.coding_plan_approved:\n        return None\n    return (\n        f\"An approved coding plan is required before {action}. Investigate the scoped \"\n        \"repositories, call request_coding_plan_approval on its own, and wait for the user \"\n        \"to approve it.\"\n    )<\/code><\/pre>\n<p>Two details make it hold.<\/p>\n<p>First, the requirement is derived from capability, not from how the request was phrased:<\/p>\n<pre class=\"wp-block-code\"><code># The heuristic improves routing, but safety cannot depend on phrasing.\n# Any repository mutation attempt must be grounded and plan-approved.\ncoding_preflight_required=repository_write_capable,<\/code><\/pre>\n<p>There is a phrasing heuristic, and it improves model routing. It is explicitly not load bearing. A user who says &#8220;just quickly patch this&#8221; does not get a different safety posture.<\/p>\n<p>Second, approval requires <strong>grounding<\/strong>, not just consent. <code>coding_grounding_error<\/code> demands that the model has called <code>understand_repository<\/code> plus a search or read in every scoped repository before it is allowed to propose a plan. When a task names three repositories, it cannot read the most convenient one and guess at the contracts in the others. Repository access stops being a search feature and becomes an evidence requirement.<\/p>\n<p>Approval state is reconstructed from persisted history on every turn, and any fresh natural-language user turn resets the approved scope. You cannot get a plan approved for one thing and then reuse the approval for another.<\/p>\n<h3>Server-side <code>confirmed<\/code> backstops<\/h3>\n<p>Prompt-level confirmation is not enough for anything that crosses the company boundary. Gmail send:<\/p>\n<pre class=\"wp-block-code\"><code>if not context.repeat_run_id and tool_input.get(\"confirmed\") is not True:\n    return ToolExecutionResult(content=GMAIL_SEND_UNCONFIRMED_MESSAGE, is_error=True)<\/code><\/pre>\n<p><code>confirmed<\/code> is also a required schema field outside scheduled runs, so the model cannot omit it and hope. Repeat task create, update and delete carry the same flag.<\/p>\n<p>The general principle: the user should see the object that will cross the boundary before it crosses. An ambiguous &#8220;go ahead&#8221; three messages earlier should not become an external message.<\/p>\n<h3>Discord approvals turn a refusal into a request<\/h3>\n<p>On Discord, a capability the user lacks does not produce a dead end. It produces an approval request posted to admins with approve and deny buttons.<\/p>\n<p>The implementation details are where the value is. The approval row survives a process restart. The requester is excluded from the admin list, so nobody approves their own request. With no eligible admins the request is marked <code>expired<\/code> rather than hanging. The decision is a single-winner compare-and-swap, so two admins clicking simultaneously cannot both grant. On approval the original request is re-run <strong>once<\/strong>, with exactly that one capability granted. And the model receives a stop-do-not-retry result, so it does not spend the intervening iterations trying alternate routes.<\/p>\n<h3>What &#8220;dangerous&#8221; means here<\/h3>\n<p>There is no data-driven risk taxonomy. Danger is expressed three ways: the capability requirement set, the <code>confirmed<\/code>-gated set, and removal of the primitive entirely. There is no merge tool. There is no write SQL. You cannot gate what does not exist.<\/p>\n<h2>The coding workspace<\/h2>\n<p>Once a plan is approved, implementation moves into a per-conversation git worktree on a generated branch.<\/p>\n<p>Edits are exact-match, single-occurrence, atomic batches. Path traversal is rejected, repository metadata is off limits, file count and byte size are bounded, and folder deletion cannot target the repository root. Any edit resets <code>validation_passed<\/code>, so a change cannot inherit a previous run&#8217;s green check.<\/p>\n<p>Validation is the part I would point at if someone asked what taking this seriously looks like. The model does not get a shell:<\/p>\n<pre class=\"wp-block-code\"><code># The model cannot provide shell commands.<\/code><\/pre>\n<p>Trusted administrators configure argv arrays. The environment is scrubbed down to <code>PATH<\/code>, <code>HOME<\/code>, <code>TMPDIR<\/code>, <code>LANG<\/code>, <code>LC_ALL<\/code> plus <code>CI=true<\/code>, so application secrets are not present. Output is capped, and the GitHub token is replaced with <code>***<\/code> in both git stderr and validation output, because the token appears in the on-disk remote URL and must never reach a prompt or a tool result.<\/p>\n<p>Then the check that I think is the cleverest thing in the file: if validation itself modified tracked files or moved <code>HEAD<\/code>, the result is invalid. A test suite that quietly rewrites a lockfile does not get to certify its own success.<\/p>\n<p>Local validation execution is <strong>disabled by default<\/strong>, with an honest reason in the config comment: &#8220;argv allowlisting is not an OS\/container sandbox.&#8221; In the default deployment there is no local test execution at all, and the real execution boundary is a draft pull request plus GitHub Actions. I would rather ship a feature flag defaulted off with a truthful comment than claim a sandbox we did not build.<\/p>\n<h2>The pull request is the product boundary<\/h2>\n<p>Publishing produces a draft PR. The constraints:<\/p>\n<ul>\n<li>Branch targets are always <code>batman-agent\/*<\/code>. Pushes are never forced.<\/li>\n<li>There is no merge primitive. The comment in the source states it directly: &#8220;There is deliberately no primitive that writes without opening a PR and no merge primitive.&#8221;<\/li>\n<li>Fork PRs are rejected. <code>update_pull_request<\/code> requires the PR to be open, in the same repository, with a head matching the agent prefix.<\/li>\n<li>Checks are evaluated against the exact published commit SHA, not a convenient earlier run.<\/li>\n<li>Failed job logs can be read, a repair prepared, and the new head validated again.<\/li>\n<li>Commits are authored as <code>DialNexa Batman Agent &lt;batman-agent@dialnexa.com&gt;<\/code>, and the PR body says &#8220;Created by the Batman agent for {requested_by}.&#8221;<\/li>\n<li>Closing a PR posts the reason as a comment. Merged PRs cannot be closed by the agent. The branch is deleted only if it carries the agent prefix and belongs to the same repository.<\/li>\n<\/ul>\n<p>One caveat the code states about itself: &#8220;a human could name a branch batman-agent\/* &#8211; but the blast radius stays confined to that namespace.&#8221; The prefix is a confinement mechanism, not proof of authorship. Do not build an audit conclusion on it.<\/p>\n<p>This workflow does not make the agent less agentic. It lets the agent complete far more of the expensive work while preserving an accountability surface engineers already know how to use.<\/p>\n<h2>Blast radius is mostly a budget problem<\/h2>\n<p>The control that does the most work in practice is money.<\/p>\n<p><code>BudgetService<\/code> enforces a per-user monthly USD budget, default $25, with an optional daily cap. Three details:<\/p>\n<pre class=\"wp-block-code\"><code># Advisory-locked read so two concurrent starts can't both slip under the cap.<\/code><\/pre>\n<p>Without that, three simultaneous requests all read the same remaining balance and all decide they can afford it.<\/p>\n<pre class=\"wp-block-code\"><code># Unknown model IDs bill at the most expensive configured rate.<\/code><\/pre>\n<p>A new model appearing in the wild must never be free. Cache writes bill at 1.25x, cache reads at 0.1x, web search at a flat $0.01 per request.<\/p>\n<p>And inside a scheduled run, the budget is enforced <em>before<\/em> each model call rather than after. <code>messages.count_tokens<\/code> runs, <code>affordable_output_tokens<\/code> shrinks <code>max_tokens<\/code> so one call cannot breach the cap, and if real billing beats the estimate, unexecuted tool calls receive:<\/p>\n<blockquote>\n<p>Not executed because the Repeat run budget was exceeded.<\/p>\n<\/blockquote>\n<p>An explicit no-side-effect-after-the-breach path. The run stops without half-completing something.<\/p>\n<p>Other bounds: three concurrent runs per user, thirty iterations per message, a 900 second session lock TTL, <code>list_due_tasks(limit=50)<\/code>, and a 50,000 event buffer safety valve.<\/p>\n<p>The nine-line allowlist and three-line denylist on the internal API proxy, the twenty-four keyword SQL denylist, the <code>batman-agent\/<\/code> prefix, the GitHub org scope, and the per-provider connector allowlists <a href=\"https:\/\/dialnexa.com\/blogs\/grounding-ai-agent-company-data\/\">from Part 2<\/a> all serve the same purpose: shrink what a mistake can reach.<\/p>\n<h2>Reversibility as a design axis<\/h2>\n<p>I now evaluate agent actions largely by how recoverable they are.<\/p>\n<p>A draft can be edited. An isolated branch can be deleted. A proposed memory can be rejected. A browser connection can be revoked. A scheduled task can be disabled.<\/p>\n<p>A sent email cannot be unsent. A merged production change is expensive to undo. Money spent through a connector is gone.<\/p>\n<p>The closer an action sits to the irreversible end, the more explicit the approval and the evidence should be. That is why <code>gmail_send_email<\/code> has a server-side flag and <code>gmail_search<\/code> does not, and why the Outrank purchase endpoint is blocked outright rather than gated.<\/p>\n<h2>What is missing, plainly<\/h2>\n<p>The gap list matters more than the feature list, because anyone evaluating this should know where it is thin.<\/p>\n<p><strong>The capability model is effectively one bit.<\/strong> One capability, eleven tools. Sending email as the user, scheduling autonomous spend, overwriting a team-shared skill, generating client-facing PDFs and querying production Postgres all require nothing beyond having Alfred enabled. Those are gated by presence (is a Gmail account linked, is a connector configured) rather than by role.<\/p>\n<p><strong>Two enforcement mechanisms can drift.<\/strong> <code>required_capability()<\/code> is consulted only in the Discord approval branch. Hard enforcement is hand-written <code>has_capability<\/code> checks inside dispatch. A new gated tool added to the dict but not given a dispatch check would be gated on Discord and unenforced everywhere else. That should be one mechanism.<\/p>\n<p><strong>No dry-run mode anywhere.<\/strong> The nearest analogue is inspecting a diff before publishing.<\/p>\n<p><strong>Idempotency is nearly absent.<\/strong> It exists for managed connector writes, model supplied and optional. Gmail send, PR creation and workspace publish have none. The interrupted-tool repair text tells the model &#8220;Call the tool again if you still need its result,&#8221; which for a send or a push is a duplicate-side-effect path.<\/p>\n<p><strong>No rate limiting on tool calls or Alfred routes.<\/strong> The limits are dollars, thirty iterations, and three parallel runs. The parallel-run cap is explicitly soft: &#8220;a race between simultaneous sends can briefly overshoot by one; the cap is a guardrail against runaway spend, not a hard scheduler.&#8221;<\/p>\n<p><strong>Cancellation probably does not stop in-flight tools.<\/strong> Cancel cancels the driving task. Tool tasks are independent and awaited plainly, so a cancel during a git push raises in the loop without cancelling the push.<\/p>\n<p><strong>Discord approvals never time out.<\/strong> There is no sweep for pending rows. An approval clicked next week will re-run the original request text.<\/p>\n<p><strong><code>escalate_to_opus<\/code> is self-service.<\/strong> Any user can move a session to the more expensive model. The only backstop is the budget.<\/p>\n<p><strong>No structured audit table.<\/strong> Auditability comes from the chat transcript, which is genuinely rich: exact tool sequence, complete inputs, complete results, error flags, per-call cost, branch and PR for repository writes. What it lacks is the roles held at execution time, which have to be inferred from current user records. For an access-control incident, that is the wrong direction to be inferring in.<\/p>\n<p><strong>No anomaly detection, circuit breaker or global kill switch.<\/strong> Turning Alfred off means flipping a per-user flag, removing the API key, or restarting the process.<\/p>\n<h2>The real goal is trusted momentum<\/h2>\n<p>Nobody wants to approve every log read. Nobody wants an agent making consequential external changes in the background.<\/p>\n<p>The design problem is to remove friction from reversible work and concentrate human attention at the point of meaningful impact. Get that split right and people stop thinking about the agent&#8217;s permissions at all, which is the actual sign that it worked.<\/p>\n<p><a href=\"https:\/\/dialnexa.com\/blogs\/production-ai-agent-reliability\/\">Next, in Part 5<\/a>: what keeps this running when browsers disconnect, processes restart and tools fail halfway through.<\/p>\n<p><em>Where would you place the human approval boundary in your highest-value AI workflow?<\/em><\/p>\n<hr style=\"margin:2.5rem 0 1.5rem;border:0;border-top:1px solid #e5e7eb;\" \/>\n<div class=\"alfred-series-pager\" style=\"display:flex;flex-wrap:wrap;gap:1rem;\">\n<div style=\"flex:1 1 240px;padding:1rem 1.25rem;border:1px solid #e0e7ff;border-radius:10px;background:#f8f7ff;\">\n<p style=\"margin:0 0 .3rem;font-size:.75rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#6b7280;\">Previous<\/p>\n<p><a href=\"https:\/\/dialnexa.com\/blogs\/company-memory-governance-postgres\/\" style=\"font-weight:600;\">Memory as a governance problem<\/a>\n<\/div>\n<div style=\"flex:1 1 240px;padding:1rem 1.25rem;border:1px solid #e0e7ff;border-radius:10px;background:#f8f7ff;\">\n<p style=\"margin:0 0 .3rem;font-size:.75rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#6b7280;\">Next in the series<\/p>\n<p><a href=\"https:\/\/dialnexa.com\/blogs\/production-ai-agent-reliability\/\" style=\"font-weight:600;\">Production reliability<\/a>\n<\/div>\n<\/div>\n<p style=\"margin-top:1.5rem;font-size:.95rem;color:#4b5563;\">Alfred runs on <a href=\"https:\/\/dialnexa.com\/\">DialNexa<\/a>, our voice AI platform for businesses. If you are building internal agents and want to compare notes, the comments are open.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Six rungs of agent agency, capability checks re-run at dispatch, mandatory coding plan approval with repository grounding, and why there is deliberately no merge primitive.<\/p>\n","protected":false},"author":1,"featured_media":6708,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[717],"tags":[46,5,3],"class_list":["post-6680","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-business-automation","tag-conversational-ai","tag-voice-ai"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Graduated AI Agency: Capability Gates, Plan Approval and the Pull Request Boundary<\/title>\n<meta name=\"description\" content=\"Six rungs of agent agency, capability checks re-run at dispatch, mandatory coding plan approval with repository grounding, and why there is deliberately no merge primitive.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Graduated AI Agency: Capability Gates, Plan Approval and the Pull Request Boundary\" \/>\n<meta property=\"og:description\" content=\"Six rungs of agent agency, capability checks re-run at dispatch, mandatory coding plan approval with repository grounding, and why there is deliberately no merge primitive.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/\" \/>\n<meta property=\"og:site_name\" content=\"DialNexa\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-27T14:24:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-27T14:48:48+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/dialnexa.com\/blogs\/wp-content\/uploads\/2026\/07\/alfred-part-4-agency-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Aditya Kamat\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Aditya Kamat\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/\"},\"author\":{\"name\":\"Aditya Kamat\",\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#\\\/schema\\\/person\\\/1af38c86cbe30b471e5c350bfb15926c\"},\"headline\":\"How to Give AI Agency Without Giving It Reckless Power\",\"datePublished\":\"2026-07-27T14:24:34+00:00\",\"dateModified\":\"2026-07-27T14:48:48+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/\"},\"wordCount\":2313,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/alfred-part-4-agency-1.png\",\"keywords\":[\"business automation\",\"conversational ai\",\"Voice AI\"],\"articleSection\":[\"Engineering\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/\",\"url\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/\",\"name\":\"Graduated AI Agency: Capability Gates, Plan Approval and the Pull Request Boundary\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/alfred-part-4-agency-1.png\",\"datePublished\":\"2026-07-27T14:24:34+00:00\",\"dateModified\":\"2026-07-27T14:48:48+00:00\",\"description\":\"Six rungs of agent agency, capability checks re-run at dispatch, mandatory coding plan approval with repository grounding, and why there is deliberately no merge primitive.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/#primaryimage\",\"url\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/alfred-part-4-agency-1.png\",\"contentUrl\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/alfred-part-4-agency-1.png\",\"width\":1200,\"height\":630,\"caption\":\"Building Alfred part 4: giving an AI agent graduated agency with capability gates\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/ai-agent-graduated-agency\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Give AI Agency Without Giving It Reckless Power\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#website\",\"url\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/\",\"name\":\"DialNexa Blog\",\"description\":\"Voice AI insights, customer communication playbooks, sales automation guides, and contact center operations advice from DialNexa.\",\"publisher\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#organization\",\"name\":\"DialNexa\",\"url\":\"https:\\\/\\\/dialnexa.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/wp-content\\\/uploads\\\/2025\\\/10\\\/cropped-cropped-favicon-300x300-1.png\",\"caption\":\"DialNexa\"},\"image\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#\\\/schema\\\/person\\\/1af38c86cbe30b471e5c350bfb15926c\",\"name\":\"Aditya Kamat\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/44bc46159de51fb66b83a36901f74a2f90b84ae23178c4a55584b7b2861317ba?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/44bc46159de51fb66b83a36901f74a2f90b84ae23178c4a55584b7b2861317ba?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/44bc46159de51fb66b83a36901f74a2f90b84ae23178c4a55584b7b2861317ba?s=96&d=mm&r=g\",\"caption\":\"Aditya Kamat\"},\"description\":\"Co-Founder of DialNexa. Expert in voice AI, conversational technology, and enterprise telephony. Building the future of AI-powered customer engagement.\",\"sameAs\":[\"https:\\\/\\\/dialnexa.com\"],\"jobTitle\":\"Co-Founder\",\"url\":\"https:\\\/\\\/dialnexa.com\",\"worksFor\":{\"@id\":\"https:\\\/\\\/dialnexa.com\\\/blogs\\\/#organization\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Graduated AI Agency: Capability Gates, Plan Approval and the Pull Request Boundary","description":"Six rungs of agent agency, capability checks re-run at dispatch, mandatory coding plan approval with repository grounding, and why there is deliberately no merge primitive.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/","og_locale":"en_US","og_type":"article","og_title":"Graduated AI Agency: Capability Gates, Plan Approval and the Pull Request Boundary","og_description":"Six rungs of agent agency, capability checks re-run at dispatch, mandatory coding plan approval with repository grounding, and why there is deliberately no merge primitive.","og_url":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/","og_site_name":"DialNexa","article_published_time":"2026-07-27T14:24:34+00:00","article_modified_time":"2026-07-27T14:48:48+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/dialnexa.com\/blogs\/wp-content\/uploads\/2026\/07\/alfred-part-4-agency-1.png","type":"image\/png"}],"author":"Aditya Kamat","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Aditya Kamat","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/#article","isPartOf":{"@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/"},"author":{"name":"Aditya Kamat","@id":"https:\/\/dialnexa.com\/blogs\/#\/schema\/person\/1af38c86cbe30b471e5c350bfb15926c"},"headline":"How to Give AI Agency Without Giving It Reckless Power","datePublished":"2026-07-27T14:24:34+00:00","dateModified":"2026-07-27T14:48:48+00:00","mainEntityOfPage":{"@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/"},"wordCount":2313,"commentCount":0,"publisher":{"@id":"https:\/\/dialnexa.com\/blogs\/#organization"},"image":{"@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/#primaryimage"},"thumbnailUrl":"https:\/\/dialnexa.com\/blogs\/wp-content\/uploads\/2026\/07\/alfred-part-4-agency-1.png","keywords":["business automation","conversational ai","Voice AI"],"articleSection":["Engineering"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/","url":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/","name":"Graduated AI Agency: Capability Gates, Plan Approval and the Pull Request Boundary","isPartOf":{"@id":"https:\/\/dialnexa.com\/blogs\/#website"},"primaryImageOfPage":{"@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/#primaryimage"},"image":{"@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/#primaryimage"},"thumbnailUrl":"https:\/\/dialnexa.com\/blogs\/wp-content\/uploads\/2026\/07\/alfred-part-4-agency-1.png","datePublished":"2026-07-27T14:24:34+00:00","dateModified":"2026-07-27T14:48:48+00:00","description":"Six rungs of agent agency, capability checks re-run at dispatch, mandatory coding plan approval with repository grounding, and why there is deliberately no merge primitive.","breadcrumb":{"@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/#primaryimage","url":"https:\/\/dialnexa.com\/blogs\/wp-content\/uploads\/2026\/07\/alfred-part-4-agency-1.png","contentUrl":"https:\/\/dialnexa.com\/blogs\/wp-content\/uploads\/2026\/07\/alfred-part-4-agency-1.png","width":1200,"height":630,"caption":"Building Alfred part 4: giving an AI agent graduated agency with capability gates"},{"@type":"BreadcrumbList","@id":"https:\/\/dialnexa.com\/blogs\/ai-agent-graduated-agency\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/dialnexa.com\/blogs\/"},{"@type":"ListItem","position":2,"name":"How to Give AI Agency Without Giving It Reckless Power"}]},{"@type":"WebSite","@id":"https:\/\/dialnexa.com\/blogs\/#website","url":"https:\/\/dialnexa.com\/blogs\/","name":"DialNexa Blog","description":"Voice AI insights, customer communication playbooks, sales automation guides, and contact center operations advice from DialNexa.","publisher":{"@id":"https:\/\/dialnexa.com\/blogs\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/dialnexa.com\/blogs\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/dialnexa.com\/blogs\/#organization","name":"DialNexa","url":"https:\/\/dialnexa.com","logo":{"@type":"ImageObject","url":"https:\/\/dialnexa.com\/blogs\/wp-content\/uploads\/2025\/10\/cropped-cropped-favicon-300x300-1.png","caption":"DialNexa"},"image":{"@id":"https:\/\/dialnexa.com\/blogs\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/dialnexa.com\/blogs\/#\/schema\/person\/1af38c86cbe30b471e5c350bfb15926c","name":"Aditya Kamat","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/44bc46159de51fb66b83a36901f74a2f90b84ae23178c4a55584b7b2861317ba?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/44bc46159de51fb66b83a36901f74a2f90b84ae23178c4a55584b7b2861317ba?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/44bc46159de51fb66b83a36901f74a2f90b84ae23178c4a55584b7b2861317ba?s=96&d=mm&r=g","caption":"Aditya Kamat"},"description":"Co-Founder of DialNexa. Expert in voice AI, conversational technology, and enterprise telephony. Building the future of AI-powered customer engagement.","sameAs":["https:\/\/dialnexa.com"],"jobTitle":"Co-Founder","url":"https:\/\/dialnexa.com","worksFor":{"@id":"https:\/\/dialnexa.com\/blogs\/#organization"}}]}},"_links":{"self":[{"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/posts\/6680","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/comments?post=6680"}],"version-history":[{"count":2,"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/posts\/6680\/revisions"}],"predecessor-version":[{"id":6698,"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/posts\/6680\/revisions\/6698"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/media\/6708"}],"wp:attachment":[{"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/media?parent=6680"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/categories?post=6680"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dialnexa.com\/blogs\/wp-json\/wp\/v2\/tags?post=6680"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}