Build a minimal AI agent combining OpenClaw's retrieval-based memory system with SOUL.md identity mechanism. The agent is distributed as a CLI called xz, which provides both interactive TUI mode and command-line access to memory/history.
Core Philosophy:
- Identity (WHO) โ SOUL.md/USER.md - Injected at session start
- Knowledge (WHAT) โ Pre-loaded MEMORY.md + searchable via
xz memory - History โ Searchable via
xz history, paginated access - Scheduler โ TUI-embedded (2s tick), tasks execute in chat flow
- Agent invokes itself โ Skills wrap
xzCLI commands
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TUI Mode (xz) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Background Scheduler (ๆฏ 2s ๆฃๆฅ) โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Check tasks โ โ โ Task due? โ โ โ Insert wakeup msg โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Context Window (200K) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ [PRE-LOADED: Always in context] โ
โ - SOUL.md (~1-2K tokens) Identity: who I am โ
โ - USER.md (~0.5-1K tokens) Identity: who the user is โ
โ - MEMORY.md (~2-4K tokens) Knowledge: key facts โ
โ - Recent daily logs (~1-2K) Knowledge: recent events โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ [ACTIVE: Growing conversation] โ
โ - User messages โ
โ - Agent responses โ
โ - โฐ Wakeup messages (from scheduler) โ
โ - Task execution output โ
โ - Compaction at ~150K (rarely needed) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ
โ Identity Docs โ โ Knowledge Source โ โ Search Index โ
โ โ โ โ โ (SQLite) โ
โ .agents/SOUL.md โ โ .agents/MEMORY.md โ โ โ
โ .agents/USER.md โ โ memory/2026-03-03 โ โ BM25 (FTS5) โ
โ โ โ memory/2026-03-04 โ โ Vector (vec0) โ
โโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Chat History (SQLite) โ โ
โ โ sessions + messages + FTS5 โ โ
โ โ (stored in data/agent.db) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ xz CLI (Self-Invocation) โ
โ โ
โ xz memory search <query> โ
โ xz memory get <file> --lines โ
โ xz history search <query> โ
โ xz history session <id> โ
โ xz history list โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ๅฝ ~/.xz/config.toml ไธๅญๅจๆถ๏ผTUI ๅฏๅจไผ่ฟๅ
ฅ้
็ฝฎๅๅฏผ๏ผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ค Welcome to xz - AI Agent โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ ้ฆๆฌกไฝฟ็จ๏ผ่ฏท้ๆฉๆจกๅๆไพๅ๏ผ โ
โ โ
โ [1] Kimi Code (ๆจ่) โ
โ โ ๆฃๆตๅฐๆฌๅฐ OAuth ๅญ่ฏ โ
โ ๆจกๅ: kimi-for-coding (256K context) โ
โ โ
โ [2] OpenAI โ
โ ้่ฆ OPENAI_API_KEY โ
โ โ
โ [3] Anthropic (Claude) โ
โ ้่ฆ ANTHROPIC_API_KEY โ
โ โ
โ [4] ่ชๅฎไน OpenAI-compatible โ
โ ๅ
ผๅฎน OpenAI API ็็ฌฌไธๆนๆๅก โ
โ โ
โ > 1 โ
โ โ
ๅทฒ้ๆฉ Kimi Code โ
โ ้
็ฝฎๅทฒไฟๅญๅฐ ~/.xz/config.toml โ
โ โ
โ ๆ Enter ๅผๅง... โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# ~/.xz/config.toml
[model]
provider = "kimi" # kimi | openai | anthropic | custom
model = "kimi-for-coding" # ๅ
ทไฝๆจกๅID
base_url = "https://api.kimi.com/coding/v1" # API base URL
# OAuth ๆ API Key ้
็ฝฎ
[auth]
type = "oauth" # oauth | api_key
# OAuth ้
็ฝฎ๏ผๅฆ Kimi๏ผ
oauth_credentials_path = "~/.kimi/credentials/kimi-code.json"
# ๆ API Key๏ผๅฆ OpenAI๏ผ
# api_key = "sk-..."
# ้ซ็บง้
็ฝฎ๏ผๅฏ้๏ผ
[context]
max_tokens = 262144 # ๆๅคงไธไธๆ
preload_identity = true # ้ขๅ ่ฝฝ SOUL.md/USER.md
preload_memory = true # ้ขๅ ่ฝฝ MEMORY.md
[scheduler]
enabled = true # ๅฏ็จๅ
็ฝฎ่ฐๅบฆๅจ
check_interval_ms = 2000 # ๆฃๆฅ้ด้
[memory]
hybrid_search = true # ๅฏ็จๆททๅๆ็ดข
semantic_weight = 0.7 # ่ฏญไนๆ็ดขๆ้
keyword_weight = 0.3 # ๅ
ณ้ฎ่ฏๆ็ดขๆ้
# ้ฆๆฌก้
็ฝฎๆ ๅฟ
[setup]
completed = true # ๆฏๅฆๅฎๆ้ฆๆฌก้
็ฝฎ
completed_at = "2026-03-03T12:00:00+08:00"# ๆฅ็ๅฝๅ้
็ฝฎ
xz config
# ไฟฎๆน้
็ฝฎ๏ผไบคไบๅผ๏ผ
xz config set
# ๅๆขๆจกๅ
xz config provider <provider>
xz config model <model>
# ้ๆฐ่ฟ่ก้ฆๆฌก้
็ฝฎๅๅฏผ
xz config setup --reset
# ้ช่ฏ้
็ฝฎ
xz config verify// src/config/index.ts
import { existsSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
import { parse } from '@iarna/toml';
const CONFIG_DIR = join(homedir(), '.xz');
const CONFIG_FILE = join(CONFIG_DIR, 'config.toml');
export interface XZConfig {
model: {
provider: 'kimi' | 'openai' | 'anthropic' | 'custom';
model: string;
baseUrl: string;
};
auth: {
type: 'oauth' | 'api_key';
oauthCredentialsPath?: string;
apiKey?: string;
};
context: {
maxTokens: number;
preloadIdentity: boolean;
preloadMemory: boolean;
};
scheduler: {
enabled: boolean;
checkIntervalMs: number;
};
memory: {
hybridSearch: boolean;
semanticWeight: number;
keywordWeight: number;
};
setup: {
completed: boolean;
completedAt?: string;
};
}
// ๆฃๆฅๆฏๅฆ้ฆๆฌกไฝฟ็จ
export function isFirstRun(): boolean {
return !existsSync(CONFIG_FILE);
}
// ๅ ่ฝฝ้
็ฝฎ
export function loadConfig(): XZConfig {
if (isFirstRun()) {
throw new FirstRunError('Config not found. Run setup first.');
}
const content = readFileSync(CONFIG_FILE, 'utf-8');
return parse(content) as XZConfig;
}
// ไฟๅญ้
็ฝฎ
export function saveConfig(config: XZConfig): void {
if (!existsSync(CONFIG_DIR)) {
mkdirSync(CONFIG_DIR, { recursive: true });
}
const toml = stringify(config);
writeFileSync(CONFIG_FILE, toml);
}// src/tui/setup-wizard.ts
import { isFirstRun, saveConfig } from '../config';
import { detectKimiCredentials } from '../config/kimi';
export async function runSetupWizard(): Promise<void> {
console.clear();
console.log('๐ค Welcome to xz - AI Agent\n');
// ๆฃๆตๅฏ็จ็ๆไพๅ
const providers = await detectAvailableProviders();
console.log('้ฆๆฌกไฝฟ็จ๏ผ่ฏท้ๆฉๆจกๅๆไพๅ๏ผ\n');
providers.forEach((p, i) => {
console.log(`[${i + 1}] ${p.name}`);
if (p.detected) {
console.log(` โ ๆฃๆตๅฐๆฌๅฐ้
็ฝฎ`);
}
console.log(` ${p.description}\n`);
});
// ็จๆท้ๆฉ
const choice = await prompt('> ');
const selected = providers[parseInt(choice) - 1];
// ๆ นๆฎ้ๆฉ้
็ฝฎ
const config = await configureProvider(selected);
// ไฟๅญ้
็ฝฎ
config.setup = { completed: true, completedAt: new Date().toISOString() };
saveConfig(config);
console.log('\nโ
้
็ฝฎๅทฒไฟๅญๅฐ ~/.xz/config.toml');
console.log('ๆ Enter ๅผๅง...');
await prompt('');
}
// ๆฃๆตๅฏ็จ็ๆไพๅ
async function detectAvailableProviders(): Promise<ProviderOption[]> {
const providers: ProviderOption[] = [
{
id: 'kimi',
name: 'Kimi Code (ๆจ่)',
description: 'ๆจกๅ: kimi-for-coding (256K context)',
detected: detectKimiCredentials(),
},
{
id: 'openai',
name: 'OpenAI',
description: '้่ฆ OPENAI_API_KEY',
detected: !!process.env.OPENAI_API_KEY,
},
{
id: 'anthropic',
name: 'Anthropic (Claude)',
description: '้่ฆ ANTHROPIC_API_KEY',
detected: !!process.env.ANTHROPIC_API_KEY,
},
{
id: 'custom',
name: '่ชๅฎไน OpenAI-compatible',
description: 'ๅ
ผๅฎน OpenAI API ็็ฌฌไธๆนๆๅก',
detected: false,
},
];
return providers;
}# Interactive TUI mode (default)
# ้ฆๆฌก่ฟ่ก๏ผ่ฟๅ
ฅ้
็ฝฎๅๅฏผ
# ๅทฒ้
็ฝฎ๏ผ่ฟๅ
ฅ TUI
xz
# Memory commands
xz memory search <query> [--limit N] [--semantic] [--page N]
xz memory get <file> [--start-line N] [--end-line N]
xz memory list [--date YYYY-MM-DD]
# History commands
xz history search <query> [--limit N] [--session ID] [--page N]
xz history session <session-id> [--limit N] [--offset N]
xz history list [--limit N] [--page N]
# Skill management
xz skill list
xz skill create <name> [--from-template]
xz skill reload
# Scheduler (TUI mode only)
xz schedule list # List scheduled tasks
xz schedule add <task> <time> # Add task (e.g., "daily backup" "09:00")
xz schedule remove <task-id> # Remove task
# Note: Tasks only execute while TUI is running (every 2s check)// src/cli/index.ts
import { Command } from 'commander';
const program = new Command('xz');
// Memory commands
program
.command('memory')
.description('Knowledge memory operations')
.addCommand(
new Command('search')
.argument('<query>', 'Search query')
.option('-l, --limit <n>', 'Results per page', '10')
.option('-p, --page <n>', 'Page number', '1')
.option('--semantic', 'Use semantic search', false)
.option('--keyword', 'Use keyword search only', false)
.action(async (query, options) => {
const results = await memorySearch(query, {
limit: parseInt(options.limit),
offset: (parseInt(options.page) - 1) * parseInt(options.limit),
semantic: options.semantic,
keywordOnly: options.keyword
});
console.table(results);
})
)
.addCommand(
new Command('get')
.argument('<file>', 'Memory file path')
.option('-s, --start-line <n>', 'Start line', '1')
.option('-e, --end-line <n>', 'End line')
.action(async (file, options) => {
const content = await memoryGet(file, {
lineStart: parseInt(options.startLine),
lineEnd: options.endLine ? parseInt(options.endLine) : undefined
});
console.log(content);
})
);
// History commands
program
.command('history')
.description('Chat history operations')
.addCommand(
new Command('search')
.argument('<query>', 'Search query')
.option('-l, --limit <n>', 'Results per page', '10')
.option('-p, --page <n>', 'Page number', '1')
.option('--date-from <date>', 'Start date (YYYY-MM-DD)')
.option('--date-to <date>', 'End date (YYYY-MM-DD)')
.action(async (query, options) => {
const results = await historySearch(query, {
limit: parseInt(options.limit),
offset: (parseInt(options.page) - 1) * parseInt(options.limit),
dateFrom: options.dateFrom,
dateTo: options.dateTo
});
printHistoryResults(results, parseInt(options.page), parseInt(options.limit));
})
)
.addCommand(
new Command('session')
.argument('<session-id>', 'Session ID')
.option('-l, --limit <n>', 'Messages per page', '20')
.option('-o, --offset <n>', 'Offset', '0')
.action(async (sessionId, options) => {
const session = await getSession(sessionId, {
limit: parseInt(options.limit),
offset: parseInt(options.offset)
});
printSession(session, parseInt(options.offset));
})
)
.addCommand(
new Command('list')
.option('-l, --limit <n>', 'Sessions per page', '10')
.option('-p, --page <n>', 'Page number', '1')
.action(async (options) => {
const sessions = await listSessions({
limit: parseInt(options.limit),
offset: (parseInt(options.page) - 1) * parseInt(options.limit)
});
console.table(sessions);
})
);
// Default: TUI mode
if (process.argv.length === 2) {
// No args - check first run
if (isFirstRun()) {
await runSetupWizard();
}
startTUI();
} else {
program.parse();
}interface PaginatedResult<T> {
items: T[];
page: number;
perPage: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
}
interface MemorySearchResult {
file: string;
lineStart: number;
lineEnd: number;
snippet: string;
score: number;
}
interface HistorySearchResult {
sessionId: string;
sessionTitle?: string;
messageId: string;
role: 'user' | 'assistant' | 'tool';
content: string;
timestamp: string;
score: number;
}
// Paginated output format
function printPaginated<T>(
result: PaginatedResult<T>,
formatItem: (item: T) => string
): void {
result.items.forEach(formatItem);
console.log(`\nPage ${result.page}/${result.totalPages} (${result.total} total)`);
if (result.hasPrev) console.log('Use --page', result.page - 1, 'for previous');
if (result.hasNext) console.log('Use --page', result.page + 1, 'for next');
}Skills wrap xz CLI commands for the agent to use:
// ~/.claude/skills/memory-search/SKILL.md
---
name: memory-search
description: Search knowledge memory for facts, decisions, or observations
argument-hint: "<query> [--page N]"
disable-model-invocation: false
---
Search the knowledge base when you need to recall information not in the pre-loaded MEMORY.md.
**Usage from agent:**
```bash
xz memory search "<query>" --limit 5When to use:
- User asks about something from past conversations
- Need specific details not in current context
- Looking for user preferences or decisions
Handling results:
- Results include file path, line numbers, and snippet
- Use
xz memory get <file> --start-line N --end-line Nto read full content - Ask user if they want to see more results (--page 2, etc.)
name: history-search description: Search chat history for past conversations argument-hint: " [--page N] [--date-from YYYY-MM-DD]" disable-model-invocation: false
Search past chat sessions when user references earlier discussions.
Usage from agent:
xz history search "<query>" --limit 5When to use:
- User says "like we discussed yesterday"
- Need context from earlier sessions
- Looking for specific decisions or code from past chats
Handling pagination:
- Default shows 10 results per page
- If user wants more, use
--page 2, etc. - To see full session:
xz history session <session-id> --limit 20
name: get-session description: Get full message history of a specific chat session argument-hint: " [--offset N]" disable-model-invocation: true
Retrieve a complete session. Use when the user asks to see what was discussed.
Usage:
xz history session <session-id> --limit 20 --offset 0Pagination:
- Use --offset to paginate through long sessions
- Increase offset by limit for next page
### Skill Implementation (Bash Tool)
```typescript
// Agent executes skills via bash tool
const bashTool: Tool = {
name: 'bash',
description: 'Execute bash commands including xz CLI for memory/history search',
parameters: Type.Object({
command: Type.String(),
timeout: Type.Optional(Type.Number({ default: 60 }))
})
};
// Example: Agent searching memory
// command: "xz memory search \"git preferences\" --limit 5"
// Example: Agent getting specific content
// command: "xz memory get memory/2026-03-03.md --start-line 15 --end-line 25"
// Example: Agent searching history
// command: "xz history search \"database schema\" --limit 5 --date-from 2026-02-01"
| Layer | Type | Content | Access Pattern |
|---|---|---|---|
| Identity | Pre-loaded | SOUL.md, USER.md |
Full injection into system prompt |
| Knowledge | Pre-loaded | MEMORY.md, recent daily logs |
Full injection into system prompt |
| Retrieval | On-demand | All history & memory via xz CLI |
Via skills that invoke xz |
interface SystemPromptBuilder {
soul(): string; // SOUL.md - who I am
user(): string; // USER.md - who the user is
memory(): string; // MEMORY.md - key facts
recentDaily(days?: number): string; // Recent daily logs
}
// System prompt structure (~5-10K tokens typical)
const systemPrompt = `
${soul} // ~1-2K tokens
${user} // ~0.5-1K tokens
${memory} // ~2-4K tokens
${recentDaily} // ~1-2K tokens
You have access to the xz CLI for retrieval:
- xz memory search <query> [--page N]
- xz memory get <file> [--start-line N] [--end-line N]
- xz history search <query> [--page N]
- xz history session <id> [--offset N]
Use these when you need information not in the pre-loaded context.
`;-- Single database: data/agent.db
-- Knowledge index (for xz memory search)
CREATE TABLE knowledge_chunks (
id TEXT PRIMARY KEY,
file TEXT NOT NULL,
line_start INTEGER,
line_end INTEGER,
content TEXT NOT NULL,
tags TEXT, -- JSON array
created_at INTEGER,
updated_at INTEGER
);
CREATE VIRTUAL TABLE knowledge_fts USING fts5(content, content='knowledge_chunks');
CREATE VIRTUAL TABLE knowledge_vec USING vec0(id TEXT PRIMARY KEY, embedding FLOAT[1536]);
-- Chat history
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
created_at INTEGER,
updated_at INTEGER,
title TEXT,
message_count INTEGER DEFAULT 0
);
CREATE TABLE messages (
id TEXT PRIMARY KEY,
session_id TEXT,
role TEXT,
content TEXT,
tool_calls TEXT,
metadata TEXT,
created_at INTEGER
);
CREATE VIRTUAL TABLE messages_fts USING fts5(content, content='messages');
-- Scheduled tasks (TUI-embedded scheduler)
CREATE TABLE scheduled_tasks (
id TEXT PRIMARY KEY,
description TEXT NOT NULL, -- What to do when triggered
cron TEXT, -- Cron expression (optional)
execute_at INTEGER, -- Next execution timestamp (for one-time)
interval_seconds INTEGER, -- Recurring interval (optional)
is_recurring BOOLEAN DEFAULT 0,
is_enabled BOOLEAN DEFAULT 1,
last_executed_at INTEGER,
last_execution_status TEXT, -- 'success' | 'failed' | null
last_execution_output TEXT, -- Output from last run
created_at INTEGER,
updated_at INTEGER
);
-- Task execution log
CREATE TABLE task_executions (
id TEXT PRIMARY KEY,
task_id TEXT,
started_at INTEGER,
completed_at INTEGER,
status TEXT, -- 'success' | 'failed'
output TEXT, -- Task output
error TEXT, -- Error message if failed
FOREIGN KEY (task_id) REFERENCES scheduled_tasks(id)
);- No separate service: TUI mode only, user must keep window open
- Self-contained: Scheduler runs inside TUI process
- Transparent: Task execution visible in chat flow
- Context-aware: Tasks execute with full conversation context
TUI Process
โโโ UI Thread (pi-tui)
โ โโโ Render chat
โ โโโ Handle input
โ โโโ Display messages
โ
โโโ Scheduler Thread
โโโ Timer (every 2s)
โโโ Check SQLite (scheduled_tasks table)
โโโ Trigger task โ Insert "wakeup" message
// 1. Scheduler detects due task
const dueTasks = await scheduler.getDueTasks();
for (const task of dueTasks) {
// 2. Insert wakeup message into conversation
const wakeupMessage: Message = {
role: 'system',
content: `[Scheduled Task: ${task.description}]`,
metadata: { type: 'wakeup', taskId: task.id }
};
// 3. Add to context (like user message)
await conversation.addMessage(wakeupMessage);
// 4. Notify UI to render
tui.addSystemMessage(`โฐ Task: ${task.description}`);
// 5. Trigger agent response (same as user input)
await agent.handleWakeup(task);
}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ค xz gpt-4o-mini โฐ Next: 09:00 ๐ 2 pending โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ ๐ง You โ
โ Check git status โ
โ โ
โ ๐ค Assistant โ
โ On branch main, nothing to commit... โ
โ โ
โ โฐ System โ
โ [Scheduled Task: Daily backup] โ
โ โ
โ ๐ค Assistant โ
โ Starting daily backup... โ
โ [bash: ./scripts/backup.sh] โ
โ Backup completed: backup-2026-03-03.tar.gz โ
โ โ
โ ๐ง You โ
โ Thanks โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ > _ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
interface ScheduledTask {
id: string;
description: string; // Natural language instruction
cron?: string; // "0 9 * * *" for 9am daily
executeAt?: number; // Unix timestamp for one-time
intervalSeconds?: number; // 3600 for hourly
isRecurring: boolean;
isEnabled: boolean;
lastExecutedAt?: number;
lastExecutionStatus?: 'success' | 'failed';
}
class Scheduler {
private timer: NodeJS.Timer;
private checkIntervalMs = 2000; // 2 seconds
// Start checking (called when TUI starts)
start(): void {
this.timer = setInterval(() => this.tick(), this.checkIntervalMs);
}
// Stop checking (called when TUI exits)
stop(): void {
clearInterval(this.timer);
}
// Check for due tasks
private async tick(): Promise<void> {
const now = Date.now();
const dueTasks = await this.db.prepare(`
SELECT * FROM scheduled_tasks
WHERE is_enabled = 1
AND (execute_at <= ? OR
(is_recurring = 1 AND
(last_executed_at IS NULL OR
last_executed_at + interval_seconds * 1000 <= ?)))
`).all(now, now);
for (const task of dueTasks) {
await this.triggerTask(task);
}
}
// Trigger task execution
private async triggerTask(task: ScheduledTask): Promise<void> {
// Update last_executed
await this.db.prepare(
'UPDATE scheduled_tasks SET last_executed_at = ? WHERE id = ?'
).run(Date.now(), task.id);
// For one-time tasks, disable after execution
if (!task.isRecurring) {
await this.db.prepare(
'UPDATE scheduled_tasks SET is_enabled = 0 WHERE id = ?'
).run(task.id);
}
// Notify agent via wakeup message
this.onTaskDue?.(task);
}
// CRUD operations
async addTask(task: Omit<ScheduledTask, 'id'>): Promise<string>;
async removeTask(id: string): Promise<void>;
async listTasks(): Promise<ScheduledTask[]>;
async getNextTask(): Promise<ScheduledTask | null>;
// Callback when task is due
onTaskDue?: (task: ScheduledTask) => void;
}const scheduleTaskTool: Tool = {
name: 'schedule_task',
description: 'Schedule a task for future execution. Tasks only run while TUI is open.',
parameters: Type.Object({
description: Type.String({ description: 'What to do when triggered' }),
when: Type.String({
description: 'When to execute. Supports: "HH:MM" (daily), "in X minutes/hours", ISO timestamp'
}),
recurring: Type.Optional(Type.String({
enum: ['daily', 'hourly', 'none'],
default: 'none'
}))
})
};
// Examples:
// schedule_task("Check emails", "09:00", "daily")
// schedule_task("Remind about meeting", "in 30 minutes", "none")
// schedule_task("Backup database", "2026-03-04T02:00:00+08:00", "none")Limitations (by design):
- Tasks only execute while TUI is running
- If computer sleeps, tasks may be delayed
- No persistent background service
Best practices:
- Use for "while I'm working" reminders
- Use for periodic maintenance during active sessions
- For critical timed tasks, use system cron +
xz once <task>pattern
xz/
โโโ package.json
โโโ tsconfig.json
โโโ bin/
โ โโโ xz.js # CLI entry point
โ
โโโ src/
โ โโโ cli/
โ โ โโโ index.ts # CLI parser & commands
โ โ โโโ memory.ts # xz memory commands
โ โ โโโ history.ts # xz history commands
โ โ โโโ skill.ts # xz skill commands
โ โ โโโ schedule.ts # xz schedule commands
โ โ
โ โโโ tui/
โ โ โโโ index.ts # Interactive TUI mode
โ โ โโโ app.ts
โ โ โโโ chat.ts
โ โ โโโ input.ts
โ โ
โ โโโ core/
โ โ โโโ agent.ts # Main agent orchestration
โ โ โโโ llm.ts
โ โ โโโ heartbeat.ts
โ โ โโโ scheduler.ts
โ โ
โ โโโ config/ # Configuration management
โ โ โโโ index.ts # Config load/save + isFirstRun()
โ โ โโโ types.ts # XZConfig interface
โ โ โโโ wizard.ts # First-run setup wizard
โ โ โโโ kimi.ts # Kimi OAuth integration
โ โ โโโ validators.ts # Config validation
โ โ
โ โโโ identity/
โ โ โโโ index.ts
โ โ โโโ loader.ts # Load SOUL.md, USER.md
โ โ โโโ builder.ts # Build system prompt
โ โ
โ โโโ knowledge/
โ โ โโโ index.ts
โ โ โโโ loader.ts # Load MEMORY.md, daily logs
โ โ โโโ chunker.ts # 400-token chunks
โ โ โโโ search.ts # Hybrid search
โ โ โโโ manager.ts # Read/append operations
โ โ
โ โโโ history/
โ โ โโโ index.ts
โ โ โโโ database.ts # SQLite connection
โ โ โโโ session.ts # Session CRUD
โ โ โโโ messages.ts # Message storage
โ โ โโโ search.ts # History search (BM25 + vector)
โ โ โโโ compaction.ts # Session compaction
โ โ
โ โโโ scheduler/ # TUI-embedded scheduler (2s tick)
โ โ โโโ index.ts
โ โ โโโ database.ts # Schedule schema in agent.db
โ โ โโโ manager.ts # Task CRUD
โ โ โโโ runner.ts # Execute due tasks
โ โ โโโ ticker.ts # 2s interval checker
โ โ
โ โโโ skills/
โ โ โโโ index.ts
โ โ โโโ loader.ts # Load skills from .agents/skills/
โ โ โโโ manager.ts # Hot reload
โ โ โโโ registry.ts # Skill registry
โ โ โโโ builtin/ # Built-in skills (xz memory, xz history)
โ โ โโโ memory-search/
โ โ โ โโโ SKILL.md
โ โ โโโ history-search/
โ โ โ โโโ SKILL.md
โ โ โโโ get-session/
โ โ โโโ SKILL.md
โ โ
โ โโโ tools/
โ โโโ index.ts
โ โโโ bash.ts # Includes xz CLI invocation
โ โโโ memory.ts # memory_append
โ โโโ skill.ts # Skill invocation
โ
โโโ .agents/ # User's agent files
โ โโโ SOUL.md
โ โโโ USER.md
โ โโโ MEMORY.md
โ โโโ memory/
โ โ โโโ 2026-03-03.md
โ โโโ skills/ # User's custom skills
โ
โโโ .claude/ # Compatibility skills
โ โโโ skills/
โ โโโ git-helpers/
โ โโโ SKILL.md
โ
โโโ data/
โโโ agent.db # SQLite: knowledge + history + schedule
# Search memory
$ xz memory search "git workflow" --limit 5
File Lines Score Snippet
memory/2026-03-03.md 15-22 0.92 User prefers short git log format...
memory/2026-03-02.md 8-15 0.85 Discussed git rebase vs merge...
Page 1/3 (12 total)
Use --page 2 for next
# Get specific content
$ xz memory get memory/2026-03-03.md --start-line 15 --end-line 22
## 09:15:00 - [preference] Git log format
User prefers `git log --oneline` for brevity.
# Search history
$ xz history search "database schema" --date-from 2026-02-01
Session Date Role Preview
2026-03-01-abc123 2026-03-01 14:23 user Let's design the database...
2026-03-01-abc123 2026-03-01 14:24 assistant I suggest SQLite with...
# View session
$ xz history session 2026-03-01-abc123 --limit 20
[Session: Database Design Discussion]
14:23 user: Let's design the database schema...
14:24 assistant: I suggest SQLite with FTS5...
...
--offset 20 for more
# List sessions
$ xz history list --page 1
ID Title Messages Date
2026-03-03-xyz789 Memory System Design 45 2026-03-03
2026-03-02-def456 Git Helpers Skill 23 2026-03-02# Start interactive session
$ xz
# In TUI:
> What are my git preferences?
๐ค Based on your pre-loaded MEMORY.md, you prefer short git logs.
> /memory-search "docker setup"
๐ค [bash: xz memory search "docker setup" --limit 5]
Found: memory/2026-02-28.md mentions Docker setup...
> Show me what we discussed yesterday
๐ค [bash: xz history search "discussion" --date-from 2026-03-02 --limit 5]
Found session 2026-03-02-def456: "Git Helpers Skill"
> /get-session 2026-03-02-def456
๐ค [bash: xz history session 2026-03-02-def456 --limit 20]
[Shows full session content]# List scheduled tasks
$ xz schedule list
ID Description Next Run Recurring
backup Daily backup 2026-03-04 02:00 daily
email-check Check emails 2026-03-03 09:00 daily
# Add task
$ xz schedule add "Daily backup" "02:00" --recurring daily
โ
Task scheduled: backup
# Remove task
$ xz schedule remove backup
โ
Task removedIn TUI - Task Execution:
# User has TUI open, working...
> Working on feature X...
โฐ System
[Scheduled Task: Check emails]
๐ค Assistant
Checking emails...
[bash: check-email.sh]
3 new emails. 1 requires attention: "Review requested for PR #42"
> Thanks, I'll review it later.
Important: Tasks only execute while TUI is running. If you close the window, tasks are delayed until next time you open xz.
- CLI framework (commander.js)
xzbinary entry point- Config system (
~/.xz/config.toml) - First-run setup wizard (model selection)
- TUI with pi-tui (default mode)
- Kimi Code OAuth ้ๆ๏ผ่ฏปๅ
~/.kimi/credentials๏ผ
- SOUL.md, USER.md loading
- System prompt builder
- Pre-load into context
- Markdown memory files
- Chunking & indexing
xz memory searchcommandxz memory getcommand
- SQLite schema
- Session/message storage
xz history searchcommandxz history session/listcommands
- Built-in skills that wrap xz CLI
- Skill:
memory-search - Skill:
history-search - Skill:
get-session
- Compaction at 150K threshold
- Optional pre-flush
- SQLite schema for scheduled_tasks
- 2s interval ticker in TUI
- Wakeup message flow
schedule_tasktool
- Hot reload for skills
- Pagination UX
- Task execution logging
ๅฆๆไฝ ๅทฒ็ป่ฎข้
ไบ Kimi Code๏ผๆฌๅฐๅทฒๆ kimi CLI ้
็ฝฎ๏ผๅฏไปฅ็ดๆฅๅค็จ่ฎค่ฏไฟกๆฏใ
Kimi CLI ๅทฒๅญๅจ OAuth token ๅจ ~/.kimi/credentials/ใ
// src/config/kimi.ts
import { readFileSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
export function getKimiCredentials() {
const credPath = join(homedir(), '.kimi', 'credentials', 'kimi-code.json');
const cred = JSON.parse(readFileSync(credPath, 'utf-8'));
// ๆฃๆฅ token ๆฏๅฆ่ฟๆ
if (cred.expires_at * 1000 < Date.now()) {
// ้่ฆไฝฟ็จ refresh_token ๅทๆฐ๏ผๆๆ็คบ็จๆท่ฟ่ก `kimi /login`
throw new Error('Token expired. Run: kimi /login');
}
return {
baseUrl: 'https://api.kimi.com/coding/v1',
accessToken: cred.access_token,
refreshToken: cred.refresh_token,
expiresAt: cred.expires_at
};
}
// ่ทๅ access token ็จไบ API ่ฐ็จ
export async function getKimiAccessToken(): Promise<string> {
const cred = getKimiCredentials();
return cred.accessToken;
}ไป Kimi Code ๆงๅถๅฐ่ทๅ API Key๏ผ็ถๅ้ ็ฝฎ็ฏๅขๅ้๏ผ
# ~/.zshrc ๆ ~/.bashrc
export ANTHROPIC_BASE_URL=https://api.kimi.com/coding/
export ANTHROPIC_AUTH_TOKEN=<ไฝ ็ Kimi Code API Key>
export ANTHROPIC_MODEL=kimi-for-coding
export ANTHROPIC_SMALL_FAST_MODEL=kimi-for-codingๆไฝฟ็จ kimi-for-coding ๆจกๅ๏ผ
# xz ๅฏๅจๆถๅ ่ฝฝ
export XZ_PROVIDER=kimi
export XZ_MODEL=kimi-for-coding
export XZ_BASE_URL=https://api.kimi.com/coding/v1
export XZ_API_KEY=$(cat ~/.kimi/credentials/kimi-code.json | jq -r .access_token)// src/llm.ts - ้
็ฝฎ pi-ai ไฝฟ็จ Kimi
import { getModel } from '@mariozechner/pi-ai';
import { getKimiAccessToken } from './config/kimi';
export async function createKimiModel() {
const accessToken = await getKimiAccessToken();
// Kimi ไฝฟ็จ OpenAI-compatible API
const model = {
id: 'kimi-for-coding',
name: 'Kimi For Coding',
api: 'openai-completions',
provider: 'kimi',
baseUrl: 'https://api.kimi.com/coding/v1',
reasoning: true,
input: ['text', 'image'],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, // ๅ
ๅซๅจ่ฎข้
ไธญ
contextWindow: 262144, // 256K context
maxTokens: 65536,
headers: {
'Authorization': `Bearer ${accessToken}`
}
};
return model;
}OAuth token ไผ่ฟๆ๏ผ้่ฆ่ชๅจๅทๆฐ๏ผ
// src/config/kimi.ts
export async function refreshKimiToken(refreshToken: string): Promise<Credentials> {
const response = await fetch('https://api.kimi.com/auth/token/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken })
});
const newCred = await response.json();
// ไฟๅญๆฐๅญ่ฏ
saveCredentials(newCred);
return newCred;
}# ๆฃๆฅ Kimi CLI ๆฏๅฆๅทฒ็ปๅฝ
$ xz auth check
โ
Kimi Code OAuth found (~/.kimi/credentials/kimi-code.json)
Expires: 2025-03-04 12:00:00 (23 hours remaining)
# ๆๆๅจๆฃๆฅ
$ ls ~/.kimi/credentials/kimi-code.json
$ cat ~/.kimi/credentials/kimi-code.json | jq '.expires_at'# ๆนๅผ 1: ไฝฟ็จๆฌๅฐ OAuth
xz --provider kimi --use-oauth
# ๆนๅผ 2: ไฝฟ็จ็ฏๅขๅ้
export XZ_PROVIDER=kimi
export XZ_MODEL=kimi-for-coding
xz
# ๆนๅผ 3: ๆพๅผ API Key
xz --provider kimi --api-key <your-key>- ๆจกๅ:
kimi-for-coding(K2.5) - ไธไธๆ: 256K tokens
- ้ขๅบฆ: ๅจๆๆงๅทๆฐ๏ผไธ Kimi Code ่ฎข้ ๅฅ้ค็ธๅ ณ
- ๅนถๅ: ๆ้ซ 30 ๅนถๅ
- ้ๅบฆ: ๆ้ซ 100 tokens/s
{
"name": "xz",
"bin": {
"xz": "./bin/xz.js"
},
"dependencies": {
"@mariozechner/pi-ai": "^0.55.0",
"@mariozechner/pi-tui": "^0.55.0",
"@sinclair/typebox": "^0.34.0",
"commander": "^12.0.0",
"yaml": "^2.4.0",
"better-sqlite3": "^9.0.0"
},
"optionalDependencies": {
"sqlite-vec": "^0.1.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}