Skill Lifecycle

Provenance, usage telemetry, recoverable archive, and rollback for agent-created skills

Agent-created skills carry provenance metadata and usage telemetry that lets you track, archive, restore, and roll back skills across their entire lifetime.

python
from praisonaiagents import Agent

agent = Agent(
    name="Self-Improving Agent",
    instructions="Create skills when you learn something new; archive stale ones.",
    tools=["skill_manage", "skills_list"],
)
agent.start("Learn how to summarise weekly reports, then save that as a skill.")

The user teaches a workflow; lifecycle tools track creation, usage, archive, and rollback.

Diagram

Skill Lifecycle

rollback

✨ Created

⚡ Used

📊 Telemetry Updated

✏️ Edited / Patched

💾 .skill.bak

🗄️ Archived

🔄 Restored

Quick Start

  1. Create a Skill with Provenance
    python
    from praisonaiagents import SkillManager
    
    mgr = SkillManager()
    mgr.discover()
    
    # agent_created=True stamps frontmatter with provenance fields
    result = mgr.create_skill(
        "csv-analysis",
        "# CSV Analysis\n1. Load with pandas\n2. Clean nulls\n3. Summarise",
        agent_created=True
    )
    print(result)
    # {"success": True, "skill": "csv-analysis", "path": "/path/to/skill"}

    The resulting SKILL.md frontmatter is automatically stamped:

    yaml
    ---
    name: csv-analysis
    description: Generated skill
    agent-created: true
    created-at: "2024-06-24T10:00:00Z"
    use-count: 0
    last-used: null
    patch-count: 0
    ---
  2. Telemetry Updates on Use
    python
    # Each invoke() or get_instructions() call increments use-count and sets last-used
    body = mgr.invoke("csv-analysis", raw_args="sales.csv")
    
    # SKILL.md frontmatter is updated in place:
    # use-count: 1
    # last-used: "2024-06-24T10:05:00Z"
  3. Recoverable Delete and Restore
    python
    # delete_skill archives by default — the skill is NOT gone
    mgr.delete_skill("csv-analysis")
    print(mgr.list_archived_skills())  # ["csv-analysis"]
    
    # Bring it back
    mgr.restore_skill("csv-analysis")
  4. Rollback a Broken Edit
    python
    # edit_skill and patch_skill save a snapshot before mutating
    mgr.edit_skill("csv-analysis", "# CSV Analysis v2\n... broken content ...")
    
    # Undo the last change
    result = mgr.rollback_skill("csv-analysis")
    print(result)
    # {"success": True, "skill": "csv-analysis"}

How It Works

Diagram
skills_archive.skill.bakSKILL.mdSkillManagerAgentskills_archive.skill.bakSKILL.mdSkillManagerAgentagent-created: true\ncreated-at: <iso>\nuse-count: 0\npatch-count: 0skills_archive/name/skill is active againcreate_skill(“name”, content, agent_created=True)write frontmatter + bodyinvoke(“name”) or get_instructions(“name”)update use-count + last-used in placeedit_skill(“name”, new_content)snapshot prior SKILL.mdwrite new content, increment patch-countrollback_skill(“name”)read snapshotrestore prior contentdelete_skill(“name”)move skill directoryrestore_skill(“name”)move back to active dir

Frontmatter Fields

Five fields are added to SKILL.md frontmatter when agent_created=True is passed to create_skill, or set manually. The parser accepts both hyphen (agent-created) and underscore (agent_created) forms.

FieldFrontmatter KeyTypeDefaultWritten When
agent_createdagent-createdboolFalsecreate_skill(agent_created=True)
created_atcreated-atstr (ISO 8601)Nonecreate_skill() call timestamp
use_countuse-countint0Incremented on each invoke() / get_instructions()
last_usedlast-usedstr (ISO 8601)NoneSet on each invoke() / get_instructions()
patch_countpatch-countint0Incremented on each edit_skill() / patch_skill()

Backward-compatible: Skills without these fields parse fine — all fields default to their zero/None values.

Telemetry only counts successful activations. If get_instructions() returns None because the skill failed to activate, use-count and last-used are not updated. A broken skill stays at use-count: 0, keeping telemetry trustworthy.


idle_days Helper

SkillProperties.idle_days returns the number of days since the skill was last meaningfully active.

python
from praisonaiagents import SkillManager

mgr = SkillManager()
mgr.discover()

skill = mgr.get_skill("csv-analysis")
days = skill.properties.idle_days
# Returns days since last_used, or since created_at if the skill has never been used.
# Returns None when neither last_used nor created_at is set.

if days is not None and days > 30:
    print(f"csv-analysis has been idle for {days:.1f} days — consider archiving")

idle_days exposes the data for lifecycle decisions. The policy (what “stale” threshold to apply, when to auto-archive) lives in plugin code, not in core.


Archive / Restore

Directory Layout

