Skip to content

Chapter 5: Agent Configuration

In the previous chapter, we saw how the Session Manager coordinates creating, attaching, and routing messages to sessions. It knows that a session exists and where it lives. But it doesn't decide what kind of agent runs inside it — what personality it has, which tools it can touch, or what external servers it connects to.

That's the job of Agent Configuration.

Why JSON Config Files?

Imagine you're running a company. You don't give every employee the same job description and the same security badge. A front-desk receptionist needs access to the visitor log but not the server room. A sysadmin needs shell access but doesn't need the marketing database.

Agent Configuration works the same way. It's like a character sheet in a role-playing game combined with a security badge: it defines what an agent is (its personality and prompt), what it can do (which tools are available), and what it must not do (which tools are blocked or restricted).

Without configuration files, every kiro-cli session would behave identically — same tools, same prompt, same permissions. With them, you can create specialized agents:

  • A docs agent that can only read files and search — no shell access, no writes
  • A builder agent that has access to internal MCP servers for code search and deployment
  • A planner agent that can read everything but delegates all changes to sub-agents

Each agent is a single JSON file. Drop it in the right directory, and kiro-cli picks it up automatically.

Use Case: Running a Specific Agent

Let's say you have a docs agent configured for documentation work. You launch it:

kiro-cli --agent docs

Behind the scenes, kiro-cli:

  1. Scans the agent directories for a file where "name": "docs"
  2. Loads and validates the JSON
  3. Resolves the system prompt (which might be inline text or a file:// reference)
  4. Starts a session where the Agent Loop only sees the tools and permissions defined in that config

The agent never knows about tools it wasn't given. It can't escalate its own permissions. The config is the single source of truth.

Where Agent Configs Live

kiro-cli looks for agent JSON files in two places, in priority order:

Location Scope Example Path
Workspace Project-specific .kiro/agents/*.json
Global User-wide ~/.kiro/agents/*.json

Workspace agents take priority — if both directories contain an agent named "docs", the workspace version wins. There are also three built-in agents (kiro_default, kiro_planner, kiro_guide) that are always available even with no JSON files on disk.

Key Concepts

The Agent File Schema

Every agent config is a JSON object. Only one field is required: name. Everything else has sensible defaults. Here's a minimal agent:

{
  "name": "minimal-agent"
}

That's valid — it creates an agent with no custom prompt, no tools, and no MCP servers. Not very useful, but it loads.

A more realistic agent looks like this:

{
  "name": "docs",
  "description": "Documentation-only agent",
  "prompt": "You help write and review docs.",
  "tools": ["read", "grep", "glob"],
  "allowedTools": ["read", "grep", "glob"],
  "resources": ["file://README.md"]
}

Let's break down the key fields.

System Prompt (prompt)

The prompt field sets the agent's personality and instructions — equivalent to a system prompt. It accepts either inline text or a file:// URI:

{
  "prompt": "You are a careful code reviewer."
}
{
  "prompt": "file://./my-prompt.md"
}

File URIs are resolved relative to the agent config file's directory. This lets you keep long prompts in separate markdown files rather than cramming them into JSON.

Tools Visibility (tools)

The tools array controls which tools the agent can see — what gets sent to the LLM as available capabilities. The syntax supports several patterns:

Pattern Meaning
"*" All tools (built-in + all MCP)
"read" A specific built-in tool
"@builtin" All built-in tools
"@server-name" All tools from an MCP server
"@server-name/tool" One specific MCP tool
"@server-name/edit_*" Glob match on MCP tools
"#agent-name" Another agent (as a sub-agent tool)

Auto-Approve List (allowedTools)

The allowedTools set determines which tools can run without asking the user for confirmation. Even if a tool is visible via tools, it still requires approval unless it's in allowedTools:

{
  "tools": ["read", "write", "shell"],
  "allowedTools": ["read"]
}

Here the agent can see read, write, and shell, but only read runs without a confirmation prompt. The user must approve every write and shell invocation.

Tool Settings (toolsSettings)

For fine-grained control, toolsSettings lets you configure individual tools with allow/deny lists:

{
  "toolsSettings": {
    "shell": {
      "allowedCommands": ["git status", "npm test"],
      "denyByDefault": true
    },
    "write": {
      "deniedPaths": ["/etc", "/usr"]
    }
  }
}

This restricts the shell tool to only git status and npm test, and prevents the write tool from touching system directories.

MCP Servers (mcpServers)

Agents can launch their own MCP (Model Context Protocol) servers — external processes that provide additional tools. Each entry is either a local (stdio) or remote (HTTP) server:

{
  "mcpServers": {
    "builder-mcp": {
      "command": "builder-mcp",
      "args": ["--skill-paths", "/path/to/skills"]
    },
    "remote-api": {
      "url": "https://mcp.example.com/sse"
    }
  }
}

Local servers are spawned as child processes; remote servers are connected over HTTP. Tools from MCP servers are referenced as @server-name/tool-name in the tools array.

Hooks (hooks)

Hooks are shell commands that run at specific lifecycle points:

{
  "hooks": {
    "agentSpawn": [
      { "command": "echo 'Agent started'" }
    ],
    "userPromptSubmit": [
      { "command": "date >> /tmp/prompt-log.txt" }
    ]
  }
}

The five hook triggers are:

Trigger When It Fires
agentSpawn Agent session is created
userPromptSubmit User sends a message
preToolUse Before a tool executes
postToolUse After a tool executes
stop Agent finishes responding

Hook output is captured and can be injected into the agent's context. Each hook has configurable timeout_ms, max_output_size, and cache_ttl_seconds.

Resources (resources)

The resources array lists files to include in the agent's context automatically:

{
  "resources": [
    "file://README.md",
    "file://.kiro/steering/**/*.md"
  ]
}

