Skip to content

trading_agent.subagents.subagent_definition

One subagent: a helper a desk can hand a task to, defined in a Markdown file.

The format is Claude Code's subagent format, so definitions can be shared with it. YAML frontmatter, then the subagent's instructions:


name: news_reader description: Reads recent news about a company or index and reports what matters for trading. tools: web_search, web_fetch, search_knowledge model: qwen max_turns: 12 timeout: 300


You read news for a trading desk...

  • tools is a comma-separated list or a YAML list. Without it the subagent gets a safe, read-only set. Order tools are given only when listed by name.
  • model is a backend name from trading_agent.toml, a list of them in order of preference, or "inherit" for the desk's own order. Claude Code's aliases opus, sonnet and haiku mean the Claude backend with that model.
  • max_turns and timeout (seconds) are trading-agent's additions; Claude Code ignores them.

Typical usage example:

definition = SubagentParser().parse(Path('workspace/shared/agents/news_reader.md'))

NAME_PATTERN module-attribute

NAME_PATTERN = compile('^[a-z][a-z0-9_-]{0,63}$')

DEFAULT_MAXIMUM_TURNS module-attribute

DEFAULT_MAXIMUM_TURNS = 12

DEFAULT_TIMEOUT_SECONDS module-attribute

DEFAULT_TIMEOUT_SECONDS = 300

MAXIMUM_TIMEOUT_SECONDS module-attribute

MAXIMUM_TIMEOUT_SECONDS = 1800

SubagentFormatError

Bases: ValueError

A subagent file has no frontmatter, or a field is not valid.

SubagentDefinition dataclass

SubagentDefinition(name, description, instructions, tool_names, models, maximum_turns, timeout_seconds, path)

One subagent.

Attributes:

Name Type Description
name str

The subagent's name, used by delegate_task.

description str

When to use it, shown to the desk.

instructions str

Its system prompt, the Markdown after the frontmatter.

tool_names tuple[str, ...] | None

The tools it asked for, or None for the default read-only set.

models tuple[str, ...] | None

The backends or Claude aliases it asked for, in order, or None to inherit the desk's order.

maximum_turns int

The most model calls one task may make.

timeout_seconds int

How long one task may run before it is stopped.

path Path

The file it was read from.

SubagentParser

Reads subagent files.

parse

parse(path)

Reads one subagent file.

Parameters:

Name Type Description Default
path Path

The Markdown file.

required

Returns:

Type Description
SubagentDefinition

The subagent.

Raises:

Type Description
SubagentFormatError

The file has no frontmatter, or its name, description, model or limits are not valid.

Source code in src/trading_agent/subagents/subagent_definition.py
def parse(self, path: Path) -> SubagentDefinition:
    """Reads one subagent file.

    Args:
        path (Path): The Markdown file.

    Returns:
        SubagentDefinition: The subagent.

    Raises:
        SubagentFormatError: The file has no frontmatter, or its name, description, model or limits are not valid.
    """
    text = path.read_text(encoding='utf-8')
    match = _FRONTMATTER_PATTERN.match(text)
    if match is None:
        raise SubagentFormatError(
            f'{path} does not start with frontmatter between two --- lines'
        )
    try:
        frontmatter = yaml.safe_load(match.group(1))
    except yaml.YAMLError as error:
        raise SubagentFormatError(
            f'{path} has frontmatter that is not valid YAML: {error}'
        ) from error
    if not isinstance(frontmatter, dict):
        raise SubagentFormatError(f'{path} frontmatter is not a mapping')
    name = str(frontmatter.get('name', path.stem)).strip()
    if not NAME_PATTERN.match(name):
        raise SubagentFormatError(
            f'Subagent name {name!r} in {path} must be lower-case letters, digits, hyphens and underscores, starting with a letter'
        )
    description = str(frontmatter.get('description', '')).strip()
    if not description:
        raise SubagentFormatError(
            f'Subagent {name} in {path} needs a description'
        )
    return SubagentDefinition(
        name=name,
        description=description,
        instructions=text[match.end() :].strip(),
        tool_names=self._names(frontmatter.get('tools')),
        models=self._models(frontmatter.get('model')),
        maximum_turns=self._whole_number(
            frontmatter.get('max_turns', DEFAULT_MAXIMUM_TURNS),
            'max_turns',
            path,
            DEFAULT_MAXIMUM_TURNS * 10,
        ),
        timeout_seconds=self._whole_number(
            frontmatter.get('timeout', DEFAULT_TIMEOUT_SECONDS),
            'timeout',
            path,
            MAXIMUM_TIMEOUT_SECONDS,
        ),
        path=path,
    )