~/.praisonai/
├── skills/               # active skills
│   ├── csv-analysis/
│   │   ├── SKILL.md
│   │   └── .skill.bak    # rollback snapshot (after edit/patch)
│   └── weekly-summary/
│       └── SKILL.md
└── skills_archive/       # archived skills (sibling of skills/)
    ├── csv-analysis/
    └── old-formatter.20240624153012/  # collision-suffixed

The archive store is always the sibling directory of the first active skills directory (e.g., <first_skill_dir>/../skills_archive/).

Collision Behaviour

If a skill with the same name already exists in skills_archive/ when archiving, a UTC timestamp suffix is appended: `<name>.

`. This prevents overwriting a previously archived version.

In-Memory State

archive_skill() removes the skill from the in-memory index immediately. After restore_skill(), call discover() or use the returned path to add it back manually if needed in the same session.

python
from praisonaiagents import SkillManager

mgr = SkillManager()
mgr.discover()

# Archive
mgr.archive_skill("csv-analysis")
print("csv-analysis" in mgr.skill_names)  # False — removed from index

# Restore
mgr.restore_skill("csv-analysis")
mgr.discover()                             # re-index after restore
print("csv-analysis" in mgr.skill_names)  # True

Rollback

rollback_skill() is a single-step undo of the most recent edit_skill() or patch_skill() call.

Diagram

edit_skill

edit_skill

rollback_skill

SKILL.md v1

.skill.bak = v1

SKILL.md v2

.skill.bak = v2

SKILL.md v3

SKILL.md = v2

What saves a snapshot:

  • edit_skill(name, content) — always saves prior SKILL.md
  • patch_skill(name, old, new) when file_path is None or "SKILL.md" — saves prior SKILL.md

What does NOT save a snapshot:

  • patch_skill(name, old, new, file_path="scripts/helper.py") — modifying a non-SKILL.md file does not create a .skill.bak

Error when no snapshot exists:

python
result = mgr.rollback_skill("csv-analysis")
# {"success": False, "error": "No rollback snapshot for skill 'csv-analysis'"}

Common Patterns

Self-Improving Agent Loop

python
from praisonaiagents import Agent, SkillManager

# Agent creates and improves skills over time
agent = Agent(
    name="Self-Improving Agent",
    instructions="""
    When you learn something new, create a skill for it.
    When a skill becomes outdated, archive it.
    """,
    tools=["skill_manage", "skills_list"]
)

# The agent's skill_manage calls automatically:
# 1. stamp created-at, use-count, patch-count on creation
# 2. increment use-count on each use
# 3. archive (not delete) when retiring a skill
agent.start("Learn how to summarise weekly reports, then use that knowledge.")

Safe Edit-with-Rollback Wrapper

python
from praisonaiagents import SkillManager

mgr = SkillManager()
mgr.discover()

def safe_edit(skill_name: str, new_content: str) -> bool:
    result = mgr.edit_skill(skill_name, new_content)
    if not result["success"]:
        print(f"Edit failed: {result['error']}")
        return False

    # Verify the skill still activates correctly
    instructions = mgr.get_instructions(skill_name)
    if instructions is None:
        print("Skill broken after edit — rolling back")
        mgr.rollback_skill(skill_name)
        return False

    return True

safe_edit("csv-analysis", "# CSV Analysis v2\nImproved steps...")

Inspect Telemetry from a Curator Script

python
from praisonaiagents import SkillManager

mgr = SkillManager()
mgr.discover()

STALE_THRESHOLD_DAYS = 30

for skill in mgr.skills:
    props = skill.properties
    idle = props.idle_days

    print(f"{props.name}:")
    print(f"  agent-created : {getattr(props, 'agent_created', False)}")
    print(f"  use-count     : {getattr(props, 'use_count', 0)}")
    print(f"  last-used     : {getattr(props, 'last_used', None)}")
    print(f"  patch-count   : {getattr(props, 'patch_count', 0)}")
    print(f"  idle-days     : {idle}")

    if idle is not None and idle > STALE_THRESHOLD_DAYS:
        print(f"  → STALE: archiving")
        mgr.archive_skill(props.name)

Best Practices

Trust the Recoverable Default

delete_skill() archives by default. Only pass hard=True when you mean it. The archive store lets you recover skills that were accidentally retired.

Don't Chain Edits Expecting Multi-Step Undo

Each edit_skill or patch_skill overwrites .skill.bak. If you need to preserve multiple prior versions, copy SKILL.md externally before each edit, or use a version control system for the skills directory.

Telemetry Counts Successful Activations Only

get_instructions() returns None when a skill fails to activate, and in that case use-count and last-used are not updated. A skill that is broken will not appear to be active in your telemetry — safe to archive without inflating its apparent usage.

Curator Policy Belongs in Plugins

idle_days and the provenance fields expose the data needed to make lifecycle decisions. The policy — what idle threshold triggers archiving, whether agent-created skills get different treatment — belongs in plugin code or your own scripts, not in application code. This keeps core behaviour predictable.

Verify After Restore

After restore_skill(), call mgr.discover() (or mgr.add_skill(path)) to re-index the skill in the current session. restore_skill() moves the directory but does not automatically re-add it to the in-memory index.


Related