Glob patterns are supported. These files are loaded when the session starts and their content is available to the agent without it needing to explicitly read them.

How It All Fits Together

Here's the flow from kiro-cli --agent docs to a running session:

sequenceDiagram
    participant User
    participant CLI as kiro-cli
    participant Loader as Config Loader
    participant SM as Session Manager
    participant AL as Agent Loop

    User->>CLI: kiro-cli --agent docs
    CLI->>Loader: load_agents()
    Loader->>Loader: Scan ~/.kiro/agents/ + .kiro/agents/
    Loader->>Loader: Parse JSON, resolve prompt, validate
    Loader-->>CLI: Vec<LoadedAgentConfig>
    CLI->>SM: create_session(agent="docs")
    SM->>SM: Find "docs" in loaded configs
    SM->>AL: Start loop with LoadedAgentConfig
    AL->>AL: Apply tools, permissions, prompt

The Config Loader scans both directories, parses every .json file it finds, resolves any file:// prompts, and hands back a list of LoadedAgentConfig objects. The Session Manager picks the one matching the requested name and passes it to the Agent Loop, which uses it to determine what tools to offer the LLM and how to handle permissions.

Internal Implementation

The agent configuration system lives in four files under crates/agent/src/agent/agent_config/:

Type Definitions (definitions.rs)

The core struct is AgentConfigV2025_08_22, which maps directly to the JSON schema. It's wrapped in a versioned enum for future schema evolution:

// crates/agent/src/agent/agent_config/definitions.rs
pub enum AgentConfig {
    V2025_08_22(AgentConfigV2025_08_22),
}

The inner struct holds every field from the JSON — name, global_prompt, tools, allowed_tools, mcp_servers, hooks, resources, tools_settings, and model. Each field has accessor methods on the outer AgentConfig enum so callers don't need to match on the version.

Loading (load.rs)

