Advanced Features

August 14, 2026 · View on GitHub

Sub-Agent Spawning

The LLM can use the Spawn tool to create independent sub-agents that run tasks in parallel. Each sub-agent has its own conversation context, inherits the parent agent's runtime tool policy, and shares the parent agent's LLM provider (connection pool reuse). Fork-mode overrides can further restrict inherited tools but cannot restore tools denied to the parent.

Use Cases

  • "Search these 3 files simultaneously and summarize each"
  • "Run tests and lint in parallel"
  • "Search for X in the codebase while reading Y"

Limits

SettingDefaultDescription
Max parallel sub-agents5Prevents resource exhaustion
Sub-agent max turns10Per sub-agent run turn limit
Sub-agent max tokens4096Per sub-agent response token limit

Behavior

  • Sub-agents auto-approve all tool calls (no confirmation prompts)
  • Sub-agents cannot exceed the parent agent's runtime tool policy
  • Sub-agents do not save sessions
  • Sub-agents run silently (no stdout output)
  • All results are merged and returned to the parent agent

Hook System

Event-driven hooks execute shell commands at specific points in the tool lifecycle, enabling auto-formatting, linting, auditing, and more.

Hook Types

TypeTriggerBehavior
pre_tool_useBefore tool executionNon-zero exit blocks the tool
post_tool_useAfter tool executionNon-blocking; errors are logged
stopWhen agent session endsNon-blocking

Configuration

# Auto-format Rust files after modification
[[hooks.post_tool_use]]
name = "rustfmt"
tool_match = ["Write", "Edit"]
file_match = ["*.rs"]
command = "rustfmt ${TOOL_INPUT_FILE_PATH}"

# Auto-format TypeScript files after modification
[[hooks.post_tool_use]]
name = "prettier"
tool_match = ["Write", "Edit"]
file_match = ["*.ts", "*.tsx"]
command = "npx prettier --write ${TOOL_INPUT_FILE_PATH}"

# Audit ExecCommand commands
[[hooks.post_tool_use]]
name = "audit-log"
tool_match = ["ExecCommand"]
command = "echo \"$(date): ${TOOL_INPUT_COMMAND}\" >> .aionrs/audit.log"

# Run lint on session end
[[hooks.stop]]
name = "final-lint"
command = "cargo clippy --quiet 2>&1 | tail -5"

Environment Variables

Hook commands can reference these variables via ${VAR} syntax:

VariableDescription
TOOL_NAMETool name
TOOL_INPUTFull tool input JSON
TOOL_INPUT_FILE_PATHFile path (if the tool has a file_path parameter)
TOOL_INPUT_COMMANDCommand (if the tool has a command parameter)
TOOL_INPUT_PATTERNSearch pattern (if the tool has a pattern parameter)
TOOL_OUTPUTTool output (post_tool_use only)

Matching Rules

  • tool_match: glob patterns matching tool names; empty = match all
  • file_match: glob patterns matching file paths; empty = match all
  • Default timeout: 30 seconds, configurable via timeout_ms

Prompt Caching (Anthropic)

Prompt caching stores system prompts and tool definitions on Anthropic's servers, so subsequent requests only process the changed parts.

  • First request: full input token cost + 25% write premium
  • Subsequent requests: cached portion costs only 10%
  • Cache TTL: 5 minutes (auto-renewed on each hit)

Configuration

[providers.anthropic]
api_key = "sk-ant-xxx"
prompt_caching = true   # default true (Anthropic only)

Token Stats

With caching enabled, stats show cache data:

[turns: 3 | tokens: 100 in (5000 cached) / 200 out | cache: 5000 created, 5000 read]

VCR Recording & Replay

Record real API interactions and replay them in tests — no API key or network needed.

Usage

# Record mode
VCR_MODE=record VCR_CASSETTE=tests/cassettes/my_test.json \
  aionrs -k sk-ant-xxx "Read Cargo.toml"

# Replay mode (in tests)
VCR_MODE=replay VCR_CASSETTE=tests/cassettes/my_test.json \
  aionrs "Read Cargo.toml"

Features

  • Auto-sanitization: sensitive headers (api-key, auth, token) are replaced with [REDACTED] during recording
  • JSON-formatted cassette files, editable by hand
  • Supports recording/replay of SSE streaming responses

Logging

Structured JSON file logging with daily rotation, powered by the tracing crate. All internal events (LLM requests/responses, tool execution, MCP connections, compaction) are captured with structured fields.

Enabling

Three ways to enable logging, from highest to lowest priority:

  1. CLI parameter: --log-dir /path/to/logs (automatically enables logging)
  2. Config file: add a [logging] section (global or project-level)
  3. Default: logging is disabled unless explicitly configured
