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
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 apending 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:| Action | Purpose | Staged by default? |
|---|---|---|
| create | Create new skills | ✅ Yes |
| edit | Replace skill content | ✅ Yes |
| patch | Targeted find/replace | ✅ Yes |
| delete | Archive by default (recoverable) | ✅ Yes |
| write_file | Add skill resources | ✅ Yes |
| remove_file | Delete skill files | ✅ Yes |
| archive | Move skill to archive store | ✅ Yes |
| restore | Return archived skill to active | ✅ Yes |
| list_archived | List archived skills | — (read-only) |
| rollback | Undo last edit/patch | ✅ Yes |
| pending | List staged mutations | — (read-only) |
| approve | Apply a staged mutation | — (approval action) |
| reject | Discard a staged mutation | — (approval action) |
Configuration
Constructor & environment
| Option | Type | Default | Description |
|---|---|---|---|
SkillManager(write_approval=...) | bool | None | None (env-resolved) | Per-instance override for staging policy |
SKILL_WRITE_APPROVAL env var | str | 1 (true) | Set to 0/false/off/no/disabled to disable staging by default |
SKILL_MAX_PENDING env var | int | 100 | Maximum pending proposals before new ones are rejected |
propose (per call) | bool | None | None (inherits) | True → stage; False → write directly; None → use manager default |
propose= arg > SkillManager(write_approval=...) > SKILL_WRITE_APPROVAL env var > default True
Skill management actions
| Action | Required Args | Optional Args | What it does |
|---|---|---|---|
create | name, content | category, propose | Create a new skill (staged by default) |
edit | name, content | propose | Replace an existing skill’s SKILL.md body |
patch | name, old_string, new_string | file_path, replace_all, propose | Fuzzy find-and-replace within a skill file |
delete | name | hard, propose | Archives by default; pass hard=True for permanent removal |
write_file | name, file_path, file_content | propose | Add/overwrite a file inside the skill |
remove_file | name, file_path | propose | Delete a file from within the skill |
archive | name | propose | Move the skill to the recoverable archive store |
restore | name | skill_dir, propose | Bring an archived skill back into active use |
list_archived | — | — | List names of archived skills |
rollback | name | propose | Undo the most recent edit/patch (single-step) |
pending | — | name (filter) | Returns list of staged mutations |
approve | name (id or skill name) | — | Apply the staged mutation |
reject | name (id or skill name) | — | Discard the staged mutation |
Python API Reference
Response shapes
Staged (default) —propose=True:
propose=False:
Mutation methods
Approval state machine
Approval requires a valid This prevents a stray or partially-written pending directory from clobbering an existing active skill. Malformed
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:.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:
| Alias | Long form |
|---|---|
mgr.create(name, content, category=None, propose=None) | mgr.create_skill(...) |
mgr.edit(name, content, propose=None) | mgr.edit_skill(...) |
mgr.patch(name, old, new, file_path=None, replace_all=False, propose=None) | mgr.patch_skill(...) |
mgr.delete(name, propose=None) | mgr.delete_skill(...) |
mgr.write_file(name, file_path, file_content, propose=None) | mgr.write_skill_file(...) |
mgr.remove_file(name, file_path, propose=None) | mgr.remove_skill_file(...) |
Pending Store & Audit Log
Both artefacts live under the user-owned skills base directory (honouringPRAISONAI_HOME):
| Path | Format | Purpose |
|---|---|---|
<skills_dir>/.pending_skills.json | JSON map {id: record} | Staged mutations awaiting approval |
<skills_dir>/.skill_audit.log | JSON-lines | Append-only audit: proposed / approved / approval_failed / rejected events |
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.
| Pending dir contents | Surfaced by list_pending()? |
|---|---|
SKILL.md + valid .proposal.json | ✅ Yes (action read from proposal) |
SKILL.md, no proposal | ✅ Yes (treated as create) |
SKILL.md + malformed .proposal.json | ✅ Yes (fallback to create) |
Malformed .proposal.json, no SKILL.md | ❌ No |
| Empty / stray directory | ❌ No |
Storage Location
Base-dir change (PR #2236):
create_skill now always writes to the user-owned get_skills_dir() (which honours PRAISONAI_HOME). Previously it could fall back to an admin-managed location like /etc/praison/skills. Set PRAISONAI_HOME to control where skills land.- Project:
./.praisonai/skills/— and./.claude/skills/for compatibility - Ancestor walk: any
.praisonai/skillsor.claude/skillsin parent directories - User:
~/.praisonai/skills/(or$PRAISONAI_HOME/skills/) - System (Unix):
/etc/praison/skills/
<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
| Guard | Purpose | Implementation |
|---|---|---|
| Size Limits | Prevent resource exhaustion | 100KB SKILL.md, 1MB files |
| Name Validation | Secure identifiers | [a-z0-9][a-z0-9._-]* pattern, 64 char limit |
| Path Validation | Prevent traversal | Block .., absolute paths, encoded attacks |
| Atomic Writes | Prevent corruption | Temp file + rename operations |
| Allowed Subdirs | Restrict file placement | references/, templates/, scripts/, assets/ only |
| Max Pending | Prevent accumulation | SKILL_MAX_PENDING=100 (101st returns error) |
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 withpraisonai 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
Best Practices
Keep propose=True for unattended gateway/bot deployments
Keep propose=True for unattended gateway/bot deployments
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.Use SKILL_WRITE_APPROVAL=0 only in trusted local CI/dev
Use SKILL_WRITE_APPROVAL=0 only in trusted local CI/dev
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 by ID, not by name
Approve by ID, not by name
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.Security-First Design
Security-First Design
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.
Error Handling
Error Handling
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.Don't pre-create empty pending directories
Don't pre-create empty pending directories
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.Related
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