The load_agents() function is the entry point. It:

  1. Resolves the workspace agents directory (.kiro/agents/ or .amazonq/cli-agents/)
  2. Resolves the global agents directory (~/.kiro/agents/ or ~/.aws/amazonq/cli-agents/)
  3. Reads every .json file, normalizes alias keys in toolsSettings, and deserializes
  4. Deduplicates by name (first file alphabetically wins within a directory; workspace beats global)
  5. Resolves file:// URIs in the prompt field to actual file content
  6. Appends the three built-in agents (kiro_default, kiro_planner, kiro_guide)
// crates/agent/src/agent/agent_config/load.rs
pub async fn load_agents<P: SystemProvider>(
    system: &P,
) -> Result<(Vec<LoadedAgentConfig>, Vec<AgentConfigError>), ...>

The function returns both valid configs and a list of errors for malformed files — it doesn't fail on one bad config.

Parsing (parse.rs)

This module handles the semantic interpretation of tool name strings and resource paths. It defines ToolNameKind — the enum that distinguishes "*" from "@server/tool" from "#agent":

// crates/agent/src/agent/agent_config/parse.rs
pub enum ToolNameKind<'a> {
    All,                    // "*"
    McpFullName { .. },     // "@server/tool"
    McpServer { .. },       // "@server"
    BuiltIn(&'a str),       // "read", "write"
    Agent(&'a str),         // "#my-agent"
    // ... and glob variants
}

Permissions (permissions.rs)

The RuntimePermissions struct in crates/agent/src/agent/permissions.rs tracks what's been granted during a session. It works alongside the static config: the config says what's possible, and runtime permissions track what the user has approved so far.

// crates/agent/src/agent/permissions.rs
pub struct RuntimePermissions {
    filesystem: FileSystemPermissions,
    trusted_tools: HashSet<CanonicalToolName>,
    denied_tools: HashSet<CanonicalToolName>,
    allowed_commands: Vec<String>,
}

When the Agent Loop wants to run a tool, it checks: Is the tool in allowedTools? → auto-approve. Is it in trusted_tools (runtime)? → auto-approve. Otherwise → ask the user.

The Loaded Result (mod.rs)

Everything comes together in LoadedAgentConfig, which wraps the raw config with its resolved prompt and source metadata:

// crates/agent/src/agent/agent_config/mod.rs
pub struct LoadedAgentConfig {
    source: ConfigSource,           // Workspace, Global, BuiltIn, Ephemeral
    config: AgentConfig,
    resolved_global_prompt: ResolvedGlobalPrompt,
    global_prompt_prefix: Option<String>,
    global_prompt_suffix: Option<String>,
}

The ConfigSource enum tracks where the config came from, which matters for debugging and for priority resolution when names collide.

Real-World Example

The amzn-builder agent (from ~/.kiro/agents/) demonstrates the pattern well: its tools field grants visibility to ["@builtin", "@builder-mcp"] — all built-in tools plus everything from the builder-mcp MCP server. But allowedTools only auto-approves a subset: read-only built-ins (fs_read, code, glob, grep) and specific MCP tools like @builder-mcp/InternalSearch. The agent can see write and shell, but the user must approve each use. It also fires a metrics hook on agentSpawn.

Summary

Agent Configuration is the declarative layer that turns a generic kiro-cli session into a specialized, permission-scoped agent. The key takeaways:

Concept What It Controls
prompt Agent personality and instructions
tools Which tools the LLM can see
allowedTools Which tools run without user approval
toolsSettings Fine-grained per-tool restrictions
mcpServers External tool servers to launch
hooks Shell commands at lifecycle events
resources Files auto-loaded into context

The config is loaded once at session creation and stays immutable for the session's lifetime (with the exception of runtime permissions accumulated through user approvals).

What's Next

The agent is configured — it has a prompt, a set of tools, and permission rules. But configuration is static. What actually runs the conversation? What sends prompts to the LLM, dispatches tool calls, and loops until the model says "done"?

That's the Agent Loop — the heartbeat of every kiro-cli session. Let's see how it works.