# `ClaudeWrapper.Agents`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/agents.ex#L58)

File-backed read/write access to Claude Code's on-disk **agent**
definitions.

Claude Code resolves user-level agents from
`~/.claude/agents/<stem>.md`. Each file is plain markdown with a
YAML-style frontmatter block delimited by `---` lines. The
frontmatter carries the agent's metadata (name, description, optional
tool allow-list, optional model); the body is the agent's system
prompt.

This is distinct from `ClaudeWrapper.Commands.Agents`, which shells
out to `claude agents`. This module reads and writes the files
directly.

Two levels of granularity for reads:

  * `list/1` -- enumerate every agent at the root with summary
    metadata (name, description, tools, model, file path).
  * `get/2` -- read one agent's full record including the prompt body
    and unknown frontmatter keys.

Plus `write/3`, `write_new/3`, and `delete/2` for mutations.

## Frontmatter format

Real-world agents look like:

    ---
    name: auditor
    description: Multi-line folded description...
    tools: Read, Glob, Grep, Bash
    model: sonnet
    ---

    You are an auditor. ...

The parser is permissive: only `name`, `description`, `tools`, and
`model` are typed. `tools` is a comma-separated list. Any other
`key: value` pairs land in `Definition.extra` so unknown future keys
survive a round trip. Frontmatter is optional -- a body-only file
parses fine, with `name` defaulting to the file stem.

List-valued and folded frontmatter keys (anything this parser cannot
reduce to a single inline scalar) are tolerated by capturing their
raw inline value into `extra` rather than failing the parse.

## File stem vs name

By convention an agent's `name` matches its filename stem:
`auditor.md` carries `name: auditor`. The two can diverge -- the
parser keeps both. Lookup, write, and delete all key on the file
stem (that's what the filesystem indexes), not the frontmatter
`name`.

## Example

    {:ok, root} = ClaudeWrapper.Agents.home()
    {:ok, summaries} = ClaudeWrapper.Agents.list(root)

    for s <- summaries do
      IO.puts("#{s.file_stem}: #{s.description}")
    end

    {:ok, agent} = ClaudeWrapper.Agents.get(root, "auditor")
    IO.puts(agent.body)

# `attrs`

```elixir
@type attrs() :: Enumerable.t()
```

Attributes for `write/3` and `write_new/3`.

A keyword list or map. All keys are optional:

  * `:name` -- frontmatter `name`; defaults to the file stem
  * `:description` -- frontmatter `description`
  * `:tools` -- list of tool names, rendered comma-joined
  * `:model` -- frontmatter `model`
  * `:body` -- the agent prompt body
  * `:extra` -- map of additional frontmatter `key => string` pairs,
    rendered in sorted order after the typed keys

# `t`

```elixir
@type t() :: %ClaudeWrapper.Agents{root: String.t()}
```

# `at`

```elixir
@spec at(String.t()) :: t()
```

Use a specific path as the agents root. Useful for tests (point at a
temp dir) and non-default installs.

# `delete`

```elixir
@spec delete(t(), String.t()) :: :ok | {:error, ClaudeWrapper.Error.t()}
```

Remove the `<file_stem>.md` agent.

Returns `{:error, %ClaudeWrapper.Error{kind: :not_found}}` when no such
file exists.

# `get`

```elixir
@spec get(t(), String.t()) ::
  {:ok, ClaudeWrapper.Agents.Definition.t()} | {:error, ClaudeWrapper.Error.t()}
```

Read one agent by file stem (the basename of `<stem>.md` under the
root) into a full `Definition`.

Returns `{:error, %ClaudeWrapper.Error{kind: :not_found}}` when no such
file exists.

# `home`

```elixir
@spec home() :: {:ok, t()} | {:error, ClaudeWrapper.Error.t()}
```

Resolve the default agents root, `~/.claude/agents`.

Returns `{:error, %ClaudeWrapper.Error{kind: :no_home}}` when the user
home cannot be determined.

# `list`

```elixir
@spec list(t()) ::
  {:ok, [ClaudeWrapper.Agents.Summary.t()]} | {:error, ClaudeWrapper.Error.t()}
```

List every `*.md` agent at the root, sorted by file stem.

Returns `{:ok, []}` when the root directory does not exist (a fresh
install with no user agents). Files that fail to parse are skipped
rather than failing the whole listing.

# `root`

```elixir
@spec root(t()) :: String.t()
```

The configured root directory.

# `write`

```elixir
@spec write(t(), String.t(), attrs()) :: :ok | {:error, ClaudeWrapper.Error.t()}
```

Write (create or overwrite) an agent at `<file_stem>.md`.

Creates the agents root directory if it does not exist. See `t:attrs/0`
for the accepted attributes. To fail when the agent already exists
instead of overwriting, use `write_new/3`.

Returns `{:error, %ClaudeWrapper.Error{kind: :invalid_stem}}` (with the
stem in `:reason`) for empty, `.`, `..`, or stems containing slashes
or NUL bytes.

# `write_new`

```elixir
@spec write_new(t(), String.t(), attrs()) :: :ok | {:error, ClaudeWrapper.Error.t()}
```

Like `write/3` but returns `{:error, %ClaudeWrapper.Error{kind:
:already_exists}}` when the agent already exists. Useful for
create-only flows where overwriting would be a bug.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
