auto: sync OpenClaw config 2026-09-16 16:13

This commit is contained in:
2026-09-16 16:13:36 +08:00
parent c51006a57d
commit 5ac4289f66
35 changed files with 4827 additions and 65 deletions
@@ -0,0 +1,18 @@
{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "self-improving-agent",
"ownerHandle": "pskoett",
"installedAt": 1789545691162,
"artifact": {
"kind": "archive",
"sha256": "89f2a239f9d675c4c5787cf61c17f709cd38cb9c7d395f9dc50f487a59a47291",
"integrity": "sha256-ifKiOfnWdcTFeHz2HBf3Cc04y5x9OV+dxQ9IelmkcpE="
},
"skillFile": {
"path": "SKILL.md",
"sha256": "eff889950f23bb8a3de90cd617481bbf2557f9495f884bd5b1e9b7604a20817c"
},
"fileTreeSha256": "sha256:cd72f5c1910da8f21f4e326cc4f3b39de8b5bd371411f6a026f1f72b6747de8d",
"installedVersion": "4.0.2"
}
@@ -0,0 +1,175 @@
# Changelog
All notable changes to this skill are documented here, including anything an
agent or user must do when upgrading. Format follows
[Keep a Changelog](https://keepachangelog.com/); versions follow
[SemVer](https://semver.org/) and match `version` in `SKILL.md` frontmatter.
Read this before upgrading. General upgrade rules:
- **OpenClaw hook changes only take effect after re-copying and restarting.**
The hook runs from a copy: re-run
`cp -r ~/.openclaw/skills/self-improving-agent/hooks/openclaw ~/.openclaw/hooks/self-improvement`
and restart the gateway after upgrading.
- `.learnings/` files are user data and are never migrated or overwritten by
upgrades; first-use initialisation is idempotent and only creates missing
files.
## [Unreleased]
## [4.0.2] - 2026-08-06
> 4.0.1 is skipped: that version number was already used by a build
> published to ClawHub, so it is not reusable here.
### Fixed
- **Install commands and clone URL in the docs were wrong** (#24). The
advertised `clawdhub install` command does not exist. The documented
commands are now `openclaw skills install @pskoett/self-improving-agent`
(built-in, installs into the active workspace) and
`clawhub install @pskoett/self-improving-agent` (ClawHub CLI,
`npm i -g clawhub`, installs into `./skills` under the current working
directory). Both take the `@owner/slug` form — a bare slug is only
accepted for already-installed or unambiguous skills. Fixed in
`README.md`, `SKILL.md`, and `references/openclaw-integration.md`.
- The manual `git clone` URL used the wrong owner handle
(`peterskoett``pskoett`). The old handle only resolved via a web
redirect, which `git clone` does not follow, so manual installs failed on
a fresh machine.
### Added
- GitHub Actions CI (`.github/workflows/ci.yml` at the repo root): runs the
hook test suite, type-checks `handler.ts` (strict mode, against a CI-only
type stub), and syntax-checks `extract-skill.sh` on every push to master
and every PR.
### Changed
- **Repo restructured for ClawHub publishing**: the skill package now lives
in the repo's `self-improving-agent/` subfolder (SKILL.md, assets, hooks,
references, scripts, this changelog), keeping repo-level files (README,
`.github/`) out of the published skill. Install by copying the subfolder,
not the repo root — install/upgrade commands in the docs are updated.
- `SKILL.md` frontmatter `name` corrected from `self-improvement` to
`self-improving-agent` to match the skill folder name, as the Agent Skills
spec requires. The OpenClaw *hook* keeps its `self-improvement` name, so
existing hook installs and `openclaw hooks enable self-improvement` are
unaffected.
### Upgrade notes (4.0.x → 4.0.2)
Documentation only — no skill behavior, hook, or `.learnings/` changes. No
action needed beyond reinstalling if you want the corrected install docs.
## [4.0.0] - 2026-07-04
### Removed
- **This distribution is now OpenClaw-only.** Removed the Claude Code /
Codex / Copilot integrations: `scripts/activator.sh`,
`scripts/error-detector.sh`, `references/hooks-setup.md`, and the
multi-agent setup/support sections in `SKILL.md` and
`references/uninstall.md`. For other agents, use the original multi-agent
version: https://github.com/pskoett/pskoett-ai-skills.
### Changed
- Promotion targets are now the OpenClaw workspace files (`SOUL.md`,
`TOOLS.md`, `AGENTS.md`), with the project's own agent file as the target
for project-specific patterns.
- Hook docs and code comments describe the session-end sweep on its own
terms (OpenClaw has no per-tool-call event) instead of contrasting with
other platforms.
### Upgrade notes (0.3.0 → 4.0.0)
1. If you configured the Claude Code/Codex hooks from earlier versions,
remove the stale `.claude/settings.json` / `.codex/settings.json` entries
pointing at `activator.sh` / `error-detector.sh`, and switch to the
original multi-agent skill for those platforms.
2. Nothing changes for OpenClaw installs beyond the usual re-copy of the
hook directory; `.learnings/` data and entry formats are unaffected.
## [0.3.0] - 2026-07-04
### Added
- **Pattern-Key generalized to all three log files**: the `ERRORS.md` and
`FEATURE_REQUESTS.md` entry formats now carry a `Pattern-Key` field
(recommended for errors, optional for features), joining `LEARNINGS.md`
where it was previously limited to the simplify-and-harden feed.
- **Pattern-Key Taxonomy** section in `SKILL.md`: controlled `area.symptom`
namespaces (`api`, `auth`, `build`, `config`, `deps`, `fs`, `net`,
`runtime`, `shell`, `vcs`, plus `simplify`/`harden`), with reuse-before-mint
and one-key-per-entry rules.
- The OpenClaw session-end error sweep stamps deterministic `Pattern-Key`
values on auto-detected entries (e.g. `ModuleNotFoundError`
`deps.module-not-found`), making them recurrence-countable with no agent
discipline required.
### Changed
- Grep-by-`Pattern-Key` is now the documented **default dedup step** when
logging; keyword grep is the fallback. Recurrences are folded into the
existing entry (`Recurrence-Count`, `Last-Seen`, `See Also`) instead of
duplicated.
- Tightened `SKILL.md` (hook sections, taxonomy, duplicated setup blocks) to
keep the always-loaded skill prompt compact.
### Fixed
- `scripts/extract-skill.sh` is now committed with the executable bit set —
previously every install needed a manual `chmod +x`.
### Upgrade notes (0.2.0 → 0.3.0)
1. Re-copy the OpenClaw hook and restart the gateway (see general rules
above) to get Pattern-Key stamping on swept entries.
2. Existing `.learnings/` entries without `Pattern-Key` remain valid — the
field is additive. Add keys opportunistically when touching old entries.
## [0.2.0] - 2026-07-04
### Added
- **OpenClaw session-end error sweep** (`hooks/openclaw/`): the hook now also
fires on `command:new` / `command:reset`, scans the ended session's
transcript for the same error patterns as `scripts/error-detector.sh`, and
appends pending entries (`Source: openclaw-error-sweep`) to
`<workspace>/.learnings/ERRORS.md`. Opt-in: runs only when
`<workspace>/.learnings/` exists. Excerpts are capped, truncated, redacted,
and deduplicated.
- Pending-triage note in the bootstrap reminder when auto-detected error
entries await review.
- Test suite: `node --test hooks/openclaw/handler.test.js` (no dependencies).
- Docs: "Error Detection on OpenClaw" section and platform support matrix in
`references/openclaw-integration.md`; uninstall guide in
`references/uninstall.md`; this changelog.
### Changed
- `hooks/openclaw/HOOK.md` events metadata is now
`["agent:bootstrap", "command:new", "command:reset"]`.
- Docs now state explicitly that `scripts/error-detector.sh` is Claude Code
only (OpenClaw has no `PostToolUse` equivalent).
### Upgrade notes (0.1.x → 0.2.0)
1. Re-copy the hook and restart the gateway (see general rules above).
2. To enable the new error sweep: `mkdir -p ~/.openclaw/workspace/.learnings`.
Without that directory, behavior is identical to 0.1.0.
3. No breaking changes; no `.learnings/` migration needed.
## [0.1.0] - 2026-01-31
### Added
- Initial release: `SKILL.md` with logging formats (`LEARNINGS.md`,
`ERRORS.md`, `FEATURE_REQUESTS.md`), promotion workflow, and skill
extraction.
- OpenClaw bootstrap-reminder hook (`agent:bootstrap`).
- Claude Code hook scripts: `activator.sh` (UserPromptSubmit) and
`error-detector.sh` (PostToolUse).
- Reference guides for hooks setup and OpenClaw integration.
@@ -0,0 +1,594 @@
---
name: self-improving-agent
description: "Captures learnings, errors, and corrections to enable continuous improvement. Use when: (1) A command or operation fails unexpectedly, (2) User corrects Claude ('No, that's wrong...', 'Actually...'), (3) User requests a capability that doesn't exist, (4) An external API or tool fails, (5) Claude realizes its knowledge is outdated or incorrect, (6) A better approach is discovered for a recurring task. Also review learnings before major tasks."
version: "4.0.2"
metadata:
---
# Self-Improvement Skill
Log learnings and errors to markdown files for continuous improvement. Agents can later process these into fixes, and important learnings get promoted to workspace memory. This version of the skill is built for OpenClaw only — for other agents, see the original multi-agent version at https://github.com/pskoett/pskoett-ai-skills.
## First-Use Initialisation
Before logging anything, ensure the `.learnings/` directory and files exist in the project or workspace root. If any are missing, create them:
```bash
mkdir -p .learnings
[ -f .learnings/LEARNINGS.md ] || printf "# Learnings\n\nCorrections, insights, and knowledge gaps captured during development.\n\n**Categories**: correction | insight | knowledge_gap | best_practice\n\n---\n" > .learnings/LEARNINGS.md
[ -f .learnings/ERRORS.md ] || printf "# Errors\n\nCommand failures and integration errors.\n\n---\n" > .learnings/ERRORS.md
[ -f .learnings/FEATURE_REQUESTS.md ] || printf "# Feature Requests\n\nCapabilities requested by the user.\n\n---\n" > .learnings/FEATURE_REQUESTS.md
```
Never overwrite existing files. This is a no-op if `.learnings/` is already initialised.
Do not log secrets, tokens, private keys, environment variables, or full source/config files unless the user explicitly asks for that level of detail. Prefer short summaries or redacted excerpts over raw command output or full transcripts.
If you want automatic reminders and session-end error detection, enable the opt-in hook described in [Optional: Enable Hook](#optional-enable-hook).
## Quick Reference
| Situation | Action |
|-----------|--------|
| Command/operation fails | Log to `.learnings/ERRORS.md` |
| User corrects you | Log to `.learnings/LEARNINGS.md` with category `correction` |
| User wants missing feature | Log to `.learnings/FEATURE_REQUESTS.md` |
| API/external tool fails | Log to `.learnings/ERRORS.md` with integration details |
| Knowledge was outdated | Log to `.learnings/LEARNINGS.md` with category `knowledge_gap` |
| Found better approach | Log to `.learnings/LEARNINGS.md` with category `best_practice` |
| Simplify/Harden recurring patterns | Log/update `.learnings/LEARNINGS.md` with `Source: simplify-and-harden` and a stable `Pattern-Key` |
| Similar to existing entry | Grep by `Pattern-Key` first, link with `**See Also**`, bump `Recurrence-Count` |
| Workflow improvements | Promote to `AGENTS.md` (workspace) |
| Tool gotchas | Promote to `TOOLS.md` (workspace) |
| Behavioral patterns | Promote to `SOUL.md` (workspace) |
## OpenClaw Setup
OpenClaw uses workspace-based prompt injection with automatic skill loading.
### Installation
**Via OpenClaw's built-in installer (recommended)** — installs into the
active OpenClaw workspace:
```bash
openclaw skills install @pskoett/self-improving-agent
```
**Via the ClawHub CLI** (`npm i -g clawhub`) — installs into `./skills`
under the current working directory, not the workspace:
```bash
clawhub install @pskoett/self-improving-agent
```
**Manual** (the skill lives in the repo's `self-improving-agent/` subfolder;
copy that folder, not the repo root):
```bash
git clone https://github.com/pskoett/self-improving-agent.git /tmp/self-improving-agent-repo
cp -r /tmp/self-improving-agent-repo/self-improving-agent ~/.openclaw/skills/self-improving-agent
```
Remade for openclaw from original repo : https://github.com/pskoett/pskoett-ai-skills - https://github.com/pskoett/pskoett-ai-skills/tree/main/skills/self-improvement
### Workspace Structure
OpenClaw injects these files into every session:
```
~/.openclaw/workspace/
├── AGENTS.md # Multi-agent workflows, delegation patterns
├── SOUL.md # Behavioral guidelines, personality, principles
├── TOOLS.md # Tool capabilities, integration gotchas
├── MEMORY.md # Long-term memory (main session only)
├── memory/ # Daily memory files
│ └── YYYY-MM-DD.md
└── .learnings/ # This skill's log files
├── LEARNINGS.md
├── ERRORS.md
└── FEATURE_REQUESTS.md
```
### Create Learning Files
```bash
mkdir -p ~/.openclaw/workspace/.learnings
```
Then create the log files (or copy from `assets/`):
- `LEARNINGS.md` — corrections, knowledge gaps, best practices
- `ERRORS.md` — command failures, exceptions
- `FEATURE_REQUESTS.md` — user-requested capabilities
### Promotion Targets
When learnings prove broadly applicable, promote them to workspace files:
| Learning Type | Promote To | Example |
|---------------|------------|---------|
| Behavioral patterns | `SOUL.md` | "Be concise, avoid disclaimers" |
| Workflow improvements | `AGENTS.md` | "Spawn sub-agents for long tasks" |
| Tool gotchas | `TOOLS.md` | "Git push needs auth configured first" |
### Inter-Session Communication
OpenClaw provides tools to share learnings across sessions:
- **sessions_list** — View active/recent sessions
- **sessions_history** — Read another session's transcript
- **sessions_send** — Send a learning to another session
- **sessions_spawn** — Spawn a sub-agent for background work
Use these only in trusted environments and only when the user explicitly wants cross-session sharing. Prefer sending a short sanitized summary and relevant file paths, not raw transcripts, secrets, or full command output.
### Optional: Enable Hook
For automatic reminders at session start and error detection at session end:
```bash
cp -r ~/.openclaw/skills/self-improving-agent/hooks/openclaw ~/.openclaw/hooks/self-improvement
openclaw hooks enable self-improvement
```
Fires on `agent:bootstrap` (injects the reminder, plus a pending-triage note
when auto-detected errors await review) and on `command:new`/`command:reset`
(sweeps the ended session's transcript for error patterns into
`<workspace>/.learnings/ERRORS.md`; opt-in — runs only when `.learnings/`
exists). OpenClaw has no per-tool-call hook event, so error detection happens
at session end. See `references/openclaw-integration.md` for details and
sweep limitations.
## Logging Format
### Learning Entry
Append to `.learnings/LEARNINGS.md`:
```markdown
## [LRN-YYYYMMDD-XXX] category
**Logged**: ISO-8601 timestamp
**Priority**: low | medium | high | critical
**Status**: pending
**Area**: frontend | backend | infra | tests | docs | config
### Summary
One-line description of what was learned
### Details
Full context: what happened, what was wrong, what's correct
### Suggested Action
Specific fix or improvement to make
### Metadata
- Source: conversation | error | user_feedback
- Related Files: path/to/file.ext
- Tags: tag1, tag2
- See Also: LRN-20250110-001 (if related to existing entry)
- Pattern-Key: area.symptom (recommended; e.g. deps.module-not-found, simplify.dead_code — see Pattern-Key Taxonomy)
- Recurrence-Count: 1 (optional)
- First-Seen: 2025-01-15 (optional)
- Last-Seen: 2025-01-15 (optional)
---
```
### Error Entry
Append to `.learnings/ERRORS.md`:
```markdown
## [ERR-YYYYMMDD-XXX] skill_or_command_name
**Logged**: ISO-8601 timestamp
**Priority**: high
**Status**: pending
**Area**: frontend | backend | infra | tests | docs | config
### Summary
Brief description of what failed
### Error
```
Actual error message or output
```
### Context
- Command/operation attempted
- Input or parameters used
- Environment details if relevant
- Summary or redacted excerpt of relevant output (avoid full transcripts and secret-bearing data by default)
### Suggested Fix
If identifiable, what might resolve this
### Metadata
- Reproducible: yes | no | unknown
- Related Files: path/to/file.ext
- See Also: ERR-20250110-001 (if recurring)
- Pattern-Key: area.symptom (recommended; e.g. net.connection-refused — see Pattern-Key Taxonomy)
- Recurrence-Count: 1 (optional)
- First-Seen: 2025-01-15 (optional)
- Last-Seen: 2025-01-15 (optional)
---
```
### Feature Request Entry
Append to `.learnings/FEATURE_REQUESTS.md`:
```markdown
## [FEAT-YYYYMMDD-XXX] capability_name
**Logged**: ISO-8601 timestamp
**Priority**: medium
**Status**: pending
**Area**: frontend | backend | infra | tests | docs | config
### Requested Capability
What the user wanted to do
### User Context
Why they needed it, what problem they're solving
### Complexity Estimate
simple | medium | complex
### Suggested Implementation
How this could be built, what it might extend
### Metadata
- Frequency: first_time | recurring
- Related Features: existing_feature_name
- Pattern-Key: area.symptom (optional — features usually dedupe by capability name; use a key only for recurring themes, e.g. api.missing-endpoint)
---
```
## ID Generation
Format: `TYPE-YYYYMMDD-XXX`
- TYPE: `LRN` (learning), `ERR` (error), `FEAT` (feature)
- YYYYMMDD: Current date
- XXX: Sequential number or random 3 chars (e.g., `001`, `A7B`)
Examples: `LRN-20250115-001`, `ERR-20250115-A3F`, `FEAT-20250115-002`
## Resolving Entries
When an issue is fixed, update the entry:
1. Change `**Status**: pending``**Status**: resolved`
2. Add resolution block after Metadata:
```markdown
### Resolution
- **Resolved**: 2025-01-16T09:00:00Z
- **Commit/PR**: abc123 or #42
- **Notes**: Brief description of what was done
```
Other status values:
- `in_progress` - Actively being worked on
- `wont_fix` - Decided not to address (add reason in Resolution notes)
- `promoted` - Elevated to a workspace file (`SOUL.md`, `TOOLS.md`, `AGENTS.md`)
## Promoting to Workspace Memory
When a learning is broadly applicable (not a one-off fix), promote it to a workspace file so every session inherits it.
### When to Promote
- Learning applies across multiple files/features
- Knowledge any contributor (human or AI) should know
- Prevents recurring mistakes
- Documents project-specific conventions
### Promotion Targets
| Target | What Belongs There |
|--------|-------------------|
| `SOUL.md` | Behavioral guidelines, communication style, principles |
| `TOOLS.md` | Tool capabilities, usage patterns, integration gotchas |
| `AGENTS.md` | Workflows, delegation patterns, automation rules |
When the learning is specific to a project repo you work in (not the
workspace), promote to that project's own agent file (e.g. its `AGENTS.md`)
instead.
### How to Promote
1. **Distill** the learning into a concise rule or fact
2. **Add** to appropriate section in target file (create file if needed)
3. **Update** original entry:
- Change `**Status**: pending``**Status**: promoted`
- Add `**Promoted**: SOUL.md`, `TOOLS.md`, or `AGENTS.md`
### Promotion Examples
**Learning** (verbose):
> Project uses pnpm workspaces. Attempted `npm install` but failed.
> Lock file is `pnpm-lock.yaml`. Must use `pnpm install`.
**In TOOLS.md** (concise):
```markdown
## Build & Dependencies
- Package manager: pnpm (not npm) - use `pnpm install`
```
**Learning** (verbose):
> When modifying API endpoints, must regenerate TypeScript client.
> Forgetting this causes type mismatches at runtime.
**In AGENTS.md** (actionable):
```markdown
## After API Changes
1. Regenerate client: `pnpm run generate:api`
2. Check for type errors: `pnpm tsc --noEmit`
```
## Pattern-Key Taxonomy
`Pattern-Key` is the stable dedup and recurrence key for entries in all three
log files: keyword grep misses semantically identical but differently-worded
entries, a shared key does not — and reliable keys are what make
`Recurrence-Count` and the promotion rule work.
**Format**: `area.symptom` — exactly two levels, lowercase, hyphenated
(e.g. `deps.module-not-found`). Keep symptoms generic enough to recur: no
file names, versions, or hostnames in keys.
| Area | Scope | Example Keys |
|------|-------|--------------|
| `api` | External API/service behavior | `api.rate-limit`, `api.schema-mismatch`, `api.missing-endpoint` |
| `auth` | Credentials, tokens, scopes | `auth.token-expired`, `auth.missing-scope` |
| `build` | Compilation, bundling, CI | `build.type-error`, `build.missing-artifact` |
| `config` | Config files, env vars, settings | `config.missing-env`, `config.invalid-json` |
| `deps` | Package managers, dependencies | `deps.module-not-found`, `deps.npm-error`, `deps.version-conflict` |
| `fs` | Filesystem | `fs.no-such-file`, `fs.permission-denied` |
| `net` | Network connectivity | `net.connection-refused`, `net.timeout` |
| `runtime` | Language/runtime errors not covered above | `runtime.type-error`, `runtime.python-exception` |
| `shell` | Shell/CLI mechanics | `shell.command-not-found`, `shell.nonzero-exit` |
| `vcs` | Git and other version control | `vcs.fatal-error`, `vcs.merge-conflict` |
| `simplify` / `harden` | Code-quality patterns from the simplify-and-harden feed | `simplify.dead_code`, `harden.input_validation` |
**Rules:**
1. **Reuse before minting**: `grep -rh "Pattern-Key:" .learnings/ | sort -u`
a near-match beats a new key.
2. **One key per manual entry**; auto-swept OpenClaw entries may carry
several — reduce to one when triaging.
3. **Mint new areas sparingly** — only when several entries would share one.
4. **Generic sweep keys** (`runtime.error`, `runtime.failure`) mean
"unclassified" — replace with a specific key during triage.
## Recurring Pattern Detection
If logging something similar to an existing entry:
1. **Search by key first**: `grep -n "Pattern-Key: area.symptom" .learnings/*.md`
— this is the default dedup check and catches rewordings that keyword
search misses
2. **Fallback keyword search**: `grep -ri "keyword" .learnings/` for entries
logged without a key
3. **Fold, don't duplicate**: on a hit, update the existing entry — bump
`Recurrence-Count`, set `Last-Seen`, add `**See Also**` — instead of
creating a new one
4. **Bump priority** if issue keeps recurring
5. **Consider systemic fix**: Recurring issues often indicate:
- Missing knowledge (→ promote to `TOOLS.md` or `SOUL.md`)
- Missing automation (→ add to `AGENTS.md`)
- Architectural problem (→ create tech debt ticket)
## Simplify & Harden Feed
Use this workflow to ingest recurring patterns from the `simplify-and-harden`
skill and turn them into durable prompt guidance.
### Ingestion Workflow
1. Read `simplify_and_harden.learning_loop.candidates` from the task summary.
2. For each candidate, use `pattern_key` as the stable dedupe key.
3. Search `.learnings/LEARNINGS.md` for an existing entry with that key:
- `grep -n "Pattern-Key: <pattern_key>" .learnings/LEARNINGS.md`
4. If found:
- Increment `Recurrence-Count`
- Update `Last-Seen`
- Add `See Also` links to related entries/tasks
5. If not found:
- Create a new `LRN-...` entry
- Set `Source: simplify-and-harden`
- Set `Pattern-Key`, `Recurrence-Count: 1`, and `First-Seen`/`Last-Seen`
### Promotion Rule (System Prompt Feedback)
Promote recurring patterns into agent context/system prompt files when all are true:
- `Recurrence-Count >= 3`
- Seen across at least 2 distinct tasks
- Occurred within a 30-day window
Promotion targets: `SOUL.md`, `TOOLS.md`, or `AGENTS.md` (workspace), or the
project's own agent file when the pattern is project-specific.
Write promoted rules as short prevention rules (what to do before/while coding),
not long incident write-ups.
## Periodic Review
Review `.learnings/` at natural breakpoints:
### When to Review
- Before starting a new major task
- After completing a feature
- When working in an area with past learnings
- Weekly during active development
### Quick Status Check
```bash
# Count pending items
grep -h "Status\*\*: pending" .learnings/*.md | wc -l
# List pending high-priority items
grep -B5 "Priority\*\*: high" .learnings/*.md | grep "^## \["
# Find learnings for a specific area
grep -l "Area\*\*: backend" .learnings/*.md
```
### Review Actions
- Resolve fixed items
- Promote applicable learnings
- Link related entries
- Escalate recurring issues
## Detection Triggers
Automatically log when you notice:
**Corrections** (→ learning with `correction` category):
- "No, that's not right..."
- "Actually, it should be..."
- "You're wrong about..."
- "That's outdated..."
**Feature Requests** (→ feature request):
- "Can you also..."
- "I wish you could..."
- "Is there a way to..."
- "Why can't you..."
**Knowledge Gaps** (→ learning with `knowledge_gap` category):
- User provides information you didn't know
- Documentation you referenced is outdated
- API behavior differs from your understanding
**Errors** (→ error entry):
- Command returns non-zero exit code
- Exception or stack trace
- Unexpected output or behavior
- Timeout or connection failure
## Priority Guidelines
| Priority | When to Use |
|----------|-------------|
| `critical` | Blocks core functionality, data loss risk, security issue |
| `high` | Significant impact, affects common workflows, recurring issue |
| `medium` | Moderate impact, workaround exists |
| `low` | Minor inconvenience, edge case, nice-to-have |
## Area Tags
Use to filter learnings by codebase region:
| Area | Scope |
|------|-------|
| `frontend` | UI, components, client-side code |
| `backend` | API, services, server-side code |
| `infra` | CI/CD, deployment, Docker, cloud |
| `tests` | Test files, testing utilities, coverage |
| `docs` | Documentation, comments, READMEs |
| `config` | Configuration files, environment, settings |
## Best Practices
1. **Log immediately** - context is freshest right after the issue
2. **Be specific** - future agents need to understand quickly
3. **Include reproduction steps** - especially for errors
4. **Link related files** - makes fixes easier
5. **Suggest concrete fixes** - not just "investigate"
6. **Use consistent categories** - enables filtering
7. **Promote aggressively** - if in doubt, add to `TOOLS.md` or `SOUL.md`
8. **Review regularly** - stale learnings lose value
## Gitignore Options
**Keep learnings local** (per-developer):
```gitignore
.learnings/
```
This repo uses that default to avoid committing sensitive or noisy local logs by accident.
**Track learnings in repo** (team-wide):
Don't add to .gitignore - learnings become shared knowledge.
**Hybrid** (track templates, ignore entries):
```gitignore
.learnings/*.md
!.learnings/.gitkeep
```
## Upgrading & Uninstalling
Read `CHANGELOG.md` before upgrading — it carries per-version notes, and
hook changes require re-copying the hook and restarting the gateway.
To disable or remove the skill, follow `references/uninstall.md`:
`.learnings/` is user data (review before deleting), and content promoted to
`SOUL.md`/`TOOLS.md`/`AGENTS.md` stays until removed manually.
## Automatic Skill Extraction
When a learning is valuable enough to become a reusable skill, extract it using the provided helper.
### Skill Extraction Criteria
A learning qualifies for skill extraction when ANY of these apply:
| Criterion | Description |
|-----------|-------------|
| **Recurring** | Has `See Also` links to 2+ similar issues |
| **Verified** | Status is `resolved` with working fix |
| **Non-obvious** | Required actual debugging/investigation to discover |
| **Broadly applicable** | Not project-specific; useful across codebases |
| **User-flagged** | User says "save this as a skill" or similar |
### Extraction Workflow
1. **Identify candidate**: Learning meets extraction criteria
2. **Run helper** (or create manually):
```bash
~/.openclaw/skills/self-improving-agent/scripts/extract-skill.sh skill-name --dry-run
~/.openclaw/skills/self-improving-agent/scripts/extract-skill.sh skill-name
```
3. **Customize SKILL.md**: Fill in template with learning content
4. **Update learning**: Set status to `promoted_to_skill`, add `Skill-Path`
5. **Verify**: Read skill in fresh session to ensure it's self-contained
### Manual Extraction
If you prefer manual creation:
1. Create `skills/<skill-name>/SKILL.md`
2. Use template from `assets/SKILL-TEMPLATE.md`
3. Follow [Agent Skills spec](https://agentskills.io/specification):
- YAML frontmatter with `name` and `description`
- Name must match folder name
- No README.md inside skill folder
### Extraction Detection Triggers
Watch for these signals that a learning should become a skill:
**In conversation:**
- "Save this as a skill"
- "I keep running into this"
- "This would be useful for other projects"
- "Remember this pattern"
**In learning entries:**
- Multiple `See Also` links (recurring issue)
- High priority + resolved status
- Category: `best_practice` with broad applicability
- User feedback praising the solution
### Skill Quality Gates
Before extraction, verify:
- [ ] Solution is tested and working
- [ ] Description is clear without original context
- [ ] Code examples are self-contained
- [ ] No project-specific hardcoded values
- [ ] Follows skill naming conventions (lowercase, hyphens)
@@ -0,0 +1,6 @@
{
"ownerId": "kn70cjr952qdec1nx70zs6wefn7ynq2t",
"slug": "self-improving-agent",
"version": "4.0.2",
"publishedAt": 1785999527780
}
@@ -0,0 +1,5 @@
# Errors Log
Command failures, exceptions, and unexpected behaviors.
---
@@ -0,0 +1,5 @@
# Feature Requests
Capabilities requested by user that don't currently exist.
---
@@ -0,0 +1,45 @@
# Learnings
Corrections, insights, and knowledge gaps captured during development.
**Categories**: correction | insight | knowledge_gap | best_practice
**Areas**: frontend | backend | infra | tests | docs | config
**Statuses**: pending | in_progress | resolved | wont_fix | promoted | promoted_to_skill
## Status Definitions
| Status | Meaning |
|--------|---------|
| `pending` | Not yet addressed |
| `in_progress` | Actively being worked on |
| `resolved` | Issue fixed or knowledge integrated |
| `wont_fix` | Decided not to address (reason in Resolution) |
| `promoted` | Elevated to SOUL.md, TOOLS.md, or AGENTS.md |
| `promoted_to_skill` | Extracted as a reusable skill |
## Skill Extraction Fields
When a learning is promoted to a skill, add these fields:
```markdown
**Status**: promoted_to_skill
**Skill-Path**: skills/skill-name
```
Example:
```markdown
## [LRN-20250115-001] best_practice
**Logged**: 2025-01-15T10:00:00Z
**Priority**: high
**Status**: promoted_to_skill
**Skill-Path**: skills/docker-m1-fixes
**Area**: infra
### Summary
Docker build fails on Apple Silicon due to platform mismatch
...
```
---
@@ -0,0 +1,177 @@
# Skill Template
Template for creating skills extracted from learnings. Copy and customize.
---
## SKILL.md Template
```markdown
---
name: skill-name-here
description: "Concise description of when and why to use this skill. Include trigger conditions."
---
# Skill Name
Brief introduction explaining the problem this skill solves and its origin.
## Quick Reference
| Situation | Action |
|-----------|--------|
| [Trigger 1] | [Action 1] |
| [Trigger 2] | [Action 2] |
## Background
Why this knowledge matters. What problems it prevents. Context from the original learning.
## Solution
### Step-by-Step
1. First step with code or command
2. Second step
3. Verification step
### Code Example
\`\`\`language
// Example code demonstrating the solution
\`\`\`
## Common Variations
- **Variation A**: Description and how to handle
- **Variation B**: Description and how to handle
## Gotchas
- Warning or common mistake #1
- Warning or common mistake #2
## Related
- Link to related documentation
- Link to related skill
## Source
Extracted from learning entry.
- **Learning ID**: LRN-YYYYMMDD-XXX
- **Original Category**: correction | insight | knowledge_gap | best_practice
- **Extraction Date**: YYYY-MM-DD
```
---
## Minimal Template
For simple skills that don't need all sections:
```markdown
---
name: skill-name-here
description: "What this skill does and when to use it."
---
# Skill Name
[Problem statement in one sentence]
## Solution
[Direct solution with code/commands]
## Source
- Learning ID: LRN-YYYYMMDD-XXX
```
---
## Template with Scripts
For skills that include executable helpers:
```markdown
---
name: skill-name-here
description: "What this skill does and when to use it."
---
# Skill Name
[Introduction]
## Quick Reference
| Command | Purpose |
|---------|---------|
| `./scripts/helper.sh` | [What it does] |
| `./scripts/validate.sh` | [What it does] |
## Usage
### Automated (Recommended)
\`\`\`bash
./skills/skill-name/scripts/helper.sh [args]
\`\`\`
### Manual Steps
1. Step one
2. Step two
## Scripts
| Script | Description |
|--------|-------------|
| `scripts/helper.sh` | Main utility |
| `scripts/validate.sh` | Validation checker |
## Source
- Learning ID: LRN-YYYYMMDD-XXX
```
---
## Naming Conventions
- **Skill name**: lowercase, hyphens for spaces
- Good: `docker-m1-fixes`, `api-timeout-patterns`
- Bad: `Docker_M1_Fixes`, `APITimeoutPatterns`
- **Description**: Start with action verb, mention trigger
- Good: "Handles Docker build failures on Apple Silicon. Use when builds fail with platform mismatch."
- Bad: "Docker stuff"
- **Files**:
- `SKILL.md` - Required, main documentation
- `scripts/` - Optional, executable code
- `references/` - Optional, detailed docs
- `assets/` - Optional, templates
---
## Extraction Checklist
Before creating a skill from a learning:
- [ ] Learning is verified (status: resolved)
- [ ] Solution is broadly applicable (not one-off)
- [ ] Content is complete (has all needed context)
- [ ] Name follows conventions
- [ ] Description is concise but informative
- [ ] Quick Reference table is actionable
- [ ] Code examples are tested
- [ ] Source learning ID is recorded
After creating:
- [ ] Update original learning with `promoted_to_skill` status
- [ ] Add `Skill-Path: skills/skill-name` to learning metadata
- [ ] Test skill by reading it in a fresh session
@@ -0,0 +1,68 @@
---
name: self-improvement
description: "Injects self-improvement reminder at bootstrap and sweeps ended sessions for errors"
metadata: {"openclaw":{"emoji":"🧠","events":["agent:bootstrap","command:new","command:reset"]}}
---
# Self-Improvement Hook
Injects a reminder to evaluate learnings during agent bootstrap, and detects
errors from ended sessions.
OpenClaw has no per-tool-call hook event, so errors cannot be detected in
real time after each command. This hook detects them with a session-end
error sweep instead.
## What It Does
**On `agent:bootstrap`** (before workspace files are injected):
- Adds a reminder block to check `.learnings/` for relevant entries
- Prompts the agent to log corrections, errors, and discoveries
- If auto-detected errors are awaiting triage, includes a pending-triage note
**On `command:new` / `command:reset`** (session end):
- Locates the transcript of the session that just ended
(`context.previousSessionEntry.sessionFile`, falling back to
`<workspace>/sessions/<sessionId>.jsonl`)
- Scans it against a fixed error-pattern list
(`Error:`, `command not found`, `Traceback`, `npm ERR!`, …)
- Appends a `pending` entry to `<workspace>/.learnings/ERRORS.md` with short,
truncated, redacted excerpts (max 5 per sweep) for the next session to triage
- Stamps each entry with deterministic `Pattern-Key` values derived from the
matched pattern (e.g. `deps.module-not-found`, `shell.command-not-found`),
so auto-detected errors can be deduplicated and recurrence-counted by key
(see the Pattern-Key Taxonomy in `SKILL.md`)
## Opt-In and Safety
- The sweep only runs when `<workspace>/.learnings/` exists — create that
directory to enable it, delete it to disable it
- `ERRORS.md` is created only if missing and is otherwise appended to, never
overwritten
- Excerpts are truncated to 200 characters and common secret shapes (bearer
tokens, API keys, GitHub/Slack/AWS tokens, JWTs, long opaque blobs) are
redacted before writing; excerpts already present in `ERRORS.md` are skipped
- Hook failures are swallowed so the gateway is never affected; set
`SELF_IMPROVEMENT_HOOK_DEBUG=1` to log failures
## Configuration
No configuration needed. Enable with:
```bash
openclaw hooks enable self-improvement
```
Enable the error sweep by creating the learnings directory:
```bash
mkdir -p ~/.openclaw/workspace/.learnings
```
## Testing
```bash
node --test hooks/openclaw/handler.test.js
```
@@ -0,0 +1,448 @@
/**
* Self-Improvement Hook for OpenClaw
*
* OpenClaw has no per-tool-call hook event, so errors cannot be detected in
* real time after each command. This hook detects them at session end
* instead:
*
* - agent:bootstrap Injects the self-improvement reminder before
* workspace files are injected, including a note
* when auto-detected errors are awaiting triage.
* - command:new / :reset Session-end sweep: scans the transcript of the
* session that just ended for error patterns and
* appends a pending entry to
* <workspace>/.learnings/ERRORS.md.
*
* The sweep is opt-in: it only runs when <workspace>/.learnings/ exists.
* Excerpts are truncated and redacted before being written.
*/
const fs = require('node:fs/promises');
const path = require('node:path');
const REMINDER_NAME = 'SELF_IMPROVEMENT_REMINDER.md';
const REMINDER_PATH = REMINDER_NAME;
const REMINDER_HEADER = '## Self-Improvement Reminder';
const REMINDER_CONTENT = `
${REMINDER_HEADER}
After completing tasks, evaluate whether any learnings should be captured.
Only log if this repo or workspace is using the self-improvement skill.
Before logging:
- Create only missing \`.learnings/\` files; never overwrite existing content
- Do not log secrets, tokens, private keys, environment variables, or raw transcripts
- Prefer short summaries or redacted excerpts over full command output
**Log when:**
- User corrects you → \`.learnings/LEARNINGS.md\`
- Command/operation fails → \`.learnings/ERRORS.md\`
- User wants missing capability → \`.learnings/FEATURE_REQUESTS.md\`
- You discover your knowledge was wrong → \`.learnings/LEARNINGS.md\`
- You find a better approach → \`.learnings/LEARNINGS.md\`
**Promote when pattern is proven:**
- Behavioral patterns → \`SOUL.md\`
- Workflow improvements → \`AGENTS.md\`
- Tool gotchas → \`TOOLS.md\`
Keep entries simple: date, title, what happened, and what to do differently.
`.trim();
// Error-detection patterns. Ordered specific → generic: the first matching pattern
// supplies the Pattern-Key stamped on swept entries, which is what makes
// auto-detected errors dedup-able and recurrence-countable (see the
// "Pattern-Key Taxonomy" section in SKILL.md).
const ERROR_PATTERN_KEYS = [
['command not found', 'shell.command-not-found'],
['No such file', 'fs.no-such-file'],
['Permission denied', 'fs.permission-denied'],
['ModuleNotFoundError', 'deps.module-not-found'],
['npm ERR!', 'deps.npm-error'],
['Traceback', 'runtime.python-exception'],
['SyntaxError', 'runtime.syntax-error'],
['TypeError', 'runtime.type-error'],
['Exception', 'runtime.exception'],
['fatal:', 'vcs.fatal-error'],
['exit code', 'shell.nonzero-exit'],
['non-zero', 'shell.nonzero-exit'],
['error:', 'runtime.error'],
['Error:', 'runtime.error'],
['ERROR:', 'runtime.error'],
['failed', 'runtime.failure'],
['FAILED', 'runtime.failure'],
];
const SWEEP_SOURCE = 'openclaw-error-sweep';
const MAX_EXCERPTS = 5;
const MAX_EXCERPT_LENGTH = 200;
const ERRORS_FILE_HEADER = '# Errors\n\nCommand failures and integration errors.\n\n---\n';
// Best-effort redaction of common secret shapes before anything is written.
const REDACTION_RULES = [
[/\b(api[_-]?key|token|secret|password|passwd|authorization|credential)s?\b(\s*[=:]\s*)\S+/gi, '$1$2[REDACTED]'],
[/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [REDACTED]'],
[/\bgh[pousr]_[A-Za-z0-9]{16,}\b/g, '[REDACTED]'],
[/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, '[REDACTED]'],
[/\bAKIA[0-9A-Z]{16}\b/g, '[REDACTED]'],
[/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\b/g, '[REDACTED-JWT]'],
[/\b[A-Za-z0-9_-]{40,}\b/g, '[REDACTED-BLOB]'],
];
function isObject(value) {
return !!value && typeof value === 'object';
}
function isInjectedReminderFile(value) {
if (!isObject(value) || value.path !== REMINDER_PATH) {
return false;
}
return (
value.virtual === true ||
(typeof value.content === 'string' && value.content.includes(REMINDER_HEADER))
);
}
function redactSensitiveText(text) {
let result = text;
for (const [pattern, replacement] of REDACTION_RULES) {
result = result.replace(pattern, replacement);
}
return result;
}
function sanitizeExcerptLine(line) {
let excerpt = redactSensitiveText(line.trim()).split('```').join("'''");
if (excerpt.length > MAX_EXCERPT_LENGTH) {
excerpt = `${excerpt.slice(0, MAX_EXCERPT_LENGTH)}`;
}
return excerpt;
}
function collectTextFragments(value, out, depth = 0) {
if (depth > 4 || out.length > 200) {
return;
}
if (typeof value === 'string') {
out.push(value);
return;
}
if (Array.isArray(value)) {
for (const item of value) {
collectTextFragments(item, out, depth + 1);
}
return;
}
if (isObject(value)) {
if (typeof value.text === 'string') {
out.push(value.text);
}
if ('content' in value) {
collectTextFragments(value.content, out, depth + 1);
}
}
}
function matchErrorPatternKey(line) {
for (const [pattern, patternKey] of ERROR_PATTERN_KEYS) {
if (line.includes(pattern)) {
return patternKey;
}
}
return null;
}
async function scanTranscriptForErrors(sessionFilePath) {
let raw;
try {
raw = await fs.readFile(sessionFilePath, 'utf-8');
} catch {
return [];
}
const excerpts = [];
const seen = new Set();
for (const jsonLine of raw.split('\n')) {
if (excerpts.length >= MAX_EXCERPTS) {
break;
}
const trimmed = jsonLine.trim();
if (!trimmed) {
continue;
}
let entry;
try {
entry = JSON.parse(trimmed);
} catch {
continue;
}
if (!isObject(entry) || !isObject(entry.message)) {
continue;
}
const fragments = [];
collectTextFragments(entry.message.content, fragments);
for (const fragment of fragments) {
for (const line of fragment.split('\n')) {
const patternKey = matchErrorPatternKey(line);
if (!patternKey) {
continue;
}
const excerpt = sanitizeExcerptLine(line);
if (!excerpt || seen.has(excerpt)) {
continue;
}
seen.add(excerpt);
excerpts.push({ excerpt, patternKey });
if (excerpts.length >= MAX_EXCERPTS) {
return excerpts;
}
}
}
}
return excerpts;
}
function resolveSessionFilePath(context, workspaceDir) {
const sessionEntry = isObject(context.previousSessionEntry)
? context.previousSessionEntry
: isObject(context.sessionEntry)
? context.sessionEntry
: {};
if (typeof sessionEntry.sessionFile === 'string' && sessionEntry.sessionFile.trim()) {
return sessionEntry.sessionFile;
}
const sessionId =
typeof sessionEntry.sessionId === 'string' ? sessionEntry.sessionId.trim() : '';
if (sessionId && workspaceDir) {
return path.join(workspaceDir, 'sessions', `${sessionId}.jsonl`);
}
return undefined;
}
function generateEntryId(timestamp) {
const yyyymmdd = timestamp.toISOString().slice(0, 10).replace(/-/g, '');
const suffix = Math.random().toString(36).slice(2, 5).toUpperCase().padEnd(3, '0');
return `ERR-${yyyymmdd}-${suffix}`;
}
function formatErrorEntry(params) {
const { excerpts, sessionKey, sessionFilePath, action, timestamp } = params;
const plural = excerpts.length === 1 ? '' : 's';
const patternKeys = [...new Set(excerpts.map((item) => item.patternKey))];
return [
`## [${generateEntryId(timestamp)}] openclaw_session_sweep`,
'',
`**Logged**: ${timestamp.toISOString()}`,
'**Priority**: medium',
'**Status**: pending',
'**Area**: config',
'',
'### Summary',
`Session-end sweep detected ${excerpts.length} possible error${plural} in the previous OpenClaw session.`,
'',
'### Error',
'```',
...excerpts.map((item) => item.excerpt),
'```',
'',
'### Context',
`- Detected by the self-improvement hook on \`/${action}\` (OpenClaw has no per-tool-call hook, so errors are swept from the session transcript at session end)`,
`- Session key: ${sessionKey || 'unknown'}`,
`- Session transcript: ${sessionFilePath}`,
'- Excerpts are truncated and redacted; check the transcript for full context',
'',
'### Suggested Fix',
'Triage this entry: if the error was real and non-obvious, keep it and fill in the fix; otherwise mark it resolved or delete it. Before keeping it, grep for its Pattern-Key(s) and fold recurrences into the existing entry (bump Recurrence-Count) instead of duplicating.',
'',
'### Metadata',
`- Source: ${SWEEP_SOURCE}`,
'- Reproducible: unknown',
...patternKeys.map((patternKey) => `- Pattern-Key: ${patternKey}`),
'',
'---',
].join('\n');
}
async function handleSessionEndSweep(event) {
const context = event.context;
const workspaceDir =
typeof context.workspaceDir === 'string' && context.workspaceDir.trim()
? context.workspaceDir
: undefined;
if (!workspaceDir) {
return;
}
// Opt-in gate: only sweep when the workspace uses the self-improvement skill.
const learningsDir = path.join(workspaceDir, '.learnings');
try {
const stats = await fs.stat(learningsDir);
if (!stats.isDirectory()) {
return;
}
} catch {
return;
}
const sessionFilePath = resolveSessionFilePath(context, workspaceDir);
if (!sessionFilePath) {
return;
}
const excerpts = await scanTranscriptForErrors(sessionFilePath);
if (excerpts.length === 0) {
return;
}
const errorsFilePath = path.join(learningsDir, 'ERRORS.md');
let existing = '';
try {
existing = await fs.readFile(errorsFilePath, 'utf-8');
} catch {
// Missing file is fine; it is created below.
}
const freshExcerpts = excerpts.filter((item) => !existing.includes(item.excerpt));
if (freshExcerpts.length === 0) {
return;
}
const entry = formatErrorEntry({
excerpts: freshExcerpts,
sessionKey: typeof event.sessionKey === 'string' ? event.sessionKey : '',
sessionFilePath,
action: event.action,
timestamp: event.timestamp instanceof Date ? event.timestamp : new Date(),
});
if (!existing) {
try {
await fs.writeFile(errorsFilePath, `${ERRORS_FILE_HEADER}\n${entry}\n`, { flag: 'wx' });
return;
} catch (err) {
if (!isObject(err) || err.code !== 'EEXIST') {
throw err;
}
}
}
await fs.appendFile(errorsFilePath, `\n${entry}\n`);
}
async function countPendingSweepEntries(workspaceDir) {
if (!workspaceDir) {
return 0;
}
let content;
try {
content = await fs.readFile(path.join(workspaceDir, '.learnings', 'ERRORS.md'), 'utf-8');
} catch {
return 0;
}
return content
.split(/^## /m)
.slice(1)
.filter(
(section) =>
section.includes(`Source: ${SWEEP_SOURCE}`) && section.includes('**Status**: pending'),
).length;
}
async function handleBootstrap(event) {
// Skip sub-agent sessions to avoid bootstrap issues
// Sub-agents have sessionKey patterns like "agent:main:subagent:..."
const sessionKey = event.sessionKey || '';
if (sessionKey.includes(':subagent:')) {
return;
}
// Inject the reminder as a virtual bootstrap file
// Check that bootstrapFiles is an array before pushing
if (!Array.isArray(event.context.bootstrapFiles)) {
return;
}
const occupiedByOtherFile = event.context.bootstrapFiles.some(
(file) => isObject(file) && file.path === REMINDER_PATH && !isInjectedReminderFile(file),
);
if (occupiedByOtherFile) {
return;
}
let reminderContent = REMINDER_CONTENT;
const workspaceDir =
typeof event.context.workspaceDir === 'string' && event.context.workspaceDir.trim()
? event.context.workspaceDir
: undefined;
const pendingSweepCount = await countPendingSweepEntries(workspaceDir);
if (pendingSweepCount > 0) {
const plural = pendingSweepCount === 1 ? 'y' : 'ies';
reminderContent +=
`\n\n**Pending triage:** ${pendingSweepCount} auto-detected error entr${plural} ` +
`(Source: ${SWEEP_SOURCE}) in \`.learnings/ERRORS.md\` await review. ` +
'Confirm, resolve, or delete them when convenient.';
}
const cleanedBootstrapFiles = event.context.bootstrapFiles.filter(
(file, index, files) =>
!isInjectedReminderFile(file) ||
files.findIndex((candidate) => isInjectedReminderFile(candidate)) === index,
);
const reminderFile = {
name: REMINDER_NAME,
path: REMINDER_PATH,
content: reminderContent,
missing: false,
virtual: true,
};
const existingIndex = cleanedBootstrapFiles.findIndex((file) => isInjectedReminderFile(file));
if (existingIndex === -1) {
cleanedBootstrapFiles.push(reminderFile);
} else {
cleanedBootstrapFiles[existingIndex] = reminderFile;
}
event.context.bootstrapFiles = cleanedBootstrapFiles;
}
const handler = async (event) => {
// Safety checks for event structure
if (!event || typeof event !== 'object') {
return;
}
if (!event.context || typeof event.context !== 'object') {
return;
}
try {
if (event.type === 'agent' && event.action === 'bootstrap') {
await handleBootstrap(event);
return;
}
if (event.type === 'command' && (event.action === 'new' || event.action === 'reset')) {
await handleSessionEndSweep(event);
}
} catch (err) {
// Never break the gateway on hook failure.
if (process.env.SELF_IMPROVEMENT_HOOK_DEBUG) {
console.error('[self-improvement] hook failed:', err);
}
}
};
module.exports = handler;
module.exports.default = handler;
@@ -0,0 +1,229 @@
/**
* Tests for the OpenClaw self-improvement hook.
*
* Run with: node --test hooks/openclaw/
* (no dependencies; uses the built-in node:test runner)
*/
const assert = require('node:assert/strict');
const fs = require('node:fs/promises');
const os = require('node:os');
const path = require('node:path');
const { test, beforeEach, afterEach } = require('node:test');
const handler = require('./handler.js');
let workspaceDir;
beforeEach(async () => {
workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'self-improvement-hook-'));
});
afterEach(async () => {
await fs.rm(workspaceDir, { recursive: true, force: true });
});
function makeBootstrapEvent(overrides = {}) {
return {
type: 'agent',
action: 'bootstrap',
sessionKey: 'agent:main:whatsapp',
timestamp: new Date('2026-07-04T12:00:00Z'),
messages: [],
context: { workspaceDir, bootstrapFiles: [] },
...overrides,
};
}
function makeCommandEvent(action, sessionFile) {
return {
type: 'command',
action,
sessionKey: 'agent:main:whatsapp',
timestamp: new Date('2026-07-04T12:00:00Z'),
messages: [],
context: {
workspaceDir,
commandSource: 'whatsapp',
previousSessionEntry: { sessionId: 'abc123', sessionFile },
},
};
}
async function writeTranscript(lines) {
const sessionsDir = path.join(workspaceDir, 'sessions');
await fs.mkdir(sessionsDir, { recursive: true });
const sessionFile = path.join(sessionsDir, 'abc123.jsonl');
await fs.writeFile(sessionFile, lines.map((line) => JSON.stringify(line)).join('\n'));
return sessionFile;
}
function toolResultLine(text) {
return {
type: 'message',
message: { role: 'toolResult', content: [{ type: 'text', text }] },
};
}
const errorsFile = () => path.join(workspaceDir, '.learnings', 'ERRORS.md');
test('bootstrap injects the reminder as a virtual file', async () => {
const event = makeBootstrapEvent();
await handler(event);
assert.equal(event.context.bootstrapFiles.length, 1);
const injected = event.context.bootstrapFiles[0];
assert.equal(injected.path, 'SELF_IMPROVEMENT_REMINDER.md');
assert.equal(injected.virtual, true);
assert.match(injected.content, /## Self-Improvement Reminder/);
assert.doesNotMatch(injected.content, /Pending triage/);
});
test('bootstrap skips sub-agent sessions', async () => {
const event = makeBootstrapEvent({ sessionKey: 'agent:main:subagent:xyz' });
await handler(event);
assert.equal(event.context.bootstrapFiles.length, 0);
});
test('bootstrap deduplicates a previously injected reminder', async () => {
const event = makeBootstrapEvent();
event.context.bootstrapFiles.push({
name: 'SELF_IMPROVEMENT_REMINDER.md',
path: 'SELF_IMPROVEMENT_REMINDER.md',
content: '## Self-Improvement Reminder\n\nstale copy',
virtual: true,
});
await handler(event);
assert.equal(event.context.bootstrapFiles.length, 1);
});
test('session-end sweep appends detected errors to ERRORS.md', async () => {
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
const sessionFile = await writeTranscript([
{ type: 'message', message: { role: 'user', content: 'run the build' } },
toolResultLine('npm ERR! missing script: build\nbash: tsc: command not found'),
{ type: 'message', message: { role: 'assistant', content: 'Build is broken.' } },
]);
await handler(makeCommandEvent('new', sessionFile));
const content = await fs.readFile(errorsFile(), 'utf-8');
assert.match(content, /^# Errors/);
assert.match(content, /## \[ERR-20260704-[A-Z0-9]{3}\] openclaw_session_sweep/);
assert.match(content, /npm ERR! missing script: build/);
assert.match(content, /command not found/);
assert.match(content, /Source: openclaw-error-sweep/);
assert.match(content, /\*\*Status\*\*: pending/);
assert.match(content, /- Pattern-Key: deps\.npm-error/);
assert.match(content, /- Pattern-Key: shell\.command-not-found/);
});
test('sweep stamps the most specific Pattern-Key and dedupes keys', async () => {
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
const sessionFile = await writeTranscript([
// 'ModuleNotFoundError' must win over the generic 'Error:'/'Traceback' buckets
toolResultLine("ModuleNotFoundError: No module named 'requests'"),
toolResultLine('TypeError: cannot read properties of undefined (first)'),
toolResultLine('TypeError: cannot read properties of undefined (second)'),
]);
await handler(makeCommandEvent('new', sessionFile));
const content = await fs.readFile(errorsFile(), 'utf-8');
assert.match(content, /- Pattern-Key: deps\.module-not-found/);
const typeErrorKeys = content.split('- Pattern-Key: runtime.type-error').length - 1;
assert.equal(typeErrorKeys, 1);
assert.doesNotMatch(content, /- Pattern-Key: runtime\.error/);
});
test('sweep does nothing when .learnings/ does not exist (opt-in gate)', async () => {
const sessionFile = await writeTranscript([toolResultLine('fatal: not a git repository')]);
await handler(makeCommandEvent('new', sessionFile));
await assert.rejects(fs.access(errorsFile()));
});
test('sweep does nothing when the transcript has no errors', async () => {
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
const sessionFile = await writeTranscript([
{ type: 'message', message: { role: 'assistant', content: 'All good!' } },
]);
await handler(makeCommandEvent('new', sessionFile));
await assert.rejects(fs.access(errorsFile()));
});
test('sweep never overwrites an existing ERRORS.md', async () => {
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
await fs.writeFile(errorsFile(), '# Errors\n\nExisting notes.\n\n---\n');
const sessionFile = await writeTranscript([toolResultLine('Error: connection refused')]);
await handler(makeCommandEvent('reset', sessionFile));
const content = await fs.readFile(errorsFile(), 'utf-8');
assert.match(content, /Existing notes\./);
assert.match(content, /Error: connection refused/);
});
test('sweep is idempotent for already-logged excerpts', async () => {
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
const sessionFile = await writeTranscript([toolResultLine('Error: connection refused')]);
await handler(makeCommandEvent('new', sessionFile));
await handler(makeCommandEvent('new', sessionFile));
const content = await fs.readFile(errorsFile(), 'utf-8');
const occurrences = content.split('Error: connection refused').length - 1;
assert.equal(occurrences, 1);
});
test('sweep redacts secrets and truncates long lines', async () => {
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
const longTail = 'x'.repeat(300);
// Fixture secret is assembled at runtime so the literal never appears in
// this file and secret scanners don't flag it as an exposed credential.
const fakeKey = ['sk', 'live', '1234567890'].join('-');
const sessionFile = await writeTranscript([
toolResultLine(
`Error: request failed with api_key=${fakeKey} Bearer abc.def.ghi token: hunter2 ${longTail}`,
),
]);
await handler(makeCommandEvent('new', sessionFile));
const content = await fs.readFile(errorsFile(), 'utf-8');
assert.doesNotMatch(content, new RegExp(fakeKey));
assert.doesNotMatch(content, /hunter2/);
assert.match(content, /\[REDACTED\]/);
assert.doesNotMatch(content, /x{250}/);
});
test('sweep falls back to <workspace>/sessions/<sessionId>.jsonl', async () => {
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
await writeTranscript([toolResultLine('Traceback (most recent call last):')]);
await handler(makeCommandEvent('new', undefined));
const content = await fs.readFile(errorsFile(), 'utf-8');
assert.match(content, /Traceback/);
});
test('bootstrap surfaces pending sweep entries for triage', async () => {
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
const sessionFile = await writeTranscript([toolResultLine('Permission denied (publickey)')]);
await handler(makeCommandEvent('new', sessionFile));
const event = makeBootstrapEvent();
await handler(event);
const injected = event.context.bootstrapFiles[0];
assert.match(injected.content, /\*\*Pending triage:\*\* 1 auto-detected error entry/);
});
test('handler ignores unrelated events and malformed input', async () => {
await handler(null);
await handler({ type: 'gateway', action: 'startup', context: {} });
await handler({ type: 'command', action: 'stop', context: { workspaceDir } });
// A sweep with a missing transcript must not throw.
await fs.mkdir(path.join(workspaceDir, '.learnings'), { recursive: true });
await handler(makeCommandEvent('new', path.join(workspaceDir, 'sessions', 'missing.jsonl')));
await assert.rejects(fs.access(errorsFile()));
});
@@ -0,0 +1,465 @@
/**
* Self-Improvement Hook for OpenClaw
*
* OpenClaw has no per-tool-call hook event, so errors cannot be detected in
* real time after each command. This hook detects them at session end
* instead:
*
* - agent:bootstrap Injects the self-improvement reminder before
* workspace files are injected, including a note
* when auto-detected errors are awaiting triage.
* - command:new / :reset Session-end sweep: scans the transcript of the
* session that just ended for error patterns and
* appends a pending entry to
* <workspace>/.learnings/ERRORS.md.
*
* The sweep is opt-in: it only runs when <workspace>/.learnings/ exists.
* Excerpts are truncated and redacted before being written.
*/
import fs from 'node:fs/promises';
import path from 'node:path';
import type { HookHandler } from 'openclaw/hooks';
const REMINDER_NAME = 'SELF_IMPROVEMENT_REMINDER.md';
const REMINDER_PATH = REMINDER_NAME;
const REMINDER_HEADER = '## Self-Improvement Reminder';
const REMINDER_CONTENT = `${REMINDER_HEADER}
After completing tasks, evaluate whether any learnings should be captured.
Only log if this repo or workspace is using the self-improvement skill.
Before logging:
- Create only missing \`.learnings/\` files; never overwrite existing content
- Do not log secrets, tokens, private keys, environment variables, or raw transcripts
- Prefer short summaries or redacted excerpts over full command output
**Log when:**
- User corrects you → \`.learnings/LEARNINGS.md\`
- Command/operation fails → \`.learnings/ERRORS.md\`
- User wants missing capability → \`.learnings/FEATURE_REQUESTS.md\`
- You discover your knowledge was wrong → \`.learnings/LEARNINGS.md\`
- You find a better approach → \`.learnings/LEARNINGS.md\`
**Promote when pattern is proven:**
- Behavioral patterns → \`SOUL.md\`
- Workflow improvements → \`AGENTS.md\`
- Tool gotchas → \`TOOLS.md\`
Keep entries simple: date, title, what happened, and what to do differently.`;
// Error-detection patterns. Ordered specific → generic: the first matching pattern
// supplies the Pattern-Key stamped on swept entries, which is what makes
// auto-detected errors dedup-able and recurrence-countable (see the
// "Pattern-Key Taxonomy" section in SKILL.md).
const ERROR_PATTERN_KEYS: Array<[string, string]> = [
['command not found', 'shell.command-not-found'],
['No such file', 'fs.no-such-file'],
['Permission denied', 'fs.permission-denied'],
['ModuleNotFoundError', 'deps.module-not-found'],
['npm ERR!', 'deps.npm-error'],
['Traceback', 'runtime.python-exception'],
['SyntaxError', 'runtime.syntax-error'],
['TypeError', 'runtime.type-error'],
['Exception', 'runtime.exception'],
['fatal:', 'vcs.fatal-error'],
['exit code', 'shell.nonzero-exit'],
['non-zero', 'shell.nonzero-exit'],
['error:', 'runtime.error'],
['Error:', 'runtime.error'],
['ERROR:', 'runtime.error'],
['failed', 'runtime.failure'],
['FAILED', 'runtime.failure'],
];
interface SweepExcerpt {
excerpt: string;
patternKey: string;
}
const SWEEP_SOURCE = 'openclaw-error-sweep';
const MAX_EXCERPTS = 5;
const MAX_EXCERPT_LENGTH = 200;
const ERRORS_FILE_HEADER = '# Errors\n\nCommand failures and integration errors.\n\n---\n';
// Best-effort redaction of common secret shapes before anything is written.
const REDACTION_RULES: Array<[RegExp, string]> = [
[/\b(api[_-]?key|token|secret|password|passwd|authorization|credential)s?\b(\s*[=:]\s*)\S+/gi, '$1$2[REDACTED]'],
[/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [REDACTED]'],
[/\bgh[pousr]_[A-Za-z0-9]{16,}\b/g, '[REDACTED]'],
[/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, '[REDACTED]'],
[/\bAKIA[0-9A-Z]{16}\b/g, '[REDACTED]'],
[/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\b/g, '[REDACTED-JWT]'],
[/\b[A-Za-z0-9_-]{40,}\b/g, '[REDACTED-BLOB]'],
];
type HookEvent = Parameters<HookHandler>[0];
function isObject(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object';
}
function isInjectedReminderFile(value: unknown): boolean {
if (!isObject(value) || value.path !== REMINDER_PATH) {
return false;
}
return (
value.virtual === true ||
(typeof value.content === 'string' && value.content.includes(REMINDER_HEADER))
);
}
function redactSensitiveText(text: string): string {
let result = text;
for (const [pattern, replacement] of REDACTION_RULES) {
result = result.replace(pattern, replacement);
}
return result;
}
function sanitizeExcerptLine(line: string): string {
let excerpt = redactSensitiveText(line.trim()).split('```').join("'''");
if (excerpt.length > MAX_EXCERPT_LENGTH) {
excerpt = `${excerpt.slice(0, MAX_EXCERPT_LENGTH)}`;
}
return excerpt;
}
function collectTextFragments(value: unknown, out: string[], depth = 0): void {
if (depth > 4 || out.length > 200) {
return;
}
if (typeof value === 'string') {
out.push(value);
return;
}
if (Array.isArray(value)) {
for (const item of value) {
collectTextFragments(item, out, depth + 1);
}
return;
}
if (isObject(value)) {
if (typeof value.text === 'string') {
out.push(value.text);
}
if ('content' in value) {
collectTextFragments(value.content, out, depth + 1);
}
}
}
function matchErrorPatternKey(line: string): string | null {
for (const [pattern, patternKey] of ERROR_PATTERN_KEYS) {
if (line.includes(pattern)) {
return patternKey;
}
}
return null;
}
async function scanTranscriptForErrors(sessionFilePath: string): Promise<SweepExcerpt[]> {
let raw: string;
try {
raw = await fs.readFile(sessionFilePath, 'utf-8');
} catch {
return [];
}
const excerpts: SweepExcerpt[] = [];
const seen = new Set<string>();
for (const jsonLine of raw.split('\n')) {
if (excerpts.length >= MAX_EXCERPTS) {
break;
}
const trimmed = jsonLine.trim();
if (!trimmed) {
continue;
}
let entry: unknown;
try {
entry = JSON.parse(trimmed);
} catch {
continue;
}
if (!isObject(entry) || !isObject(entry.message)) {
continue;
}
const fragments: string[] = [];
collectTextFragments(entry.message.content, fragments);
for (const fragment of fragments) {
for (const line of fragment.split('\n')) {
const patternKey = matchErrorPatternKey(line);
if (!patternKey) {
continue;
}
const excerpt = sanitizeExcerptLine(line);
if (!excerpt || seen.has(excerpt)) {
continue;
}
seen.add(excerpt);
excerpts.push({ excerpt, patternKey });
if (excerpts.length >= MAX_EXCERPTS) {
return excerpts;
}
}
}
}
return excerpts;
}
function resolveSessionFilePath(
context: Record<string, unknown>,
workspaceDir: string | undefined,
): string | undefined {
const sessionEntry = isObject(context.previousSessionEntry)
? context.previousSessionEntry
: isObject(context.sessionEntry)
? context.sessionEntry
: {};
const sessionFile = (sessionEntry as Record<string, unknown>).sessionFile;
if (typeof sessionFile === 'string' && sessionFile.trim()) {
return sessionFile;
}
const sessionIdValue = (sessionEntry as Record<string, unknown>).sessionId;
const sessionId = typeof sessionIdValue === 'string' ? sessionIdValue.trim() : '';
if (sessionId && workspaceDir) {
return path.join(workspaceDir, 'sessions', `${sessionId}.jsonl`);
}
return undefined;
}
function generateEntryId(timestamp: Date): string {
const yyyymmdd = timestamp.toISOString().slice(0, 10).replace(/-/g, '');
const suffix = Math.random().toString(36).slice(2, 5).toUpperCase().padEnd(3, '0');
return `ERR-${yyyymmdd}-${suffix}`;
}
function formatErrorEntry(params: {
excerpts: SweepExcerpt[];
sessionKey: string;
sessionFilePath: string;
action: string;
timestamp: Date;
}): string {
const { excerpts, sessionKey, sessionFilePath, action, timestamp } = params;
const plural = excerpts.length === 1 ? '' : 's';
const patternKeys = [...new Set(excerpts.map((item) => item.patternKey))];
return [
`## [${generateEntryId(timestamp)}] openclaw_session_sweep`,
'',
`**Logged**: ${timestamp.toISOString()}`,
'**Priority**: medium',
'**Status**: pending',
'**Area**: config',
'',
'### Summary',
`Session-end sweep detected ${excerpts.length} possible error${plural} in the previous OpenClaw session.`,
'',
'### Error',
'```',
...excerpts.map((item) => item.excerpt),
'```',
'',
'### Context',
`- Detected by the self-improvement hook on \`/${action}\` (OpenClaw has no per-tool-call hook, so errors are swept from the session transcript at session end)`,
`- Session key: ${sessionKey || 'unknown'}`,
`- Session transcript: ${sessionFilePath}`,
'- Excerpts are truncated and redacted; check the transcript for full context',
'',
'### Suggested Fix',
'Triage this entry: if the error was real and non-obvious, keep it and fill in the fix; otherwise mark it resolved or delete it. Before keeping it, grep for its Pattern-Key(s) and fold recurrences into the existing entry (bump Recurrence-Count) instead of duplicating.',
'',
'### Metadata',
`- Source: ${SWEEP_SOURCE}`,
'- Reproducible: unknown',
...patternKeys.map((patternKey) => `- Pattern-Key: ${patternKey}`),
'',
'---',
].join('\n');
}
async function handleSessionEndSweep(event: HookEvent): Promise<void> {
const context = event.context as Record<string, unknown>;
const workspaceDir =
typeof context.workspaceDir === 'string' && context.workspaceDir.trim()
? context.workspaceDir
: undefined;
if (!workspaceDir) {
return;
}
// Opt-in gate: only sweep when the workspace uses the self-improvement skill.
const learningsDir = path.join(workspaceDir, '.learnings');
try {
const stats = await fs.stat(learningsDir);
if (!stats.isDirectory()) {
return;
}
} catch {
return;
}
const sessionFilePath = resolveSessionFilePath(context, workspaceDir);
if (!sessionFilePath) {
return;
}
const excerpts = await scanTranscriptForErrors(sessionFilePath);
if (excerpts.length === 0) {
return;
}
const errorsFilePath = path.join(learningsDir, 'ERRORS.md');
let existing = '';
try {
existing = await fs.readFile(errorsFilePath, 'utf-8');
} catch {
// Missing file is fine; it is created below.
}
const freshExcerpts = excerpts.filter((item) => !existing.includes(item.excerpt));
if (freshExcerpts.length === 0) {
return;
}
const entry = formatErrorEntry({
excerpts: freshExcerpts,
sessionKey: typeof event.sessionKey === 'string' ? event.sessionKey : '',
sessionFilePath,
action: event.action,
timestamp: event.timestamp instanceof Date ? event.timestamp : new Date(),
});
if (!existing) {
try {
await fs.writeFile(errorsFilePath, `${ERRORS_FILE_HEADER}\n${entry}\n`, { flag: 'wx' });
return;
} catch (err) {
if (!isObject(err) || (err as { code?: string }).code !== 'EEXIST') {
throw err;
}
}
}
await fs.appendFile(errorsFilePath, `\n${entry}\n`);
}
async function countPendingSweepEntries(workspaceDir: string | undefined): Promise<number> {
if (!workspaceDir) {
return 0;
}
let content: string;
try {
content = await fs.readFile(path.join(workspaceDir, '.learnings', 'ERRORS.md'), 'utf-8');
} catch {
return 0;
}
return content
.split(/^## /m)
.slice(1)
.filter(
(section) =>
section.includes(`Source: ${SWEEP_SOURCE}`) && section.includes('**Status**: pending'),
).length;
}
async function handleBootstrap(event: HookEvent): Promise<void> {
// Skip sub-agent sessions to avoid bootstrap issues
// Sub-agents have sessionKey patterns like "agent:main:subagent:..."
const sessionKey = event.sessionKey || '';
if (sessionKey.includes(':subagent:')) {
return;
}
const context = event.context as Record<string, unknown>;
// Inject the reminder as a virtual bootstrap file
// Check that bootstrapFiles is an array before pushing
if (!Array.isArray(context.bootstrapFiles)) {
return;
}
const occupiedByOtherFile = context.bootstrapFiles.some(
(file) => isObject(file) && file.path === REMINDER_PATH && !isInjectedReminderFile(file),
);
if (occupiedByOtherFile) {
return;
}
let reminderContent = REMINDER_CONTENT;
const workspaceDir =
typeof context.workspaceDir === 'string' && context.workspaceDir.trim()
? context.workspaceDir
: undefined;
const pendingSweepCount = await countPendingSweepEntries(workspaceDir);
if (pendingSweepCount > 0) {
const plural = pendingSweepCount === 1 ? 'y' : 'ies';
reminderContent +=
`\n\n**Pending triage:** ${pendingSweepCount} auto-detected error entr${plural} ` +
`(Source: ${SWEEP_SOURCE}) in \`.learnings/ERRORS.md\` await review. ` +
'Confirm, resolve, or delete them when convenient.';
}
const cleanedBootstrapFiles = context.bootstrapFiles.filter(
(file, index, files) =>
!isInjectedReminderFile(file) ||
files.findIndex((candidate) => isInjectedReminderFile(candidate)) === index,
);
const reminderFile = {
name: REMINDER_NAME,
path: REMINDER_PATH,
content: reminderContent,
missing: false,
virtual: true,
};
const existingIndex = cleanedBootstrapFiles.findIndex((file) => isInjectedReminderFile(file));
if (existingIndex === -1) {
cleanedBootstrapFiles.push(reminderFile);
} else {
cleanedBootstrapFiles[existingIndex] = reminderFile;
}
context.bootstrapFiles = cleanedBootstrapFiles;
}
const handler: HookHandler = async (event) => {
// Safety checks for event structure
if (!event || typeof event !== 'object') {
return;
}
if (!event.context || typeof event.context !== 'object') {
return;
}
try {
if (event.type === 'agent' && event.action === 'bootstrap') {
await handleBootstrap(event);
return;
}
if (event.type === 'command' && (event.action === 'new' || event.action === 'reset')) {
await handleSessionEndSweep(event);
}
} catch (err) {
// Never break the gateway on hook failure.
if (process.env.SELF_IMPROVEMENT_HOOK_DEBUG) {
console.error('[self-improvement] hook failed:', err);
}
}
};
export default handler;
@@ -0,0 +1,374 @@
# Entry Examples
Concrete examples of well-formatted entries with all fields.
## Learning: Correction
```markdown
## [LRN-20250115-001] correction
**Logged**: 2025-01-15T10:30:00Z
**Priority**: high
**Status**: pending
**Area**: tests
### Summary
Incorrectly assumed pytest fixtures are scoped to function by default
### Details
When writing test fixtures, I assumed all fixtures were function-scoped.
User corrected that while function scope is the default, the codebase
convention uses module-scoped fixtures for database connections to
improve test performance.
### Suggested Action
When creating fixtures that involve expensive setup (DB, network),
check existing fixtures for scope patterns before defaulting to function scope.
### Metadata
- Source: user_feedback
- Related Files: tests/conftest.py
- Tags: pytest, testing, fixtures
---
```
## Learning: Knowledge Gap (Resolved)
```markdown
## [LRN-20250115-002] knowledge_gap
**Logged**: 2025-01-15T14:22:00Z
**Priority**: medium
**Status**: resolved
**Area**: config
### Summary
Project uses pnpm not npm for package management
### Details
Attempted to run `npm install` but project uses pnpm workspaces.
Lock file is `pnpm-lock.yaml`, not `package-lock.json`.
### Suggested Action
Check for `pnpm-lock.yaml` or `pnpm-workspace.yaml` before assuming npm.
Use `pnpm install` for this project.
### Metadata
- Source: error
- Related Files: pnpm-lock.yaml, pnpm-workspace.yaml
- Tags: package-manager, pnpm, setup
### Resolution
- **Resolved**: 2025-01-15T14:30:00Z
- **Commit/PR**: N/A - knowledge update
- **Notes**: Added to TOOLS.md for future reference
---
```
## Learning: Promoted to TOOLS.md
```markdown
## [LRN-20250115-003] best_practice
**Logged**: 2025-01-15T16:00:00Z
**Priority**: high
**Status**: promoted
**Promoted**: TOOLS.md
**Area**: backend
### Summary
API responses must include correlation ID from request headers
### Details
All API responses should echo back the X-Correlation-ID header from
the request. This is required for distributed tracing. Responses
without this header break the observability pipeline.
### Suggested Action
Always include correlation ID passthrough in API handlers.
### Metadata
- Source: user_feedback
- Related Files: src/middleware/correlation.ts
- Tags: api, observability, tracing
---
```
## Learning: Promoted to AGENTS.md
```markdown
## [LRN-20250116-001] best_practice
**Logged**: 2025-01-16T09:00:00Z
**Priority**: high
**Status**: promoted
**Promoted**: AGENTS.md
**Area**: backend
### Summary
Must regenerate API client after OpenAPI spec changes
### Details
When modifying API endpoints, the TypeScript client must be regenerated.
Forgetting this causes type mismatches that only appear at runtime.
The generate script also runs validation.
### Suggested Action
Add to agent workflow: after any API changes, run `pnpm run generate:api`.
### Metadata
- Source: error
- Related Files: openapi.yaml, src/client/api.ts
- Tags: api, codegen, typescript
---
```
## Error Entry
```markdown
## [ERR-20250115-A3F] docker_build
**Logged**: 2025-01-15T09:15:00Z
**Priority**: high
**Status**: pending
**Area**: infra
### Summary
Docker build fails on M1 Mac due to platform mismatch
### Error
```
error: failed to solve: python:3.11-slim: no match for platform linux/arm64
```
### Context
- Command: `docker build -t myapp .`
- Dockerfile uses `FROM python:3.11-slim`
- Running on Apple Silicon (M1/M2)
### Suggested Fix
Add platform flag: `docker build --platform linux/amd64 -t myapp .`
Or update Dockerfile: `FROM --platform=linux/amd64 python:3.11-slim`
### Metadata
- Reproducible: yes
- Related Files: Dockerfile
---
```
## Error Entry: Recurring Issue
```markdown
## [ERR-20250120-B2C] api_timeout
**Logged**: 2025-01-20T11:30:00Z
**Priority**: critical
**Status**: pending
**Area**: backend
### Summary
Third-party API timeout during request processing
### Error
```
TimeoutError: Request to api.example.com timed out after 30000ms
```
### Context
- Command: POST /api/process
- Timeout set to 30s
- Occurs during peak hours (lunch, evening)
### Suggested Fix
Implement retry with exponential backoff. Consider circuit breaker pattern.
### Metadata
- Reproducible: yes (during peak hours)
- Related Files: src/services/api-client.ts
- See Also: ERR-20250115-X1Y, ERR-20250118-Z3W
---
```
## Feature Request
```markdown
## [FEAT-20250115-001] export_to_csv
**Logged**: 2025-01-15T16:45:00Z
**Priority**: medium
**Status**: pending
**Area**: backend
### Requested Capability
Export analysis results to CSV format
### User Context
User runs weekly reports and needs to share results with non-technical
stakeholders in Excel. Currently copies output manually.
### Complexity Estimate
simple
### Suggested Implementation
Add `--output csv` flag to the analyze command. Use standard csv module.
Could extend existing `--output json` pattern.
### Metadata
- Frequency: recurring
- Related Features: analyze command, json output
---
```
## Feature Request: Resolved
```markdown
## [FEAT-20250110-002] dark_mode
**Logged**: 2025-01-10T14:00:00Z
**Priority**: low
**Status**: resolved
**Area**: frontend
### Requested Capability
Dark mode support for the dashboard
### User Context
User works late hours and finds the bright interface straining.
Several other users have mentioned this informally.
### Complexity Estimate
medium
### Suggested Implementation
Use CSS variables for colors. Add toggle in user settings.
Consider system preference detection.
### Metadata
- Frequency: recurring
- Related Features: user settings, theme system
### Resolution
- **Resolved**: 2025-01-18T16:00:00Z
- **Commit/PR**: #142
- **Notes**: Implemented with system preference detection and manual toggle
---
```
## Learning: Promoted to Skill
```markdown
## [LRN-20250118-001] best_practice
**Logged**: 2025-01-18T11:00:00Z
**Priority**: high
**Status**: promoted_to_skill
**Skill-Path**: skills/docker-m1-fixes
**Area**: infra
### Summary
Docker build fails on Apple Silicon due to platform mismatch
### Details
When building Docker images on M1/M2 Macs, the build fails because
the base image doesn't have an ARM64 variant. This is a common issue
that affects many developers.
### Suggested Action
Add `--platform linux/amd64` to docker build command, or use
`FROM --platform=linux/amd64` in Dockerfile.
### Metadata
- Source: error
- Related Files: Dockerfile
- Tags: docker, arm64, m1, apple-silicon
- See Also: ERR-20250115-A3F, ERR-20250117-B2D
---
```
## Extracted Skill Example
When the above learning is extracted as a skill, it becomes:
**File**: `skills/docker-m1-fixes/SKILL.md`
```markdown
---
name: docker-m1-fixes
description: "Fixes Docker build failures on Apple Silicon (M1/M2). Use when docker build fails with platform mismatch errors."
---
# Docker M1 Fixes
Solutions for Docker build issues on Apple Silicon Macs.
## Quick Reference
| Error | Fix |
|-------|-----|
| `no match for platform linux/arm64` | Add `--platform linux/amd64` to build |
| Image runs but crashes | Use emulation or find ARM-compatible base |
## The Problem
Many Docker base images don't have ARM64 variants. When building on
Apple Silicon (M1/M2/M3), Docker attempts to pull ARM64 images by
default, causing platform mismatch errors.
## Solutions
### Option 1: Build Flag (Recommended)
Add platform flag to your build command:
\`\`\`bash
docker build --platform linux/amd64 -t myapp .
\`\`\`
### Option 2: Dockerfile Modification
Specify platform in the FROM instruction:
\`\`\`dockerfile
FROM --platform=linux/amd64 python:3.11-slim
\`\`\`
### Option 3: Docker Compose
Add platform to your service:
\`\`\`yaml
services:
app:
platform: linux/amd64
build: .
\`\`\`
## Trade-offs
| Approach | Pros | Cons |
|----------|------|------|
| Build flag | No file changes | Must remember flag |
| Dockerfile | Explicit, versioned | Affects all builds |
| Compose | Convenient for dev | Requires compose |
## Performance Note
Running AMD64 images on ARM64 uses Rosetta 2 emulation. This works
for development but may be slower. For production, find ARM-native
alternatives when possible.
## Source
- Learning ID: LRN-20250118-001
- Category: best_practice
- Extraction Date: 2025-01-18
```
@@ -0,0 +1,333 @@
# OpenClaw Integration
Complete setup and usage guide for integrating the self-improvement skill with OpenClaw.
## Overview
OpenClaw uses workspace-based prompt injection combined with event-driven hooks. Context is injected from workspace files at session start, and hooks can trigger on lifecycle events.
## Workspace Structure
```
~/.openclaw/
├── workspace/ # Working directory
│ ├── AGENTS.md # Multi-agent coordination patterns
│ ├── SOUL.md # Behavioral guidelines and personality
│ ├── TOOLS.md # Tool capabilities and gotchas
│ ├── MEMORY.md # Long-term memory (main session only)
│ └── memory/ # Daily memory files
│ └── YYYY-MM-DD.md
├── skills/ # Installed skills
│ └── <skill-name>/
│ └── SKILL.md
└── hooks/ # Custom hooks
└── <hook-name>/
├── HOOK.md
└── handler.ts
```
## Quick Setup
### 1. Install the Skill
```bash
openclaw skills install @pskoett/self-improving-agent
```
Or with the ClawHub CLI (`npm i -g clawhub`) — note this installs into
`./skills` under the current working directory, not the workspace:
```bash
clawhub install @pskoett/self-improving-agent
```
Or copy manually — the skill package is the `self-improving-agent/`
subfolder of the repo, not the repo root:
```bash
git clone https://github.com/pskoett/self-improving-agent.git /tmp/self-improving-agent-repo
cp -r /tmp/self-improving-agent-repo/self-improving-agent ~/.openclaw/skills/self-improving-agent
```
### 2. Install the Hook (Optional)
Copy the hook to OpenClaw's hooks directory:
```bash
cp -r ~/.openclaw/skills/self-improving-agent/hooks/openclaw ~/.openclaw/hooks/self-improvement
```
Enable the hook:
```bash
openclaw hooks enable self-improvement
```
The hook does two things:
- **`agent:bootstrap`** — injects the self-improvement reminder into session
context (and flags auto-detected errors awaiting triage)
- **`command:new` / `command:reset`** — sweeps the transcript of the session
that just ended for error patterns and appends pending entries to
`<workspace>/.learnings/ERRORS.md` (only if `.learnings/` exists — see
[Error Detection](#error-detection))
### 3. Create Learning Files
Create the `.learnings/` directory in your workspace:
```bash
mkdir -p ~/.openclaw/workspace/.learnings
```
Or in the skill directory:
```bash
mkdir -p ~/.openclaw/skills/self-improving-agent/.learnings
```
## Injected Prompt Files
### AGENTS.md
Purpose: Multi-agent workflows and delegation patterns.
```markdown
# Agent Coordination
## Delegation Rules
- Use explore agent for open-ended codebase questions
- Spawn sub-agents for long-running tasks
- Use sessions_send for cross-session communication
## Session Handoff
When delegating to another session:
1. Provide full context in the handoff message
2. Include relevant file paths
3. Specify expected output format
```
### SOUL.md
Purpose: Behavioral guidelines and communication style.
```markdown
# Behavioral Guidelines
## Communication Style
- Be direct and concise
- Avoid unnecessary caveats and disclaimers
- Use technical language appropriate to context
## Error Handling
- Admit mistakes promptly
- Provide corrected information immediately
- Log significant errors to learnings
```
### TOOLS.md
Purpose: Tool capabilities, integration gotchas, local configuration.
```markdown
# Tool Knowledge
## Self-Improvement Skill
Log learnings to `.learnings/` for continuous improvement.
## Local Tools
- Document tool-specific gotchas here
- Note authentication requirements
- Track integration quirks
```
## Learning Workflow
### Capturing Learnings
1. **In-session**: Log to `.learnings/` as usual
2. **Cross-session**: Promote to workspace files
### Promotion Decision Tree
```
Is the learning project-specific?
├── Yes → Keep in .learnings/
└── No → Is it behavioral/style-related?
├── Yes → Promote to SOUL.md
└── No → Is it tool-related?
├── Yes → Promote to TOOLS.md
└── No → Promote to AGENTS.md (workflow)
```
### Promotion Format Examples
**From learning:**
> Git push to GitHub fails without auth configured - triggers desktop prompt
**To TOOLS.md:**
```markdown
## Git
- Don't push without confirming auth is configured
- Use `gh auth status` to check GitHub CLI auth
```
## Inter-Agent Communication
OpenClaw provides tools for cross-session communication:
Use these only when cross-session sharing is explicitly needed and the environment is trusted. Prefer short sanitized summaries over raw transcripts, command output, or secret-bearing content.
### sessions_list
View active and recent sessions:
```
sessions_list(activeMinutes=30, messageLimit=3)
```
### sessions_history
Read transcript from another session:
```
sessions_history(sessionKey="session-id", limit=50)
```
Only read another session's transcript when the user explicitly wants shared context or continuation across sessions.
### sessions_send
Send message to another session:
```
sessions_send(sessionKey="session-id", message="Learning: API requires X-Custom-Header")
```
Prefer sending a concise learning summary plus relevant paths rather than forwarding raw transcript content.
### sessions_spawn
Spawn a background sub-agent:
```
sessions_spawn(task="Research X and report back", label="research")
```
## Available Hook Events
| Event | When It Fires |
|-------|---------------|
| `agent:bootstrap` | Before workspace files inject |
| `command:new` | When `/new` command issued |
| `command:reset` | When `/reset` command issued |
| `command:stop` | When `/stop` command issued |
| `gateway:startup` | When gateway starts |
| `gateway:shutdown` | When gateway shuts down |
| `message:received` / `message:sent` | Around message delivery |
| `session:compact:before` / `:after` | Around session compaction |
**Important:** OpenClaw has **no per-tool-call event** — nothing fires after
each individual tool call, so real-time per-command error detection is not
possible. Error detection is done at session end instead (see below).
## Error Detection
The skill's hook (`hooks/openclaw/`) implements a **session-end error
sweep**:
1. When `/new` or `/reset` ends a session, the hook resolves the ended
session's transcript from `context.previousSessionEntry` (falling back to
`<workspace>/sessions/<sessionId>.jsonl`) — the same source OpenClaw's
bundled `session-memory` hook uses.
2. The transcript is scanned against a fixed error-pattern list (`Error:`,
`command not found`, `Traceback`, `npm ERR!`, `Permission denied`, …).
3. Matches are appended to `<workspace>/.learnings/ERRORS.md` as a `pending`
entry with `Source: openclaw-error-sweep`, containing at most 5 short
excerpts (truncated to 200 chars, common secret shapes redacted,
duplicates skipped). Each entry is stamped with deterministic
`Pattern-Key` values derived from the matched pattern (for example
`ModuleNotFoundError``deps.module-not-found`), so recurrences can be
counted by key during triage — see the Pattern-Key Taxonomy in `SKILL.md`.
4. At the next `agent:bootstrap`, the injected reminder includes a
**pending triage** note so the agent reviews the auto-detected entries —
confirming real errors, filling in fixes, or deleting noise.
### Enabling / Disabling the Sweep
The sweep is opt-in and gated on the `.learnings/` directory:
```bash
# Enable
mkdir -p ~/.openclaw/workspace/.learnings
# Disable (reminder injection keeps working)
rm -r ~/.openclaw/workspace/.learnings
```
### Sweep Limitations
- Sessions that are never ended with `/new` or `/reset` are not swept.
- Detection happens at session end, not immediately after the failing
command — there is no per-tool-call event to hook.
- Pattern matching is heuristic; it can flag false positives such as prose
containing the word "failed" — that's what the triage step is for.
- Excerpts are redacted with best-effort rules; treat `.learnings/` as
potentially sensitive and keep it out of version control by default.
## Detection Triggers
### Standard Triggers
- User corrections ("No, that's wrong...")
- Command failures (non-zero exit codes)
- API errors
- Knowledge gaps
### OpenClaw-Specific Triggers
| Trigger | Action |
|---------|--------|
| Tool call error | Log to TOOLS.md with tool name |
| Session handoff confusion | Log to AGENTS.md with delegation pattern |
| Model behavior surprise | Log to SOUL.md with expected vs actual |
| Skill issue | Log to .learnings/ or report upstream |
## Verification
Check hook is registered:
```bash
openclaw hooks list
```
Check skill is loaded:
```bash
openclaw status
```
## Troubleshooting
### Hook not firing
1. Ensure hooks enabled in config
2. Restart gateway after config changes
3. Check gateway logs for errors
### Learnings not persisting
1. Verify `.learnings/` directory exists
2. Check file permissions
3. Ensure workspace path is configured correctly
### Error sweep not writing entries
1. Verify `<workspace>/.learnings/` exists (the sweep is opt-in and skips
silently without it)
2. End the session with `/new` or `/reset` — the sweep only runs on those
commands
3. Confirm a transcript exists in `<workspace>/sessions/`
4. Run the gateway with `SELF_IMPROVEMENT_HOOK_DEBUG=1` to surface hook errors
5. Remember excerpts already present in `ERRORS.md` are skipped (dedupe)
### Skill not loading
1. Check skill is in skills directory
2. Verify SKILL.md has correct frontmatter
3. Run `openclaw status` to see loaded skills
@@ -0,0 +1,55 @@
# Uninstall Guide
How to disable or fully remove the self-improvement skill. **Disabling** turns
off automatic behavior but keeps your data; **removing** deletes the skill's
files. They are different operations — do the one you mean.
> **Your learnings are data, not skill machinery.** `.learnings/` contains the
> errors, corrections, and insights the skill captured for you. Review or
> archive it before deleting anything. Content the skill *promoted* into
> `SOUL.md`, `TOOLS.md`, or `AGENTS.md` is part of those files now — removing
> the skill does not (and should not) automatically remove it.
## Disable only
```bash
# Stop the hook (reminder injection + session-end error sweep)
openclaw hooks disable self-improvement
```
To keep the bootstrap reminder but disable only the error sweep, remove the
learnings directory instead (archive it first if it has entries):
```bash
rm -r ~/.openclaw/workspace/.learnings
```
Restart the gateway after hook changes.
## Remove completely
```bash
# 1. Disable and remove the hook
openclaw hooks disable self-improvement
rm -r ~/.openclaw/hooks/self-improvement
# 2. Remove the skill
rm -r ~/.openclaw/skills/self-improving-agent
# 3. Optional — remove captured learnings (REVIEW FIRST, this is your data)
rm -r ~/.openclaw/workspace/.learnings
```
Then restart the gateway and verify with `openclaw hooks list` and
`openclaw status`.
Manually review `SOUL.md`, `TOOLS.md`, and `AGENTS.md` in
`~/.openclaw/workspace/` for sections promoted by this skill and delete the
ones you no longer want. If the skill logged `.learnings/` directories into
project repos you worked in, review and remove those separately.
## Verification
- `openclaw hooks list` no longer shows `self-improvement`
- New sessions no longer contain `SELF_IMPROVEMENT_REMINDER.md`
- No stray `.learnings/` directories remain that you didn't choose to keep
@@ -0,0 +1,221 @@
#!/bin/bash
# Skill Extraction Helper
# Creates a new skill from a learning entry
# Usage: ./extract-skill.sh <skill-name> [--dry-run]
set -e
# Configuration
SKILLS_DIR="./skills"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
usage() {
cat << EOF
Usage: $(basename "$0") <skill-name> [options]
Create a new skill from a learning entry.
Arguments:
skill-name Name of the skill (lowercase, hyphens for spaces)
Options:
--dry-run Show what would be created without creating files
--output-dir Relative output directory under current path (default: ./skills)
-h, --help Show this help message
Examples:
$(basename "$0") docker-m1-fixes
$(basename "$0") api-timeout-patterns --dry-run
$(basename "$0") pnpm-setup --output-dir ./skills/custom
The skill will be created in: \$SKILLS_DIR/<skill-name>/
EOF
}
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
# Parse arguments
SKILL_NAME=""
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN=true
shift
;;
--output-dir)
if [ -z "${2:-}" ] || [[ "${2:-}" == -* ]]; then
log_error "--output-dir requires a relative path argument"
usage
exit 1
fi
SKILLS_DIR="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
-*)
log_error "Unknown option: $1"
usage
exit 1
;;
*)
if [ -z "$SKILL_NAME" ]; then
SKILL_NAME="$1"
else
log_error "Unexpected argument: $1"
usage
exit 1
fi
shift
;;
esac
done
# Validate skill name
if [ -z "$SKILL_NAME" ]; then
log_error "Skill name is required"
usage
exit 1
fi
# Validate skill name format (lowercase, hyphens, no spaces)
if ! [[ "$SKILL_NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
log_error "Invalid skill name format. Use lowercase letters, numbers, and hyphens only."
log_error "Examples: 'docker-fixes', 'api-patterns', 'pnpm-setup'"
exit 1
fi
# Validate output path to avoid writes outside current workspace.
if [[ "$SKILLS_DIR" = /* ]]; then
log_error "Output directory must be a relative path under the current directory."
exit 1
fi
if [[ "$SKILLS_DIR" =~ (^|/)\.\.(/|$) ]]; then
log_error "Output directory cannot include '..' path segments."
exit 1
fi
SKILLS_DIR="${SKILLS_DIR#./}"
SKILLS_DIR="./$SKILLS_DIR"
SKILL_PATH="$SKILLS_DIR/$SKILL_NAME"
# Check if skill already exists
if [ -d "$SKILL_PATH" ] && [ "$DRY_RUN" = false ]; then
log_error "Skill already exists: $SKILL_PATH"
log_error "Use a different name or remove the existing skill first."
exit 1
fi
# Dry run output
if [ "$DRY_RUN" = true ]; then
log_info "Dry run - would create:"
echo " $SKILL_PATH/"
echo " $SKILL_PATH/SKILL.md"
echo ""
echo "Template content would be:"
echo "---"
cat << TEMPLATE
name: $SKILL_NAME
description: "[TODO: Add a concise description of what this skill does and when to use it]"
---
# $(echo "$SKILL_NAME" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
[TODO: Brief introduction explaining the skill's purpose]
## Quick Reference
| Situation | Action |
|-----------|--------|
| [Trigger condition] | [What to do] |
## Usage
[TODO: Detailed usage instructions]
## Examples
[TODO: Add concrete examples]
## Source Learning
This skill was extracted from a learning entry.
- Learning ID: [TODO: Add original learning ID]
- Original File: .learnings/LEARNINGS.md
TEMPLATE
echo "---"
exit 0
fi
# Create skill directory structure
log_info "Creating skill: $SKILL_NAME"
mkdir -p "$SKILL_PATH"
# Create SKILL.md from template
cat > "$SKILL_PATH/SKILL.md" << TEMPLATE
---
name: $SKILL_NAME
description: "[TODO: Add a concise description of what this skill does and when to use it]"
---
# $(echo "$SKILL_NAME" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
[TODO: Brief introduction explaining the skill's purpose]
## Quick Reference
| Situation | Action |
|-----------|--------|
| [Trigger condition] | [What to do] |
## Usage
[TODO: Detailed usage instructions]
## Examples
[TODO: Add concrete examples]
## Source Learning
This skill was extracted from a learning entry.
- Learning ID: [TODO: Add original learning ID]
- Original File: .learnings/LEARNINGS.md
TEMPLATE
log_info "Created: $SKILL_PATH/SKILL.md"
# Suggest next steps
echo ""
log_info "Skill scaffold created successfully!"
echo ""
echo "Next steps:"
echo " 1. Edit $SKILL_PATH/SKILL.md"
echo " 2. Fill in the TODO sections with content from your learning"
echo " 3. Add references/ folder if you have detailed documentation"
echo " 4. Add scripts/ folder if you have executable code"
echo " 5. Update the original learning entry with:"
echo " **Status**: promoted_to_skill"
echo " **Skill-Path**: skills/$SKILL_NAME"
@@ -0,0 +1,60 @@
## Description:
Captures learnings, errors, and corrections to enable continuous improvement. Use when: (1) A command or operation fails unexpectedly, (2) User corrects Claude ('No, that's wrong...', 'Actually...'), (3) User requests a capability that doesn't exist, (4) An external API or tool fails, (5) Claude realizes its knowledge is outdated or incorrect, (6) A better approach is discovered for a recurring task. Also review learnings before major tasks.
This skill is ready for commercial/non-commercial use.
## Publisher:
[pskoett](https://clawhub.ai/user/pskoett)
### License/Terms of Use:
MIT-0
## Use Case:
Developers and agent users use this skill to capture corrections, command failures, feature requests, outdated knowledge, and reusable best practices in workspace learning files. OpenClaw users can also opt into a hook that reminds agents to review learnings and sweeps ended sessions for possible errors.
### Deployment Geography for Use:
Global
## Known Risks and Mitigations:
Risk: The optional hook may read ended session transcripts and persist excerpts in .learnings, with only best-effort redaction.
Mitigation: Enable the hook only in trusted workspaces, keep .learnings out of version control, and review captured entries before sharing or promotion.
Risk: Automatically detected errors can include false positives or incomplete context.
Mitigation: Triage pending sweep entries before keeping them, promoting them, or converting them into workspace guidance.
Risk: Uninstall or cleanup commands can delete .learnings, which is user data.
Mitigation: Review or archive .learnings before running deletion commands.
## Reference(s):
- [ClawHub skill page](https://clawhub.ai/pskoett/skills/self-improving-agent)
- [OpenClaw integration guide](references/openclaw-integration.md)
- [Uninstall guide](references/uninstall.md)
- [Examples](references/examples.md)
## Skill Output:
**Output Type(s):** [Markdown, Files, Shell commands, Configuration, Guidance]
**Output Format:** [Markdown entries with inline shell commands and configuration snippets]
**Output Parameters:** [1D]
**Other Properties Related to Output:** [Creates or appends to .learnings files; the optional hook writes truncated, redacted error excerpts for later triage.]
## Skill Version(s):
4.0.2 (source: frontmatter, changelog, server evidence)
## Ethical Considerations:
Users should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.