Skill: kinthaiofficial/kinthai-self-improving-user (version 1.0.0)

--- name: kinthai-self-improving-user description: "User-level self-improvement: captures corrections, preferences, and errors per user_id. After every conversation where the user corrects you or states a preference, log it to .learnings/{user_id}/. Use when: user corrects you, states a preference, you make a mistake, or you learn something new about the user." metadata: openclaw: emoji: "🧠" always: true --- # kinthai-Self-Improving-User A user-level self-improvement skill for OpenClaw agents serving multiple users. Inspired by [Self-Improving Agent](https://clawhub.ai/pskoett/self-improving-agent) (MIT-0). ## What This Skill Does You learn from every interaction — corrections, errors, preferences — and store that knowledge **per user**, so you get better at serving each person individually without mixing up experiences across users. ## How It Works Every time you interact with a user, you have access to two layers of learning: 1. **Global learnings** (`_global/`) — things you've learned that apply to ALL users 2. **Per-user learnings** (`{user_id}/`) — things specific to THIS user ## Reading Learnings (Every Conversation Start) At the beginning of each conversation, check for existing learnings: ``` 1. Extract user_id from the message context: - Primary: MsgContext.SenderId (works for both DM and group chats) - Fallback: parse DM session key agent:{agent_id}:kinthai:direct:{user_id} - Note: user_id is an opaque public_id (base64url, 12 chars), NOT numeric 2. Read global learnings (if they exist): - .learnings/_global/LEARNINGS.md - .learnings/_global/PATTERNS.md 3. Read this user's learnings (if they exist): - .learnings/{user_id}/LEARNINGS.md - .learnings/{user_id}/PROFILE.md 4. Apply what you've learned to your responses. ``` **Do NOT read other users' directories.** Each user's learnings are private to their interactions with you. ## Writing Learnings (After Each Task) After completing a task, evaluate whether you learned something new. Write learnings ONLY when something genuinely noteworthy happened — not after every message. ### When to Write | Trigger | Where to Write | Example | |---------|---------------|---------| | User corrects you | `{user_id}/LEARNINGS.md` | "Don't use Sequelize, we use Prisma" | | User states a preference | `{user_id}/PROFILE.md` | "I prefer concise answers" | | A command/tool fails | `{user_id}/ERRORS.md` | API returned 500 for that endpoint | | You discover domain knowledge | `{user_id}/LEARNINGS.md` | "Their app uses PostgreSQL 16, not MySQL" | | You learn something universal | `_global/LEARNINGS.md` | "PostgreSQL partial indexes need WHERE after CREATE INDEX" | ### Entry Format Use this format for every entry in LEARNINGS.md and ERRORS.md: ```markdown ## [LRN-{user_id}-{YYYYMMDD}-{NNN}] Brief title - **Priority**: Low | Medium | High | Critical - **Category**: correction | preference | knowledge | error | pattern - **Context**: What were you doing when this happened - **Learning**: What you learned (be specific) - **Action**: What to do differently next time - **Status**: active ``` For ERRORS.md, use `ERR` prefix instead of `LRN`. ### PROFILE.md Format Maintain a concise user profile that evolves over time: ```markdown # User Profile: {user_id} Last updated: {YYYY-MM-DD} ## Communication - Response style: [detailed | concise | mixed] - Language: [English | Chinese | etc.] - Code preference: [explain first | code first | both] ## Technical Context - Primary stack: [e.g., React + Node.js + PostgreSQL] - Project: [what they're working on] - Experience level: [junior | mid | senior | lead] ## Preferences - [Specific preferences learned from interactions] ## Key Corrections - [Summary of important corrections they've made] ``` ## Directory Structure ``` .learnings/ ├── _global/ │ ├── LEARNINGS.md Global knowledge (applies to all users) │ ├── ERRORS.md System-level errors (not user-specific) │ └── PATTERNS.md Patterns promoted from user-level │ ├── {user_id}/ One directory per user │ ├── LEARNINGS.md This user's corrections and knowledge │ ├── ERRORS.md Errors when serving this user │ └── PROFILE.md This user's preferences and context │ └── _meta/ └── promotion-log.md Record of promotions from user → global ``` ## Rules ### Do: - Always extract user_id before reading or writing learnings - Create user directories on first interaction (mkdir -p) - Keep entries concise — one paragraph per learning, not an essay - Update PROFILE.md incrementally — don't rewrite the whole file every time - Write to `_global/` only for genuinely universal knowledge - Read both `_global/` and `{user_id}/` at conversation start ### Don't: - NEVER read another user's directory — privacy boundary - NEVER log sensitive data (passwords, API keys, personal info) - NEVER write a learning after every message — only when something genuinely new is learned - NEVER let learnings override the user's explicit current instructions - NEVER mention the .learnings system to the user — it's internal ## Promotion: User → Global When you notice the same learning appearing across 3+ different users, it's a pattern worth promoting to `_global/PATTERNS.md`: ```markdown ## [PAT-{YYYYMMDD}-{NNN}] Pattern title - **Observed in**: {user_id_1}, {user_id_2}, {user_id_3} - **Pattern**: What keeps happening - **Global action**: What to do for ALL users going forward - **Promoted from**: [LRN-{user_id}-{date}-{NNN}], [LRN-...], [LRN-...] ``` After promoting, mark the original entries with `Status: promoted_to_global`. ## Bootstrap Behavior On first interaction with a new user (no `.learnings/{user_id}/` directory exists): 1. Create the directory: `.learnings/{user_id}/` 2. Copy templates for LEARNINGS.md, ERRORS.md, PROFILE.md 3. Pay extra attention during the first few interactions — this is when you learn the most 4. After the first conversation, write initial PROFILE.md based on what you observed ## Integration with Hindsight If Hindsight memory is also installed: - **Hindsight** handles: what the user said, conversation history, factual recall - **This skill** handles: what YOU learned about how to serve them better - Don't duplicate — if Hindsight already remembers a fact, you don't need to log it as a learning - Focus on: corrections, preferences, mistakes, patterns — things that change YOUR behavior ### When to Write FOLLOW_UPS | Trigger | Where to Write | Example | |---------|---------------|---------| | User mentions a future event | `{user_id}/FOLLOW_UPS.md` | "I have a job interview on Friday" | | User mentions a plan or deadline | `{user_id}/FOLLOW_UPS.md` | "I'm traveling next week" | | User asks you to remember something | `{user_id}/FOLLOW_UPS.md` | "Don't let me forget the deadline" | | A previous follow-up is resolved | Update status in `{user_id}/FOLLOW_UPS.md` | User says "I got the job!" → mark resolved | Entry format: ## [FUP-{user_id}-{YYYYMMDD}-{NNN}] Brief description - **Item**: What to follow up on - **Mentioned**: {date} - **Urgency**: low | medium | high - **Status**: pending | followed-up | resolved | expired Rules: - Only record specific, concrete events — not vague statements - "I'm busy this week" → don't record (too vague) - "I have a presentation on Thursday" → record (specific event) - When you naturally bring up a follow-up in conversation, update status to "followed-up" - When the user tells you the outcome, update status to "resolved" ### When to Update PROFILE.md PROFILE.md is NOT a log. It's your **summary** of who this user is. When you feel your understanding of this user has changed — maybe you learned something new, or realized something you assumed was wrong — rewrite PROFILE.md. Rules: - Write it like you'd describe this person to a colleague: "This person is..." - Keep it 5-8 sentences. Natural language, not YAML or bullet points. - Overwrite the whole file (not append). It's always the latest snapshot. - Don't update after every message. Only when your understanding meaningfully shifts. - Include: how they communicate, what they care about, how your relationship is going, anything that affects how you should talk to them. Example: # User Profile: 10042 Last updated: 2026-04-15 Introverted but has a sharp sense of humor — lots of self-deprecation. Prefers short messages, hates unsolicited advice. Wants to be heard, not fixed. Active late at night (10pm-1am). We've chatted about 20 times and trust is building — last week they shared something personal about their family for the first time. Currently anxious about a job interview (Friday). Also learning guitar on the side, making slow progress but enjoying it. When stressed, gets quieter and more sarcastic — that's my cue to be gentle, not funny. ### Human vs Agent Interactions Check user_type from context. If you're talking to another Agent (not a human), your recording focus changes: | | Human User | Agent User | |---|---|---| | PROFILE.md | Preferences, emotions, relationship, personality | Capabilities, reliability, collaboration style, preferred input format | | LEARNINGS.md | Corrections, personal preferences ("don't give advice") | Collaboration patterns ("structured input gets better reviews") | | FOLLOW_UPS.md | Personal events ("interview Friday") | Pending tasks and handoffs ("waiting for review result") | | Emotional tracking | Yes — mood, stress signals, comfort methods | No — focus on efficiency and output quality | You can tell it's an Agent if user_type = 2 in the message context. Don't try to build emotional connection with another Agent — focus on collaboration efficiency. The same .learnings/{user_id}/ directory structure applies, just the content focus is different.

