Q_PLUGIN_SYSTEM_ARCHITECTURE_R2.md
Current Specs/Connector and Integration Specs/Q_PLUGIN_SYSTEM_ARCHITECTURE_R2.md
ELNOR REPO READER TEXT MIRROR
Original path: Current Specs/Connector and Integration Specs/Q_PLUGIN_SYSTEM_ARCHITECTURE_R2.md
Source repo: /Users/OpenClaw1/Elnor/Elnor Specs
Git branch: main
Git commit: dbaa25962edc11ab30e8d4ca1715f9ae5bf77331
Generated: 2026-06-09T01:23:58.539Z
---
# Q Dashboard Plugin System Architecture R2
**Date:** 2026-04-11
**Version:** R2 — consolidated from R1.1 + Addendum A. Full plugin framework with learning, memory, error handling, self-improvement, DOC72 entity graph integration, and DOC24 tool capability alignment.
**Status:** Architecture specification
**Scope:** Extensible plugin framework for Q Dashboard
**Owns:** Plugin manifest schema, plugin lifecycle, tab registration, [+] menu integration, plugin settings surface, plugin scaffold template, Elnor plugin builder workflow, plugin DOC72 knowledge layer, execution telemetry, error learning, self-improvement proposals
**Related:** DOC3 (skill/connector registry), DOC24 (capability registry), DOC23 (task system), DOC20 (tab system)
---
## 1. Design Philosophy
### 1.1 What a Plugin Is
A plugin is a self-contained extension package that adds capabilities to Q and ELNOR. A plugin can be as simple as a single tool or as complex as a full application with its own UI, multiple API connections, background services, and AI-driven automation.
The plugin system is designed with one critical requirement: **Elnor must be able to build new plugins from natural language instructions.** The manifest schema and scaffold template are the contract that makes this possible. If the contract is well-defined, a coding agent can produce conforming plugins from a conversation like "Build me a plugin that monitors Westlaw alerts and downloads new cases."
### 1.2 Plugin Complexity Spectrum
| Complexity | Example | Connectors | Service | Tab | Agent Instructions |
|-----------|---------|-----------|---------|-----|-------------------|
| **Minimal** | Hue lighting | 1 (local bridge) | ❌ | ❌ | ❌ |
| **Tool-only** | Screenshot/OCR | 0 (native APIs) | ❌ | ❌ | ❌ |
| **Light** | Apple Reminders | 0 (AppleScript) | ❌ | ❌ | ✅ (push deadlines) |
| **Medium** | Spotify | 1 (Spotify API) | ✅ Weekly intake | ❌ | ✅ (music preferences) |
| **Medium** | YouTube | 1 (YouTube API) | ❌ | ❌ | ✅ (transcript processing) |
| **Full** | PACER | 2 (PACER + CourtListener) | ✅ Case monitor | ✅ Dashboard | ✅ (per-case rules) |
| **Full** | Pipedrive | 1 (Pipedrive API) | ✅ CRM sync | ✅ Pipeline view | ✅ (lead processing) |
| **Complex** | Custom research tool | 3+ (multiple APIs) | ✅ Multiple services | ✅ | ✅ |
### 1.3 Key Principles
1. **Programmatic services are free.** No LLM tokens for scheduled checks, API calls, file downloads, data sync. AI enters only when the user configures agent instructions.
2. **Connectors are referenced, not embedded.** A plugin declares which connectors it needs. The connector handles auth. The plugin handles logic.
3. **Everything is optional.** Tab, service, agent instructions, connectors — a plugin only declares what it needs.
4. **Plugins are portable.** A plugin directory is self-contained. Copy it to another Q installation, configure credentials, done.
5. **Elnor can build them.** The manifest + template + contract is the spec a coding agent follows.
---
## 2. Plugin Manifest Schema (Comprehensive)
```typescript
interface PluginManifest {
// ═══════════════════════════════════════════════
// IDENTITY
// ═══════════════════════════════════════════════
plugin_id: string; // Unique ID: "pacer", "spotify", "westlaw-monitor"
display_name: string; // Human-readable: "PACER Federal Courts"
description: string; // Short description for plugin directory
long_description?: string; // Detailed description for plugin detail view
version: string; // Semver: "1.0.0"
author: string; // "ELNOR System", "Will Schall", or third-party
icon: string; // Lucide icon name or path to custom SVG
category: PluginCategory;
tags?: string[]; // Searchable tags: ["legal", "court", "monitoring"]
// ═══════════════════════════════════════════════
// DATA SOURCES — what external things does this plugin talk to?
// ═══════════════════════════════════════════════
connectors?: PluginConnectorRef[]; // External API connections (can be multiple)
local_apps?: LocalAppRef[]; // Desktop apps this plugin interacts with
web_access?: WebAccessConfig; // Does this plugin need to access the web?
filesystem_access?: FilesystemAccessConfig; // Does this plugin read/write files?
// ═══════════════════════════════════════════════
// CAPABILITIES — what can this plugin do?
// ═══════════════════════════════════════════════
tools: PluginToolDefinition[]; // Tools Elnor can call (always required, at least 1)
// ═══════════════════════════════════════════════
// SERVICES — background programmatic processes (no LLM)
// ═══════════════════════════════════════════════
services?: PluginServiceDefinition[]; // Can have multiple services
// ═══════════════════════════════════════════════
// PROCESSES — external programs this plugin runs
// ═══════════════════════════════════════════════
processes?: ExternalProcessDefinition[]; // Separate programs managed by the plugin
// ═══════════════════════════════════════════════
// AGENT INTELLIGENCE — how does Elnor use this plugin smartly?
// ═══════════════════════════════════════════════
agent_instructions?: AgentInstructionTemplate[]; // Default/configurable automation rules
doc24_triggers: string[]; // Semantic triggers for DOC24 routing (required)
elnor_awareness_prompt?: string; // Natural language description for Elnor's system context
// ═══════════════════════════════════════════════
// KNOWLEDGE — what does this plugin remember?
// ═══════════════════════════════════════════════
doc72_config?: Doc72Config; // Knowledge graph integration
// ═══════════════════════════════════════════════
// UI — what does this plugin show?
// ═══════════════════════════════════════════════
tab?: PluginTabDefinition; // Optional custom tab in workspace
config_ui?: PluginConfigUI; // Configuration panel (always has one, but can be auto-generated)
// ═══════════════════════════════════════════════
// OUTPUTS — what does this plugin produce?
// ═══════════════════════════════════════════════
outputs?: PluginOutputConfig; // Notifications, calendar events, tasks, files, etc.
// ═══════════════════════════════════════════════
// DEPENDENCIES & REQUIREMENTS
// ═══════════════════════════════════════════════
requires_internet: boolean;
requires_plugins?: string[]; // Other plugins this depends on
platform?: ("macos" | "windows" | "linux")[]; // Platform restrictions
min_q_version?: string; // Minimum Q Dashboard version
// ═══════════════════════════════════════════════
// LIFECYCLE
// ═══════════════════════════════════════════════
lifecycle?: PluginLifecycleConfig;
// ═══════════════════════════════════════════════
// INTELLIGENCE — how does the plugin learn and improve?
// ═══════════════════════════════════════════════
intelligence?: {
// Telemetry
telemetry_level: "full" | "summary" | "errors_only" | "none";
// Error handling
error_handling: {
auto_retry: boolean;
max_retries: number;
retry_delay_seconds: number;
escalate_after_failures: number; // Alert user after N consecutive failures
self_healing_enabled: boolean; // Can Elnor attempt to fix errors autonomously?
};
// Learning
learning: {
track_usage_patterns: boolean; // Build tool preference model
track_effectiveness: boolean; // Track whether results are acted on
participate_in_dream_cycle: boolean;
generate_improvement_proposals: boolean;
};
// Self-improvement boundaries
self_improvement: {
allowed_changes: (
| "config_values" // Can adjust config (e.g., timeout, retry count)
| "agent_instructions" // Can modify/add agent instructions
| "service_schedule" // Can adjust service frequency
| "tool_parameters" // Can adjust default parameters
| "code" // Can modify plugin code (requires coding agent)
)[];
requires_approval: boolean; // Must user approve changes?
max_auto_changes_per_week?: number; // Limit on autonomous changes
};
};
}
// ─────────────────────────────────────────────────
// CATEGORY
// ─────────────────────────────────────────────────
type PluginCategory =
| "legal" // PACER, Westlaw, court systems
| "productivity" // CRM, project management, task tools
| "media" // Spotify, YouTube, podcast apps
| "communication" // Slack, Teams, email
| "smart_home" // Hue, HomeKit
| "finance" // Billing, time tracking, accounting
| "development" // GitHub, CI/CD, code tools
| "research" // Academic databases, search engines
| "system" // Screenshots, clipboard, OS utilities
| "custom"; // User-built, doesn't fit a category
```
### 2.1 Connector References
A plugin can reference one or more connectors. Each connector handles its own auth independently.
```typescript
interface PluginConnectorRef {
connector_id: string; // Unique ID: "pacer_api", "courtlistener_api"
display_name: string; // "PACER API", "CourtListener RECAP"
required: boolean; // Must this connector be configured for the plugin to work?
// Auth configuration
auth: ConnectorAuth;
// API configuration
base_url?: string; // Default API base URL
endpoints?: Record<string, string>; // Named endpoints for the service
// Rate limiting
rate_limit?: {
requests_per_minute?: number;
requests_per_day?: number;
concurrent_max?: number;
};
// Cost tracking (if the API costs money)
cost_model?: ConnectorCostModel;
// Tools this connector provides
tools: string[]; // Tool IDs from the tools array that use this connector
}
interface ConnectorAuth {
type: "oauth2" | "api_key" | "credentials" | "local" | "none";
// OAuth2
oauth2_config?: {
authorization_url?: string;
token_url?: string;
scopes?: string[];
redirect_uri?: string;
pkce?: boolean;
};
// API key
api_key_config?: {
header_name?: string; // "Authorization", "X-API-Key", etc.
prefix?: string; // "Bearer ", "Token ", etc.
};
// Username/password
credentials_config?: {
fields: { name: string; label: string; type: "text" | "password" }[];
};
// Local (e.g., Hue bridge — discovered on LAN)
local_config?: {
discovery_method: "mdns" | "ip_scan" | "manual";
pairing_method?: "button_press" | "pin" | "none";
};
}
interface ConnectorCostModel {
type: "free" | "per_use" | "per_page" | "subscription" | "metered";
unit_cost?: number; // Cost per unit in cents
unit_label?: string; // "page", "request", "document"
max_per_item?: number; // Cost cap per item in cents (e.g., $3 = 300)
free_threshold?: {
amount: number; // In cents
period: "monthly" | "quarterly" | "yearly";
};
track_costs: boolean; // Should Q track cumulative costs?
}
```
### 2.2 Local App References
Plugins can interact with desktop applications:
```typescript
interface LocalAppRef {
app_id: string; // "microsoft_word", "apple_reminders", "terminal"
display_name: string; // "Microsoft Word"
// How does the plugin interact with this app?
interaction: "applescript" | "jxa" | "cli" | "shortcuts" | "url_scheme" | "ipc";
// AppleScript / JXA
applescript_bundle_id?: string; // "com.microsoft.Word"
// CLI
cli_command?: string; // "open", "osascript", custom binary path
// URL scheme
url_scheme?: string; // "maps://", "shortcuts://", "x-apple-reminder://"
// Is the app required?
required: boolean;
fallback_message?: string; // "Install Microsoft Word to use this feature"
}
```
### 2.3 Web Access Configuration
```typescript
interface WebAccessConfig {
enabled: boolean;
purpose: string; // "Scrape court docket pages", "Fetch RSS feeds"
// Allowed domains (security — plugin can only access these)
allowed_domains?: string[]; // ["pacer.uscourts.gov", "courtlistener.com"]
// Or unrestricted (requires explicit user approval)
unrestricted?: boolean;
// Methods
methods: ("fetch" | "scrape" | "browser_automation")[];
// If browser automation (Playwright, Puppeteer)
browser_config?: {
headless: boolean;
persistent_session?: boolean; // Keep cookies/session across runs
};
}
```
### 2.4 Filesystem Access Configuration
```typescript
interface FilesystemAccessConfig {
read_paths?: string[]; // Paths the plugin can read from
write_paths?: string[]; // Paths the plugin can write to
watch_paths?: string[]; // Paths the plugin watches for changes
// Path variables (resolved at runtime)
// Available: {home}, {documents}, {desktop}, {onedrive_firm}, {onedrive_personal},
// {elnor_memory}, {plugin_data}, {project_root}
// Examples:
// "{onedrive_firm}/Cases/{case_name}/PACER Docs"
// "{elnor_memory}/plugins/pacer/cache"
// "{home}/Desktop"
}
```
### 2.5 Tool Definitions
```typescript
interface PluginToolDefinition {
tool_id: string; // "pacer_search_cases"
display_name: string; // "Search PACER Cases"
description: string; // For LLM tool discovery and DOC24 routing
// Which connector provides this tool (if any)?
connector_ref?: string; // "pacer_api" — matches a connector_id
// Parameters
parameters: Record<string, ToolParameter>;
// Return type
returns: {
type: string; // "json", "file", "text", "void"
schema?: Record<string, any>; // JSON schema of return value
};
// Execution
requires_ai: boolean; // Does this tool need an LLM to execute?
execution_type: "sync" | "async"; // Sync returns immediately, async returns a job ID
timeout_seconds?: number; // Max execution time
// Cost (if the tool costs money)
cost?: {
model: "free" | "per_use" | "per_page" | "variable";
estimate?: string; // "$0.10/page, $3 max" — displayed to user
track: boolean; // Add to cost tracker
};
// Safety
destructive: boolean; // Does this tool modify/delete external data?
requires_confirmation?: boolean; // Prompt user before executing?
confirmation_message?: string; // "This will download from PACER ($0.10/page). Proceed?"
}
interface ToolParameter {
type: "string" | "number" | "boolean" | "enum" | "date" | "file_path" | "array";
description: string;
required: boolean;
default?: any;
enum_values?: string[]; // For type: "enum"
validation?: string; // Regex or validation rule
}
```
### 2.6 Service Definitions
```typescript
interface PluginServiceDefinition {
service_id: string; // "pacer_case_monitor"
display_name: string; // "PACER Case Monitor"
description: string;
// Is this a programmatic service or does it need AI?
requires_ai: false; // Services are ALWAYS programmatic.
// AI is triggered via agent_instructions, not services.
// Entry point
entry_point: string; // "services/PacerMonitorService.ts" — relative to plugin dir
// Schedule (user-configurable, these are defaults)
schedule: {
type: "cron" | "interval" | "event_driven" | "manual_only";
default_cron?: string; // "0 7,12,17 * * 1-5"
default_interval_minutes?: number;
event_trigger?: string; // "file_changed", "email_received", etc.
user_configurable: boolean; // Can user change the schedule?
min_interval_minutes?: number; // Minimum allowed interval (prevent abuse)
};
// Resource management
max_runtime_seconds?: number; // Kill if exceeds this
max_memory_mb?: number; // Memory limit
retry_on_failure: boolean;
max_retries?: number;
retry_delay_seconds?: number;
// What the service produces
output_events: ServiceOutputEvent[];
// Health monitoring
health_check?: {
endpoint?: string; // Health check URL for HTTP-based services
interval_seconds: number;
failure_threshold: number; // How many failures before marking unhealthy
};
}
interface ServiceOutputEvent {
event_type: string; // "new_document_downloaded", "sync_complete", "error"
description: string;
payload_schema?: Record<string, any>; // What data the event carries
can_trigger_agent: boolean; // Can this event trigger agent instructions?
}
```
### 2.7 External Process Definitions
Some plugins need to run entirely separate programs (like OnlyOffice Document Server):
```typescript
interface ExternalProcessDefinition {
process_id: string; // "onlyoffice_document_server"
display_name: string; // "OnlyOffice Document Server"
description: string;
// How to start
command: string; // "node", "python3", binary path
args: string[]; // ["server.js", "--port", "8443"]
cwd?: string; // Working directory (relative to plugin dir)
env?: Record<string, string>; // Environment variables
// Lifecycle
start_with_plugin: boolean; // Auto-start when plugin is enabled
start_on_demand?: boolean; // Start only when first needed
restart_on_crash: boolean;
max_restart_attempts?: number;
// Resource management
port?: number; // Port the process listens on
port_range?: [number, number]; // Try ports in this range if default is taken
health_check_url?: string; // "http://localhost:8443/health"
startup_timeout_seconds?: number; // How long to wait for process to be ready
// Bundling
bundled: boolean; // Is the binary/package included in the plugin?
install_command?: string; // "npm install" or "pip install -r requirements.txt"
size_estimate_mb?: number; // For user awareness
}
```
### 2.8 Agent Instructions
```typescript
interface AgentInstructionTemplate {
instruction_id: string; // "summarize_new_filing"
display_name: string; // "Summarize New Filings"
description: string; // Shown to user in config UI
// Trigger
trigger: {
type: "service_event" | "schedule" | "manual" | "file_change" | "tool_result";
event_type?: string; // Matches ServiceOutputEvent.event_type
schedule_cron?: string; // For schedule-type triggers
file_pattern?: string; // Glob pattern for file_change triggers
tool_id?: string; // For tool_result triggers
};
// Condition (optional — filter which events trigger the instruction)
condition?: {
expression: string; // User-editable condition string
description: string; // "Only trigger for motions, orders, and opinions"
examples: string[]; // Example conditions for user guidance
};
// What Elnor does
instruction: string; // Natural language instruction
instruction_editable: boolean; // Can the user modify the instruction?
// Execution
execution: {
method: "direct_agent" | "doc23_task";
task_template?: string; // DOC23 task template ID if method is doc23_task
model_preference?: string; // Preferred model for this instruction
think_level?: string; // Preferred think level
max_tokens?: number; // Token budget
timeout_seconds?: number;
};
// Output routing
output_routing?: {
save_to_doc72: boolean; // Store result in knowledge graph
notify_user: boolean; // Toast/notice on completion
save_to_file?: string; // File path template to save output
add_to_calendar?: boolean; // If deadlines are extracted
push_to_reminders?: boolean; // Push to Apple Reminders
create_task?: boolean; // Create DOC23 task
};
// User can configure
enabled_by_default: boolean;
scope_configurable: boolean; // Can user set per-item (e.g., per case)?
}
```
### 2.9 DOC72 Knowledge Configuration
```typescript
interface Doc72Config {
domains: string[]; // ["litigation", "legal"] — domain tags for DOC72 nodes
// Entity schemas this plugin writes
entity_schemas: {
schema_id: string; // "federal_case", "judge", "expert_witness"
node_type: string; // DOC72 canonical type: "entity", "preference", "observation"
subtype: string; // "federal_case", "music_artist", etc.
fields: Record<string, {
type: string;
description: string;
required: boolean;
}>;
dedup_key?: string; // Field used for deduplication: "case_number", "spotify_uri"
}[];
// Retention
retention?: {
auto_decay: boolean; // Decay old data (reduce confidence over time)
decay_period_days?: number; // After how many days of inactivity
max_nodes?: number; // Hard limit on nodes this plugin can create
};
}
```
### 2.10 Tab Definition
```typescript
interface PluginTabDefinition {
component_path: string; // "tabs/PacerTab.tsx" — relative to plugin dir
label: string; // "PACER Monitor" — shown in [+] submenu
icon: string; // Lucide icon name
// Tab behavior
singleton: boolean; // Only one instance allowed? (e.g., PACER dashboard = true)
allow_multiple?: boolean; // Can open multiple tabs? (e.g., different case views)
// Props the tab component receives (see PluginTabProps in §4)
receives_context: boolean; // Does the tab need current workspace context?
}
```
### 2.11 Output Configuration
```typescript
interface PluginOutputConfig {
// Notification channels
notifications?: {
toast: boolean; // Q toast notifications
notices_inbox: boolean; // Floating Palette notices
morning_briefing: boolean; // Include in daily briefing
email?: boolean; // Send via DOC16 email
apple_reminders?: boolean; // Push to Apple Reminders plugin
};
// Data outputs
files?: {
output_directory: string; // Default output path (with variables)
naming_template?: string; // "doc_{docket_number}_{title}.pdf"
organize_by?: string; // "case", "date", "type"
};
// Calendar integration
calendar?: {
create_events: boolean;
default_reminders?: number[]; // Days before: [14, 7, 1]
calendar_target?: string; // "q_calendar" | "m365" | "both"
};
// Task creation
tasks?: {
create_tasks: boolean;
task_template?: string; // DOC23 task template
default_priority?: string;
};
}
```
### 2.12 Lifecycle Configuration
```typescript
interface PluginLifecycleConfig {
on_install?: {
script?: string; // Script path to run
install_dependencies?: string; // "npm install" or "pip install -r requirements.txt"
setup_prompts?: { // Questions to ask user during install
prompt_id: string;
question: string;
type: "text" | "password" | "select" | "confirm";
options?: string[]; // For select type
}[];
};
on_enable?: {
script?: string;
start_services?: boolean; // Auto-start services
register_tools?: boolean; // Register tools with DOC3/DOC24
};
on_disable?: {
script?: string;
stop_services?: boolean;
deregister_tools?: boolean;
preserve_data?: boolean; // Keep DOC72 data and config
};
on_uninstall?: {
script?: string;
cleanup_doc72?: boolean; // Remove DOC72 nodes (with confirmation)
cleanup_files?: boolean; // Remove downloaded files
cleanup_config?: boolean; // Remove saved credentials
};
on_update?: {
migration_script?: string; // Run when plugin is updated to new version
};
}
```
---
## 3. Plugin Directory Structure
```
plugins/
├── pacer/
│ ├── manifest.json ← Plugin manifest (schema above)
│ ├── README.md ← Human-readable description
│ ├── CHANGELOG.md ← Version history
│ │
│ ├── tools/ ← Tool implementations
│ │ ├── pacer_search.ts
│ │ ├── pacer_docket.ts
│ │ ├── pacer_download.ts
│ │ ├── recap_search.ts
│ │ └── index.ts ← Exports all tools
│ │
│ ├── services/ ← Programmatic services (no LLM)
│ │ ├── PacerMonitorService.ts
│ │ └── CmEcfEmailCapture.ts
│ │
│ ├── tabs/ ← Custom tab components
│ │ ├── PacerTab.tsx
│ │ ├── CaseDetailPanel.tsx
│ │ ├── SearchPanel.tsx
│ │ └── CostDashboard.tsx
│ │
│ ├── config/ ← Configuration UI components
│ │ └── PacerConfig.tsx
│ │
│ ├── types/ ← TypeScript interfaces
│ │ └── index.ts
│ │
│ ├── data/ ← Mock data for development
│ │ └── mockCases.ts
│ │
│ └── assets/ ← Icons, images
│ └── pacer-icon.svg
```
---
## 4. Plugin Tab Props Interface
Every plugin tab component receives a standard props interface:
```typescript
interface PluginTabProps {
// Identity
pluginId: string;
pluginConfig: Record<string, any>;
// Data access
dataProvider: DataProvider; // Access to EC data (or mock data)
doc72: Doc72Client; // Read/write knowledge graph
// Actions
commands: CommandDispatcher; // Emit EC commands
openTab: (type: string, title: string, meta?: any) => void;
openDocument: (path: string) => void;
showToast: (message: string, options?: ToastOptions) => void;
showConfirmDialog: (message: string, buttons: string[]) => Promise<string>;
// Plugin service status
serviceStatus: Record<string, ServiceStatus>;
// Agent interaction
askAgent: (instruction: string, context?: any) => Promise<string>;
// Cross-plugin
callPluginTool: (pluginId: string, toolId: string, params: any) => Promise<any>;
}
interface ServiceStatus {
state: "running" | "stopped" | "error" | "starting";
last_run?: string;
next_run?: string;
last_error?: string;
stats?: Record<string, any>;
}
```
---
## 5. Elnor-Buildable Plugins
### 5.1 The Core Idea
The plugin manifest schema IS the spec that a coding agent follows to create new plugins. If Elnor (via Claude Code, Codex, or any coding agent) knows the manifest schema and the scaffold template, it can build a conforming plugin from natural language instructions.
"Elnor, build me a plugin that monitors my firm's Westlaw alerts and downloads new cases."
Elnor:
1. Creates a new plugin directory from the scaffold template
2. Defines the manifest (connectors, tools, services, agent instructions)
3. Implements the tools and service
4. Optionally builds a tab component
5. Registers everything
6. Tests the plugin
7. Installs it in Q
### 5.2 Plugin Scaffold Template
The scaffold template lives at a known location. Elnor (or any coding agent) copies it to start a new plugin:
```
plugins/_template/
├── manifest.json.template ← Manifest with placeholders
├── README.md.template
├── tools/
│ └── example_tool.ts.template ← Example tool implementation
├── services/
│ └── example_service.ts.template ← Example service implementation
├── tabs/
│ └── ExampleTab.tsx.template ← Example tab component
├── config/
│ └── ExampleConfig.tsx.template ← Example config panel
├── types/
│ └── index.ts.template
└── BUILDING_A_PLUGIN.md ← Documentation for the coding agent
```
### 5.3 BUILDING_A_PLUGIN.md
This document is what the coding agent reads. It explains:
```markdown
# Building a Q Plugin
## Overview
A Q plugin is a directory containing a manifest.json and implementation files.
The manifest declares what the plugin does. The files implement it.
## Step-by-Step
### 1. Copy the template
Copy `plugins/_template/` to `plugins/your-plugin-id/`
### 2. Define the manifest
Fill in manifest.json with:
- Identity (id, name, description, icon, category)
- Connectors (what APIs does this talk to? what auth?)
- Tools (what can Elnor do with this plugin?)
- Services (any background monitoring/sync? what schedule?)
- Agent instructions (what should Elnor do when events happen?)
- Tab (does this need a dashboard UI?)
- DOC72 config (what should be remembered in the knowledge graph?)
- Outputs (notifications, files, calendar events, tasks?)
- DOC24 triggers (what phrases should route to this plugin?)
### 3. Implement tools
Each tool is a function that takes parameters and returns a result.
Tools can call connector APIs, run local commands, read files, etc.
### 4. Implement services (if any)
Services are programmatic functions that run on a schedule.
NO LLM calls in services. Services check APIs, download files, compare data.
Services produce events. Events can trigger agent instructions (which DO use LLM).
### 5. Implement tab (if any)
Tab is a React component that receives PluginTabProps.
Use the standard Q design tokens and components.
The tab renders inside the workspace tab frame.
### 6. Test
Run the plugin locally. Verify tools work, service runs, tab renders.
### 7. Register
Place in plugins/ directory. Q auto-discovers on next launch.
## Contract Rules
- Tools MUST have descriptive names and descriptions (Elnor uses these for routing)
- Services MUST NOT call LLMs — they are programmatic only
- Agent instructions MUST have clear trigger conditions
- Connectors MUST handle auth token refresh
- Tab components MUST use PluginTabProps interface
- File writes MUST use the configured output directory
- All external API calls MUST go through the connector's rate limiter
- DOC72 writes MUST include principal_id and scope
- Cost-incurring operations MUST be tracked and surfaced to the user
```
### 5.4 Elnor's Plugin Builder Awareness
Add to Elnor's system context:
```
You can BUILD new plugins for Q Dashboard. When Will asks you to create a new
integration, capability, or monitoring system, consider whether it should be a plugin.
A plugin should be built when:
- The capability involves an external API or service
- It benefits from scheduled background processing
- It would benefit from a dedicated management UI (tab)
- It involves recurring automation with configurable rules
- Will says "build me a plugin" or "I want to add [capability] to Q"
To build a plugin:
1. Read the plugin scaffold template at plugins/_template/BUILDING_A_PLUGIN.md
2. Copy the template to plugins/{new_plugin_id}/
3. Define the manifest.json with all required sections
4. Implement tools, services, tabs as needed
5. Register DOC24 semantic triggers
6. Test everything
7. Install by placing in the plugins/ directory
The manifest schema is the contract. Follow it exactly. Every field has a purpose.
If you're unsure about a field, read the schema comments in the manifest.json.template.
You can also MODIFY existing plugins — add new tools, change service schedules,
update agent instructions, expand the tab UI. Treat plugin code like any other
code in the Q Dashboard project.
```
### 5.5 Example: Elnor Builds a Westlaw Plugin
User: "Elnor, I want a plugin that monitors my Westlaw alerts. When new cases matching my search criteria appear, download them and summarize them."
Elnor (via coding agent):
1. Creates `plugins/westlaw-alerts/`
2. Manifest:
- Connector: Westlaw API (OAuth2, requires Westlaw credentials)
- Tools: `westlaw_search`, `westlaw_get_document`, `westlaw_list_alerts`, `westlaw_get_alert_results`
- Service: `WestlawAlertMonitor` — checks alerts daily at 7 AM
- Agent instruction: "When new alert results found, download documents and summarize holdings"
- Tab: `WestlawTab` — shows configured alerts, recent results, downloaded documents
- DOC72: `legal_opinion` entities with holdings, citations, court, date
- DOC24 triggers: "Westlaw", "case law", "legal research", "case alert"
3. Implements each piece
4. Installs and tests
Total time: ~2-4 hours for the coding agent. Will reviews and customizes.
---
## 6. [+] Menu Integration
```
┌──────────────────────────┐
│ CREATE │
│ 📝 New Note │
│ ☐ New To-Do List │
│ 💬 New Chat │
│ 🌐 New Web Browser │
│ 🔒 New Incognito Web │
│ 📁 New Tab Group │
├──────────────────────────┤
│ OPEN │
│ 🏠 Home │
│ 📊 Tasks │
│ 📅 Calendar │
│ ⚙ Settings │
│ ... │
├──────────────────────────┤
│ PLUGINS ▸ │
└──────────────────────────┘
┌──────────────────────┐
│ ⚖ PACER Monitor │ ← Only plugins with tab defined
│ 📊 Pipedrive │
│ 📋 Westlaw Alerts │
│ ─────────────────── │
│ ⚙ Manage Plugins │ ← Opens Settings > Plugins
└──────────────────────┘
```
Only plugins with `tab` defined appear in the PLUGINS submenu. Plugins without tabs (Hue, Apple Reminders) are tools only — configured in Settings, used via chat.
---
## 7. Connector ↔ Plugin Relationship in Skills Page
### 7.1 Connectors Tab Update
The existing Connectors & Accounts tab in the Skills page shows all connectors. Connectors that belong to a plugin show a badge:
```
┌──────────────────────────────────────────────────────────┐
│ ⚖ PACER API ● Active │
│ wbrody · 10 tools · Part of PACER plugin │
│ [Configure via Plugin] │
├──────────────────────────────────────────────────────────┤
│ 📚 CourtListener RECAP ● Active │
│ API token · 4 tools · Part of PACER plugin │
│ [Configure via Plugin] │
├──────────────────────────────────────────────────────────┤
│ ✉ Gmail ● Active │
│ will@schallfirm.com · 8 tools · Standalone connector │
│ [Configure] │
├──────────────────────────────────────────────────────────┤
│ ◆ Microsoft 365 ● Active │
│ will@schallfirm.com · 14 tools · Standalone connector │
│ [Configure] │
└──────────────────────────────────────────────────────────┘
```
- "Part of PACER plugin" — clicking Configure opens the PACER plugin config, not a standalone connector config
- "Standalone connector" — not managed by any plugin, configured independently
### 7.2 My Capabilities Tab Update
Tools from plugins show the `Plugin` badge (already in mockup's `TY` object):
```
┌──────────────────────────────────────────────────────────┐
│ pacer_search_cases ● Active │
│ [Plugin] Search PACER by party, case number, court │
│ Cost: $0.10/page · Uses: 34 · Last: 4h ago │
└──────────────────────────────────────────────────────────┘
```
---
## 8. Plugin Settings Page
```
Settings > Plugins & Extensions
INSTALLED
┌──────────────────────────────────────────────────────────┐
│ ⚖ PACER Federal Courts ● Active │
│ Federal court records, docket monitoring, case research │
│ Connectors: PACER API (●), CourtListener (●) │
│ Service: Running — last check 2h ago, next in 4h │
│ Tab: PACER Monitor · Cases: 20 · Cost: $14.30/qtr │
│ [Configure] [View Tab] [Disable] │
├──────────────────────────────────────────────────────────┤
│ 🎵 Spotify ● Active │
│ Connectors: Spotify API (●) │
│ Service: Weekly intake — last: Sun 3:01 AM │
│ [Configure] [Disable] │
├──────────────────────────────────────────────────────────┤
│ 💡 Philips Hue ○ Disabled │
│ Connectors: Hue Bridge (○ disconnected) │
│ [Enable] [Uninstall] │
└──────────────────────────────────────────────────────────┘
BUILD NEW
┌──────────────────────────────────────────────────────────┐
│ 🔧 Ask Elnor to build a plugin │
│ Describe what you want and Elnor will create it. │
│ [Start Building...] │
│ │
│ Or load from folder: [Choose Folder...] │
└──────────────────────────────────────────────────────────┘
AVAILABLE
┌──────────────────────────────────────────────────────────┐
│ Browse community plugins on ClawHub... │
└──────────────────────────────────────────────────────────┘
```
---
---
## 9. Plugin Entity Nodes — Base Knowledge Layer
### 9.1 The Missing Layer
The telemetry, error, and build record layers all describe what a plugin *does*. But DOC72 also needs a fundamental entity node for each plugin that describes what it *is*. This is the base layer — without it, Elnor can't reason about plugins as first-class things in its knowledge graph.
**No new canonical node types.** Everything in this section uses DOC72's existing canonical node types. Plugins are `type: "entity", subtype: "plugin"`. Connectors are `type: "entity", subtype: "connector"`. Build records are `type: "procedure"`. Telemetry is `type: "observation"`. Errors are `type: "observation"`. Proposals are `type: "fact"`. The `tool_capability` node type already exists per the DOC24 Tool Capability Nodes Amendment — plugin tools use that same type. No 12th canonical type is introduced.
**This node is auto-created for EVERY installed plugin, regardless of how it was installed:**
| Installation Method | Entity Created By |
|--------------------|------------------|
| Elnor builds it via coding agent | Elnor creates entity + build record |
| User installs from ClawHub | Auto-created by plugin registration system on install |
| Claude Code drops it in the plugins folder | Auto-created on next Q launch when plugin is discovered |
| User loads from folder via Settings | Auto-created when user clicks [Load from folder] |
| Pre-bundled with Q | Auto-created on first launch |
The trigger is simple: **whenever the plugin system detects a new manifest.json in the plugins directory, it reads the manifest and creates (or updates) a DOC72 plugin entity node.**
### 9.2 Plugin Entity Schema
```typescript
interface PluginEntityNode {
node_id: string; // "plugin_entity_pacer"
type: "entity";
subtype: "plugin";
domain: "plugin_registry";
// ═══ Identity (from manifest) ═══
plugin_id: string; // "pacer"
display_name: string; // "PACER Federal Courts"
description: string; // Full description
version: string; // "1.0.0"
author: string; // "ELNOR System"
category: string; // "legal"
icon: string; // "Scale"
// ═══ What it can do (capability summary) ═══
capabilities_summary: string; // Natural language: "Searches federal court records,
// monitors cases for new filings, downloads documents,
// tracks expert witnesses and judges, extracts deadlines."
tool_count: number; // 10
tool_names: string[]; // ["pacer_search_cases", "pacer_get_docket", ...]
has_service: boolean; // true
service_description?: string; // "Monitors 20 cases on configurable schedule"
has_tab: boolean; // true
tab_description?: string; // "Case monitoring dashboard with docket browser"
has_agent_instructions: boolean; // true
// ═══ What it connects to ═══
connector_names: string[]; // ["PACER API", "CourtListener RECAP"]
connector_count: number; // 2
local_apps?: string[]; // ["Microsoft Word"] — if it interacts with desktop apps
web_domains?: string[]; // ["pacer.uscourts.gov", "courtlistener.com"]
// ═══ How it was installed ═══
install_method: "elnor_built" | "clawhub" | "manual_folder" | "pre_bundled" | "coding_agent";
installed_at: string; // ISO timestamp
installed_by: string; // "elnor", "will", "system"
source_url?: string; // ClawHub URL or GitHub repo if applicable
// ═══ Current state ═══
state: "active" | "disabled" | "error" | "updating";
last_state_change: string;
// ═══ Relationship refs ═══
build_record_ref?: string; // DOC72 node_id of build record (if Elnor-built)
connector_entity_refs?: string[]; // DOC72 node_ids of connector entities
related_domain_refs?: string[]; // DOC72 node_ids of related domains
// e.g., PACER plugin → linked to case entities
// ═══ Usage summary (updated by dream cycle) ═══
total_tool_calls: number; // Lifetime
total_cost_cents: number; // Lifetime
last_used: string; // ISO timestamp
avg_daily_calls?: number; // Rolling 30-day average
error_rate?: number; // Rolling 30-day error rate (0-1)
// ═══ User notes ═══
user_notes?: string; // Free-text notes the user has added about this plugin
// ═══ Standard DOC72 fields ═══
principal_id: "will";
scope: "personal";
created_at: string;
updated_at: string;
schema_version: 1;
}
```
### 9.3 Auto-Registration Flow
```typescript
// Runs on Q launch and when plugins directory changes
async function syncPluginEntities(): Promise<void> {
const installedPlugins = await discoverPlugins("plugins/");
const existingEntities = await doc72.query({
type: "entity", subtype: "plugin", domain: "plugin_registry"
});
for (const manifest of installedPlugins) {
const existing = existingEntities.find(e => e.plugin_id === manifest.plugin_id);
if (!existing) {
// ═══ 1. Create plugin entity node ═══
const pluginNodeId = `plugin_entity_${manifest.plugin_id}`;
await doc72.write({
node_id: pluginNodeId,
type: "entity",
subtype: "plugin",
domain: "plugin_registry",
plugin_id: manifest.plugin_id,
display_name: manifest.display_name,
description: manifest.description,
version: manifest.version,
author: manifest.author,
category: manifest.category,
icon: manifest.icon,
capabilities_summary: generateCapabilitySummary(manifest),
tool_count: manifest.tools.length,
tool_names: manifest.tools.map(t => t.tool_id),
has_service: !!manifest.services?.length,
has_tab: !!manifest.tab,
has_agent_instructions: !!manifest.agent_instructions?.length,
connector_names: manifest.connectors?.map(c => c.display_name) || [],
connector_count: manifest.connectors?.length || 0,
install_method: detectInstallMethod(manifest),
installed_at: new Date().toISOString(),
installed_by: "system",
state: "active",
last_state_change: new Date().toISOString(),
total_tool_calls: 0,
total_cost_cents: 0,
last_used: "never",
principal_id: "will",
scope: "personal",
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
schema_version: 1,
});
// ═══ 2. Create tool_capability nodes (per DOC24 amendment) ═══
// Each tool in the manifest gets a tool_capability node in DOC72.
// This is the SAME node type defined in the DOC24 Tool Capability
// Nodes Amendment — not a new type. Plugin tools follow the same
// pattern as MCP tools, OpenClaw wrappers, and AppleScript tools.
for (const tool of manifest.tools) {
const toolNodeId = `tool_cap_${manifest.plugin_id}_${tool.tool_id}`;
await doc72.write({
node_id: toolNodeId,
type: "tool_capability", // Existing DOC24 amendment node type
canonical_name: tool.display_name,
payload: {
tool_source: "plugin",
tool_id: tool.tool_id,
plugin_id: manifest.plugin_id,
description: tool.description,
capabilities_summary: [tool.tool_id],
connector_ref: tool.connector_ref || null,
cost_model: tool.cost?.model || "free",
requires_ai: tool.requires_ai,
},
principal_id: "will",
scope: "personal",
});
// Edge: plugin --has_tool--> tool_capability
await doc72.createEdge(pluginNodeId, toolNodeId, "has_tool");
}
// ═══ 3. Create connector entity nodes ═══
for (const conn of (manifest.connectors || [])) {
const connNodeId = `connector_entity_${manifest.plugin_id}_${conn.connector_id}`;
await doc72.write({
node_id: connNodeId,
type: "entity",
subtype: "connector",
domain: "plugin_registry",
connector_id: conn.connector_id,
display_name: conn.display_name,
service_name: conn.display_name,
managed_by_plugin: manifest.plugin_id,
standalone: false,
auth_type: conn.auth.type,
auth_status: "unconfigured",
tool_count: conn.tools.length,
total_calls: 0,
total_cost_cents: 0,
last_used: "never",
error_rate: 0,
api_base_url: conn.base_url || null,
rate_limit_info: conn.rate_limit
? `${conn.rate_limit.requests_per_minute || "?"} req/min` : null,
cost_info: conn.cost_model
? `${conn.cost_model.type}: $${(conn.cost_model.unit_cost || 0) / 100}/${conn.cost_model.unit_label || "unit"}` : null,
principal_id: "will",
scope: "personal",
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
});
// Edge: plugin --uses_connector--> connector
await doc72.createEdge(pluginNodeId, connNodeId, "uses_connector");
// Edge: tool_capability --provided_by--> connector (for each tool this connector provides)
for (const toolId of conn.tools) {
const toolNodeId = `tool_cap_${manifest.plugin_id}_${toolId}`;
await doc72.createEdge(toolNodeId, connNodeId, "provided_by");
}
}
// ═══ 4. Create alternative_to edges between tools that do similar things ═══
// e.g., pacer_search_cases and recap_search both search cases
// DOC24's routing cascade uses these edges + experience to pick the best tool
if (manifest.tools.length > 1) {
await createAlternativeEdges(manifest);
}
// ═══ 5. Register tools in DOC24 capability registry (operational) ═══
for (const tool of manifest.tools) {
await doc24.capabilityRegistry.register({
id: tool.tool_id,
display_name: tool.display_name,
description: tool.description,
source_type: "plugin",
plugin_id: manifest.plugin_id,
capability_tags: [manifest.category, ...manifest.tags || []],
graph_node_id: `tool_cap_${manifest.plugin_id}_${tool.tool_id}`,
});
}
// ═══ 6. Notify Elnor ═══
addNotice({
source: "Plugin System",
title: `New plugin discovered: ${manifest.display_name}`,
desc: `${manifest.description}. ${manifest.tools.length} tools available.`,
});
} else if (existing.version !== manifest.version) {
// Plugin updated — update entity and re-sync tools
await doc72.update(existing.node_id, {
version: manifest.version,
tool_count: manifest.tools.length,
tool_names: manifest.tools.map(t => t.tool_id),
capabilities_summary: generateCapabilitySummary(manifest),
updated_at: new Date().toISOString(),
});
// Also update/create any new tool_capability nodes for added tools
// and remove nodes for removed tools
await syncToolCapabilityNodes(manifest, existing.node_id);
}
}
// Check for removed plugins
for (const entity of existingEntities) {
if (!installedPlugins.find(m => m.plugin_id === entity.plugin_id)) {
await doc72.update(entity.node_id, {
state: "removed",
last_state_change: new Date().toISOString(),
updated_at: new Date().toISOString(),
});
// Deregister tools from DOC24 capability registry
await doc24.capabilityRegistry.deregisterByPlugin(entity.plugin_id);
// Don't delete tool_capability or connector nodes — preserve history
}
}
}
// Create alternative_to edges between plugin tools that serve similar functions
async function createAlternativeEdges(manifest: PluginManifest): Promise<void> {
// Example: pacer_search_cases and recap_search both search for cases
// DOC24's experience-informed routing uses these edges to pick the best option
//
// Detection: tools with overlapping capability descriptions or explicitly
// declared alternatives in the manifest
//
// For PACER plugin: pacer_search_cases <--alternative_to--> recap_search
// pacer_get_docket <--alternative_to--> recap_get_docket
// pacer_download_document <--alternative_to--> recap_download_document
}
function generateCapabilitySummary(manifest: PluginManifest): string {
// Generate a natural language summary of what the plugin can do
// This is what Elnor reads to understand the plugin
const parts: string[] = [];
if (manifest.tools.length > 0) {
const toolDescs = manifest.tools.slice(0, 5).map(t => t.description).join(", ");
parts.push(`Provides ${manifest.tools.length} tools: ${toolDescs}`);
}
if (manifest.services?.length) {
parts.push(`Runs ${manifest.services.length} background service(s): ${manifest.services.map(s => s.display_name).join(", ")}`);
}
if (manifest.tab) {
parts.push(`Has a management tab: ${manifest.tab.label}`);
}
if (manifest.connectors?.length) {
parts.push(`Connects to: ${manifest.connectors.map(c => c.display_name).join(", ")}`);
}
return parts.join(". ") + ".";
}
```
### 9.4 What Elnor Can Do With Plugin Entities
Because plugins are DOC72 entities, Elnor can reason about them like any other knowledge:
**"What plugins do I have?"**
Elnor queries DOC72 for `{type: "entity", subtype: "plugin", state: "active"}` and lists them with their capability summaries. No hardcoded list needed.
**"What can the PACER plugin do?"**
Elnor retrieves the PACER plugin entity node and reads `capabilities_summary`, `tool_names`, `service_description`, `tab_description`. Elnor can also cross-reference with telemetry to add: "You've used it 247 times, mainly for docket pulls and party searches."
**"Which plugin handles court filings?"**
Elnor searches plugin entities by description/capabilities matching "court filings" → finds PACER plugin. This is DOC24 semantic routing using DOC72 knowledge.
**"I just installed a new plugin from ClawHub. Does Elnor know about it?"**
Yes. `syncPluginEntities()` runs on launch and detects the new manifest. Creates the entity node. Elnor gets a notice. Next time Elnor's context loads, the plugin entity is in the knowledge graph and Elnor can route queries to its tools.
**"Elnor, can you build something like the PACER plugin but for state courts?"**
Elnor retrieves the PACER plugin entity (capabilities, connector structure, tool list) PLUS the PACER build record (design decisions, implementation notes, issues encountered). Uses both to scaffold a new state court plugin informed by prior experience.
**"Which plugins haven't I used in a while?"**
Elnor queries plugin entities sorted by `last_used`. Dream cycle also detects this automatically.
### 9.5 Connector Entity Nodes
Connectors — whether standalone or plugin-managed — also get DOC72 entity nodes:
```typescript
interface ConnectorEntityNode {
node_id: string; // "connector_entity_pacer_api"
type: "entity";
subtype: "connector";
domain: "plugin_registry";
connector_id: string; // "pacer_api"
display_name: string; // "PACER API"
service_name: string; // "PACER (uscourts.gov)"
// Relationship
managed_by_plugin?: string; // "pacer" — or null if standalone
standalone: boolean; // true if not part of any plugin
// Auth
auth_type: string; // "credentials", "oauth2", "api_key"
auth_status: "healthy" | "expired" | "invalid" | "unconfigured";
last_auth_refresh?: string;
// Usage
tool_count: number;
total_calls: number;
total_cost_cents: number;
last_used: string;
error_rate: number;
// Metadata
api_base_url?: string;
rate_limit_info?: string; // "180 req/min"
cost_info?: string; // "$0.10/page, $3 max/doc"
principal_id: "will";
scope: "personal";
created_at: string;
updated_at: string;
}
```
This means Elnor knows: "I have 6 connectors. 4 are healthy, 1 has expired auth (PACER — needs password refresh), 1 is unconfigured (Zoom — waiting for OAuth). The PACER connector is managed by the PACER plugin. Gmail is standalone."
### 9.6 Keeping Entities Current
Plugin and connector entity nodes are updated by:
| Trigger | What Updates |
|---------|-------------|
| Plugin installed/uninstalled | Entity created/marked removed |
| Plugin enabled/disabled | `state` field |
| Plugin updated (new version) | `version`, `capabilities_summary`, `tool_names`, etc. |
| Tool called | `total_tool_calls`, `last_used` (batched, not per-call) |
| Cost incurred | `total_cost_cents` (batched) |
| Auth refreshed/expired | Connector entity `auth_status` |
| Dream cycle runs | `avg_daily_calls`, `error_rate` (rolling calculations) |
| User adds notes | `user_notes` field |
### 9.7 Full Graph Structure for a Plugin
When a plugin is fully registered, the DOC72 graph contains these nodes and edges:
```
Plugin Entity (type: entity, subtype: plugin)
│
│ ──has_tool──→ Tool Capability Node (type: tool_capability) ← DOC24 amendment pattern
│ │
│ │ ──provided_by──→ Connector Entity (type: entity, subtype: connector)
│ │
│ │ ──alternative_to──→ Tool Capability Node (another tool, same function)
│ │ ↑
│ │ │ DOC24's experience-informed routing uses
│ │ │ Beta confidence on these nodes to pick
│ │ │ the best tool when alternatives exist
│ │
│ │ ──executed_via──← Procedure Node (type: procedure) ← DOC3 skills
│ │ (when a learned procedure uses this tool)
│ │
│ └── Experience record (α/β confidence, success_count, total_usage_count)
│ Updated by DOC24's execution receipt handler on every tool call
│
│ ──uses_connector──→ Connector Entity (type: entity, subtype: connector)
│
│ ──has_build_record──→ Build Record (type: procedure, domain: plugin_development)
│ (only for Elnor-built plugins)
│
│ ──related_to──→ Domain Entities (type: entity, subtype: federal_case / music_artist / etc.)
│ (links plugin to the domain it serves)
│
├── Telemetry Records (type: observation, domain: plugin_telemetry)
├── Error Records (type: observation, domain: plugin_errors)
└── Improvement Proposals (type: fact, domain: plugin_proposals)
```
**Example: PACER plugin graph:**
```
plugin_entity_pacer (entity/plugin)
├── has_tool → tool_cap_pacer_search_cases (tool_capability)
│ ├── provided_by → connector_entity_pacer_api (entity/connector)
│ ├── alternative_to → tool_cap_recap_search (tool_capability)
│ │ └── provided_by → connector_entity_courtlistener (entity/connector)
│ └── experience: α=42, β=3, confidence=0.93
├── has_tool → tool_cap_pacer_get_docket (tool_capability)
│ ├── provided_by → connector_entity_pacer_api
│ ├── alternative_to → tool_cap_recap_get_docket
│ └── experience: α=28, β=1, confidence=0.97
├── has_tool → tool_cap_pacer_download_document (tool_capability)
│ ├── provided_by → connector_entity_pacer_api
│ ├── alternative_to → tool_cap_recap_download_document
│ └── experience: α=55, β=4, confidence=0.93
├── has_tool → tool_cap_recap_search (tool_capability)
│ ├── provided_by → connector_entity_courtlistener
│ └── experience: α=30, β=8, confidence=0.79
├── uses_connector → connector_entity_pacer_api (entity/connector)
│ auth_status: healthy, total_cost: $14.30
├── uses_connector → connector_entity_courtlistener (entity/connector)
│ auth_status: healthy, total_cost: $0 (free)
├── has_build_record → plugin_build_pacer_v1 (procedure)
├── related_to → case_entity_brooge (entity/federal_case)
├── related_to → case_entity_narayanan (entity/federal_case)
├── telemetry → tel_20260411_* (47 observation nodes this week)
├── errors → err_pacer_ratelimit_* (2 observation nodes this month)
└── proposals → prop_reduce_inactive_frequency (1 pending fact node)
```
DOC24's routing cascade traverses this graph: when Will asks "check for new filings," DOC24 matches the query to the PACER plugin's semantic triggers, finds the `pacer_get_docket` and `recap_get_docket` tool_capability nodes via the `has_tool` edges, checks the `alternative_to` edge between them, compares experience (RECAP confidence 0.79 vs PACER 0.97), but also checks cost (RECAP free vs PACER $0.10/page). The routing logic in DOC24's cascade weighs both factors — for a docket check, RECAP-first is the right call because the cost advantage outweighs the slightly lower confidence. This is the RECAP-first pattern expressed as graph knowledge rather than hardcoded `if (recap) { try recap first }` logic.
### 9.8 Node Type Summary
For clarity — here is every DOC72 node used by the plugin system and which canonical type it maps to:
| Plugin Concept | DOC72 Canonical Type | Subtype / Domain | New Type? |
|---------------|---------------------|------------------|-----------|
| Plugin itself | `entity` | `subtype: "plugin"`, `domain: "plugin_registry"` | ❌ No |
| Connector | `entity` | `subtype: "connector"`, `domain: "plugin_registry"` | ❌ No |
| Individual tool | `tool_capability` | (per DOC24 amendment) | ❌ Already exists |
| Build record | `procedure` | `domain: "plugin_development"` | ❌ No |
| Telemetry record | `observation` | `domain: "plugin_telemetry"` | ❌ No |
| Error record | `observation` | `domain: "plugin_errors"` | ❌ No |
| Error pattern | `observation` | `domain: "plugin_errors"` | ❌ No |
| Usage pattern | `observation` | `domain: "plugin_telemetry"` | ❌ No |
| Improvement proposal | `fact` | `domain: "plugin_proposals"` | ❌ No |
| Plugin schema reference | `fact` | `domain: "plugin_development"` | ❌ No |
| Cost tracking | `observation` | `domain: "plugin_telemetry"` | ❌ No |
**Zero new canonical node types. Everything fits the existing DOC72 schema.**
---
## 10. Execution Telemetry
### 10.1 What Gets Recorded
Every plugin tool call and service execution produces a telemetry record. This is NOT the full response payload — it's lightweight metadata about the operation.
```typescript
interface PluginTelemetryRecord {
record_id: string;
timestamp: string; // ISO
// What happened
plugin_id: string; // "pacer"
operation_type: "tool_call" | "service_run" | "agent_instruction";
operation_id: string; // Tool ID, service ID, or instruction ID
// Context
trigger: "user_chat" | "agent_instruction" | "scheduled" | "manual" | "cross_plugin";
conversation_id?: string; // If triggered from chat
case_ref?: string; // If associated with a specific case/project
// Parameters (sanitized — no secrets, no full document content)
params_summary: string; // "search_cases(party='Brooge', court='cacd')"
// Result
outcome: "success" | "failure" | "partial" | "timeout" | "cancelled";
result_summary?: string; // "4 cases found" or "Downloaded 22-page PDF"
error?: PluginErrorRecord; // If outcome is failure (see §3)
// Performance
duration_ms: number;
cost_cents?: number; // If the operation cost money
tokens_used?: number; // If AI was involved (agent instructions)
// Usefulness signal
user_acted_on_result?: boolean; // Did user open the document, click the link, etc.?
user_feedback?: "positive" | "negative" | "none";
// DOC72 storage
principal_id: "will";
scope: "personal";
}
```
### 10.2 Storage
Telemetry records are stored as DOC72 observation nodes with `domain: "plugin_telemetry"`:
```typescript
// DOC72 node
{
node_id: "tel_20260411_143022_pacer_search",
type: "observation",
domain: "plugin_telemetry",
subtype: "tool_call",
plugin_id: "pacer",
content: "pacer_search_cases(party='Brooge Energy', court='cacd') → 4 results, 340ms, $0.10",
outcome: "success",
timestamp: "2026-04-11T14:30:22Z",
// ... other fields
}
```
### 10.3 Retention Policy
Telemetry nodes are lightweight but accumulate. Retention:
- **Last 30 days:** Full detail retained
- **30-90 days:** Aggregated to daily summaries per plugin (e.g., "PACER: 12 tool calls, 11 success, 1 failure, $1.40 cost, avg 280ms")
- **90+ days:** Aggregated to monthly summaries
- **Error records:** Never aggregated — kept individually for pattern detection
### 10.4 What Elnor Learns from Telemetry
DOC3's extraction pipeline periodically reviews telemetry and extracts patterns:
**Tool preference patterns:**
```
Observation: "When Will asks about case status, pacer_get_docket is called 85% of
the time and pacer_get_case_summary 15%. Docket is preferred — use it first."
→ DOC72 preference node: {domain: "plugin_usage", subject: "case_status_tool",
preferred: "pacer_get_docket", confidence: 0.85}
```
**Temporal patterns:**
```
Observation: "PACER searches spike on Monday mornings (avg 8 calls vs 2 on other days).
Will likely checks cases at start of week."
→ DOC72 observation node: {domain: "plugin_usage", pattern: "monday_morning_pacer_spike"}
```
**Cost patterns:**
```
Observation: "80% of PACER costs come from 3 cases (Brooge, Narayanan, Henderson).
Other 17 watched cases rarely incur charges."
→ DOC72 fact node: {domain: "plugin_cost", content: "Top 3 cases drive 80% of PACER spend"}
```
**Effectiveness patterns:**
```
Observation: "When pacer_search_cases returns >10 results, Will only clicks the first 3.
Consider limiting default results to 5."
→ DOC72 observation node: {domain: "plugin_effectiveness", suggestion: "reduce_default_results"}
```
---
## 11. Error Handling & Error Learning
### 11.1 Error Classification
Every plugin error is classified into a taxonomy:
```typescript
interface PluginErrorRecord {
error_id: string;
timestamp: string;
plugin_id: string;
operation_id: string; // Which tool/service failed
// Classification
category: PluginErrorCategory;
severity: "critical" | "warning" | "info";
// Detail
message: string; // Human-readable error message
technical_detail?: string; // Stack trace, API response, etc.
// Context
params_at_failure?: string; // What parameters were used
environment?: string; // Network state, time of day, etc.
// Resolution
resolved: boolean;
resolution_method?: "auto_retry" | "config_change" | "user_action" | "code_fix" | "unresolved";
resolution_detail?: string; // What fixed it
resolution_timestamp?: string;
// Learning
recurrence_count: number; // How many times this same error has occurred
first_seen: string; // When this error type first appeared
pattern_id?: string; // Groups related errors into a pattern
}
type PluginErrorCategory =
| "auth_expired" // Token expired, need re-auth
| "auth_invalid" // Credentials wrong
| "rate_limited" // API rate limit hit
| "network_error" // Connection failed, timeout
| "api_error" // API returned error response
| "not_found" // Requested resource doesn't exist
| "permission_denied" // Insufficient permissions
| "data_format" // Unexpected response format
| "service_unavailable" // External service is down
| "local_error" // Local file/process issue
| "config_error" // Plugin misconfigured
| "resource_exhausted" // Disk full, memory limit, etc.
| "logic_error" // Bug in plugin code
| "user_cancelled" // User aborted the operation
| "unknown"; // Unclassified
```
### 11.2 Error Display
Each plugin shows its error state in multiple places:
**Plugin Settings page:**
```
⚖ PACER Federal Courts ● Active
Errors: 1 warning in last 24h
├─ ⚠ Rate limited at 2:14 PM (auto-resolved, backed off 60s)
└─ Last 7 days: 2 warnings, 0 critical, 98% success rate
[View error log]
```
**Plugin tab (if it has one):**
```
ERRORS
├─ ● Healthy — last 5 checks clean
│
│ Recent:
│ ⚠ Apr 11 2:14 PM — Rate limited by PACER. Backed off 60s. Auto-resolved.
│ ✓ Apr 10 — All 3 checks clean
│ ⚠ Apr 9 7:01 AM — CourtListener timeout. Fell back to direct PACER.
│ ✓ Apr 8 — All 3 checks clean
│
│ Recurring patterns:
│ ℹ CourtListener timeouts occur ~1x/week, usually resolved by retry.
│ Elnor auto-falls back to PACER (costs extra ~$0.30/occurrence).
```
**Floating Palette notices (critical errors only):**
```
⚠ PACER plugin: Authentication expired. Re-enter credentials in Settings.
```
**Toast (critical errors only):**
```
"PACER auth expired — monitoring paused. [Fix in Settings]"
```
### 11.3 Error Learning in DOC72
Errors are stored as DOC72 observation nodes with `domain: "plugin_errors"`:
```typescript
{
node_id: "err_pacer_ratelimit_20260411",
type: "observation",
domain: "plugin_errors",
plugin_id: "pacer",
content: "PACER API rate limited at 2:14 PM. 47 requests in preceding 60 seconds.
Auto-backed off 60s. Resumed successfully.",
category: "rate_limited",
severity: "warning",
resolved: true,
resolution_method: "auto_retry",
recurrence_count: 3,
pattern_id: "pacer_rate_limit_pattern",
}
```
### 11.4 Error Pattern Detection
DOC8's dream cycle reviews error records and identifies patterns:
**Recurring errors:**
```
Pattern: "pacer_rate_limit_pattern"
Occurrences: 3 in last 30 days
Context: Always during bulk docket pulls (>30 requests in <2 minutes)
Learning: "When pulling dockets for multiple cases, add 2-second delay between cases
to stay under PACER's rate limit."
→ DOC72 procedure node: rate limiting strategy for bulk PACER operations
→ Elnor applies this automatically on next bulk pull
```
**Auth cycle errors:**
```
Pattern: "pacer_auth_expiry_pattern"
Occurrences: 12 in last 90 days (every ~7 days)
Context: PACER auth token expires weekly
Learning: "PACER tokens expire every ~7 days. Proactively refresh at 6 days."
→ Plugin service updated: add pre-emptive token refresh
```
**Unfixable errors:**
```
Pattern: "courtlistener_intermittent_timeout"
Occurrences: 6 in last 30 days
Context: CourtListener API occasionally times out during peak hours (2-4 PM ET)
Learning: "CourtListener is less reliable 2-4 PM ET. Schedule RECAP checks for
off-peak hours or increase timeout to 30s."
→ DOC72 observation: service reliability pattern
→ Suggestion surfaced to user: "Move PACER monitoring to 7 AM to avoid CourtListener peak"
```
### 11.5 Error Handling for Elnor-Built Plugins
When Elnor builds a plugin and it has errors:
1. **Build-time errors** (syntax, missing dependencies): Elnor sees the error immediately during the build process and fixes it — standard coding agent behavior.
2. **Runtime errors** (API failures, unexpected data): Elnor logs the error via the telemetry system (§2). If the user reports the error ("Elnor, the Westlaw plugin isn't working"), Elnor:
- Reads the error log from DOC72
- Identifies the error category and pattern
- Proposes a fix (code change, config change, or user action)
- If it's a code fix, uses the coding agent to apply it
- Tests the fix
- Records the resolution in DOC72 for future reference
3. **Design errors** (wrong API, bad schema, missing tool): These surface as user complaints ("it doesn't do what I wanted"). Elnor:
- Reviews the original build conversation from DOC72
- Identifies the gap between intent and implementation
- Proposes manifest/code changes
- Applies and tests
---
## 12. Plugin-Building Knowledge
### 12.1 What Gets Stored When a Plugin Is Built
Every plugin build (whether by Elnor or manually) should capture:
```typescript
interface PluginBuildRecord {
record_id: string;
plugin_id: string;
// Build context
built_by: "elnor" | "user" | "community";
build_method: "coding_agent" | "manual" | "imported";
build_timestamp: string;
build_duration_minutes?: number;
// Original request
user_request?: string; // Natural language: "Build me a plugin that monitors Westlaw"
// Design decisions
design_decisions: {
decision: string; // "Used OAuth2 instead of API key because Westlaw requires it"
rationale: string; // "Westlaw's API only supports OAuth2 for user-scoped access"
alternatives_considered?: string; // "Considered scraping but too fragile"
}[];
// Implementation notes
implementation_notes: string[]; // "Rate limit is 100 req/min", "Auth token expires in 1 hour"
// Issues encountered during build
build_issues: {
issue: string;
resolution: string;
time_to_resolve_minutes?: number;
}[];
// Manifest snapshot (for reference)
manifest_version: string;
// Post-build modifications
modifications: {
date: string;
description: string;
reason: string;
}[];
}
```
### 12.2 DOC72 Storage
Build records become DOC72 procedural knowledge nodes:
```typescript
{
node_id: "plugin_build_pacer_v1",
type: "procedure",
domain: "plugin_development",
content: "Built PACER plugin: 2 connectors (PACER API + CourtListener),
monitoring service, custom tab with case management dashboard.
Key decisions: RECAP-first retrieval pattern to minimize cost,
keyword-based filing type classification (no LLM), per-case
agent instructions for conditional AI processing.",
build_issues: [
"PACER auth token format changed — needed to parse nextGenCSO from response",
"CourtListener pagination uses cursor, not offset — had to adjust"
],
tools_created: ["pacer_search_cases", "pacer_get_docket", ...],
total_build_hours: 6,
principal_id: "will",
scope: "personal",
}
```
### 12.3 How Elnor Uses Build Knowledge
When building a new plugin, Elnor queries DOC72 for prior build records:
**"Build me a plugin that monitors state court filings"**
Elnor retrieves:
- PACER plugin build record (similar: court monitoring)
- Spotify plugin build record (similar: scheduled intake service)
- Error patterns from existing plugins (common pitfalls)
Elnor applies lessons learned:
- "PACER plugin used RECAP-first pattern to minimize costs. State court probably doesn't have a free alternative, but I should check."
- "PACER plugin's monitoring service needed rate limiting after getting blocked. I'll add rate limiting from the start."
- "PACER plugin's filing type classifier used keyword matching — worked well, I'll reuse that pattern."
### 12.4 Plugin Manifest Knowledge in DOC72
The plugin manifest schema itself should be stored in DOC72 as a reference document:
```typescript
{
node_id: "plugin_schema_reference",
type: "fact",
domain: "plugin_development",
subtype: "schema_reference",
content: "Plugin manifest schema v1.1 — defines identity, connectors, tools,
services, processes, agent instructions, DOC72 config, tab, outputs,
lifecycle. Located at plugins/_template/manifest.json.template.",
schema_version: "1.1",
last_updated: "2026-04-11",
}
```
This means when Elnor is asked to build a plugin, it can query DOC72 for the schema reference rather than having it hardcoded in its system prompt. If the schema evolves, the DOC72 node is updated and Elnor always uses the latest version.
---
## 13. DOC8 Dream Cycle — Plugin Health Review
### 13.1 Nightly Review (Lightweight)
Every night, the dream cycle reviews plugin health:
```typescript
interface PluginNightlyReview {
tasks: [
// 1. Check each active plugin's service health
"review_service_health", // Any services crashed? Errors in last 24h?
// 2. Review error patterns
"check_error_patterns", // Any new recurring errors? Any resolved patterns?
// 3. Cost check
"daily_cost_summary", // How much did plugins cost today? Any anomalies?
// 4. Telemetry aggregation
"aggregate_daily_telemetry", // Roll up detailed telemetry into daily summary
];
}
```
### 13.2 Weekly Review (Heavier)
Weekly, the dream cycle does a deeper plugin analysis:
```typescript
interface PluginWeeklyReview {
tasks: [
// 1. Usage analysis
"identify_unused_tools", // Tools not called in 30+ days → suggest disabling
"identify_unused_plugins", // Plugins not used in 30+ days → suggest disabling
"identify_heavy_usage", // Tools called 100+ times → candidate for optimization
// 2. Performance analysis
"slow_tool_detection", // Tools with avg latency >5s → investigate
"cost_trend_analysis", // Week-over-week cost changes
"error_rate_analysis", // Error rate trending up or down?
// 3. Agent instruction review
"stale_instructions", // Instructions that never trigger → suggest removing
"ineffective_instructions", // Instructions that trigger but user ignores output → adjust
"missing_instruction_candidates",// Patterns that suggest a new instruction would help
// 4. Cross-plugin analysis
"plugin_interaction_patterns", // Which plugins are used together? Any missing connections?
"redundant_tool_detection", // Two plugins providing similar capabilities?
// 5. Generate weekly digest entry
"plugin_digest_entry", // Summary for the weekly digest
];
}
```
### 13.3 Weekly Digest Entry
The dream cycle generates a plugin health section for the weekly digest:
```
PLUGIN HEALTH — Week of Apr 7-13
Active plugins: 5 (PACER, Spotify, Hue, Reminders, YouTube)
Total tool calls: 247 (↑12% from last week)
Total cost: $8.40 (PACER: $7.20, Spotify: $0, others: $0)
Error rate: 2.1% (5 of 247 calls, all auto-resolved)
Notable:
• PACER monitoring caught 12 new filings across 20 cases
• Spotify intake updated 8 artist profiles and identified
a new genre pattern (classical during Henderson prep)
• Hue "focus mode" activated 14 times this week (↑ from 9 last week)
Suggestions:
• YouTube plugin unused for 22 days — consider disabling or
using for CLE webinar transcripts
• PACER agent instruction "summarize_new_filing" triggered 8 times
but you only opened 3 summaries — adjust trigger condition?
• New pattern detected: you always search PACER after Zoom calls
about active cases. Auto-add PACER search to post-meeting workflow?
```
---
## 14. Self-Improvement Proposals
### 14.1 How Proposals Are Generated
Based on accumulated telemetry, error patterns, and usage data, the system generates improvement proposals. These are NOT auto-applied — they go into a review queue (DOC3's promotion pipeline).
```typescript
interface PluginImprovementProposal {
proposal_id: string;
timestamp: string;
// What's being proposed
type: "new_agent_instruction" | "modify_instruction" | "new_plugin" |
"modify_plugin" | "disable_plugin" | "optimize_tool" |
"new_cross_plugin_workflow" | "config_change";
plugin_id?: string; // Which plugin (if applicable)
// The proposal
title: string; // "Add auto-PACER-search after Zoom calls"
description: string; // Full explanation
evidence: string[]; // What data supports this proposal
estimated_benefit: string; // "Saves ~5 minutes per meeting"
estimated_risk: string; // "May trigger unnecessary searches for non-case calls"
// Implementation
implementation: string; // What would change (manifest update, new instruction, etc.)
reversible: boolean; // Can this be undone easily?
// Review
status: "pending" | "approved" | "rejected" | "deferred";
reviewed_at?: string;
review_notes?: string;
}
```
### 14.2 Proposal Types
**New agent instruction:**
```
Proposal: "Add post-meeting PACER check"
Evidence: "In 8 of the last 10 Zoom calls about active cases, you searched
PACER within 15 minutes of the call ending."
Implementation: Add agent instruction to Zoom plugin:
"When meeting transcript mentions an active case, check PACER for recent filings."
Benefit: Eliminates manual search step after meetings
Risk: May trigger for meetings that mention cases casually
```
**New plugin suggestion:**
```
Proposal: "Build a Clio time tracking plugin"
Evidence: "You spend 3-5 minutes per day manually entering time in Clio.
Q already knows what you worked on (tab history, document opens, chat topics)."
Implementation: Build plugin that auto-generates time entries from Q activity data
Benefit: Saves ~20 min/week on time entry
Risk: Time entries may need manual adjustment for accuracy
```
**Plugin optimization:**
```
Proposal: "Reduce PACER monitoring frequency for low-activity cases"
Evidence: "12 of 20 watched cases had zero new filings in the last 30 days.
These are checked 3x daily, costing 36 unnecessary API calls/day."
Implementation: Auto-reduce check frequency to weekly for cases with
no activity in 14+ days. Restore to normal when activity resumes.
Benefit: Reduces unnecessary API calls by ~60%, saves $2-3/quarter
Risk: May miss a filing by up to 7 days for low-activity cases
```
**Cross-plugin workflow:**
```
Proposal: "Trial prep mode should include PACER check"
Evidence: "When you activate 'trial prep mode' (Hue + Spotify), you also
open the case docket within 10 minutes 70% of the time."
Implementation: Add PACER docket check to the trial prep mode procedure
Benefit: One command sets up your complete trial prep environment
Risk: None — docket check is read-only and free from RECAP
```
### 14.3 Where Proposals Surface
1. **Weekly digest:** "3 plugin improvement proposals this week — review in Settings > Plugins"
2. **Floating Palette notices:** Proposals appear as notices (low priority, not urgent)
3. **Plugin Settings page:** Proposals section at the bottom of each plugin's config
4. **Chat:** "Elnor, any suggestions for my plugins?" → Elnor lists pending proposals
### 14.4 Proposal Lifecycle
```
Generated by DOC8 dream cycle
↓
Stored in DOC72 as proposal node
↓
Surfaced to user (digest, notices, settings)
↓
User reviews: [Approve] [Reject] [Defer] [Modify]
↓
If approved:
- Simple changes (config, instruction): applied automatically
- Code changes (new tool, modified service): Elnor builds via coding agent
- New plugin: Elnor scaffolds and builds
↓
Result recorded in DOC72 (outcome of the proposal)
↓
Future proposals learn from approval/rejection patterns
"User always approves cost-reduction proposals but rejects frequency changes"
```
---
---
## 15. Elnor Awareness — Combined Plugin Prompts
### 15.1 Plugin Builder Prompt
Add to Elnor's system context for plugin building capability:
```
You can BUILD new plugins for Q Dashboard. When Will asks you to create a new
integration, capability, or monitoring system, consider whether it should be a plugin.
A plugin should be built when:
- The capability involves an external API or service
- It benefits from scheduled background processing
- It would benefit from a dedicated management UI (tab)
- It involves recurring automation with configurable rules
- Will says "build me a plugin" or "I want to add [capability] to Q"
To build a plugin:
1. Read the plugin scaffold template at plugins/_template/BUILDING_A_PLUGIN.md
2. Copy the template to plugins/{new_plugin_id}/
3. Define the manifest.json with all required sections
4. Implement tools, services, tabs as needed
5. Create tool_capability nodes following the DOC24 amendment pattern
6. Create edges: plugin→tools, plugin→connectors, tool→connector, tool→alternative_to
7. Register tools in DOC24 capability registry with graph_node_id references
8. Register DOC24 semantic triggers
9. Test everything
10. Install by placing in the plugins/ directory
11. Create a build record in DOC72 for future reference
The manifest schema is the contract. Follow it exactly. Every field has a purpose.
If you're unsure about a field, read the schema comments in the manifest.json.template.
You can also MODIFY existing plugins — add new tools, change service schedules,
update agent instructions, expand the tab UI. Treat plugin code like any other
code in the Q Dashboard project.
```
### 15.2 Plugin Intelligence Prompt
Add to Elnor's system context for plugin operations and learning:
```
PLUGIN INTELLIGENCE:
You maintain operational knowledge of all active plugins:
- Plugin entities are stored in DOC72 under {type: "entity", subtype: "plugin"}
- Individual tool knowledge is stored as tool_capability nodes (per DOC24 amendment)
- Connectors are stored as {type: "entity", subtype: "connector"}
- Usage telemetry is stored under domain "plugin_telemetry"
- Errors are stored under domain "plugin_errors"
- Build history is stored under domain "plugin_development"
- Improvement proposals are stored under domain "plugin_proposals"
Graph structure: plugin --has_tool--> tool_capability --provided_by--> connector
Tool alternatives linked via alternative_to edges with experience-based confidence.
DOC24's routing cascade uses this graph for experience-informed tool selection.
When a plugin tool fails:
1. Check DOC72 for prior occurrences of this error pattern
2. If a resolution exists, apply it automatically
3. If no resolution exists, diagnose the error and either fix it or escalate to Will
4. Always log the error and resolution for future reference
5. If an alternative_to tool exists, fall back to it and note the fallback in telemetry
When asked about plugin performance:
- Query plugin entity nodes for overview (tool count, state, last used)
- Query tool_capability nodes for per-tool experience (confidence, success rate)
- Query telemetry for usage stats, costs, and patterns
- Reference error history for reliability assessment
- Check improvement proposals for pending suggestions
When building a new plugin:
- Query DOC72 for prior plugin build records
- Apply lessons learned from previous builds
- Create tool_capability nodes following the DOC24 amendment pattern
- Create edges: plugin→tools, plugin→connectors, tool→connector, tool→alternative_to
- Register tools in DOC24 capability registry with graph_node_id references
- After building, create a build record in DOC72 for future reference
During dream cycle participation:
- Review all plugin telemetry from the past day/week
- Review tool_capability experience records (identify declining confidence)
- Identify patterns, anomalies, and opportunities
- Generate improvement proposals when evidence is strong
- Flag critical issues for immediate user attention
```
---
## 16. Cross-Doc References
| Document | Relevance |
|----------|-----------|
| DOC3 R11.3 | Skill/connector registry — plugins register tools and connectors here. Register `plugin_telemetry` as a learning signal source. Add plugin error patterns as learning input alongside conversation corrections. |
| DOC24 | Capability registry — plugin semantic triggers registered here. Plugin tools registered with `graph_node_id` linking to DOC72 nodes. Experience-informed routing applies to plugin tools same as any other tool. |
| DOC24 Tool Capability Nodes Amendment | Plugin tools create `tool_capability` nodes following this amendment's schema. Same experience tracking, confidence scoring, execution receipt handling. `alternative_to` edges enable graph-based routing (e.g., RECAP-first). |
| DOC23 | Task system — AI tasks triggered by plugin agent instructions. |
| DOC20 §6.20 | Tab system — plugin tabs registered as new tab types. |
| DOC20 [+] menu | Plugin submenu with arrow slide. |
| DOC72 R5.6 | Entity graph — domains: `plugin_registry`, `plugin_telemetry`, `plugin_errors`, `plugin_development`, `plugin_proposals`. No new canonical node types. |
| DOC8 | Dream cycle — plugin health review (nightly + weekly). Plugin section in weekly digest. |
| DOC16 | Email/M365 — plugins can trigger on email events. CM/ECF notification capture. |
| DOC10 | Unified engagement ledger — plugin telemetry events traceable in operations log. |
---
## 17. Implementation Notes
1. **Start with the manifest schema and registration flow.** A plugin that can register tools and appear in Settings is the minimum viable system.
2. **Add DOC72 entity auto-registration early.** Without plugin entity nodes, Elnor doesn't know plugins exist.
3. **Add tab support third.** The tab rendering infrastructure is Q-side work.
4. **Add service management fourth.** EC or a standalone service manager handles scheduled programmatic services.
5. **Add agent instructions fifth.** Requires DOC23 task triggering and LLM dispatch.
6. **Add telemetry and error tracking sixth.** Lightweight — just logging observation nodes to DOC72.
7. **Add dream cycle participation seventh.** Nightly and weekly plugin health review.
8. **Add self-improvement proposals eighth.** Requires accumulated telemetry data to generate meaningful proposals.
9. **Add Elnor builder last.** Once the system works manually, teach Elnor to build plugins by reading the scaffold.
10. **PACER is the reference implementation.** Build PACER as the first full plugin — it exercises every feature of the system.
11. **Retrofit existing integrations.** Spotify, Hue, Reminders should become plugins for consistency.
12. **The scaffold template is the most important artifact.** If the template is good, Elnor can build plugins. If it's bad, every plugin is custom work.