Skip to main content
Self-improving skills enable agents to create, edit, and manage their own capabilities dynamically — with mutations staged for human approval before they touch disk.
The user teaches the agent; skill changes stay pending until a human approves them.
To trigger skill creation automatically after every task instead of having the model call skill_manage on its own, see Self-Improving Agents.
Safe-by-default since PR #2236. Every mutation (create, edit, patch, delete, write_file, remove_file) is now staged for human approval by default. The agent receives {"status": "pending", "id": "skl-…"} instead of writing to disk. Pass propose=False to restore direct-write behaviour in trusted/local contexts.

Quick Start

1

Agent proposes a skill (default behaviour)

2

Human reviews and approves

3

Direct write (trusted/local contexts only)

4

Archive and recover skills


Approval Gate (safe-by-default)

Every mutation is staged by default so a human can review it before it becomes a live skill. The agent sees a pending response; you approve or reject via the SkillManager API or the skill_manage tool.

When to use propose=False


How It Works

The skill management system provides six mutation actions, three approval actions, and four lifecycle actions:

Configuration

Constructor & environment

Precedence: explicit propose= arg > SkillManager(write_approval=...) > SKILL_WRITE_APPROVAL env var > default True

Skill management actions


Python API Reference

Response shapes

Staged (default) — propose=True:
Direct write — propose=False:
Failure (either mode):

Mutation methods

Approval state machine

Approval requires a valid SKILL.md (since PraisonAI PR #2467). approve() checks that the pending directory contains SKILL.md before moving it into the active skills directory. A pending entry without SKILL.md returns:
This prevents a stray or partially-written pending directory from clobbering an existing active skill. Malformed .proposal.json metadata no longer blocks approval — it is treated as a legacy create record as long as SKILL.md is present.

SkillMutatorProtocol aliases

SkillManager conforms to SkillMutatorProtocol. Short-form aliases are available alongside the _skill methods:

Pending Store & Audit Log

Both artefacts always live in the user-owned skills directory (~/.praisonai/skills/, honouring PRAISONAI_HOME), regardless of cwd — so a proposal staged from one project is discoverable when the approver runs from another: Pending record shape:
Audit log line:
approve only consumes the pending record and audits “approved” if the apply succeeds. On failure, the record is kept and the audit logs “approval_failed” so you can retry.

Visibility rules

list_pending() only surfaces a pending directory when it carries a real SKILL.md. Stray temp directories or directories that contain nothing but a malformed .proposal.json are silently excluded so they cannot be advertised as approvable.

Storage Location

SkillManager uses different rules for where it writes mutations versus where it discovers existing skills for read.
Write-path change (PR #3040): create_skill now prefers the project-scoped ./.praisonai/skills/ when that directory exists in the current working directory, falling back to user home otherwise. Previously it always wrote to the user-owned get_skills_dir().

Write path (mutations)

create_skill, edit_skill, patch_skill, write_skill_file, and their aliases resolve their destination via _skills_base_dir():
  1. Project (preferred): ./.praisonai/skills/ — used when that directory already exists in the current working directory.
  2. User home (fallback): ~/.praisonai/skills/ — used otherwise. Honours PRAISONAI_HOME.
Writes never walk ancestor directories and never touch /etc/praison/skills — those apply to discovery only.
To make a project accumulate its own skills, create the folder once: mkdir -p .praisonai/skills. From then on, every mutation from that cwd lands in the project.
Where did the file land? On a successful direct create, the SDK emits an INFO log:
Enable it with logging.basicConfig(level=logging.INFO). This is the definitive signal for whether the write went to the project or the user-home fallback.

Approval writes back to the origin project

Staging a create with propose=True pins the resolved destination into the pending record (payload.base_dir). Approving that record later — even from a completely different working directory — writes the SKILL.md back to the project the proposal originated in.

Discovery path (read)

Discovery (get_default_skill_dirs()) walks a broader list — unchanged, and applies to read only:
  1. Project: ./.praisonai/skills/ — and ./.claude/skills/ for compatibility
  2. Ancestor walk: any .praisonai/skills or .claude/skills in parent directories
  3. User: ~/.praisonai/skills/ (or $PRAISONAI_HOME/skills/)
  4. System (Unix): /etc/praison/skills/
Writes deliberately skip steps 2 and 4 so mutations can never land in an ancestor or admin-managed location. Archive store: Archived skills are moved to <first_skill_dir>/../skills_archive/. If a name already exists in the archive, a UTC timestamp suffix is appended (e.g. weekly-summary.20240624153012).

Security Guards


Common Patterns

Complete approval flow

Local dev — disable staging

Skill evolution with approval

Safe edit with rollback

Using the skill_manage tool actions directly


Skill Bundles

Group related skills into named, reusable sets with praisonai skills bundle list and praisonai skills bundle show <name>. Select a whole bundle from an agent with skills=["@backend-dev"]. See Skill Bundles for the full guide.

Troubleshooting

ImportError Fix: If you see ImportError: cannot import name 'get_default_skill_directories', upgrade praisonaiagents — this was fixed in MervinPraison/PraisonAI#1687. The function was renamed from get_default_skill_directories to get_default_skill_dirs.
“max pending reached” error: You have 100 or more staged mutations. Run mgr.list_pending() and approve or reject existing proposals before creating new ones. Raise the limit with SKILL_MAX_PENDING=200 if needed.
“Pending ‘{name}’ has no SKILL.md and cannot be approved”: The pending directory exists but is missing SKILL.md. Either delete the directory under <skills_dir>/pending/<name>/ or restore a SKILL.md before retrying approve(). Introduced in PraisonAI PR #2467 to stop empty/stray pending dirs from clobbering live skills.

Best Practices

In any unattended context — Slack bots, webhooks, scheduled agents — leave propose=True (the default). This ensures no skill mutation reaches disk without a human seeing it first. Set SKILL_WRITE_APPROVAL=0 only on machines you control and trust.
Direct writes are convenient for rapid iteration in local development or a controlled CI pipeline where you own the environment. Never disable staging for agents exposed to external users or production traffic.
approve("skl-a1b2c3d4") is exact — it targets one specific proposal. approve("weekly-summary") uses a most-recent-wins fallback and can silently target the wrong proposal if multiple are pending for the same skill.
All skill operations are workspace-contained and use atomic writes via temp files. Never bypass name validation or path checks. Skills inherit workspace security automatically.
All skill operations return detailed JSON results with success flags and error messages. Always check result["success"] before proceeding. On approval failure, the pending record is kept — check mgr.list_pending() and retry.
A pending directory is only approvable when it contains SKILL.md. If you stage skills outside the skill_manage tool (e.g. via a script that writes to <skills_dir>/pending/<name>/), write SKILL.md first — approval will fail until it is present, and list_pending() will not show the entry.

Skill Lifecycle

Provenance, telemetry, archive/restore, and rollback for agent-created skills

Agent Skills

Load and configure SKILL.md skills on agents

Workspace

How workspace containment secures skill operations

Skill Capability Gates

Capability requirements and enforcement for skills

Self-Improving Agents

Auto-trigger skill_manage after each task with self_improve=True