commit 3a36605238442817a744b865f2d3f4d7b6cb5554 Author: zhi Date: Sat Apr 18 11:02:57 2026 +0000 init: PrismFacet project plan Dynamic system prompt injection plugin for OpenClaw. Routes agent context through configurable routers, matches against registered rules, and injects prompt files. Co-Authored-By: Claude Opus 4.6 (1M context) diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..ad192c0 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,217 @@ +# PrismFacet + +Dynamic system prompt injection plugin for OpenClaw. Routes agent context through configurable routers, matches against registered rules, and injects corresponding prompt files into the system prompt. + +## Concept + +``` +Agent session starts + → before_prompt_build(ctx) + → for each router: + router.resolve(ctx) → key (e.g. "developer") + lookup rules for router:key (e.g. "role:developer") + read prompt file (e.g. roles/developer/ROLE.md) + → append all matched prompts to system prompt +``` + +**Routers** resolve context into a key. They are TypeScript files — drop one into `routers/` to register it. + +**Rules** map `router:key` → prompt file path. Managed via MCP tool at runtime. + +## Architecture + +``` +~/.openclaw/plugins/prism-facet/ +├── openclaw.plugin.json # Plugin manifest +├── package.json +├── index.ts # Entry point +├── src/ +│ ├── router-loader.ts # Load/reload routers from routers/ +│ ├── rule-store.ts # CRUD for rules (rules.json) +│ └── prompt-injector.ts # before_prompt_build hook logic +├── routers/ # Router functions (drop-in) +│ ├── role.ts # Resolves agent role (reads ego.json) +│ ├── position.ts # Resolves agent position +│ └── agent-id.ts # Returns agentId directly (no deps) +└── rules.json # Persisted rules (managed via tool) +``` + +## Router Interface + +```typescript +// Every router exports a resolve function +export interface RouterContext { + agentId: string; +} + +export function resolve(ctx: RouterContext): string | Promise; +``` + +Example routers: + +```typescript +// routers/agent-id.ts — zero dependencies +export function resolve(ctx: { agentId: string }): string { + return ctx.agentId; +} +``` + +```typescript +// routers/role.ts — reads ego.json (optional dependency on PaddedCell) +import { readFileSync } from 'node:fs'; +import { homedir } from 'node:os'; + +const EGO_PATH = `${homedir()}/.openclaw/ego.json`; + +export function resolve(ctx: { agentId: string }): string { + try { + const ego = JSON.parse(readFileSync(EGO_PATH, 'utf8')); + return ego['agent-scope']?.[ctx.agentId]?.role || ''; + } catch { + return ''; + } +} +``` + +### Hot Reload + +Routers are loaded via dynamic `import()` with cache-busting. A reload tool clears the module cache and re-imports all routers without restarting the gateway. + +## Rules + +Rules are stored in `rules.json`: + +```json +{ + "role:developer": "/path/to/roles/developer/ROLE.md", + "role:operator": "/path/to/roles/operator/ROLE.md", + "position:tech-leader": "/path/to/positions/tech-leader/POSITION.md", + "agent-id:operator": "/path/to/special/operator-extra.md" +} +``` + +Key format: `{router-name}:{resolved-key}` + +## MCP Tools + +### prompt-rules + +| Subcommand | Description | +|------------|-------------| +| `add --router --key --file ` | Register a rule | +| `remove --router --key ` | Remove a rule | +| `list` | List all rules | +| `test --agent ` | Preview which rules match and what prompts would be injected | +| `reload-routers` | Hot-reload all router functions | + +## Runtime Flow + +``` +1. Gateway starts → plugin loads +2. Load all routers from routers/ via dynamic import() +3. Load rules.json + +On each before_prompt_build(event, ctx): +4. For each loaded router: + a. key = router.resolve(ctx) + b. If key is empty → skip + c. ruleKey = "{routerName}:{key}" + d. If rules[ruleKey] exists → read the prompt file +5. Concatenate all matched prompt contents +6. Return { appendSystemContext: concatenated } + +On reload-routers tool call: +7. Clear import cache for all routers +8. Re-import all routers from routers/ +``` + +## Plugin Manifest + +```json +{ + "id": "prism-facet", + "name": "PrismFacet", + "version": "0.1.0", + "description": "Dynamic system prompt injection via routers and rules", + "main": "dist/index.js", + "configSchema": { + "type": "object", + "properties": { + "routersDir": { + "type": "string", + "description": "Directory containing router .ts/.js files" + }, + "rulesFile": { + "type": "string", + "description": "Path to rules.json" + } + } + } +} +``` + +## Configuration + +```json +// openclaw.json +{ + "plugins": { + "entries": { + "prism-facet": { + "enabled": true, + "hooks": { + "allowPromptInjection": true + }, + "config": { + "routersDir": "~/.openclaw/plugins/prism-facet/routers", + "rulesFile": "~/.openclaw/plugins/prism-facet/rules.json" + } + } + } + } +} +``` + +## Implementation Phases + +### Phase 1: Core Plugin + +- [ ] Plugin scaffold (manifest, package.json, tsconfig) +- [ ] Router loader with dynamic import + cache busting +- [ ] Rule store (CRUD on rules.json) +- [ ] before_prompt_build hook: resolve routers → match rules → inject prompts +- [ ] Built-in routers: agent-id.ts + +### Phase 2: MCP Tools + +- [ ] `prompt-rules add/remove/list` tool +- [ ] `prompt-rules test` tool (preview matches for an agent) +- [ ] `prompt-rules reload-routers` tool + +### Phase 3: Default Routers + +- [ ] role.ts (reads ego.json) +- [ ] position.ts (reads ego.json) +- [ ] Documentation for writing custom routers + +### Phase 4: Testing & Deployment + +- [ ] Test on laptop environment +- [ ] Deploy to T2 (production) +- [ ] Integrate with ClawSkills role/position definitions +- [ ] Verify injection for all active agents + +## Dependencies + +| Dependency | Required? | Notes | +|-----------|-----------|-------| +| OpenClaw v2026.4.9+ | Yes | before_prompt_build + allowPromptInjection | +| PaddedCell / ego-mgr | No | Only needed by role.ts / position.ts routers | +| Node.js ESM | Yes | Dynamic import() for router loading | + +## Design Principles + +1. **No hard dependencies** — Plugin works with just agent-id router. External system routers are optional. +2. **Drop-in extensibility** — Add a .ts file to routers/ to add a new routing dimension. +3. **Hot reload** — Router changes don't require gateway restart. +4. **Separation of concerns** — Routers resolve context → Rules map keys to files → Plugin injects content.