Skip to content

trading_agent.skills.skill_writer

Creates, patches and archives the skills a desk writes for itself.

Every change is snapshotted first, so it can be undone with trading-agent checkpoints restore, and recorded as one JSON line in the desk's skill ledger, workspace/desks/<desk>/skills/ledger.jsonl. The ledger sits outside skills/agent/, so restoring a checkpoint never erases the record of what happened. An archived skill moves to skills/agent/.archive// and drops out of the index, but nothing is deleted.

Typical usage example:

writer = SkillWriter(library, checkpoints, ledger_file) writer.create('gap-down-playbook', 'What to do when NIFTY gaps down.', body, [], [], 'learned on 19 Oct')

ARCHIVE_FOLDER_NAME module-attribute

ARCHIVE_FOLDER_NAME = '.archive'

SkillWriteError

Bases: ValueError

A skill change was refused, with a reason the desk can act on.

SkillWriter

SkillWriter(library, checkpoints, ledger_file)

Changes one desk's own skills.

Creates the writer.

Parameters:

Name Type Description Default
library SkillLibrary

The desk's skills, for name checks.

required
checkpoints Checkpoints | None

Snapshots the desk's files before each change, or None to take none.

required
ledger_file Path

The JSON-lines record of every change.

required
Source code in src/trading_agent/skills/skill_writer.py
def __init__(
    self,
    library: skill_library_module.SkillLibrary,
    checkpoints: checkpoints_module.Checkpoints | None,
    ledger_file: Path,
):
    """Creates the writer.

    Args:
        library (skill_library_module.SkillLibrary): The desk's skills, for name checks.
        checkpoints (checkpoints_module.Checkpoints | None): Snapshots the desk's files before each change, or None to take none.
        ledger_file (Path): The JSON-lines record of every change.
    """
    self._library = library
    self._checkpoints = checkpoints
    self._ledger_file = ledger_file
    self._parser = skill_module.SkillParser()

create

create(name, description, body, required_programs, required_environment, reason)

Writes a new skill.

Parameters:

Name Type Description Default
name str

The skill's name.

required
description str

When to use it.

required
body str

The Markdown instructions.

required
required_programs list[str]

Programs it needs.

required
required_environment list[str]

Environment variables it needs.

required
reason str

Why it was written, for the ledger.

required

Returns:

Type Description
Path

The new SKILL.md.

Raises:

Type Description
SkillWriteError

The name or description is not valid, or a skill of that name exists.

Source code in src/trading_agent/skills/skill_writer.py
def create(
    self,
    name: str,
    description: str,
    body: str,
    required_programs: list[str],
    required_environment: list[str],
    reason: str,
) -> Path:
    """Writes a new skill.

    Args:
        name (str): The skill's name.
        description (str): When to use it.
        body (str): The Markdown instructions.
        required_programs (list[str]): Programs it needs.
        required_environment (list[str]): Environment variables it needs.
        reason (str): Why it was written, for the ledger.

    Returns:
        Path: The new SKILL.md.

    Raises:
        SkillWriteError: The name or description is not valid, or a skill of that name exists.
    """
    try:
        self._parser.check_name(name)
        self._parser.check_description(description)
    except skill_module.SkillFormatError as error:
        raise SkillWriteError(str(error)) from error
    if self._library.is_installed_name(name):
        raise SkillWriteError(
            f"{name} is one of the user's installed skills; choose another name."
        )
    directory = self._library.agent_directory / name
    if directory.exists():
        raise SkillWriteError(
            f'You already have a skill named {name}; change it with skill_patch.'
        )
    self._snapshot(f'before skill_create {name}')
    text = self._parser.frontmatter_text(
        name,
        description,
        required_programs,
        required_environment,
    )
    directory.mkdir(parents=True)
    path = directory / skill_module.SKILL_FILE_NAME
    path.write_text(text + '\n' + body.strip() + '\n', encoding='utf-8')
    self._record('create', name, reason)
    return path

patch

patch(name, old_text, new_text, reason)

Replaces exact text in one of the desk's skills, frontmatter included.

Parameters:

Name Type Description Default
name str

The skill's name.

required
old_text str

The text to replace, which must appear exactly once.

required
new_text str

The replacement.

required
reason str

Why, for the ledger.

required

Returns:

Type Description
int

The skill file's new length in characters.

Raises:

Type Description
SkillWriteError

The skill is not the desk's own, old_text is missing or repeated, or the result is not a valid skill.

Source code in src/trading_agent/skills/skill_writer.py
def patch(
    self,
    name: str,
    old_text: str,
    new_text: str,
    reason: str,
) -> int:
    """Replaces exact text in one of the desk's skills, frontmatter included.

    Args:
        name (str): The skill's name.
        old_text (str): The text to replace, which must appear exactly once.
        new_text (str): The replacement.
        reason (str): Why, for the ledger.

    Returns:
        int: The skill file's new length in characters.

    Raises:
        SkillWriteError: The skill is not the desk's own, old_text is missing or repeated, or the result is not a valid skill.
    """
    skill = self._own_skill(name)
    path = skill.directory / skill_module.SKILL_FILE_NAME
    text = path.read_text(encoding='utf-8')
    occurrences = text.count(old_text)
    if occurrences == 0:
        raise SkillWriteError(
            'old_text was not found. Load the skill again and copy the text exactly.'
        )
    if occurrences > 1:
        raise SkillWriteError(
            f'old_text appears {occurrences} times; include more surrounding text.'
        )
    new_skill_text = text.replace(old_text, new_text)
    self._check_still_valid(skill, new_skill_text)
    self._snapshot(f'before skill_patch {name}')
    path.write_text(new_skill_text, encoding='utf-8')
    self._record('patch', name, reason)
    return len(new_skill_text)

archive

archive(name, reason)

Moves one of the desk's skills out of use, into the archive.

Parameters:

Name Type Description Default
name str

The skill's name.

required
reason str

Why, for the ledger.

required

Returns:

Type Description
Path

Where the skill now lives.

Raises:

Type Description
SkillWriteError

The skill is not the desk's own.

Source code in src/trading_agent/skills/skill_writer.py
def archive(self, name: str, reason: str) -> Path:
    """Moves one of the desk's skills out of use, into the archive.

    Args:
        name (str): The skill's name.
        reason (str): Why, for the ledger.

    Returns:
        Path: Where the skill now lives.

    Raises:
        SkillWriteError: The skill is not the desk's own.
    """
    skill = self._own_skill(name)
    self._snapshot(f'before skill_archive {name}')
    archive = self._library.agent_directory / ARCHIVE_FOLDER_NAME
    archive.mkdir(parents=True, exist_ok=True)
    target = archive / name
    if target.exists():
        stamp = datetime.datetime.now(datetime.UTC).strftime('%Y%m%d%H%M%S')
        target = archive / f'{name}-{stamp}'
    shutil.move(str(skill.directory), str(target))
    self._record('archive', name, reason)
    return target