Every internal API ends up wrapped in a one-off Slack bot before an agent can touch it. Here is the pattern for giving agents safe, narrow, logged access to your real systems instead.
See the pattern in action, tap through the tabs below:
Shell access is not a tool, it is a liability with extra steps. The fastest way to connect an agent to your internal systems is to give it a terminal and a service account. It is also the fastest way to lose track of exactly what it did, why, and whether it should have been allowed to. Most teams end up with a pile of one-off Slack bots and cron scripts instead, each one hand-wired to a single internal API, because nobody trusted the agent enough to give it real access. Neither approach scales past the second or third internal system you want an agent to touch.
tool: restart_service
description: Restart a single named service in a given environment
input_schema:
service: {type: string, enum: [checkout-api, auth-api, billing-worker]}
environment: {type: string, enum: [staging, production]}
output_schema:
status: string
restarted_at: string
mutating: true
requires_approval: production
allowed_callers:
- incident-response-agent
- deploy-agent
rate_limit: 3 per hour per service
log:
destination: audit-log://tool-calls
fields: [caller, args, result, approver]
[11:02:10] agent: calling get_deploy_status(service=checkout-api)
[11:02:11] tool: read-only, no approval needed, executing
[11:02:11] tool: result = {status: degraded, last_deploy: 8m ago}
[11:02:12] agent: calling restart_service(service=checkout-api, environment=production)
[11:02:12] tool: mutating call, requires_approval=production
[11:02:12] tool: approval request sent to j.alvarez via #inc-payments
[11:03:40] approval: approved by j.alvarez (88s)
[11:03:41] tool: executing restart_service, logging caller+args+approver
[11:03:52] tool: result = {status: restarted, restarted_at: 11:03:52}
[11:03:52] agent: posting outcome summary to #inc-payments
Start here, in this order:
1. Pick one internal API or script you keep wrapping in one-off bots.
2. Write a narrow schema for it: one specific action, typed inputs, typed outputs.
3. Start with read-only tools only, no mutating actions, for the first two weeks.
4. Add an approval gate to any tool that writes, restarts, or spends money.
5. Log every call with the caller, arguments, result, and approver.
6. Expand one tool at a time, never by widening an existing tool’s scope.
The Bot Pile Problem
Every team building with agents hits the same wall around the third or fourth internal system: you want the agent to check a deploy status, look up a customer record, or restart a worker, and none of those things have a clean API an LLM can just call. So someone writes a one-off Slack bot for the deploy dashboard, a cron script for the customer lookup, and a manual runbook for the restart, because giving the agent a raw terminal felt too risky to even consider. Every one of those integrations is a separate thing to maintain, secure, and explain to the next engineer who touches it.
Automated tool access is not about giving agents more power. It is about giving them a single, consistent, auditable way to use the access you already trust a script or API with.
What Automated Tool Access Actually Does
The pattern has three parts, and none of them are exotic:
- A narrow schema for every tool: one specific action, typed inputs, typed outputs, nothing left to the model’s interpretation of what a free-text command might mean.
- An approval gate on anything that mutates state, spends money, or touches production, so a read-only lookup runs immediately and a restart or a rollback waits for a human click.
- A call log that records who called it, the agent or the person behind that agent, what arguments it passed, what came back, and who approved it if approval was required.
None of this requires exotic infrastructure. It requires deciding, tool by tool, exactly what an agent is allowed to do and writing that down as a schema instead of leaving it as an assumption.
How It’s Built
This works with whatever tool-calling convention your model already supports, OpenAI function calling, Claude’s tool use, or Gemini’s function declarations all use the same shape: a name, a description, and a typed input schema the model fills in. A thin adapter layer, whether that is a thin FastAPI wrapper, a LangChain or CrewAI tool class, or a small MCP server, sits between that schema and your actual internal API or script, and it is the only thing with real credentials. The agent never sees a database password or a service account key, it only ever sees the tool’s name, description, and schema. Anything mutating routes through an approval step in Slack or your incident channel before the adapter executes it.
A Walkthrough
An agent investigating a checkout slowdown calls a read-only get_deploy_status tool first, no approval needed, and gets back a degraded status from eight minutes ago. It decides a restart is warranted and calls restart_service for the production environment. That tool is marked as mutating and requires_approval for production, so instead of executing immediately, it posts an approval request to the incident channel with the exact arguments it wants to run. Once a human approves, the adapter executes the restart, logs the caller, the arguments, the result, and the approver, and the agent posts the outcome. Nothing happened that a person could not see coming and stop.
Where Teams Get This Wrong
The most common mistake is building one giant tool that does everything, like a run_command tool that accepts arbitrary shell strings, which puts you right back to the original terminal-access problem with extra steps. The second is skipping the approval gate on mutating tools because the pilot is “just internal,” which is exactly how a bad restart loop takes down a service at 2am. The third is not logging tool calls at all, which means a bad outcome has no trail to learn from.
FAQ
Do I need a specific agent framework for this?
No. This is a convention, not a framework requirement. It works the same way whether you are using OpenAI, Claude, Gemini, LangChain, or CrewAI, because they all support structured tool schemas.
What stops the agent from doing something dangerous?
The schema itself. If a tool only exposes restart_service with a fixed enum of service names, the agent physically cannot ask it to do anything else, and an approval gate catches the mutating calls that matter.
How is this different from giving the agent API or shell access directly?
Shell or raw API access means the agent can do anything that credential can do. A defined tool means it can only do the one specific thing you wrote a schema for, with a log of every attempt.
If your team is standardizing how agents touch internal systems and wants the DevOps and cybersecurity fundamentals underneath it done right, the DevOps and Cybersecurity track at the shed covers the agent, tooling, and access control stack in more depth. One course, no hard sell.


