Tool Extension Lifecycle
Tools are the smallest executable capability that an Aegis task session can call. A tool can read a repo file, run a bounded check, call a connector, write an artifact, inspect a source bundle, or perform another explicit operation on behalf of a workflow.
Adding a tool is a boundary change. Treat it as runtime work, not just a helper function.
Lifecycle
- Choose the tool family and owner module.
- Define the input and output contract.
- Declare the tool manifest.
- Assign permission tokens and action type.
- Implement the handler behind the right boundary helpers.
- Add negative tests before broad usage.
- Document operator-facing behavior.
- Verify capability inventory and profile reachability.
Choose An Owner Module
Prefer a focused module for a tool family instead of adding more code to src/tools/index.ts.
Current reference pattern:
src/tools/index.tsowns the registry shell and commonToolManifestcontract.src/tools/repo-tools.tsregistersrepo.*tools.src/tools/repo-boundary.tsowns realpath-aware repository containment helpers.
Use the same shape for new families:
export function registerExampleTools(registry: ToolRegistryLike): void {
registry.register<ExampleInput, ExampleOutput>({
id: "example.read",
actionType: "example-read",
requiredAllowlist: ["example:read"],
description: "Read example data.",
target: (input) => input.id,
describeResult: (result) => `${result.items.length} item(s)`,
handler: async (input, context) => {
return readExampleData(input, context);
}
});
}
Then call the family registration function from createToolRegistry().
Tool Manifest
Every tool needs a manifest with stable metadata:
| Field | Purpose |
|---|---|
id |
Stable tool id. Use a namespace such as repo.read-text, gmail.search-messages, or artifact.write. |
actionType |
Policy/audit class for the action, such as repo-read, repo-write, external-write, or repo-check. |
requiredAllowlist |
Permission tokens that a task profile must hold before runtime can call the tool. |
description |
Operator-facing capability description. |
runtimeIsolation |
Optional explicit runtime isolation metadata. If omitted, the registry infers a conservative baseline from the action type, allowlist, id, and safety metadata. |
target |
Short target string for policy, approval, trace, and UI context. |
describeResult |
Optional concise summary for traces and task-session output. |
preApprovalValidate |
Optional validation that must run before an approval checkpoint is created. |
handler |
The only place that performs the operation. |
High-risk generic tools should also declare visibility, riskLevel, and safety metadata so operators can distinguish typed safe paths from advanced escape hatches.
runtimeIsolation should name the runtime boundary a reviewer cares about:
| Field | Typical values |
|---|---|
boundary |
local-process, workspace, network, external-service, none |
networkEgress |
none, local-only, operator-configured, external-service, connector-api |
filesystem |
none, repo-read, repo-write, workspace-artifacts, repo-and-workspace, source-package |
secrets |
none, oauth-token, secret-registry, provider-credential |
subprocess |
none, bounded-host-command, container |
Use explicit metadata when inference would hide an important review fact, such as a connector API that only reads but still needs OAuth, a sandbox phase with operator-configured network access, or a tool that crosses both repo and artifact boundaries. Metadata is descriptive inventory first; handlers still need the normal containment, permission, approval, and token-access checks.
Permission Tokens
requiredAllowlist uses all-token semantics. A task profile must have every required token unless the runtime explicitly models an alternative permission set.
Good:
requiredAllowlist: ["repo:check", "verifier:command"]
This means a broad repo permission alone does not unlock host command execution.
Avoid:
requiredAllowlist: ["external-write"]
for connector-specific writes. Use connector-specific tokens such as gmail:modify, github:write, or a typed operation token when possible. Broad categories should not silently unlock concrete side effects.
Action Type And Policy
The actionType should match the real boundary crossed by the handler.
| Boundary | Typical action type |
|---|---|
| Repository read | repo-read |
| Repository mutation | repo-write |
| Bounded verifier command | repo-check |
| Workspace artifact write | workspace-write |
| External API read | external-read or connector-specific read |
| External API mutation | external-write or connector-specific write |
| Secret lookup | operator-only helper path, not agent-callable |
If a tool can mutate a repo, external system, device, payment, account, policy, secret, workflow, or Aegis itself, it needs explicit approval and negative tests proving agents cannot bypass that approval.
Handler Boundary Rules
Handlers should be thin and boundary-aware.
- Use shared path containment helpers for filesystem access.
- Use
context.verifier.run()for host commands instead of spawning shell commands directly. - Use connector runtimes for OAuth tokens instead of reading secret material directly.
- Write run artifacts through
context.workspace. - Respect
context.signalfor cancellable work. - Keep raw secret values out of traces, artifacts, thrown errors, and result summaries.
- Validate advanced write previews before token lookup or network access.
For repo tools, use resolveWithinRepoRoot() before reading, writing, or selecting a command cwd. That helper rejects sibling-prefix escapes and symlink escapes.
Approval Preview
For generic or dynamic write tools, validate the requested operation before runtime asks for approval. The preview should bind the approval to concrete inputs such as connector id, account id, method, path, target, summary, and expected side effect.
The generic OAuth API request tools in src/connectors/oauth-api-tools.ts are the current reference:
- unsafe HTTP methods require
approvalPreview; - preview fields must match the actual request;
- validation happens before token access or network execution;
- typed connector operations remain preferred for repeatable writes.
Tests
Every new tool family should have focused tests near the owning boundary.
Minimum test set:
- manifest appears in
createToolRegistry().list(); - required allowlist tokens are complete and not overly broad;
- a task without one required token cannot call the tool;
- malformed input fails before side effects;
- path, target, account, or scope escapes are rejected;
- approval-gated actions cannot run without the expected approval;
- result summaries and errors do not expose secrets;
- cancellation, timeout, or bounded-output behavior is covered for long work.
Examples:
- Repo path/cwd boundaries:
tests/verifier-bounds.test.ts - Workspace artifact boundaries:
tests/workspace-containment.test.ts - OAuth connector boundaries:
tests/oauth-connectors.test.ts - Runtime allowlist and approval behavior:
tests/runtime-policy-service.test.ts,tests/approval-checkpoints.test.ts
Operator Docs
Update docs when a tool changes what operators can do or approve.
Document:
- what the tool does;
- what local or external boundary it crosses;
- what permission tokens it requires;
- whether it can mutate state;
- what approval, preview, or verification evidence is expected;
- what artifacts or traces an operator should inspect.
For public-facing tool families, link the guide from the relevant workflow, connector, security, or architecture docs.
Capability Inventory
Registered tools appear in the capability inventory. Before calling a tool complete, verify that the inventory communicates:
- id and kind;
- action type;
- required allowlist;
- risk level or review status where applicable;
- profile reachability;
- isolation boundary;
- runtime isolation details for network egress, filesystem reach, secret access, subprocess mode, and notes.
This keeps tools inspectable before they are used by workflows or headless agents.
Review Checklist
Before merging a new or changed tool:
- The tool has a stable namespace and owner module.
- Input/output types are explicit.
- The manifest action type matches the side effect.
- Required allowlist tokens are specific and complete.
- Filesystem, command, connector, and network boundaries use shared helpers.
- High-risk writes are approval-gated.
- Negative tests cover bypass attempts.
- Secrets are redacted from outputs and errors.
- Operator docs and capability inventory still make the boundary understandable.