Architecture: reloaded-code-agents

May 30, 2026 · View on GitHub

Framework-agnostic agent configuration loading, catalog management, model resolution, permission filtering, and runtime assembly.

Upstream integrations (e.g. reloaded-code-serdesai) consume the [AgentRuntime] produced here and adapt it to their framework's agent builder.

Table of Contents

Quick Start

Three steps from markdown files to a working runtime:

use reloaded_code_agents::{AgentLoader, AgentCatalog, AgentRuntimeBuilder, AgentDefaults};
use std::path::Path;

// 1. Load agents from directory
let loader = AgentLoader::new();
let mut catalog = AgentCatalog::new();
loader.add_directory(&mut catalog, Path::new("agents"))?;

// 2. Build the runtime
let runtime = AgentRuntimeBuilder::new()
    .catalog(catalog)
    .defaults(AgentDefaults::with_model("openai/gpt-5.4"))
    .build();

// 3. Use the runtime (e.g., look up agents by name)
let agent = runtime.catalog().by_name("code-reviewer").unwrap();
// ... pass to your framework's agent builder

Phase 1: Loading

Agent definitions live in markdown files with YAML frontmatter:

---
name: code-reviewer
mode: subagent
description: Reviews code and flags high-risk issues
model: ollama-cloud/minimax-m2.7
permission:
  read: allow
  bash: deny
  task:
    "*": deny
    review-*: allow
---
You are a careful code reviewer.

Loading Pipeline

    .md file / string / bytes

             │  AgentLoader::add_directory / add_file / add_from_str

    ┌─────────────────────────────────────────────────────────────────────┐
    │ 1. CRLF -> LF normalization             crlf-to-lf-inplace          │
    │ 2. Find frontmatter delimiters          parser/mod.rs               │
    │ 3. Preprocess YAML                      preprocessor                │
    │    Rewrites colon-containing values to block scalars                │
    │ 4. Parse YAML -> serde_yaml::Value      serde_yaml                  │
    │ 5. Validate headless compatibility      no "ask" in permission.task │
    │ 6. Deserialize Value -> RawFrontmatter  serde_yaml                  │
    │ 7. Build AgentConfig                    from_raw                    │
    └──────────────────────────────┬──────────────────────────────────────┘


                            ┌─────────────┐
                            │ AgentConfig │  name, mode, model, permissions, prompt
                            └──────┬──────┘


                            ┌──────────────┐
                            │ AgentCatalog │  AHashMap<String, AgentConfig>
                            └──────────────┘  last-insert-wins on duplicate names

File Discovery

AgentLoader::add_directory walks the given root with .gitignore support (ignore crate), keeping only files matching:

agent/**/*.md
agents/**/*.md

Agent name is derived from the relative path by stripping the agent/ or agents/ prefix and .md suffix:

agent/code-reviewer.md      -> "code-reviewer"
agents/nested/deep.md        -> "nested/deep"

Frontmatter name: overrides the derived name when present.

YAML Preprocessor

The preprocessor (parser/preprocessor.rs) rewrites lines where an unquoted value contains a bare : - a YAML ambiguity. For example:

model: provider/model:tag

becomes:

model: |-
  provider/model:tag

Already-safe forms (quoted, block scalars, flow syntax, comments, indented continuation lines) are left untouched.

Phase 2: Building

Once you have an [AgentCatalog], you assemble an [AgentRuntime] that holds everything needed to run agents: the catalog, default settings, Task delegation settings, and the available tools.

Building the Runtime

   AgentRuntimeBuilder::new()
       .catalog(catalog)
       .defaults(AgentDefaults { model, temperature, top_p })
       .max_task_depth(n)        // or .task_settings(TaskSettings::with_max_depth(n))
       .tools(vec![...])         // or default_tools() if omitted
       .build()


          ┌──────────────┐
          │ AgentRuntime │  catalog + defaults + task_settings + tools
          └──────────────┘

AgentRuntime is Clone, Send, Sync, and stores no async state.

AgentDefaults

Fallback settings used when an individual agent doesn't specify them:

FieldMeaning
modelDefault provider/model-id
temperatureDefault sampling temperature
top_pDefault nucleus sampling parameter

Tool Catalog

reloaded-code-core::default_tools() returns 10 entries that AgentRuntimeBuilder uses by default:

KindTool name
Readread
Writewrite
Editedit
Globglob
Grepgrep
Bashbash
WebFetchwebfetch
TodoReadtodoread
TodoWritetodowrite
Tasktask

Model Resolution