# CLI — logs to /tmp/aionrs-logs at debug level
aionrs --log-dir /tmp/aionrs-logs --log-level debug "Read Cargo.toml"

Configuration

[logging]
enabled = true       # enable file logging (default: false; auto-enabled when dir is set)
level = "info"       # tracing filter directives (default: "info")
dir = "/path/to/logs"  # log directory (default: platform-specific, see below)

The level field accepts standard tracing filter directives:

ValueEffect
"info"Info and above for all targets
"debug"Debug and above for all targets
"aion_providers=debug,info"Debug for providers, info for everything else

Default Log Directory

When dir is not set, logs go to the platform-specific location:

PlatformPath
macOS~/Library/Logs/aionrs/
Linux$XDG_STATE_HOME/aionrs/logs/ or ~/.local/state/aionrs/logs/
Windows{data_local_dir}/aionrs/logs/

Log Format

Each line is a JSON object with structured fields:

{"timestamp":"2026-05-13T12:12:52.431Z","level":"INFO","fields":{"message":"mcp server connected","server":"sentry","tools":20},"target":"aion_mcp","spans":[{"name":"agent_run","session_id":"abc-123","msg_id":"msg-456"}]}

Key fields:

FieldDescription
targetSource crate (aion_agent, aion_providers, aion_mcp, etc.)
spans[].session_idSession ID for correlating events within a conversation
spans[].msg_idMessage ID for correlating events within a single turn

Session Correlation

All events during engine.run() — LLM streaming, tool execution, compaction — are wrapped in an agent_run span carrying session_id and msg_id. This allows filtering all logs for a specific conversation:

# Find all events for a specific session
grep '"session_id":"abc-123"' 2026-05-13.aionrs.log | jq .

Library Integration

When aionrs is used as a library (e.g. embedded in a backend server), the create_file_layer() API provides a composable tracing layer:

use aion_config::logging::{ResolvedLogging, create_file_layer};

let resolved = ResolvedLogging {
    enabled: true,
    level: "aion_agent=debug,aion_providers=debug".to_string(),
    dir: log_dir.to_path_buf(),
};
let (layer, guard) = create_file_layer(&resolved)?;

// Compose with your existing subscriber
tracing_subscriber::registry()
    .with(your_app_layer)
    .with(layer)  // aionrs logs → separate aionrs.log file
    .init();

The host application owns the global subscriber; aionrs library crates only emit tracing events and never initialize a subscriber themselves.


AGENTS.md Hierarchical Loading

AGENTS.md files provide project-specific instructions that are automatically injected into the system prompt. Files are discovered hierarchically and merged from remote to near:

  1. Global: <config_dir>/aionrs/AGENTS.md — user-level instructions for all projects
  2. Project hierarchy: Walk up from cwd to the git root (or home directory), collecting every AGENTS.md found along the way

Files closer to the working directory appear later in the prompt and take precedence (via LLM recency bias). Each file is annotated with its absolute path for traceability.

@include Directive

AGENTS.md files can include other files using @ syntax:

  • @FILENAME or @./relative/path — relative to the AGENTS.md file's directory
  • @~/path — relative to home directory
  • @/absolute/path — absolute path

Paths inside fenced code blocks are ignored. Includes are recursive (up to depth 5) with circular reference detection. Non-existent files and non-text files are silently skipped.

Example

Given this structure:

my-workspace/
├── .git/
├── AGENTS.md          ← workspace rules
└── packages/
    └── server/
        └── AGENTS.md  ← server-specific rules

Running aion in packages/server/ produces a system prompt containing both files, workspace first, then server.


Memory System

Persistent, file-based memory that allows the agent to retain project-specific knowledge across sessions. Memory is automatically loaded into the system prompt at conversation start.

Memory Types

TypePurpose
userUser's role, goals, preferences, knowledge
feedbackCorrections and confirmations on work approach
projectOngoing work context not derivable from code/git
referencePointers to external systems and resources

Storage

Memory files live in a per-project directory under the global config:

<config_dir>/aionrs/projects/<sanitized-project-path>/memory/
├── MEMORY.md              # Index (auto-loaded into prompt, max 200 lines)
├── user_role.md
├── feedback_testing.md
└── project_auth_rewrite.md

Each memory file uses YAML frontmatter:

---
name: auth rewrite
description: Auth middleware rewrite driven by compliance
type: project
---

Auth middleware rewrite is driven by legal/compliance requirements.

Configuration

Memory is enabled by default with no configuration required. The memory directory is auto-resolved from the current working directory.

Override the base directory via environment variable:

export AIONRS_MEMORY_DIR=/custom/path

