Self-Improving Agents
Let an agent capture a reusable technique as a skill — and a durable fact as memory — after each task, automatically
Enable self_improve=True on an agent and after every task it asks itself “did I just learn something reusable, or a durable fact worth remembering?” — if yes, it writes/patches a skill and/or persists a memory for next time. Use self_improve="background" to reply first and run the review off the hot path.
from praisonaiagents import Agent
agent = Agent(
name="Engineer",
instructions="Help debug and fix issues. Read project files first.",
self_improve=True,
tools=["read_file"],
)
agent.start(
"Debug this flaky deploy script and fix it. "
"Read deploy.sh in the current directory first."
)The user assigns a task; after each run the agent may write or patch a skill when it learns something reusable.
Quick Start
- Simple Usage
python from praisonaiagents import Agent agent = Agent( name="Engineer", instructions="Help debug and fix issues. Read project files first.", self_improve=True, tools=["read_file"], ) agent.start( "Debug this flaky deploy script and fix it. " "Read deploy.sh in the current directory first." )After the task ends, the agent runs a guarded review pass — but only if at least one tool was used. If a reusable technique emerged it may write a SKILL.md; otherwise it replies
NO_SKILL. - With Configuration
Require at least three tool calls before a review fires:
python from praisonaiagents import Agent class HeavySessionPolicy: def should_review(self, trajectory): return len(trajectory.get("tools_used", [])) >= 3 def review_prompt(self, trajectory): return ( "If a reusable technique emerged, call skill_manage to save it. " "Otherwise reply NO_SKILL." ) agent = Agent( name="Engineer", instructions="Help debug and fix issues. Read project files first.", self_improve=HeavySessionPolicy(), tools=["read_file"], ) agent.start( "Debug this flaky deploy script. " "Read deploy.sh, README-deploy.md, and check-staging.sh in order." ) - Background Mode (no added latency)
Reply first, run the review turn off the hot path — ideal for latency-sensitive gateway and bot agents:
python from praisonaiagents import Agent agent = Agent( name="Support Bot", instructions="Answer customer questions. Use read_file for playbooks when needed.", self_improve="background", tools=["read_file"], ) agent.start("How do I reset my password? Check reset-playbook.md if needed.") # Reply returns immediately; the skill review runs in the background after tool use. - Chat and Async Chat
The same
self_improveflag works withchat()/achat()— the review fires after any turn that used at least one tool.python import asyncio from praisonaiagents import Agent agent = Agent( name="Support", instructions="Answer customer questions. Use read_file for playbooks when needed.", self_improve=True, tools=["read_file"], ) # Sync chat — review runs after the reply agent.chat("How do I reset my password? Check reset-playbook.md if needed.") # Async chat — same behaviour, non-blocking asyncio.run(agent.achat("Escalate this ticket. Check escalation-rules.md first."))The review pass is scheduled after every chat turn that used ≥1 tool — inline for
self_improve=True, off the hot path forself_improve="background". - Background + Memory (grow skills and user model)
With
memory=True, the same guarded review pass can also persist a durable fact viastore_memory— closing the learning loop for memory, not just skills.python from praisonaiagents import Agent agent = Agent( name="Personal Assistant", instructions="You are a personal assistant on Telegram.", self_improve="background", # skills: guarded background review memory=True, # memory: same pass also persists durable facts tools=["read_file"], ) agent.chat("I prefer flights that land before 6pm — book Delhi → Bangalore next Friday.") # Reply returns immediately. Off the hot path, the guarded review may: # • call skill_manage(...) if a reusable technique emerged, and/or # • call store_memory("prefers flights landing before 6pm", ...) to remember the preference.
How It Works
After each turn — whether you called agent.start(), agent.chat(), or agent.achat(), and whether the tool ran sync or async — if self_improve is enabled:
- The policy checks
should_review(trajectory)— by default, only when ≥1 tool was called - The agent runs one extra turn with a restricted toolset —
skill_managealways, plusstore_memorywhen the agent hasmemory=True - The agent calls
skill_manageto create/patch a skill, and/orstore_memoryto persist a durable fact, or repliesNO_SKILL - The internal review exchange is trimmed back out of
chat_history
Execution Modes
self_improve accepts strings that pick when the review runs relative to the reply.
- Inline (
True,"inline","blocking","sync") — the review turn runs before the reply returns. Simplest; adds one extra LLM round-trip to reply latency. - Background (
"background","async") — the reply returns first, then the review runs off the hot path on the sharedBackgroundJobManager. No added user-visible latency.
Prefer background for latency-sensitive gateway or bot agents; prefer inline for batch jobs where latency does not matter.
Configuration Options
Agent(self_improve=...)
| Value | Behavior |
|---|---|
False (default) | Off. No review pass runs. |
True | On, review runs inline (blocks the reply) with DefaultSkillReviewPolicy() (reviews when ≥1 tool was used). |
"inline" / "blocking" / "sync" | Same as True. |
"background" / "async" | On, review runs after the reply on the shared background job runner. Recommended for gateways/bots. |
DefaultSkillReviewPolicy(min_tool_calls=N) | On (inline), requires at least N tool calls before a review fires. |
Any object implementing SkillReviewProtocol | On (inline), with your custom policy. |
When the agent also has memory=True, the guarded review turn additionally exposes store_memory so the same pass can persist a durable fact off the hot path. The set stays restricted — never the agent’s full toolset.
DefaultSkillReviewPolicy
| Option | Type | Default | Description |
|---|---|---|---|
min_tool_calls | int | 1 | Minimum tools used during the task before a review fires. Clamped to >= 1. |
MAX_PROMPT_CHARS (class constant) | int | 500 | Hard cap on how much of the original prompt is echoed into the review directive. |
SkillReviewProtocol (custom policy)
| Method | Signature | Purpose |
|---|---|---|
should_review | (trajectory: dict) -> bool | Decide whether to run the review pass at all. |
review_prompt | (trajectory: dict) -> str | Build the directive prompt for the guarded turn. |
trajectory shape: {"prompt": str, "response": str, "tools_used": list[str]}.
Choosing What to Pass
Inline vs Background
Choose based on how latency-sensitive the reply is.
| Mode | Reply latency | Best for |
|---|---|---|
True / "inline" (default) | Includes one extra LLM turn | Batch scripts, short-lived agents, tests |
"background" | Immediate | Chatbots, gateway/long-lived agents, Slack/Discord bots |
Common Patterns
Agent that gets sharper over time
from praisonaiagents import Agent
engineer = Agent(
name="Backend Engineer",
instructions="Diagnose and fix production issues. Read logs and scripts first.",
self_improve=True,
tools=["read_file"],
)
# Day 1: agent solves a flaky-deploy problem from scratch.
engineer.start(
"Why does deploy fail intermittently on the staging cluster? "
"Read deploy.sh and check-staging.sh first."
)
# After answering, the review MAY write a skill (e.g. diagnose-flaky-deploy) — not guaranteed.
# Day 2: if a skill was saved, it is now available and the agent can reuse it.
engineer.start("Staging deploys are flaky again — same as last week?")Persistent assistant that grows both skills and user model
from praisonaiagents import Agent
assistant = Agent(
name="Concierge",
instructions=(
"You are a long-running concierge for one user across many sessions. "
"Help with travel, calendar and reminders."
),
self_improve="background", # skills grow autonomously
memory=True, # user model grows autonomously — same pass
tools=["read_file"],
)
# Turn 1 — user reveals a preference
assistant.chat("I only fly economy on flights under 4 hours; premium economy otherwise.")
# Turn 2 — a totally new session (or process). The reply already reflects it.
assistant.chat("Find me options for Delhi → Singapore next month.")
# The background review from turn 1 may have persisted the seat-class rule via store_memory;
# on turn 2 memory recall surfaces it, so the agent proposes premium economy without asking.Conservative policy (only review heavy sessions)
from praisonaiagents import Agent
class MinToolsPolicy:
def should_review(self, trajectory):
return len(trajectory.get("tools_used", [])) >= 3
def review_prompt(self, trajectory):
return "Save a skill via skill_manage if reusable; else NO_SKILL."
agent = Agent(
name="Researcher",
instructions="Research and report.",
self_improve=MinToolsPolicy(),
tools=["read_file"],
)
agent.start(
"Debug this flaky deploy script. "
"Read deploy.sh, README-deploy.md, and check-staging.sh."
)Fully custom policy
from praisonaiagents import Agent
class TimedReviewPolicy:
"""Only review every 5th tool-using session."""
def __init__(self):
self.counter = 0
def should_review(self, trajectory):
if not trajectory.get("tools_used"):
return False
self.counter += 1
return self.counter % 5 == 0
def review_prompt(self, trajectory):
return (
"Reflect on this session. If a reusable technique emerged, "
"call skill_manage to save it. Otherwise reply NO_SKILL."
)
agent = Agent(
name="Worker",
instructions="Read provided files carefully.",
self_improve=TimedReviewPolicy(),
tools=["read_file"],
)
for prompt in [
"Read deploy.sh and summarize the flake.",
"Read README-deploy.md and list the checklist.",
"Read check-staging.sh — what does it simulate?",
"Read deploy.sh — what line causes random failure?",
"Read all three files and write a one-paragraph runbook.",
]:
agent.start(prompt)Guarantees
Troubleshooting
Ran self-improve and see no SKILL.md on disk? Work through these four causes in order.
1. The review returned NO_SKILL
The model judged that nothing durable emerged, so it replied NO_SKILL and wrote nothing. Enable INFO logging to see the review response:
import logging
logging.basicConfig(level=logging.INFO)2. The create was staged for approval
SKILL_WRITE_APPROVAL is on by default, so the review stages the skill instead of writing it. Check the pending store:
from praisonaiagents import SkillManager
print(SkillManager().list_pending()) # look for your skill, then approve(id)The staged proposal lives in ~/.praisonai/skills/.pending_skills.json.
3. The file went to user home, not the project cwd
./.praisonai/skills/ did not exist, so the write fell back to ~/.praisonai/skills/. Create the folder so the next run lands in the project:
mkdir -p .praisonai/skillsOn a direct write the SDK logs the exact path — grep for Skill created: after enabling INFO logging.
4. The review pass never ran
Self-improve only reviews when the task used at least one tool — the guarded review skips tool-less tasks. Confirm the agent has tools= and that the task actually invoked one.
5. Review not firing on agent.chat() / agent.achat()?
On praisonaiagents < 1.6.151, the chat and async-chat paths returned without wiring tools_used into the review hook, so the default policy always saw an empty tools list and skipped the review — silently, with no warning. Upgrade to fix:
pip install -U "praisonaiagents>=1.6.151"Confirm the fix with a one-line reproducer:
from praisonaiagents import Agent
agent = Agent(
instructions="Read files before answering.",
self_improve=True,
tools=["read_file"],
)
agent.chat("Read deploy.sh in the current directory and summarize.")
print("tools_used after turn:", agent._turn_tools_used)
# Expect [] here (buffer is consumed by the hook), but you should see the review turn run.If the review still doesn’t run on 1.6.151+, check the other accordions — the most common causes are (a) no tools= on the Agent, (b) the task didn’t actually call a tool, or (c) SKILL_WRITE_APPROVAL staged the create rather than writing directly.
6. Review wrote a skill but not a memory (or vice versa)
Each review is independent: the model persists a skill, a memory, both, or neither based on what actually emerged that turn. Writing one but not the other is normal — a reusable technique doesn’t always coincide with a durable user fact.
store_memory is only in the review toolset when memory=True is set on the Agent. If you expected a memory write but see none, confirm the Agent has memory=True (or a MemoryConfig). See Memory for where the persisted fact lands per backend.
from praisonaiagents import Agent
agent = Agent(
self_improve="background",
memory=True, # required for store_memory in the review toolset
tools=["read_file"],
)Best Practices
Start with True, then tune
The default policy is conservative (≥1 tool call). Only switch to a custom min_tool_calls when you see review LLM cost you want to cut.
Pair with named, persistent skill directories
Skills written by the review land in the project’s ./.praisonai/skills/ when that directory exists in the cwd, otherwise in ~/.praisonai/skills/. Discovery is broader (project → ancestors → user → system), but writes only use those two locations — they never walk ancestors. Run mkdir -p .praisonai/skills in the project you want captures to accumulate in.
Don't confuse with reflection
reflection improves this answer; self_improve captures a skill for next time. They compose cleanly — turn both on if you want both behaviors.
Use the protocol for fully custom policies
Any object with should_review and review_prompt satisfies SkillReviewProtocol. Use this to gate on cost, time-of-day, session length, or any other signal.
Use background mode for chatbots and gateways
If your agent lives inside a chatbot, gateway, or bot process where users notice reply latency, pass self_improve="background" instead of True. The review still runs after every task — it just doesn’t sit in front of the reply.
from praisonaiagents import Agent
agent = Agent(name="Bot", self_improve="background", tools=["read_file"])Know how SKILL_WRITE_APPROVAL stages writes
SKILL_WRITE_APPROVAL is on by default, so a review that decides to save a skill stages it as a pending proposal instead of writing to disk. Approve it later, or set SKILL_WRITE_APPROVAL=0 for direct writes in trusted local tests.
import os
os.environ["SKILL_WRITE_APPROVAL"] = "0" # direct writes — local/trusted only
from praisonaiagents import Agent
agent = Agent(name="Engineer", self_improve=True, tools=["read_file"])
agent.start("Read deploy.sh and fix the flake.")When staging is on, the destination for each proposal is pinned at staging time. If you later run SkillManager.approve(id) from a different working directory — from ~, from a CI job, from another project — the approved skill is still written back to the original project’s .praisonai/skills/. You never lose the origin by approving elsewhere.
Async tool calls made from achat or execute_tool_async are tracked identically to sync calls — the review fires whether the tool ran sync or async, and pending creates staged from an async turn behave the same as those staged from a sync turn.
Related
The underlying tool the review uses to create and patch skills
SkillsWhat skills are and how agents use them
HooksWhere the review trigger is wired — the after-agent funnel
Self ReflectionImproves answer quality within a task
MemoryThe same review pass persists durable facts via store_memory when memory=True