When an agent needs to run, you resolve which model it should use:

   resolve_model_with_catalog(model_catalog, defaults, agent)

                │  1. agent.model set?  -> parse "provider/model-id"
                │     └─ malformed?     -> MalformedModelIdentifier ("agent override")

                │  2. defaults.model?   -> parse "provider/model-id"
                │     └─ malformed?     -> MalformedModelIdentifier ("runtime default")

                │  3. neither set?       -> MissingEffectiveModel

                │  4. provider in catalog?  -> no -> UnknownProvider
                │  5. model in catalog?     -> no -> UnknownModel

          ┌────────────────┐
          │ ResolvedModel  │  provider: Box<str>, model: Box<str>
          └────────────────┘

Precedence: agent override wins over runtime default.

A malformed agent override does not fall back to the default - it errors.

Phase 3: Runtime Usage

With a built [AgentRuntime], you can query what an agent is allowed to do and which other agents it can delegate to.

Permission Filtering

Agent frontmatter may include a permission map:

In these patterns, * means any number of characters (including none), and ? means exactly one character.

permission:
  read: allow
  bash: deny
  task:
    "*": deny
    "review-*": allow

RulesetExt::from_permission_config converts this into a Ruleset (from reloaded-code-core::permissions):

PermissionRule::Action(Allow)    -> Rule { key: "read",   pattern: "*", action: Allow }
PermissionRule::Action(Deny)     -> Rule { key: "bash",   pattern: "*", action: Deny  }
PermissionRule::Pattern({ .. })  -> Rule { key: "task",   pattern: "*",        action: Deny  }
                                    Rule { key: "task",   pattern: "review-*", action: Allow }

# Wildcard permission keys are also supported
PermissionRule::Action(Allow)    -> Rule { key: "*",      pattern: "*", action: Allow }  # matches any tool

Evaluation uses last-match-wins semantics.

Allowed Tools

AgentRuntime::allowed_tools(caller_name) filters the tool catalog:

   runtime.tools()

        │  for each entry:
        │    Task  -> only if >= 1 callable subagent target exists
        │    other -> is_allowed(entry.name, "*") per Ruleset

   Vec<ToolCatalogEntry>

Callable Targets

callable_targets(catalog, caller_name) returns agents the caller may delegate to via the Task tool:

   all agents (sorted by name)

        │  filter:
        │    mode != Primary  (only All + Subagent are callable)
        │    AND
        │    if caller defines permission.task:
        │      ruleset.is_allowed("task", target.name)
        │    else (no explicit permission.task):
        │      default-allow all non-Primary targets

   Vec<&AgentConfig>

OpenCode compatibility: omitting permission.task defaults to allowing delegation to all non-Primary agents.

Reference

Error Model

AgentLoadError
├── Io { path, source }              file read / directory scan failure
├── Parse { path, source }           frontmatter YAML parse failure
│                                     source: AgentParseError
│                                       ├── MissingFrontmatter
│                                       ├── InvalidYaml { message }
│                                       └── SchemaValidation { message }
└── SchemaValidation { path, message } invalid mode, empty name, "ask" permission

ModelResolutionError
├── MalformedModelIdentifier          missing "/" or empty segments
├── MissingEffectiveModel             neither agent nor default specifies a model
├── UnknownProvider                   provider not in ModelCatalog
└── UnknownModel                      provider found but model not listed

All loader errors carry an optional path: Option<PathBuf> (None for in-memory sources, displayed as <memory>).

Testing

  • tempfile + indoc fixtures for file/directory loading tests.
  • No external services required.
  • Parser benchmarks in benches/parser.rs (Criterion).

File Map

reloaded-code-agents
├── lib.rs                  crate root, re-exports
├── catalog.rs              AgentCatalog - in-memory name -> AgentConfig store
├── extensions.rs           RulesetExt - builds Ruleset from frontmatter permissions
├── loader.rs               AgentLoader - scans dirs/files/strings -> AgentCatalog
├── parser/
│   ├── mod.rs              parse_agent() - YAML frontmatter + body extractor
│   └── preprocessor.rs     YAML preprocessor - rewrites colon-containing values
├── types/
│   ├── mod.rs              re-exports
│   ├── config.rs           AgentConfig, AgentMode, PermissionRule, parse_model_parts
│   ├── error.rs            AgentLoadError, AgentLoadResult
│   └── tool_settings.rs    AgentToolSettings, ReadToolSettings, GrepToolSettings
├── runtime/
│   ├── mod.rs              module root, re-exports
│   ├── state.rs            AgentRuntime, AgentDefaults
│   ├── builder.rs          AgentRuntimeBuilder
│   ├── model.rs            resolve_model_with_catalog(), ResolvedModel, ModelResolutionError
│   └── task.rs             callable_targets(), summarize_callable_targets(), allowed_tools()
└── benches/
    └── parser.rs           Criterion benchmarks for frontmatter parsing