ari-openclaw-plugin-development
OpenClaw plugin development patterns — hooks, manifest structure, plugin SDK, APEX/CODEX enforcement
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
OpenClaw plugin development patterns — hooks, manifest structure, plugin SDK, APEX/CODEX enforcement
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
Discord slash commands, approval routing, channel policy, button interaction patterns for OpenClaw/ARI Discord integration
Obsidian vault integration patterns — vault-analyzer.ts, /ari-vault-* commands, morning briefing snippet, PARA structure, read-only enforcement
NOVA's P1 PayThePryce pipeline — market signal ingest, card detection, price monitoring, script generation, thumbnail generation, video assembly, approval gate
CHASE's P2 Pryceless Solutions pipeline — lead discovery, 5-criteria audit, LLM qualification, Prompt Forge 4-pass lock, demo generation, outreach approval gate
NOVA's thumbnail generation pipeline — Ideogram V3 via Fal.ai (primary) + DALL-E 3 fallback, 4-variant strategy, Pokemon TCG copyright rules,
Coordinate ARI's five specialized agents for task execution
| name | ari-openclaw-plugin-development |
| description | OpenClaw plugin development patterns — hooks, manifest structure, plugin SDK, APEX/CODEX enforcement |
| triggers | ["openclaw plugin","ari plugin","plugin sdk","hook registration","before_prompt_build","before_tool_call"] |
Every ARI plugin follows this structure:
openclaw/plugins/ari-{name}/
├── index.ts # Plugin entry point (exports default plugin object)
├── package.json # pnpm dependencies
├── tsconfig.json # TypeScript strict config
└── src/
└── {feature}.ts # Implementation
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk';
import { registerMyFeature } from './src/my-feature.js';
const plugin = {
id: 'ari-{name}',
name: 'ARI {Name}',
description: 'What this plugin does',
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi): void {
registerMyFeature(api);
},
};
export default plugin;
api.on('before_prompt_build', (event) => {
// event.prompt is the current prompt text
const context = buildContext();
if (!context) return undefined;
return {
prependContext: ['[SECTION-TAG]', context].join('\n\n'),
};
});
api.on('before_tool_call', (event) => {
const verdict = evaluate(event);
if (!verdict.block) return undefined;
return {
block: true,
blockReason: verdict.reason,
};
});
type PluginConfigShape = {
path?: string;
enabled?: boolean;
};
function coerceConfig(raw: unknown): PluginConfigShape {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
const rec = raw as Record<string, unknown>;
return {
path: typeof rec.path === 'string' ? rec.path : undefined,
enabled: typeof rec.enabled === 'boolean' ? rec.enabled : undefined,
};
}
In ari-agents, every agent spawn validates context plane:
function validateContextBundlePlane(bundle: ContextBundle, agent: AgentRecord): void {
if (agent.plane === 'codex') {
if (bundle.soulFile) throw new Error('CODEX agents CANNOT receive SOUL files');
if (bundle.workspaceFiles?.length) throw new Error('CODEX agents CANNOT receive workspace files');
if (bundle.businessContext) throw new Error('CODEX agents CANNOT receive business context');
}
}
// Called before every agent spawn — cannot be bypassed
const DEFAULT_WORKSPACE_DIR = '~/.ari/workspace';
const DEFAULT_FILES = ['SOUL.md', 'USER.md', 'HEARTBEAT.md', 'AGENTS.md', 'RECOVERY.md'];
const MAX_FILE_CHARS = 10_000;
function readFileSnippet(filePath: string): string | null {
if (!fs.existsSync(filePath)) return null;
const raw = fs.readFileSync(filePath, 'utf-8');
if (!raw.trim()) return null;
return raw.slice(0, MAX_FILE_CHARS);
}
// Per-agent SOUL file loading:
// ~/.ari/workspace/agents/{agentName}/SOUL.md
function loadAgentSoulFile(workspaceDir: string, agentName: string): string | null {
const soulPath = path.join(workspaceDir, 'agents', agentName.toLowerCase(), 'SOUL.md');
return readFileSnippet(soulPath);
}
Plugins communicate across plugins via EventBus (never direct imports):
// Emitting from ari-market:
api.emit('market:snapshot-ready', { snapshot, timestamp });
// Consuming in ari-briefings:
api.on('market:snapshot-ready', (event) => {
briefingBuilder.addSection('market', event.snapshot);
});
ari-kernel runs BEFORE all other plugins. Its hooks execute first.
API key validation at startup:
// sk_or_* = OpenRouter ✅ | sk-ant-* = Anthropic ✅ | other = REJECT
function validateApiKeyFormat(key: string): 'openrouter' | 'anthropic' | 'invalid' {
if (key.startsWith('sk_or_')) return 'openrouter';
if (key.startsWith('sk-ant-')) return 'anthropic';
return 'invalid';
}
# From openclaw/ repo root:
pnpm install
pnpm build
pnpm test
# Test a specific plugin:
pnpm test -- packages/plugins/ari-kernel/
# Start gateway:
npx openclaw gateway start # 127.0.0.1:3141
~/.ari/workspace NOT ~/.openclaw/workspaceimport from 'ari-other-plugin'coerceConfig() pattern — never trust api.config shape.js extensions: ESM requires import { x } from './module.js'