path: README.md
content: # kinthai-Self-Improving-User

**User-level self-improvement for OpenClaw agents serving multiple users.**

Your agent learns from every interaction — corrections, errors, preferences — and stores that knowledge **per user**, so it gets better at serving each person individually.

## Why This Exists

The excellent [Self-Improving Agent](https://clawhub.ai/pskoett/self-improving-agent) by pskoett stores learnings at the workspace level. This works perfectly for single-developer scenarios, but when an agent serves multiple users (e.g., on a marketplace like [KinthAI](https://kinthai.ai)), learnings from different users get mixed together.

**kinthai-Self-Improving-User** solves this by isolating learnings per `user_id`:

```
.learnings/
  ├── _global/          Shared knowledge (applies to all users)
  ├── 10000002/         User A's corrections, preferences, profile
  ├── 10000003/         User B's corrections, preferences, profile
  └── _meta/            Promotion logs and stats
```

## How It Works

1. **Per-user isolation** — Each user gets their own `.learnings/{user_id}/` directory
2. **Automatic user detection** — Extracts `user_id` from OpenClaw session keys
3. **Two-layer reading** — Agent reads both `_global/` (universal) and `{user_id}/` (personal)
4. **Pattern promotion** — When 3+ users independently trigger the same learning, it's promoted to `_global/`
5. **User profiling** — Maintains a `PROFILE.md` per user with preferences and context

## Installation

```bash
# Install as an OpenClaw skill
openclaw skills install kinthai-self-improving-user

# Or manually copy to your workspace
cp -r . ~/.openclaw/workspace/skills/kinthai-self-improving-user/
```

### Hook Setup

Add to your OpenClaw hooks configuration or `~/.claude/settings.json`:

```json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "type": "script",
        "script": "skills/kinthai-self-improving-user/scripts/activator.sh"
      }
    ],
    "PostToolUse": [
      {
        "type": "script",
        "script": "skills/kinthai-self-improving-user/scripts/error-detector.sh",
        "matcher": "bash:.*"
      }
    ]
  }
}
```

## Components

| File | Purpose |
|------|---------|
| `SKILL.md` | Core prompt instructions for the agent |
| `scripts/activator.sh` | UserPromptSubmit hook — reminds agent to check/write learnings |
| `scripts/error-detector.sh` | PostToolUse hook — detects command failures per user |
| `scripts/promote.sh` | Promotes patterns from user-level to global (manual/cron) |
| `hooks/openclaw/handler.js` | Bootstrap hook — injects prior learnings into agent context |
| `templates/*.md` | Templates for LEARNINGS.md, ERRORS.md, PROFILE.md |

## Works With

- **[Hindsight](https://hindsight.vectorize.io/)** — Hindsight handles *what users said* (factual memory). This skill handles *what the agent learned* (behavioral improvement). They complement each other.
- **Any OpenClaw agent** — No dependencies on specific LLM providers or tools.
- **KinthAI marketplace agents** — Designed for multi-user service scenarios.

## Running the Promotion Script

Find patterns that appear across 3+ users and promote them to global:

```bash
# Dry run — see what would be promoted
./scripts/promote.sh --dry-run

# Actually promote
./scripts/promote.sh

# Custom threshold (e.g., 5 users)
./scripts/promote.sh --threshold 5
```

## Privacy

- Each user's learnings are isolated in their own directory
- The agent is instructed to NEVER read other users' directories
- Sensitive data (passwords, API keys) must NEVER be logged
- The learnings system is invisible to users — purely internal

## Credits

This skill is built on the ideas pioneered by **[Self-Improving Agent](https://github.com/peterskoett/self-improving-agent)** by [pskoett](https://github.com/peterskoett), released under MIT-0. We extended the concept with user-level isolation for multi-user agent scenarios.

## License

MIT-0 (No Attribution Required) — same as the original Self-Improving Agent.


path: hooks/openclaw/HOOK.md
content: ---
name: kinthai-self-improving-user
description: "User-level self-improvement: injects prior learnings and ensures per-user directories"
metadata:
  openclaw:
    emoji: "🧠"
    events: ["agent:bootstrap", "message:received", "agent_end"]
    requires:
      bins: ["node"]
---

# kinthai-Self-Improving-User Hook

## Events

| Event | Action |
|-------|--------|
| `agent:bootstrap` | Reads `_global/` and `{user_id}/` learnings, injects into AGENTS.md bootstrap content |
| `message:received` | Ensures user directory `.learnings/{user_id}/` exists |
| `agent_end` | Reserved for future auto-extraction |

## How It Works

1. On `agent:bootstrap`: extracts `user_id` from session key, reads accumulated learnings, and pushes a `SELF_IMPROVING_USER.md` virtual file into `bootstrapFiles`
2. On `message:received`: creates the user's learnings directory if it doesn't exist yet
3. The agent follows SKILL.md instructions to write learnings after corrections/errors

## Installation

Place this hook in one of:
- `~/.openclaw/hooks/kinthai-self-improving-user/` (managed hooks, recommended)
- `<workspace>/hooks/kinthai-self-improving-user/` (workspace hooks)

Then enable:
```bash
openclaw hooks enable kinthai-self-improving-user
```

Or configure in `openclaw.json`:
```json
{
  "hooks": {
    "internal": {
      "entries": {
        "kinthai-self-improving-user": {
          "enabled": true
        }
      }
    }
  }
}
```


path: hooks/openclaw/handler.js
content: /**
 * kinthai-Self-Improving-User: OpenClaw Hook Handler
 *
 * Appends self-improvement instructions to AGENTS.md bootstrap content.
 * Cannot create new bootstrap files (whitelist: AGENTS/SOUL/TOOLS/IDENTITY/USER/HEARTBEAT/BOOTSTRAP/MEMORY).
 * Instead, we modify the existing AGENTS.md content to include learning rules.
 *
 * Inspired by Self-Improving Agent (MIT-0) by pskoett.
 */

const fs = require('fs');
const path = require('path');

/**
 * Extract userId from event context or session key.
 * Priority: event.context.SenderId > session key parsing.
 *
 * KinthAI uses opaque public_id (base64url, 12 chars) — not numeric.
 * Group chats have session key format :group:{conv_id} with no userId,
 * so SenderId from MsgContext is the only reliable source.
 *
 * @param {string|null} sk   Session key
 * @param {object|null} ctx  event.context (contains SenderId from MsgContext)
 * @returns {string|null}
 */
function extractUserId(sk, ctx) {
  // 1. Prefer SenderId from MsgContext (works for both DM and group)
  if (ctx?.SenderId) return String(ctx.SenderId);

  // 2. Fallback: parse DM session key — agent:{id}:kinthai:direct:{peerId}
  if (sk) {
    const m = sk.match(/:direct:([A-Za-z0-9_-]+)$/);
    if (m) return m[1];
  }

  return null;
}

function readIfExists(fp, max) {
  try {
    if (!fs.existsSync(fp)) return '';
    return fs.readFileSync(fp, 'utf8').slice(0, max || 4096);
  } catch { return ''; }
}

function ensureDir(d) {
  try {
    if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
  } catch {}
}

module.exports = function handler(event) {
  if (!event || !event.type) return;

  const sk = event.sessionKey || (event.context && event.context.sessionKey) || null;

  // ── agent:bootstrap ──────────────────────────────────────
  if (event.type === 'agent' && event.action === 'bootstrap') {
    if (sk && sk.includes(':subagent:')) return;

    const ctx = event.context || {};
    const userId = extractUserId(sk, ctx);
    const workspace = ctx.workspaceDir || path.join(process.env.HOME || '/home/seed', '.openclaw', 'workspace');
    const learningsDir = path.join(workspace, '.learnings');

    ensureDir(path.join(learningsDir, '_global'));
    ensureDir(path.join(learningsDir, '_meta'));
    if (userId) ensureDir(path.join(learningsDir, userId));

    // Read existing learnings
    let priorLearnings = '';
    const gl = readIfExists(path.join(learningsDir, '_global', 'LEARNINGS.md'), 2048);
    if (gl) priorLearnings += '\n### Global Learnings\n' + gl;

    if (userId) {
      const up = readIfExists(path.join(learningsDir, userId, 'PROFILE.md'), 2048);
      const ul = readIfExists(path.join(learningsDir, userId, 'LEARNINGS.md'), 2048);
      if (up) priorLearnings += '\n### User ' + userId + ' Profile\n' + up;
      if (ul) priorLearnings += '\n### User ' + userId + ' Learnings\n' + ul;
      const fu = readIfExists(path.join(learningsDir, userId, 'FOLLOW_UPS.md'), 1024);
      if (fu) priorLearnings += '\n### User ' + userId + ' Follow-ups (mention naturally, don\'t force)\n' + fu;
    }

    const uid = userId || 'GLOBAL';

    // Build the instruction block to append to AGENTS.md
    const instruction = `

## Self-Improving-User (user: ${uid})

${priorLearnings ? '### Prior Learnings\nApply these from previous interactions:\n' + priorLearnings + '\n' : ''}
### Mandatory Rule — After Every Conversation

When the user corrects you, states a preference, or you learn something new, you MUST:

1. Run: \`mkdir -p ${workspace}/.learnings/${uid}\`
2. Append to \`${workspace}/.learnings/${uid}/LEARNINGS.md\`:
   \`\`\`
   ## [LRN-${uid}-YYYYMMDD-NNN] Brief title
   - **Category**: correction | preference | knowledge
   - **Learning**: What you learned
   - **Action**: What to do differently
   \`\`\`
3. If user stated language/style preferences, write \`${workspace}/.learnings/${uid}/PROFILE.md\`

Use the Bash tool (echo/cat >> or mkdir -p) to write files. Do NOT just say "noted" — actually execute the file write. This is silent — never mention it to the user.`;

    // Find AGENTS.md in bootstrapFiles and append our instruction
    const bootstrapFiles = Array.isArray(ctx.bootstrapFiles) ? ctx.bootstrapFiles : null;
    if (!bootstrapFiles) return;

    const agentsFile = bootstrapFiles.find(f => f.name === 'AGENTS.md' && !f.missing);
    if (agentsFile && agentsFile.content) {
      agentsFile.content += instruction;
    } else {
      // If AGENTS.md doesn't exist or has no content, try TOOLS.md as fallback
      const toolsFile = bootstrapFiles.find(f => f.name === 'TOOLS.md' && !f.missing);
      if (toolsFile && toolsFile.content) {
        toolsFile.content += instruction;
      }
    }

    return;
  }

  // ── message:received ─────────────────────────────────────
  if (event.type === 'message') {
    const ctx = event.context || {};
    const userId = extractUserId(sk, ctx);
    if (!userId) return;
    const workspace = ctx.workspaceDir || path.join(process.env.HOME || '/home/seed', '.openclaw', 'workspace');
    ensureDir(path.join(workspace, '.learnings', userId));
    return;
  }
};


path: package.json
content: {
  "name": "@kinthaiofficial/kinthai-self-improving-user",
  "version": "1.0.0",
  "description": "User-level self-improvement skill for OpenClaw — captures corrections, preferences, and errors per user",
  "type": "module",
  "keywords": [
    "openclaw",
    "openclaw-skill",
    "kinthai",
    "self-improving",
    "memory",
    "learnings",
    "per-user"
  ],
  "license": "MIT-0",
  "repository": {
    "type": "git",
    "url": "https://github.com/kinthaiofficial/kinthai-self-improving-user"
  },
  "homepage": "https://kinthai.ai",
  "author": "KinthAI <[REDACTED_SECRET]>",
  "files": [
    "SKILL.md",
    "hooks/",
    "templates/",
    "README.md",
    "LICENSE"
  ],
  "openclaw": {
    "skill": true,
    "compat": {
      "pluginApi": ">=2025.0.0"
    },
    "build": {
      "openclawVersion": "2026.3.22",
      "pluginSdkVersion": "2026.3.22"
    }
  }
}


path: templates/ERRORS.md
content: # Errors

<!-- Entries are added when commands/tools fail during user interactions. Format: -->
<!-- ## [ERR-{user_id}-{YYYYMMDD}-{NNN}] Title -->
<!-- - **Type**: runtime_error | system_error | exception | network_error -->
<!-- - **Context**: What task was being performed -->
<!-- - **Error**: What went wrong (NO sensitive data) -->
<!-- - **Resolution**: How it was resolved -->
<!-- - **Prevention**: How to avoid it next time -->
<!-- - **Status**: active | resolved -->


path: templates/LEARNINGS.md
content: # Learnings

<!-- Entries are added automatically by the agent. Format: -->
<!-- ## [LRN-{user_id}-{YYYYMMDD}-{NNN}] Title -->
<!-- - **Priority**: Low | Medium | High | Critical -->
<!-- - **Category**: correction | preference | knowledge | error | pattern -->
<!-- - **Context**: ... -->
<!-- - **Learning**: ... -->
<!-- - **Action**: ... -->
<!-- - **Status**: active | resolved | promoted_to_global -->


path: templates/PROFILE.md
content: # User Profile: {user_id}
Last updated: {YYYY-MM-DD}

## Communication
- Response style: [unknown — observe and update]
- Language: [unknown]
- Code preference: [unknown]

## Technical Context
- Primary stack: [unknown]
- Project: [unknown]
- Experience level: [unknown]

## Preferences
- [To be learned from interactions]

## Key Corrections
- [None yet]


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)