How It Works

  1. Agent starts → memory directory resolved from project path
  2. MEMORY.md index loaded into system prompt (truncated at 200 lines / 25 KB)
  3. Agent reads/writes memory files using standard Read/Write tools
  4. Agent maintains the MEMORY.md index as memories are added or removed

Plan Mode

A read-only exploration mode where the agent focuses on understanding the codebase and producing an implementation plan before making any changes.

How It Works

  1. Agent calls EnterPlanMode → tool access restricted to read-only (Read, Grep, Glob)
  2. Agent explores code, designs approach, writes a structured plan in its response
  3. Agent calls ExitPlanMode → full tool access restored, plan optionally saved to disk

Configuration

[plan]
enabled = true                    # Register Plan Mode tools (default: true)
plan_directory = ".aionrs/plans"  # Where plan files are saved

Workflow Phases

When in plan mode, the agent follows a structured 4-phase process:

  1. Understand — Explore the codebase with read-only tools
  2. Design — Identify files to modify, code to reuse
  3. Write the plan — Compose a clear, actionable implementation plan
  4. Submit — Call ExitPlanMode to restore full tool access

Context Compression

A cache-friendly context strategy that bounds each tool result once when it enters history, then uses token-based compaction to prevent context window overflow during long conversations.

Tiers

TierTriggerMethodLLM Call
Tool output limitA tool result exceeds tool_output_max_bytesPreserves the beginning and end once before storing the resultNo
AutocompactInput tokens approach context limitLLM summarizes the conversationYes
EmergencyInput tokens near absolute limitBlocks further API calls, asks user to start freshNo

How It Works

  • Tool output limiting runs once per result after structural output compaction. Results longer than 10,000 UTF-8 bytes retain their beginning and end with a truncation marker in the middle. Stored history is not rewritten later, preserving a stable prompt-cache prefix.

  • Legacy microcompact can still be enabled explicitly for compatibility, but defaults to off because it rewrites old history and can invalidate prompt caches.

  • Autocompact triggers when input tokens reach a threshold. By default this is context_window - output_reserve - autocompact_buffer (200,000 - 20,000 - 13,000 = 167,000 tokens). Alternatively, set autocompact_threshold_pct to trigger at a percentage of the context window (e.g. 50 = 50% of 200k = 100k tokens). The agent calls the LLM to produce a conversation summary, then replaces history with a compact boundary marker. A circuit breaker stops retrying after 3 consecutive failures.

  • Emergency is the last safety net at context_window - emergency_buffer (default: 197,000 tokens). Always active regardless of config. Blocks API calls and prompts the user to compact or start a new conversation.

Configuration

[compact]
enabled = true              # Enable compaction system (default: true)
context_window = 200000     # Context window in tokens
output_reserve = 20000      # Reserved for output generation
autocompact_buffer = 13000  # Buffer before autocompact triggers
emergency_buffer = 3000     # Buffer before emergency block
max_failures = 3            # Circuit breaker threshold
tool_output_max_bytes = 10000 # Maximum model-facing bytes per tool result
microcompact_enabled = false  # Legacy history rewriting; not recommended
# autocompact_threshold_pct = 50  # Override: trigger at N% of context_window

File State Cache

An LRU cache that tracks files the agent has recently accessed, enabling read deduplication and automatic cache updates on writes.

  • Read dedup: When the agent reads a file it has already seen (and the file hasn't changed), the cache provides the content without re-reading from disk.
  • Write/Edit auto-update: After Write or Edit operations, the cache is updated immediately with the new content.
  • Dual eviction: Entries are evicted when either the entry count limit or the total byte size limit is reached.

Configuration

[file_cache]
enabled = true                # Enable file state caching (default: true)
max_entries = 100             # Maximum cached files
max_size_bytes = 26214400     # Max total cache size (25 MB)

Output Compaction

Post-processes tool output to reduce token usage. Three levels from lightest to heaviest:

LevelTransformations
offNo transformation
safe (default)Strip ANSI escape codes, merge consecutive blank lines, collapse carriage-return progress bars
fullEverything in safe, plus: fold repeated lines, compact JSON indentation

TOON Encoding

When enabled alongside full compaction, TOON (Token-Oriented Object Notation) encodes uniform JSON arrays as compact tables:

[2]{id,name,role}:
  1,Alice,admin
  2,Bob,user

This is equivalent to:

[{"id":1,"name":"Alice","role":"admin"},{"id":2,"name":"Bob","role":"user"}]

TOON instructions are injected into the system prompt so the LLM understands the format.

Configuration

[compact]
tool_output_max_bytes = 10000 # Final model-facing limit per tool result
compaction = "safe"          # off | safe | full (default: safe)
toon = false                 # Enable TOON encoding (default: false)

Runtime Control

In --json-stream mode, the compaction level can be changed at runtime via set_config:

{"type": "set_config", "compaction": "full"}