Tools and the Approval Mechanism
If there is one outward-facing difference between Coding Agent and an ordinary agent, the answer is usually not the prompt — it is the tools.
But what this page really needs to make clear is not simply "there are 8 built-in tools". It is:
- how these tools get wired in
- who actually executes them
- why the tool surface a given session sees is more than 8
- where approval actually intercepts
- why approval and the workspace boundary are not the same thing
1. Eight fixed built-in tools — but the runtime surface is larger than eight
CodingToolRegistryFactory.createBuiltInRegistry() currently mounts exactly eight local tools:
bashread_filewrite_fileapply_patchglobgrepeditupdate_agents_md
These eight come from:
BuiltInTools.codingTools()
The read-only subset of them (BuiltInTools.readOnlyCodingToolNames()) is:
bash(exec)read_fileglobgrep
But the set of tools the model can ultimately see in a given session is not necessarily limited to those eight.
CodingAgentBuilder may also merge in:
- custom tool registry
delegate_*toolssubagent_*tools- subagent/team tools injected via
/experimental - MCP tools
So the most accurate statement is:
- fixed built-in local execution tools = 8
- the visible tool surface for the current session = fixed built-in tools + runtime-injected tools
2. Which layer actually connects tools to executors
The key entry point is not the registry, but rather:
CodingAgentBuilder.createBuiltInToolExecutor(...)
It routes each tool to a dedicated executor:
ReadFileToolExecutorWriteFileToolExecutorApplyPatchToolExecutorBashToolExecutorGlobToolExecutorGrepToolExecutorEditToolExecutorUpdateAgentsMdToolExecutor
And finally uses:
RoutingToolExecutor
to dispatch by tool name.
This matters, because it shows that AI4J's coding tools are not simply annotated functions — they are a set of dedicated executors.
3. What the actual boundary of read_file is
ReadFileToolExecutor currently hands the request off to:
WorkspaceFileService.readFile(...)
and supports:
pathstartLineendLinemaxChars
If maxChars is not provided, it falls back to:
CodingAgentOptions.defaultReadMaxChars
In other words, read_file does not have a fixed "read the whole file" semantic. It is a:
- range-scoped
- length-capped
- workspace-file-service-backed
controlled read interface.
Combined with WorkspaceContext.resolveReadablePath(...), its core boundary is:
- anything inside the workspace is readable
- extra read-only roots such as skills are also readable
- by default it cannot freely cross the workspace to read arbitrary paths
4. What the actual semantic of write_file is
WriteFileToolExecutor currently supports three modes:
createoverwriteappend
It returns:
resolvedPathmodecreatedappendedbytesWritten
But there is one implementation fact that must be stated clearly here:
Its own resolvePath(...) does not call WorkspaceContext.resolveWorkspacePath(...).
The current behavior is:
- relative paths land under the workspace root
- absolute paths are normalized and used directly
This means its boundary is not fully symmetric with read_file.
If the caller passes an absolute path, the current implementation can indeed write outside the workspace.
This is exactly why:
- approval must not be mistaken for a sandbox
- and the workspace constraint must not be mistaken for being uniform across all tools
5. Why apply_patch is more coding-native than write_file
ApplyPatchToolExecutor does not simply "write a piece of text". It:
- Validates the patch envelope:
*** Begin Patch*** End Patch
- Parses:
*** Add File:*** Update File:*** Delete File:
- Performs anchor matching on update hunks
- Applies changes file by file
- Returns a structured
ApplyPatchResult
More importantly, when locating files it goes through:
workspaceContext.resolveWorkspacePath(path)
That is, apply_patch is currently bound by the workspace boundary more strictly than write_file.
This is why, in coding scenarios, apply_patch is not "an alternative way to write files", but rather:
a code-editing tool with structural constraints, context matching, and a workspace boundary.
6. Why bash cannot be understood as just "run a command"
BashToolExecutor currently supports the actions:
execstartstatuslogswritestoplist
This shows that bash inside the Coding Agent is the union of two semantics:
- one-shot command execution:
exec - long-running process management:
start/status/logs/write/stop/list
And behind it sits directly:
LocalShellCommandExecutorSessionProcessRegistry
So bash is not an ordinary function tool — it is a session-level process-surface entry point.
7. Search, edit, and project-memory tools
Beyond the four "classic" tools above, BuiltInTools.codingTools() also mounts four more, covering search, precise editing, and cross-session project memory.
7.1 glob — filename pattern matching
GlobToolExecutor runs glob matching inside the workspace (e.g. **/*.java) and returns the list of matching paths relative to the workspace root.
Parameters:
pattern(required): the glob patternpath: the base search directory, relative to the workspace root; defaults to the workspace rootmaxResults: upper bound on returned paths
It is a read-only tool (part of readOnlyCodingToolNames), suited to letting the model quickly locate "which files satisfy some naming convention" rather than flooding the context with the entire directory.
7.2 grep — file content search
GrepToolExecutor searches file contents with a regex and returns ripgrep-style results (filename + line number + matched line).
Parameters:
pattern(required): a regular expressionpath: the base search directory or a single file, relative to the workspace rootinclude: a filename glob filter (e.g.*.java)caseInsensitive: whether the search is case-insensitive; defaults to falsemaxResults: upper bound on returned matched lines
It is likewise a read-only tool. glob answers "which files", grep answers "which files contain this piece of content" — together they are the coding agent's primary combination for code location.
7.3 edit — precise string replacement
EditToolExecutor performs precise in-file string replacement, and is complementary to apply_patch:
apply_patch: suited to structured, multi-file patches combining additions, deletions, and modificationsedit: suited to localized replacement within a single file
Parameters:
path(required): the file to edit, relative to the workspace rootold_string(required): the exact text to find; indentation and whitespace must match exactlynew_string(required): the replacement textreplaceAll: replace every match; by default only a unique match is allowed
By default old_string must match uniquely in the file, otherwise the call fails — this is a deliberate design to prevent edit from silently modifying multiple places. To replace several identical fragments, pass replaceAll=true explicitly.
7.4 update_agents_md and the AGENTS.md project memory
update_agents_md is a somewhat special built-in tool: it is dedicated to maintaining the project-level AGENTS.md memory file, letting the agent persist project conventions, decisions already made, and to-dos across sessions.
Parameters:
action(required):read/write/appendcontent: full content (used whenaction=write)text: the text to append (used whenaction=append)
Behind it sit UpdateAgentsMdToolExecutor + AgentsMdStore. The AGENTS.md lookup order is:
AGENTS.mdat the workspace root.agents/AGENTS.mdunder the workspace root
When neither exists, read returns empty content, and write creates AGENTS.md at the workspace root.
Keep these distinct:
- A Skill (see Skills usage and organization) is a "library of practices": file-based workflow knowledge, read by read_file on demand.
- AGENTS.md is "project memory": project conventions, decisions, and to-dos that the agent itself writes via
update_agents_md, persisted across sessions.
Both are part of the read-only knowledge-input surface, but AGENTS.md is writable by the agent and is runtime state the agent actively maintains.
This tool gives the coding agent the ability to "remember the key facts about this project over the long term", instead of rediscovering them every session.
8. Why a session may also see delegate_*, subagent_*, and MCP tools
Beyond the fixed local tools, CodingAgentBuilder also merges in:
CodingDelegateToolRegistrySubAgentRegistry- custom registry
- MCP registry
And when DefaultCodingRuntime spawns a child session, it additionally goes through:
CodingToolPolicyResolver
to filter the allowed tool names by agent definition.
This means tools in the Coding Agent are not a static list, but rather:
- a base local tool surface
- a runtime-extended tool surface
- the effective tool surface after per-session / per-definition filtering
So when reading tool-related code, distinguish between:
- the registration surface
- the execution surface
- the policy surface
9. Which layer approval actually intercepts at
Approval is currently not:
- an OS hook
- a shell wrapper
- a JVM agent
- "confirm after the command has already run"
It is a very explicit executor-decorator model.
The chain is:
- CLI/ACP first decides the
ApprovalMode DefaultCodingCliAgentFactoryattaches the decorator intoCodingAgentOptionsCodingAgentBuildercallsdecorate(...)when building the built-in executors- Before a tool is actually executed, the decorator first decides whether approval is required
So the approval interception points are:
- the ToolExecutor assembly stage
- the ToolExecutor call entry
This layering is clean, because it allows:
- one runtime
- different host interaction styles
to share a single approval semantic.
10. What CliToolApprovalDecorator currently actually intercepts
From the implementation, the current rules are:
manual: every tool call is approvedsafe:apply_patchis always approvedbashexec/start/stop/writeare approvedread_fileis not approved by defaultwrite_fileis not approved by default
auto: passed through by default
Read this according to "the current code", not according to what the name suggests.
Many people assume safe means "all write operations are intercepted", but that is not the case at this stage.
If you want to widen the approval scope, the correct entry points are:
- swap the decorator
- change the decorator rules
not change the prompt.
11. How a rejected approval is propagated back to the runtime
When approval is rejected, CliToolApprovalDecorator does not swallow it silently — it throws a rejection message prefixed with:
[approval-rejected]
Then CodingAgentLoopController recognizes this kind of tool result as:
BLOCKED_BY_APPROVAL
This connects "the host interaction refused execution" and "the runtime stops advancing" into a complete semantic chain.
So approval in AI4J is not a UX detail — it is part of the stop reason.
12. Why ACP approval is yet another path
Under CLI/TUI, approval is a terminal interaction:
- print the approval block
- read
y/yes
Under ACP, it is:
AcpToolApprovalDecoratorPermissionGatewaysession/request_permission
That is, approval in ACP is fundamentally a protocol round-trip, not a local stdin interaction.
But both paths still share the same core idea:
- both are ToolExecutor decorators
- both intercept before execution
- both can propagate a rejection back to the runtime
13. Why "approval" and "workspace sandbox" must be understood separately
This is the easiest place in the current docs to get wrong.
Approval answers:
- "should this call be executed at all"
The workspace boundary answers:
- "even if executed, where is this call allowed to reach"
The two are not the same layer of control.
For example, in the current implementation:
apply_patchstrictly goes throughresolveWorkspacePath(...)read_filegoes throughresolveReadablePath(...)write_filehas its own looserresolvePath(...)
So you cannot simply say:
- "turning on approval makes it safe"
- "all tools are protected by the same workspace sandbox"
The more accurate statement is:
- the path-boundary implementations of different tools are not fully symmetric
- approval is only a pre-execution control, not a filesystem isolation layer
14. How far sandbox routing goes today
ai4j-coding already has the first P3 slice: once the host binds a live sandbox via CodingAgentBuilder.sandbox(SandboxSession), bash action=exec goes through SandboxShellCommandExecutor to call SandboxSession.execute(SandboxCommand).
This chain only changes where foreground shell commands run:
no sandbox -> LocalShellCommandExecutor -> host shell
with sandbox -> SandboxShellCommandExecutor -> SandboxSession.execute(...)
The returned result carries executionEnvironment, sandboxSessionId, and sandboxProviderId, so that the CLI/TUI, logs, or an upstream host can show "where this command actually ran".
Note: this does not mean every tool has been remoted yet. read_file, write_file, apply_patch, background processes, browser, and git/project run still need subsequent slices to be wired in. For the full boundary, see Sandbox Routing.
15. Where the most stable extension points are today
If you want to integrate the Coding Agent into an enterprise environment, the most stable extension entry points are usually:
- a custom
toolRegistry - a custom
toolExecutor - a custom
ToolExecutorDecorator
They respectively fit:
- controlling the tool surface the model sees
- rewriting the actual tool execution logic
- uniformly attaching approval, audit, rate limiting, and authentication
One important constraint here:
- if you pass a custom
toolRegistry, you must also provide a matchingtoolExecutor
otherwise the tool surface and the execution surface will get out of sync.
16. The five easiest places to go wrong
16.1 Treating "fixed built-in tools" as "all visible tools"
The current session's tool surface may also contain delegate, subagent, and MCP tools.
16.2 Treating approval as an OS-level hook
It is essentially just a ToolExecutor decorator.
16.3 Imagining safe mode as stricter than the current implementation
Not every write operation is automatically approved today.
16.4 Assuming write_file and apply_patch have the same path boundary
The current implementations are not fully symmetric.
16.5 Designing only the registry, not the executor
The tool exposure surface and the execution surface must be considered together.
17. The conclusion worth remembering from this page
AI4J's current Coding Agent tools mechanism is not "8 functions + a confirmation dialog". It is an entire runtime surface:
- a registry decides what the model sees
- a dedicated executor decides how it runs locally
- a decorator decides how it is approved before execution
- a policy resolver decides which tools a given child session is allowed to use
And approval and the workspace boundary are two different controls. Separate these layers clearly, and only then can you truly understand where the controllability of the Coding Agent comes from.
18. Further reading
- Sessions, streaming, and processes
- Compact and checkpoint mechanism
- Sandbox Routing
- Lifecycle hooks and workspace trust
- Runtime architecture
→ API Javadoc: BuiltInToolExecutor (the ai4j module; the entry class for built-in tool execution routing)