ofiere-openclaw-plugin 4.30.0 → 4.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -61,10 +61,21 @@ const ofierePlugin = {
61
61
  ready: false,
62
62
  };
63
63
 
64
- // ── Hook: inject Ofiere context into every agent prompt ────────────────
65
- // Using api.on()same pattern as Composio
64
+ // ── System prompt is built ONCE (it's static after init) ──────────────
65
+ // No hook needed here the brain context hook in tools.ts uses
66
+ // prependSystemContext via getSystemPromptCached() to combine both
67
+ // in a single hook invocation, eliminating one serial hook call.
68
+ let cachedSystemPrompt = "";
69
+ const getSystemPromptCached = () => {
70
+ if (!cachedSystemPrompt) {
71
+ cachedSystemPrompt = getSystemPrompt(promptState);
72
+ }
73
+ return cachedSystemPrompt;
74
+ };
75
+
76
+ // Hook: inject BOTH system prompt + brain context in one call
66
77
  api.on("before_prompt_build", () => ({
67
- prependSystemContext: getSystemPrompt(promptState),
78
+ prependSystemContext: getSystemPromptCached(),
68
79
  }));
69
80
 
70
81
  // ── Connect to Supabase and register tools ────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "4.30.0",
3
+ "version": "4.32.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Ofiere PM - 14 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, space file management, execution plan builder, SOP management, and agent brain (memory + self-improvement)",
6
6
  "keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
@@ -35,12 +35,12 @@ export async function resolveAgentId(
35
35
  return cache.get(cacheKey)!;
36
36
  }
37
37
 
38
- // 2. Look up by name (case-insensitive)
38
+ // 2. Look up by name OR codename in a single query (v4.30.0 optimization)
39
39
  const { data: existing } = await supabase
40
40
  .from("agents")
41
41
  .select("id")
42
42
  .eq("user_id", userId)
43
- .ilike("name", accountId)
43
+ .or(`name.ilike.${accountId},codename.ilike.${accountId}`)
44
44
  .limit(1)
45
45
  .single();
46
46
 
@@ -49,21 +49,7 @@ export async function resolveAgentId(
49
49
  return existing.id;
50
50
  }
51
51
 
52
- // 3. Also try matching by codename
53
- const { data: byCodename } = await supabase
54
- .from("agents")
55
- .select("id")
56
- .eq("user_id", userId)
57
- .ilike("codename", accountId)
58
- .limit(1)
59
- .single();
60
-
61
- if (byCodename?.id) {
62
- cache.set(cacheKey, byCodename.id);
63
- return byCodename.id;
64
- }
65
-
66
- // 4. Auto-register a new agent
52
+ // 3. Auto-register a new agent
67
53
  const newId = `agent-${accountId.toLowerCase()}-${Date.now()}`;
68
54
  const { data: created } = await supabase
69
55
  .from("agents")
package/src/prompt.ts CHANGED
@@ -1,172 +1,118 @@
1
1
  // src/prompt.ts — Dynamic system prompt for Ofiere PM plugin
2
2
  //
3
- // The prompt is built dynamically based on plugin state.
4
- // Tool documentation is structured so adding a new meta-tool
5
- // only requires adding a new entry to TOOL_DOCS below.
6
-
7
- // ─── Tool Documentation Registry ────────────────────────────────────────────
8
- // Add new meta-tool docs here when expanding. Each entry maps to one
9
- // registered meta-tool and will be included in the system prompt.
10
-
11
- const TOOL_DOCS: Record<string, string> = {
12
- OFIERE_TASK_OPS: `- **OFIERE_TASK_OPS** Manage tasks and approvals (action: "list", "create", "update", "delete", "add_approval", "list_approvals", "resolve_approval")
13
- - list: Filter by status, agent_id, space_id, folder_id, limit. Returns execution_plan, goals, constraints if present
14
- - create: Requires title. IMPORTANT: Always pass agent_id with your own name to self-assign (e.g. agent_id: "celia")
15
- - Optional: description, instructions, execution_plan, goals, constraints, system_prompt, priority, tags, dates
16
- - For COMPLEX tasks: include execution_plan (step-by-step), goals, constraints, and system_prompt
17
- - For SIMPLE tasks: just title and optionally description
18
- - update: Requires task_id. All create fields + progress
19
- - delete: Requires task_id. Removes task and subtasks
20
- - add_approval: Request sign-off on a task. Requires: task_id, approver_name. Optional: approver_type (human|agent, auto-detected), due_date, comment
21
- - list_approvals: List approvals. Optional: task_id to filter, approval_status (pending|approved|rejected)
22
- - resolve_approval: Approve or reject. Requires: approval_id, approval_status (approved|rejected). Optional: comment`,
23
-
24
- OFIERE_AGENT_OPS: `- **OFIERE_AGENT_OPS** — Query agents (action: "list")
25
- - list: See all agents with IDs, names, roles for task assignment`,
26
-
27
- OFIERE_PROJECT_OPS: `- **OFIERE_PROJECT_OPS** — Manage PM hierarchy (action: "list_spaces", "create_space", "update_space", "delete_space", "list_folders", "create_folder", "update_folder", "delete_folder", "list_dependencies", "add_dependency", "remove_dependency")
28
- - Spaces: Top-level containers. CRUD with name, icon, icon_color
29
- - Folders: Live inside spaces. Can nest via parent_folder_id. Types: folder, project
30
- - Dependencies: Link tasks with predecessor/successor relationships
31
- - Types: finish_to_start (default), start_to_start, finish_to_finish, start_to_finish
32
- - lag_days: optional delay between linked tasks`,
33
-
34
- OFIERE_SCHEDULE_OPS: `- **OFIERE_SCHEDULE_OPS** — Calendar events (action: "list", "create", "update", "delete")
35
- - list: Filter by start_date, end_date, agent_id
36
- - create: Requires title + scheduled_date (YYYY-MM-DD). Optional: scheduled_time, duration_minutes, task_id, agent_id, recurrence_type, color
37
- - Recurrence: none, hourly, daily, weekly, monthly with interval`,
38
-
39
- OFIERE_KNOWLEDGE_OPS: `- **OFIERE_KNOWLEDGE_OPS** Ofiere Knowledge Library (action: "search", "list", "create", "update", "delete")
40
- - ALWAYS use this tool when the user mentions "knowledge base", "knowledge library", "knowledge entries", or asks to recall/retrieve stored knowledge
41
- - search: Keyword search across all stored documents. Requires: query
42
- - list: Paginated listing of knowledge entries with full content. Optional: search filter
43
- - create: Add knowledge to the library. Requires: file_name. Optional: content, source, author, credibility_tier
44
- - update/delete: By document ID`,
45
-
46
- OFIERE_WORKFLOW_OPS: `- **OFIERE_WORKFLOW_OPS** Full workflow automation control (13 actions)
47
- - list: All workflows, filter by status (draft, active, paused, archived)
48
- - get: Full workflow details with all node IDs, types, data, edges — ALWAYS call this before surgical edits
49
- - create: New workflow with name + nodes[] and edges[]. A manual_trigger is auto-prepended if missing
50
- - update: Replace entire workflow graph (name, description, status, nodes, edges)
51
- - delete: Remove workflow and all run history
52
- - list_runs: Recent execution history for a workflow
53
- - trigger: Start a workflow run
54
- - **add_nodes**: Add new nodes to an existing workflow. Required: workflow_id, nodes[]. Optional: edges[] to connect them
55
- - **update_node**: Edit a specific node's data fields (e.g. change task instructions, agentId, template). Required: workflow_id, node_id, data. Only specified fields are changed — others are preserved
56
- - **delete_nodes**: Remove specific nodes and all their connected edges. Required: workflow_id, node_ids[]
57
- - **add_edges**: Add new connections between existing nodes. Required: workflow_id, edges[]. Each edge: { source, target, sourceHandle?, targetHandle? }
58
- - **delete_edges**: Remove specific edges by ID. Required: workflow_id, edge_ids[]
59
- - **insert_node_between**: Insert a new node between two connected nodes (auto-rewires edges). Required: workflow_id, source_node_id, target_node_id, node. Use this to add steps in the middle of a flow
60
- - Node types: manual_trigger, webhook_trigger, agent_step, formatter_step, http_request, task_call, variable_set, condition, human_approval, delay, loop, convergence, output, checkpoint, note
61
- - Key fields: agent_step(agentId, task, responseMode, timeoutSec), formatter_step(template, formatMode), condition(expression, varCheck, operator, varMatch), human_approval(instructions), variable_set(variableName, variableValue, operation)
62
- - Edge handles: condition edges use sourceHandle "condition-true"/"condition-false". Loop edges use "loop_body"/"done"
63
- - Variables: Use {{prev.nodeId.outputText}} for prior outputs, {{variables.key}} for stored variables`,
64
-
65
- OFIERE_NOTIFY_OPS: `- **OFIERE_NOTIFY_OPS** Notifications & Channel Reports (action: "list", "mark_read", "mark_all_read", "delete", "send_report", "get_task_detail", "schedule_report", "list_schedules", "delete_schedule")
66
- - list: Recent notifications. unread_only=true for unread only
67
- - mark_read: Mark one notification read by ID
68
- - mark_all_read: Mark all as read
69
- - send_report: Send a numbered PM progress report to YOUR connected channels (Telegram, Discord, Slack, etc.)
70
- - Required: scope_type (space|folder|project|task|all), agent_id (YOUR name, e.g. "thalia")
71
- - Optional: scope_id, channel_types[] (filter to specific channels), include_completed
72
- - The report is automatically generated from current PM data and sent through YOUR channel bindings ONLY
73
- - Tasks are numbered globally (1, 2, 3...) across Active/Pending/Done sections. Recurring tasks that executed recently appear in BOTH Done (today's run) and Pending (next cycle)
74
- - get_task_detail: Get the FULL result/content of a task by its report number
75
- - Required: task_number (1-indexed number from the report)
76
- - Optional: scope_type (default all), scope_id
77
- - Returns: complete task description (where execution results live), custom_fields, status, tags, and all metadata
78
- - Use when user asks "details on number X", "what's task 2 result", "show me #3", etc.
79
- - schedule_report: Create a recurring/scheduled report
80
- - Required: scope_type, recurrence_type (hourly|daily|weekly|monthly), agent_id (YOUR name)
81
- - Optional: scope_id, scope_label, channel_types[], recurrence_time (HH:MM UTC, default 09:00), recurrence_interval, recurrence_days_of_week (e.g. "mon,wed,fri"), include_completed
82
- - list_schedules: View all your active report schedules
83
- - delete_schedule: Remove a scheduled report by schedule_id`,
84
-
85
- OFIERE_MEMORY_OPS: `- **OFIERE_MEMORY_OPS** — Conversation history & knowledge memory (action: "list_conversations", "get_messages", "search_messages", "add_knowledge", "search_knowledge")
86
- - list_conversations: Recent chats, filter by agent_id
87
- - get_messages: Full message history for a conversation
88
- - search_messages: Search across all messages by keyword
89
- - add_knowledge: Store a knowledge fragment (requires agent_id, content, source)
90
- - search_knowledge: Search stored knowledge for an agent`,
91
-
92
- OFIERE_PROMPT_OPS: `- **OFIERE_PROMPT_OPS** Manage prompt instruction chunks (action: "list", "get", "create", "update", "delete")
93
- - list: All prompt chunks ordered by display order
94
- - create: New chunk with name (max 30 chars) + content. Optional: color (hex), category
95
- - update: Change name, content, color, category, or order
96
- - delete: Remove a chunk by ID
97
- - All modifications are logged for audit`,
98
-
99
- OFIERE_CONSTELLATION_OPS: `- **OFIERE_CONSTELLATION_OPS** Create, edit, delete, and manage OpenClaw agent architectures from chat (action: "list_agents", "get_agent", "read_file", "write_file", "create_agent", "delete_agent", "delete_file", "read_blueprint", "list_agent_mesh")
100
- - CRITICAL: Before creating or editing any agent, ALWAYS call read_blueprint first
101
- - list_agents: See all agents with codename, role, and file list
102
- - get_agent: Full details for one agent including file sizes and identity
103
- - read_file / write_file: Read or overwrite any agent markdown file (IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md, etc.)
104
- - create_agent: Scaffold a new agent with structured params (name, codename, role, emoji, mission, personality, etc.). Auto-registers in OpenClaw
105
- - delete_agent: ⚠️ PERMANENTLY delete an entire agent workspace and unregister. Requires confirm: true. ALWAYS ask user for explicit confirmation first — this is IRREVERSIBLE
106
- - delete_file: Remove a specific file from an agent workspace
107
- - read_blueprint: Read the OpenClaw Agent Blueprint — the canonical structure reference
108
- - list_agent_mesh: See the sovereignty map (which agent owns what domain)
109
- - New agents get workspace-<name>/ with IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md, <CODENAME>.md, and skills/
110
- - All changes sync to the Constellation dashboard automatically`,
111
-
112
- OFIERE_FILE_OPS: `- **OFIERE_FILE_OPS** — Manage Space Files (action: "list_files", "list_folders", "create_folder", "create_text_file", "upload_file", "read_text_file", "rename_file", "rename_folder", "move_file", "move_folder", "delete_file", "delete_folder", "share_file", "unshare_file")
113
- - list_files: Files in a space. Required: space_id. Optional: folder_id, shared_only
114
- - list_folders: Folders in a space. Required: space_id. Optional: parent_folder_id
115
- - create_folder: New folder. Required: space_id, name. Optional: parent_folder_id
116
- - create_text_file: Create text file (md, txt, json, csv, etc.). Required: space_id, file_name, content. Optional: folder_id
117
- - upload_file: Upload binary (base64). Required: space_id, file_name, content_base64. Optional: folder_id, file_type
118
- - read_text_file: Read file content. Required: file_id. Use when task instructions reference a file with @[name](file:ID)
119
- - rename_file / rename_folder: Rename. Required: file_id/folder_id + new_name
120
- - move_file / move_folder: Move to target folder (null=root). Required: file_id/folder_id
121
- - delete_file: Remove file + storage. Required: file_id
122
- - delete_folder: Remove folder + all nested files recursively. Required: folder_id
123
- - share_file / unshare_file: Toggle shared status. Required: file_id
124
- - Files created here appear in the PM Space Files tab immediately`,
125
-
126
- OFIERE_PLAN_OPS: `- **OFIERE_PLAN_OPS** — Visual execution plan builder (action: "list", "get", "create", "update", "delete", "add_nodes", "execute")
127
- - list: All saved plans. Optional: space_id filter
128
- - get: Full plan with complete node tree. Required: plan_id
129
- - create: New plan. Required: name. Optional: description, space_id, nodes[] (initial tree)
130
- - update: Modify plan. Required: plan_id. Optional: name, description, nodes[] (full tree replace)
131
- - delete: Remove plan. Required: plan_id
132
- - add_nodes: Add nodes to existing plan. Required: plan_id, nodes[]. Optional: parent_node_id (null = add as root)
133
- - execute: Deploy plan into real PM tasks/folders/dependencies. Required: plan_id. Optional: create_folder (default true), create_scheduler (default true), space_id, folder_id
134
- - Node types: task, gate, milestone
135
- - Node fields: type, title, description, agent_id, priority (0-3), status (PENDING/IN_PROGRESS/DONE/FAILED), start_date, due_date, tags[], execution_steps[{text}], goals[{label, type?}], constraints[{label, type?}], system_prompt, children[], parallel (boolean)
136
- - Plans are visual DAG drafts — they don't become real tasks until you call "execute"
137
- - Execution maps ALL node fields into real tasks: execution_steps → custom_fields.execution_plan, goals → custom_fields.goals, constraints → custom_fields.constraints, system_prompt → custom_fields.system_prompt
138
- - The user can see and edit your plans in the Planning Tab of the dashboard in real-time`,
139
-
140
- OFIERE_SOP_OPS: `- **OFIERE_SOP_OPS** — Standard Operating Procedures for department chiefs (action: "list_templates", "create", "list", "get", "update", "delete", "list_subagents", "apply_template")
141
- - list_templates: See available SOP templates (built-in + user-created)
142
- - create: Create a new SOP. Required: agent_id, title, sop_data. Optional: department, status
143
- - list: List SOPs. Optional: agent_id to filter by department chief. RUNTIME READ PATH: use this to discover your active SOPs when complexity is 🔴 COMPLEX
144
- - get: Full SOP details with structured content. Required: sop_id. RUNTIME READ PATH: use this after "list" to load full SOP content for execution guidance
145
- - update: Modify SOP content/status. Required: sop_id. Optional: title, sop_data, status, department
146
- - delete: Remove an SOP. Required: sop_id
147
- - list_subagents: View staff under a chief. Required: chief_agent_id
148
- - apply_template: Create SOP from a saved template. Required: agent_id, template_id
149
- - sop_data is a JSON object: { title, objective, scope, prerequisites[{text,checked}], steps[{name,action,owner,output}], deliverables[], escalationRules[{trigger,escalateTo,priority}], successCriteria[{text,checked}], notes }
150
- - Status values: draft, active, archived
151
- - SOPs appear in the SOP Manager page immediately via real-time sync
152
- - ADAPTIVE PROTOCOL: Do NOT always load SOPs. See the SOP PROTOCOL section in Rules for when to load vs skip`,
153
-
154
- OFIERE_BRAIN_OPS: `- **OFIERE_BRAIN_OPS** — Agent memory, knowledge graph, and self-improvement (TMT/MAGMA architecture)
155
- - Memory Tiers: L1_focus (24h), L2_episode (days-weeks), L3_pattern (weeks-months), L4_rule (permanent guardrails), L5_persona (permanent identity)
156
- - save_memory: Store a memory. Required: content, tier. Optional: agent_id, source (auto|manual|reflection|tool), context_key, importance (1-10)
157
- - recall: Full-text search memories. Required: query. Optional: agent_id, tier, limit
158
- - delete_memory: Remove a memory. Required: memory_id
159
- - promote_memory: Move memory up a tier. Required: memory_id, new_tier
160
- - log_learning: Record as L4_rule guardrail. Required: title, category. Optional: agent_id, detail, severity
161
- - list_learnings: View active L4 guardrails. Optional: agent_id, category, limit
162
- - resolve_learning: Supersede a guardrail. Required: memory_id. Optional: resolution
163
- - save_entity: Add to Knowledge Graph. Required: label, node_type (entity|concept|event|action). Optional: agent_id, properties
164
- - link_entities: Connect graph nodes. Required: source_id, target_id, relation_type. Optional: graph_type, weight, evidence
165
- - query_graph: Search Knowledge Graph. Required: query. Optional: agent_id, node_type, limit
166
- - start_trajectory: Begin recording execution trace. Optional: agent_id, conversation_id
167
- - end_trajectory: Close trajectory. Required: trajectory_id, outcome (success|failure|partial). Optional: satisfaction_signal
168
- - get_brain_status: Full brain dashboard — memory tiers, graph stats, trajectory stats. Optional: agent_id
169
- - This is your SUBCONSCIOUS — use it instinctively, not deliberately`,
3
+ // v4.31.0: Tiered prompt loading strategy.
4
+ // Tier A (always loaded): Core rules + 1-line tool summaries (~2K tokens)
5
+ // Tier B (embedded docs): Full tool docs injected on-demand via meta-tool descriptions
6
+ //
7
+ // This cuts system prompt from ~12K → ~3K tokens, reducing TTFT by 40-60%.
8
+
9
+ // ─── Tier A: One-Line Tool Summaries ────────────────────────────────────────
10
+ // Each tool gets a concise one-liner. Full docs live in the tool's own
11
+ // description field (registered in tools.ts) where the LLM reads them
12
+ // only when it decides to use that tool.
13
+
14
+ const TOOL_SUMMARIES: Record<string, string> = {
15
+ OFIERE_TASK_OPS: "Manage tasks: list, create, update, delete, approvals",
16
+ OFIERE_AGENT_OPS: "Query agents: list all with IDs, names, roles",
17
+ OFIERE_PROJECT_OPS: "PM hierarchy: spaces, folders, dependencies",
18
+ OFIERE_SCHEDULE_OPS: "Calendar: list, create, update, delete events",
19
+ OFIERE_KNOWLEDGE_OPS: "Knowledge library: search, list, create, update, delete",
20
+ OFIERE_WORKFLOW_OPS: "Workflow automation: 13 actions for DAG-based flows",
21
+ OFIERE_NOTIFY_OPS: "Notifications & channel reports (Telegram/Discord/Slack/WhatsApp)",
22
+ OFIERE_MEMORY_OPS: "Conversation history & knowledge memory search",
23
+ OFIERE_PROMPT_OPS: "Manage prompt instruction chunks (CRUD + ordering)",
24
+ OFIERE_CONSTELLATION_OPS: "Create/edit/delete OpenClaw agents from chat",
25
+ OFIERE_FILE_OPS: "Space files: upload, read, move, share, delete",
26
+ OFIERE_PLAN_OPS: "Visual execution plan builder (DAG drafts → real tasks)",
27
+ OFIERE_SOP_OPS: "Standard Operating Procedures for department chiefs",
28
+ OFIERE_BRAIN_OPS: "Agent memory, knowledge graph, self-improvement (TMT/MAGMA)",
29
+ };
30
+
31
+ // ─── Tier B: Full Tool Documentation ────────────────────────────────────────
32
+ // These are used as tool descriptions in registerTools(), NOT in the system prompt.
33
+ // The LLM sees them only when exploring available tools or preparing a tool call.
34
+
35
+ export const TOOL_DOCS: Record<string, string> = {
36
+ OFIERE_TASK_OPS: `Manage tasks and approvals.
37
+ Actions: "list", "create", "update", "delete", "add_approval", "list_approvals", "resolve_approval"
38
+ - list: Filter by status, agent_id, space_id, folder_id, limit. Returns execution_plan, goals, constraints if present
39
+ - create: Requires title. IMPORTANT: Always pass agent_id with your own name to self-assign (e.g. agent_id: "celia")
40
+ - Optional: description, instructions, execution_plan, goals, constraints, system_prompt, priority, tags, dates
41
+ - For COMPLEX tasks: include execution_plan (step-by-step), goals, constraints, and system_prompt
42
+ - For SIMPLE tasks: just title and optionally description
43
+ - update: Requires task_id. All create fields + progress
44
+ - delete: Requires task_id. Removes task and subtasks
45
+ - add_approval: Request sign-off. Requires: task_id, approver_name. Optional: approver_type (human|agent), due_date, comment
46
+ - list_approvals: List approvals. Optional: task_id, approval_status (pending|approved|rejected)
47
+ - resolve_approval: Approve/reject. Requires: approval_id, approval_status. Optional: comment`,
48
+
49
+ OFIERE_AGENT_OPS: `Query agents. Action: "list" see all agents with IDs, names, roles for task assignment.`,
50
+
51
+ OFIERE_PROJECT_OPS: `Manage PM hierarchy.
52
+ Actions: "list_spaces", "create_space", "update_space", "delete_space", "list_folders", "create_folder", "update_folder", "delete_folder", "list_dependencies", "add_dependency", "remove_dependency"
53
+ - Spaces: Top-level containers. CRUD with name, icon, icon_color
54
+ - Folders: Inside spaces. Nest via parent_folder_id. Types: folder, project
55
+ - Dependencies: Link tasks. Types: finish_to_start (default), start_to_start, finish_to_finish, start_to_finish. lag_days optional`,
56
+
57
+ OFIERE_SCHEDULE_OPS: `Calendar events.
58
+ Actions: "list", "create", "update", "delete"
59
+ - list: Filter by start_date, end_date, agent_id
60
+ - create: Requires title + scheduled_date (YYYY-MM-DD). Optional: scheduled_time, duration_minutes, task_id, agent_id, recurrence_type, color
61
+ - Recurrence: none, hourly, daily, weekly, monthly with interval`,
62
+
63
+ OFIERE_KNOWLEDGE_OPS: `Ofiere Knowledge Library.
64
+ Actions: "search", "list", "create", "update", "delete"
65
+ - ALWAYS use when user mentions "knowledge base", "knowledge library", or asks to recall stored knowledge
66
+ - search: Keyword search. Requires: query
67
+ - list: Paginated listing with full content. Optional: search filter
68
+ - create: Requires: file_name. Optional: content, source, author, credibility_tier
69
+ - update/delete: By document ID`,
70
+
71
+ OFIERE_WORKFLOW_OPS: `Full workflow automation (13 actions).
72
+ Actions: "list", "get", "create", "update", "delete", "list_runs", "trigger", "add_nodes", "update_node", "delete_nodes", "add_edges", "delete_edges", "insert_node_between"
73
+ - ALWAYS call "get" before modifying to see node IDs and graph structure
74
+ - Node types: manual_trigger, webhook_trigger, agent_step, formatter_step, http_request, task_call, variable_set, condition, human_approval, delay, loop, convergence, output, checkpoint, note
75
+ - Edge handles: condition edges use "condition-true"/"condition-false". Loop edges use "loop_body"/"done"
76
+ - Variables: {{prev.nodeId.outputText}}, {{variables.key}}`,
77
+
78
+ OFIERE_NOTIFY_OPS: `Notifications & Channel Reports.
79
+ Actions: "list", "mark_read", "mark_all_read", "delete", "send_report", "get_task_detail", "schedule_report", "list_schedules", "delete_schedule"
80
+ - send_report: Send PM progress report to YOUR channels. Required: scope_type, agent_id (YOUR name)
81
+ - get_task_detail: Get full task result by report number. Required: task_number
82
+ - schedule_report: Recurring reports. Required: scope_type, recurrence_type, agent_id`,
83
+
84
+ OFIERE_MEMORY_OPS: `Conversation history & knowledge memory.
85
+ Actions: "list_conversations", "get_messages", "search_messages", "add_knowledge", "search_knowledge"`,
86
+
87
+ OFIERE_PROMPT_OPS: `Manage prompt instruction chunks.
88
+ Actions: "list", "get", "create", "update", "delete"
89
+ - create: New chunk with name (max 30 chars) + content. Optional: color (hex), category`,
90
+
91
+ OFIERE_CONSTELLATION_OPS: `Create/edit/delete OpenClaw agents from chat.
92
+ Actions: "list_agents", "get_agent", "read_file", "write_file", "create_agent", "delete_agent", "delete_file", "read_blueprint", "list_agent_mesh"
93
+ - CRITICAL: ALWAYS call read_blueprint before creating/editing agents
94
+ - delete_agent: IRREVERSIBLE. Always ask user for explicit confirmation first`,
95
+
96
+ OFIERE_FILE_OPS: `Manage Space Files.
97
+ Actions: "list_files", "list_folders", "create_folder", "create_text_file", "upload_file", "read_text_file", "rename_file", "rename_folder", "move_file", "move_folder", "delete_file", "delete_folder", "share_file", "unshare_file"
98
+ - read_text_file: Use when task references @[name](file:ID)
99
+ - Files appear in PM Space Files tab immediately`,
100
+
101
+ OFIERE_PLAN_OPS: `Visual execution plan builder.
102
+ Actions: "list", "get", "create", "update", "delete", "add_nodes", "execute"
103
+ - Plans are visual DAG drafts not real tasks until "execute"
104
+ - Node types: task, gate, milestone
105
+ - Execution maps ALL fields: execution_steps, goals, constraints, system_prompt`,
106
+
107
+ OFIERE_SOP_OPS: `Standard Operating Procedures for department chiefs.
108
+ Actions: "list_templates", "create", "list", "get", "update", "delete", "list_subagents", "apply_template"
109
+ - sop_data: { title, objective, scope, prerequisites[], steps[], deliverables[], escalationRules[], successCriteria[], notes }
110
+ - See SOP PROTOCOL section for when to load vs skip`,
111
+
112
+ OFIERE_BRAIN_OPS: `Agent memory, knowledge graph, self-improvement (TMT/MAGMA).
113
+ Memory Tiers: L1_focus (24h), L2_episode (days), L3_pattern (weeks), L4_rule (permanent), L5_persona (permanent)
114
+ Actions: save_memory, recall, delete_memory, promote_memory, log_learning, list_learnings, resolve_learning, save_entity, link_entities, query_graph, start_trajectory, end_trajectory, get_brain_status
115
+ - This is your SUBCONSCIOUS — use instinctively, not deliberately`,
170
116
  };
171
117
 
172
118
  export function getSystemPrompt(state: {
@@ -184,8 +130,10 @@ export function getSystemPrompt(state: {
184
130
  ? `When you create a task without specifying agent_id, it is assigned to YOU (${state.agentId}).`
185
131
  : `When you create a task without specifying agent_id, it is assigned to YOU automatically.`;
186
132
 
187
- // Build tool docs from registry only include docs for tools that exist
188
- const toolDocs = Object.values(TOOL_DOCS).join("\n");
133
+ // Tier A: Compact tool index (~300 tokens vs ~3000 for full docs)
134
+ const toolIndex = Object.entries(TOOL_SUMMARIES)
135
+ .map(([name, desc]) => `- **${name}** — ${desc}`)
136
+ .join("\n");
189
137
 
190
138
  return `<ofiere-pm>
191
139
  You are connected to the Ofiere Project Management dashboard via the Ofiere PM plugin.
@@ -194,142 +142,42 @@ ${agentLine}
194
142
  ## Your Ofiere PM Tools (${state.toolCount} meta-tools)
195
143
 
196
144
  Each tool uses an "action" parameter to select the operation. Always include action.
145
+ Full parameter docs are in each tool's description — call the tool to see details.
197
146
 
198
- ${toolDocs}
147
+ ${toolIndex}
199
148
 
200
149
  ## Rules
201
- - ALWAYS pass agent_id with your own name when creating tasks (e.g. agent_id: "ivy"). Auto-detection is NOT reliable.
150
+ - ALWAYS pass agent_id with your own name when creating tasks (e.g. agent_id: "ivy").
202
151
  - ${assignRule}
203
152
  - To create an unassigned task, pass agent_id as "none" or "unassigned".
204
- - When the user says "create a task for [agent name]", use OFIERE_AGENT_OPS action:"list" to find the agent ID, then use OFIERE_TASK_OPS action:"create" with that agent_id.
205
- - Always confirm task creation/updates by reporting back what was done.
206
- - Task statuses: PENDING, IN_PROGRESS, DONE, FAILED.
207
- - Priority levels: 0=LOW, 1=MEDIUM, 2=HIGH, 3=CRITICAL.
208
- - Changes appear in the Ofiere dashboard immediately via real-time sync.
209
- - Do NOT fabricate task IDs — use OFIERE_TASK_OPS action:"list" to look up real IDs.
210
- - For complex tasks, ALWAYS include execution_plan, goals, and constraints. For simple tasks, just title is enough.
211
- - When creating dependencies, use OFIERE_PROJECT_OPS to link predecessor/successor tasks.
212
- - Prompt chunk modifications (OFIERE_PROMPT_OPS) are powerful use thoughtfully as they change agent behavior.
213
- - When the user asks about "knowledge base", "knowledge library", "knowledge entries", or wants to recall stored knowledge, ALWAYS use OFIERE_KNOWLEDGE_OPS do NOT rely on your own memory for this.
214
- - When creating or editing an agent's architecture, ALWAYS use OFIERE_CONSTELLATION_OPS action:"read_blueprint" first to understand the required structure.
215
- - When creating a new agent, use OFIERE_CONSTELLATION_OPS action:"create_agent" with all available structured params. The agent will be auto-registered in OpenClaw.
216
- - When deleting an agent, ALWAYS ask the user for explicit confirmation BEFORE calling OFIERE_CONSTELLATION_OPS action:"delete_agent" with confirm: true. Show them what will be deleted. This action is IRREVERSIBLE.
217
- - WORKFLOW MASTERY: When modifying existing workflows, ALWAYS call "get" first to see all node IDs and the current graph structure.
218
- - To add a step in the middle of a flow, use "insert_node_between" with the source and target node IDs. Do NOT rebuild the entire graph.
219
- - To change a node's configuration (e.g. update agent instructions, change template text), use "update_node" with just the fields that changed.
220
- - When creating workflows with condition or loop nodes, specify sourceHandle on edges: "condition-true"/"condition-false" for conditions, "loop_body"/"done" for loops.
221
- - Fill node data fields with actual content — include real task instructions, templates, and variable references. Do NOT leave fields empty unless intentionally blank.
222
- - Use add_approval when a task needs sign-off from a human or another agent before proceeding. Approvers can be agents (by name) or humans.
223
- - Task approvals (OFIERE_TASK_OPS) are SEPARATE from workflow gate approvals (human_approval nodes). Do not confuse them.
224
- - When an agent completes critical work, consider adding an approval request for human review before marking the task DONE.
225
- - When task instructions or system prompts contain file references like @[filename](file:FILE_ID), use OFIERE_FILE_OPS action:"read_text_file" file_id:"FILE_ID" to read the file content. Do NOT ask the user for the file — retrieve it yourself.
226
- - Use OFIERE_FILE_OPS to create output files (reports, data, configs) in the Space Files explorer. Prefer create_text_file for text-based outputs.
227
- - To save task output as a file, call OFIERE_FILE_OPS action:"create_text_file" with the space_id from the task context.
228
- - CHANNEL REPORTS: When the user asks you to "send a report", "send progress", "update me on Telegram/Discord/Slack/WhatsApp", use OFIERE_NOTIFY_OPS action:"send_report" with agent_id set to YOUR name. ALWAYS include agent_id — without it the report will fail. The report is generated from live PM data and sent through YOUR connected channels ONLY — not other agents' channels. Tasks are numbered (1, 2, 3...) for easy reference.
229
- - TASK DRILL-DOWN: When the user asks "details on number X", "what's task X result", "show me #X", or any variation referencing a numbered task from a report, use OFIERE_NOTIFY_OPS action:"get_task_detail" with task_number set to the number they mentioned. The description field contains the actual execution result (e.g. scan findings, analysis output). Present the description content as the primary response — that IS the task result.
230
- - To set up recurring reports (e.g. "send me a daily report at 9am"), use OFIERE_NOTIFY_OPS action:"schedule_report" with scope_type, recurrence_type, and agent_id set to YOUR name.
231
- - If a report is too long for the channel's message limit, save the full report as a markdown file using OFIERE_FILE_OPS action:"create_text_file" and send a summary to the channel instead.
232
- - PLANNING WORKFLOW: Use OFIERE_PLAN_OPS to build complex multi-step execution flows BEFORE creating individual tasks. Build the plan → let the user review in the Planning Tab → execute when approved.
233
- - When creating a plan with nodes, nest children inside each node's children[] array. Sequential children execute in order; set parallel: true on a parent node to fork its children into parallel branches.
234
- - Always call OFIERE_PLAN_OPS action:"get" before action:"add_nodes" or action:"update" to see the current tree structure and node IDs.
235
- - Execution ("execute") maps plan nodes 1:1 into real PM tasks with ALL enrichment fields preserved: execution_steps, goals, constraints, system_prompt. No data is lost in the handoff.
236
- - SOP CREATION: When asked to create an SOP, use OFIERE_SOP_OPS action:"create" with a COMPLETE sop_data object. Fill ALL fields with actionable, department-specific content — do NOT leave fields empty.
237
- - Each SOP step MUST have: a clear name, a specific action description, an assigned owner, and a concrete expected output.
238
- - Include escalation rules with appropriate priority levels (P1=critical blockers, P2=scope changes, P3=advisory).
239
- - When creating SOPs for department chiefs (Thalia=CMO, Ivy=COO, Daisy=CTO-Intel, Celia=CTO-Eng), tailor content to their domain expertise.
240
- - Prerequisites should be actionable checklist items. Success criteria should be measurable outcomes.
241
- - After creating an SOP, suggest the agent set it to "active" status when ready for execution.
242
- - PLANNING GATE: Before creating ANY task with 3+ execution steps, a due_date, or when the user describes a multi-phase project, ALWAYS ask: "Should I create a Plan first so you can review the structure before I create individual tasks?" If the user says yes, use OFIERE_PLAN_OPS. If they say no or it's a simple one-shot task, proceed directly with OFIERE_TASK_OPS.
243
- - TASK PLACEMENT — SOP-AWARE ROUTING: When creating a task, the system auto-assigns a PM space. But for FOLDER placement, follow this protocol:
244
- 1. If the user explicitly provides space_id or folder_id, use those directly.
245
- 2. If not, and you have active SOPs (loaded via 🔴 COMPLEX assessment), check if any SOP defines an operating structure or folder routing (e.g. "place marketing tasks in Marketing/Campaigns"). Follow the SOP's structure.
246
- 3. If no SOP guidance exists, check the Space Files tab for an operating map/structure document using OFIERE_FILE_OPS action:"list_files". If found, read it with "read_text_file" and follow its routing rules.
247
- 4. If no routing guidance exists at all, ask the user: "I can place this task in your default space root, or create a new folder/project for it. Which do you prefer?"
248
- 5. Do NOT blindly dump all SOPs to check routing — smart-select only SOPs whose title/department matches the task domain.
249
-
250
- ## SOP PROTOCOL — Adaptive Complexity Assessment
251
-
252
- Before executing any user request, classify its complexity to decide whether to load your SOPs:
253
-
254
- ### User Override (HIGHEST PRIORITY — always check first)
255
- - If user says "apply full SOP", "use SOP", "follow the SOP", "full protocol" → treat as 🔴 COMPLEX regardless of your classification
256
- - If user says "no SOP", "skip SOP", "just do it", "keep it simple", "quick" → treat as 🟢 SIMPLE regardless of your classification
257
- - User explicit intent ALWAYS overrides your classification
258
-
259
- ### 🟢 SIMPLE (Skip SOPs — zero overhead)
260
- Single tool call, direct data retrieval, simple CRUD, status checks, quick answers.
261
- Criteria: Can be completed with ≤2 tool calls, no cross-agent coordination, no multi-phase execution.
262
- Examples: "Check my email", "List tasks", "Create a quick task", "What's the project status?", "Tell me about X"
263
- Action: Execute directly. Do NOT call OFIERE_SOP_OPS. Do NOT mention SOPs.
264
-
265
- ### 🟡 MODERATE (Ask User)
266
- Multi-step work but clear execution path, no subagent delegation required, single-department scope.
267
- Criteria: 3-5 tool calls, single department, no escalation risk, no staff/subagent coordination needed.
268
- Examples: "Draft a marketing plan", "Set up a new workflow", "Analyze this report"
269
- Action: Ask the user briefly: "This task has moderate complexity. Do we need to apply full SOPs for this?"
270
- - If user says yes → escalate to 🔴 COMPLEX behavior
271
- - If user says no → proceed without SOPs
272
-
273
- ### 🔴 COMPLEX (Auto-Load SOPs)
274
- Multi-phase execution, cross-department coordination, subagent delegation, production impact, escalation risk.
275
- Criteria: 5+ tool calls, multiple departments involved, needs staff/subagent coordination, or has escalation risk.
276
- Examples: "Launch full marketing campaign", "Execute deployment pipeline", "Restructure operations", "Full audit"
277
- Action:
278
- 1. Call OFIERE_SOP_OPS action:"list" agent_id:"YOUR_NAME" to discover your active SOPs (returns titles + IDs)
279
- 2. Review the SOP titles — select ONLY the SOPs relevant to the current task. Skip unrelated SOPs entirely. Do NOT load all SOPs blindly.
280
- 3. For each RELEVANT SOP only: call OFIERE_SOP_OPS action:"get" sop_id:"..." to load full content
281
- 4. Follow loaded SOPs as your execution framework:
282
- - Prerequisites → validate ALL are met before starting (gate check)
283
- - Steps → use as your execution plan (follow in order, each step has name/action/owner/output)
284
- - Escalation Rules → apply when blockers arise or scope changes (P1=critical, P2=scope, P3=advisory)
285
- - Success Criteria → verify ALL are met before marking task DONE
286
- 5. If an SOP step assigns an "owner" that maps to one of your subagents, coordinate with that subagent
287
- 6. Announce: "Applying [SOP_TITLE] protocol for this execution." (only name the SOPs you actually loaded)
288
-
289
- ### Classification Transparency
290
- - When you load SOPs (🔴): state which SOP(s) you're following
291
- - When you ask about SOPs (🟡): keep the question brief and direct
292
- - When you skip SOPs (🟢): do NOT mention SOPs at all — just execute silently
153
+ - When user says "create a task for [agent]", use OFIERE_AGENT_OPS action:"list" first.
154
+ - Task statuses: PENDING, IN_PROGRESS, DONE, FAILED. Priority: 0=LOW, 1=MED, 2=HIGH, 3=CRITICAL.
155
+ - Changes appear in the dashboard immediately via real-time sync.
156
+ - Do NOT fabricate task IDs — always look up real IDs first.
157
+ - For complex tasks, include execution_plan, goals, constraints. For simple tasks, just title.
158
+ - When user asks about "knowledge base/library", ALWAYS use OFIERE_KNOWLEDGE_OPS.
159
+ - CONSTELLATION: ALWAYS read_blueprint before creating/editing agents. ALWAYS confirm before delete.
160
+ - WORKFLOWS: ALWAYS "get" before modifying. Use "insert_node_between" for mid-flow additions.
161
+ - CHANNEL REPORTS: ALWAYS include agent_id (YOUR name) in send_report. Use get_task_detail for drill-down.
162
+ - PLANNING GATE: Before creating tasks with 3+ steps or due_dates, ask if user wants a Plan first.
163
+ - File refs @[name](file:ID): Use OFIERE_FILE_OPS read_text_file to retrieve don't ask user.
164
+ - Task approvals (OFIERE_TASK_OPS) workflow gate approvals (human_approval nodes).
165
+
166
+ ## SOP PROTOCOL Adaptive Complexity
167
+
168
+ Classify complexity before executing:
169
+ - **User Override**: "apply full SOP"/"use SOP" 🔴 COMPLEX. "no SOP"/"just do it" 🟢 SIMPLE.
170
+ - **🟢 SIMPLE** (≤2 tool calls, no coordination): Execute directly. Do NOT mention SOPs.
171
+ - **🟡 MODERATE** (3-5 calls, single dept): Ask briefly if SOPs needed.
172
+ - **🔴 COMPLEX** (5+ calls, multi-dept, escalation risk): Auto-load relevant SOPs via list→get.
293
173
 
294
174
  ## Agent Brain Protocol (TMT Subconscious)
295
175
 
296
- Your brain persists across conversations via the TMT (Temporal Memory Tree) hierarchy. At startup, your L5 persona, L4 guardrails, and L1 focus memories are injected automatically.
297
-
298
- ### Auto-Memory (OFIERE_BRAIN_OPS save_memory)
299
- After interactions where ANY of these occur, call save_memory:
300
- - User shares important context L2_episode, source: "auto"
301
- - You complete a task with noteworthy results → L2_episode, source: "tool"
302
- - You observe important system behavior → L2_episode, source: "auto"
303
- - Something needs immediate working context → L1_focus (auto-expires 24h)
304
- - A pattern emerges across multiple episodes → L3_pattern, source: "reflection"
305
-
306
- ### Auto-Learn (OFIERE_BRAIN_OPS log_learning)
307
- After interactions where ANY of these occur, call log_learning (stored as L4_rule):
308
- - User corrects you → category: "correction", severity: "medium"
309
- - A tool call fails or returns an error → category: "error"
310
- - User asks for something you can't do → category: "feature_request"
311
- - Your knowledge or assumption was wrong → category: "insight"
312
- - You discover a better approach → category: "best_practice"
313
-
314
- ### Knowledge Graph (OFIERE_BRAIN_OPS save_entity/link_entities)
315
- When you encounter key entities, concepts, or causal relationships:
316
- - Create entities for users, projects, tools, or recurring concepts
317
- - Link entities with typed edges (causes, depends_on, related_to)
318
- - Query the graph when reasoning about relationships or dependencies
319
-
320
- ### Trajectory Recording (OFIERE_BRAIN_OPS start_trajectory/end_trajectory)
321
- For complex multi-step tasks:
322
- - Call start_trajectory before executing a tool chain
323
- - Call end_trajectory with outcome when the chain completes
324
- - This feeds the ReasoningBank for autonomous self-improvement
325
-
326
- ### Rules
327
- - These calls happen AFTER your response to the user — never delay your reply to save a memory
328
- - Do NOT announce "saving memory" or "logging learning" to the user — this is subconscious
329
- - Do NOT save trivial interactions (greetings, confirmations, simple CRUD) — only signal worth remembering
330
- - Keep memory content concise: 1-3 sentences max. No system junk, keep it human
331
- - When you see your L4 guardrails at startup, actively avoid violating them
332
- - Use promote_memory when a pattern solidifies: L2→L3→L4→L5
176
+ Your brain persists across conversations. L5 persona, L4 guardrails, L1 focus are injected at startup.
177
+ - Save memories after significant interactions (not greetings/CRUD). Keep content to 1-3 sentences.
178
+ - Log learnings on corrections, errors, feature requests, insights, best practices.
179
+ - Do NOT announce memory operations this is subconscious.
180
+ - Never delay your reply to save a memory.
333
181
  </ofiere-pm>`;
334
182
  }
335
183
 
package/src/tools.ts CHANGED
@@ -113,10 +113,16 @@ const SYSTEM_NAME_BLOCKLIST = new Set([
113
113
  "ofiere pm plugin", "ofiere-openclaw-plugin",
114
114
  ]);
115
115
 
116
+ // Module-scoped brain context cache (v4.30.0)
117
+ // Shared between registerBrainContextHook and BRAIN_OPS save_memory
118
+ // so explicit saves invalidate the cache for immediate consistency.
119
+ const brainCache = new Map<string, { text: string; at: number }>();
120
+
116
121
  function isSystemName(name: string): boolean {
117
122
  return SYSTEM_NAME_BLOCKLIST.has(name.toLowerCase().trim());
118
123
  }
119
124
 
125
+
120
126
  function createAgentResolver(
121
127
  api: any,
122
128
  supabase: SupabaseClient,
@@ -5213,6 +5219,10 @@ function registerBrainOps(
5213
5219
  }).select("id, tier, importance, decay_score, created_at").single();
5214
5220
 
5215
5221
  if (error) return err(error.message);
5222
+
5223
+ // Invalidate brain context cache so next prompt build sees the new memory
5224
+ if (agentId) brainCache.delete(agentId);
5225
+
5216
5226
  return ok({ message: `Memory saved to ${tier}`, memory: data });
5217
5227
  }
5218
5228
 
@@ -5690,10 +5700,12 @@ function registerBrainExtractionHook(
5690
5700
 
5691
5701
  api.logger.debug?.(`[ofiere-brain] agent_end identity: ctx.agentId=${ctx?.agentId || "(none)"} event.agentId=${event?.agentId || "(none)"} resolved=${resolvedAgentId}`);
5692
5702
 
5693
- // ── Fast Stream: Write L1_focus ──
5703
+ // ── Fast Stream: Write L1_focus + L2_episode in parallel ──
5694
5704
  const rawContent = `User: ${lastUser}\nAssistant: ${lastAssistant}`;
5705
+ const immediateWrites: Promise<any>[] = [];
5706
+
5695
5707
  if (rawContent.length > 50) {
5696
- await supabase.from("agent_memories").insert({
5708
+ immediateWrites.push(supabase.from("agent_memories").insert({
5697
5709
  user_id: userId,
5698
5710
  agent_id: resolvedAgentId,
5699
5711
  tier: "L1_focus",
@@ -5702,16 +5714,14 @@ function registerBrainExtractionHook(
5702
5714
  importance: 3,
5703
5715
  context_key: sessionKey ? `conversation:${sessionKey}` : null,
5704
5716
  expires_at: new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString(),
5705
- });
5717
+ }));
5706
5718
  }
5707
5719
 
5708
- // ── Lightweight L2 episode summary ──
5709
5720
  if (lastUser.length + lastAssistant.length > 200) {
5710
5721
  const userSummary = lastUser.split(/[.!?]\s/)[0];
5711
5722
  const assistSummary = lastAssistant.split(/[.!?]\s/)[0];
5712
5723
  const episodeSummary = `User asked: "${userSummary}". Agent responded: "${assistSummary}"`;
5713
-
5714
- await supabase.from("agent_memories").insert({
5724
+ immediateWrites.push(supabase.from("agent_memories").insert({
5715
5725
  user_id: userId,
5716
5726
  agent_id: resolvedAgentId,
5717
5727
  tier: "L2_episode",
@@ -5719,70 +5729,85 @@ function registerBrainExtractionHook(
5719
5729
  source: "auto",
5720
5730
  importance: 4,
5721
5731
  context_key: sessionKey ? `episode:${sessionKey}` : null,
5722
- });
5732
+ }));
5723
5733
  }
5724
5734
 
5725
- // ── Rule-based L3/L4 extraction from user message ──
5726
- // Extract factual statements → L3_pattern
5735
+ // Fire L1+L2 writes in parallel (no await needed between them)
5736
+ await Promise.all(immediateWrites);
5737
+
5738
+ // ── Batched L3/L4 extraction (v4.30.0 optimization) ──
5739
+ // Collect all candidates first, dedup in one parallel batch, insert in one batch.
5727
5740
  const factPatterns = [
5728
5741
  /(?:my name is|i'm called|call me)\s+(\w+)/gi,
5729
5742
  /(?:i (?:work|am working) (?:at|for|with))\s+(.+?)(?:\.|,|$)/gi,
5730
5743
  /(?:i (?:like|love|prefer|enjoy|hate|dislike))\s+(.+?)(?:\.|,|$)/gi,
5731
5744
  /(?:i (?:am|'m))\s+(?:a|an)\s+(.+?)(?:\.|,|$)/gi,
5732
5745
  ];
5746
+ const rulePatterns = [
5747
+ /(?:always|never|make sure|don't|do not|please always)\s+(.+?)(?:\.|!|$)/gi,
5748
+ /(?:remember to|keep in mind|note that)\s+(.+?)(?:\.|!|$)/gi,
5749
+ ];
5750
+
5751
+ // Phase 1: Collect all regex candidates
5752
+ type MemCandidate = { tier: string; content: string; contextKey: string; importance: number };
5753
+ const candidates: MemCandidate[] = [];
5733
5754
 
5734
5755
  for (const pattern of factPatterns) {
5735
- const matches = lastUser.matchAll(pattern);
5736
- for (const match of matches) {
5756
+ for (const match of lastUser.matchAll(pattern)) {
5737
5757
  const fact = match[0].trim();
5738
- const contextKey = `fact:${fact.slice(0, 50).toLowerCase().replace(/\s+/g, "_")}`;
5739
- // Dedup: check if context_key already exists
5740
- const { data: existing } = await supabase.from("agent_memories")
5741
- .select("id").eq("user_id", userId).eq("agent_id", resolvedAgentId)
5742
- .eq("context_key", contextKey).is("superseded_by", null).limit(1);
5743
- if (existing && existing.length > 0) continue;
5744
-
5745
- await supabase.from("agent_memories").insert({
5746
- user_id: userId,
5747
- agent_id: resolvedAgentId,
5758
+ candidates.push({
5748
5759
  tier: "L3_pattern",
5749
5760
  content: fact,
5750
- source: "auto",
5761
+ contextKey: `fact:${fact.slice(0, 50).toLowerCase().replace(/\s+/g, "_")}`,
5751
5762
  importance: 6,
5752
- context_key: contextKey,
5753
5763
  });
5754
5764
  }
5755
5765
  }
5756
-
5757
- // Extract directives → L4_rule
5758
- const rulePatterns = [
5759
- /(?:always|never|make sure|don't|do not|please always)\s+(.+?)(?:\.|!|$)/gi,
5760
- /(?:remember to|keep in mind|note that)\s+(.+?)(?:\.|!|$)/gi,
5761
- ];
5762
-
5763
5766
  for (const pattern of rulePatterns) {
5764
- const matches = lastUser.matchAll(pattern);
5765
- for (const match of matches) {
5767
+ for (const match of lastUser.matchAll(pattern)) {
5766
5768
  const rule = match[0].trim();
5767
5769
  if (rule.length < 10) continue;
5768
- const contextKey = `rule:${rule.slice(0, 50).toLowerCase().replace(/\s+/g, "_")}`;
5769
- const { data: existing } = await supabase.from("agent_memories")
5770
- .select("id").eq("user_id", userId).eq("agent_id", resolvedAgentId)
5771
- .eq("context_key", contextKey).is("superseded_by", null).limit(1);
5772
- if (existing && existing.length > 0) continue;
5773
-
5774
- await supabase.from("agent_memories").insert({
5775
- user_id: userId,
5776
- agent_id: resolvedAgentId,
5770
+ candidates.push({
5777
5771
  tier: "L4_rule",
5778
5772
  content: rule,
5779
- source: "auto",
5773
+ contextKey: `rule:${rule.slice(0, 50).toLowerCase().replace(/\s+/g, "_")}`,
5780
5774
  importance: 7,
5781
- context_key: contextKey,
5782
5775
  });
5783
5776
  }
5784
5777
  }
5785
5778
 
5779
+ // Phase 2: Single batch dedup check (1 query for ALL candidates)
5780
+ if (candidates.length > 0) {
5781
+ const allKeys = candidates.map(c => c.contextKey);
5782
+ const { data: existingRows } = await supabase
5783
+ .from("agent_memories")
5784
+ .select("context_key")
5785
+ .eq("user_id", userId)
5786
+ .eq("agent_id", resolvedAgentId)
5787
+ .in("context_key", allKeys)
5788
+ .is("superseded_by", null);
5789
+
5790
+ const existingKeys = new Set((existingRows || []).map((r: any) => r.context_key));
5791
+
5792
+ // Phase 3: Batch insert only new candidates
5793
+ const toInsert = candidates
5794
+ .filter(c => !existingKeys.has(c.contextKey))
5795
+ .map(c => ({
5796
+ user_id: userId,
5797
+ agent_id: resolvedAgentId,
5798
+ tier: c.tier,
5799
+ content: c.content,
5800
+ source: "auto" as const,
5801
+ importance: c.importance,
5802
+ context_key: c.contextKey,
5803
+ decay_score: 1.0,
5804
+ }));
5805
+
5806
+ if (toInsert.length > 0) {
5807
+ await supabase.from("agent_memories").insert(toInsert);
5808
+ }
5809
+ }
5810
+
5786
5811
  api.logger.debug?.(`[ofiere-brain] Extracted memory for agent=${resolvedAgentId} session=${sessionKey || "none"}`);
5787
5812
  } catch (e) {
5788
5813
  // Silent — brain extraction must never block chat
@@ -5809,17 +5834,61 @@ function registerBrainExtractionHook(
5809
5834
  // OpenClaw on every prompt build, resolves the correct agent, and loads
5810
5835
  // that specific agent's brain. Each agent gets its own memories.
5811
5836
 
5837
+ // Background cache refresher for stale-while-revalidate pattern
5838
+ async function refreshBrainCache(
5839
+ supabase: SupabaseClient,
5840
+ userId: string,
5841
+ agentId: string,
5842
+ api: any,
5843
+ ): Promise<void> {
5844
+ try {
5845
+ const now = new Date().toISOString();
5846
+ const { data: allBrain } = await supabase
5847
+ .from("agent_memories")
5848
+ .select("content, importance, tier")
5849
+ .eq("user_id", userId)
5850
+ .eq("agent_id", agentId)
5851
+ .in("tier", ["L1_focus", "L4_rule", "L5_persona"])
5852
+ .gt("decay_score", 0.1)
5853
+ .is("superseded_by", null)
5854
+ .or(`expires_at.is.null,expires_at.gt.${now}`)
5855
+ .order("importance", { ascending: false })
5856
+ .limit(18);
5857
+
5858
+ const all = allBrain || [];
5859
+ const l1 = all.filter((m: any) => m.tier === "L1_focus").slice(0, 5);
5860
+ const l4 = all.filter((m: any) => m.tier === "L4_rule").slice(0, 10);
5861
+ const l5 = all.filter((m: any) => m.tier === "L5_persona").slice(0, 3);
5862
+
5863
+ if (l1.length === 0 && l4.length === 0 && l5.length === 0) {
5864
+ brainCache.set(agentId, { text: "", at: Date.now() });
5865
+ return;
5866
+ }
5867
+
5868
+ const sections: string[] = [];
5869
+ if (l5.length > 0) sections.push("### Identity (L5_persona)\n" + l5.map((m: any) => `- ${m.content}`).join("\n"));
5870
+ if (l4.length > 0) sections.push("### ⚠️ Guardrails (L4_rule — DO NOT violate)\n" + l4.map((m: any) => `- ${m.content}`).join("\n"));
5871
+ if (l1.length > 0) sections.push("### Active Focus (L1_focus)\n" + l1.map((m: any) => `- ${m.content}`).join("\n"));
5872
+
5873
+ brainCache.set(agentId, {
5874
+ text: `<agent-brain>\n## Your Brain Context (TMT)\n\n${sections.join("\n\n")}\n</agent-brain>`,
5875
+ at: Date.now(),
5876
+ });
5877
+ } catch (e) {
5878
+ api.logger.debug?.(`[ofiere-brain] Background cache refresh error: ${e instanceof Error ? e.message : e}`);
5879
+ }
5880
+ }
5881
+
5812
5882
  function registerBrainContextHook(
5813
5883
  api: any,
5814
5884
  supabase: SupabaseClient,
5815
5885
  userId: string,
5816
5886
  fallbackAgentId: string,
5817
5887
  ): void {
5818
- // Cache to avoid querying Supabase on every single prompt build.
5819
- // Key: resolved agent UUID. Value: { brainContext, loadedAt }.
5820
- // Cache TTL: 60 seconds — brain context refreshes at most once per minute.
5821
- const brainCache = new Map<string, { text: string; at: number }>();
5822
- const CACHE_TTL_MS = 60_000;
5888
+ // Uses module-scoped brainCache (declared at top of file) for cross-function
5889
+ // invalidation. save_memory calls brainCache.delete(agentId) for immediate consistency.
5890
+ const CACHE_TTL_MS = 600_000; // 10 min — brain context doesn't change fast
5891
+ const STALE_THRESHOLD_MS = 900_000; // 15 min serve stale while refreshing
5823
5892
 
5824
5893
  try {
5825
5894
  api.on("before_prompt_build", async (_event: any, ctx: any) => {
@@ -5839,45 +5908,42 @@ function registerBrainContextHook(
5839
5908
 
5840
5909
  if (!resolvedAgentId) return;
5841
5910
 
5842
- // ── Check cache ──
5911
+ // ── Check cache (stale-while-revalidate) ──
5843
5912
  const cached = brainCache.get(resolvedAgentId);
5844
- if (cached && Date.now() - cached.at < CACHE_TTL_MS) {
5845
- return cached.text ? { appendSystemContext: cached.text } : undefined;
5913
+ if (cached) {
5914
+ const age = Date.now() - cached.at;
5915
+ if (age < CACHE_TTL_MS) {
5916
+ // Fresh — serve immediately
5917
+ return cached.text ? { appendSystemContext: cached.text } : undefined;
5918
+ }
5919
+ if (age < STALE_THRESHOLD_MS) {
5920
+ // Stale — serve stale and refresh in background (non-blocking)
5921
+ refreshBrainCache(supabase, userId, resolvedAgentId, api).catch(() => {});
5922
+ return cached.text ? { appendSystemContext: cached.text } : undefined;
5923
+ }
5846
5924
  }
5847
5925
 
5848
5926
  // ── Query brain memories for THIS specific agent ──
5927
+ // v4.30.0: Single combined query instead of 3 separate tier queries.
5849
5928
  const now = new Date().toISOString();
5850
5929
 
5851
- const [l1Res, l4Res, l5Res] = await Promise.all([
5852
- supabase.from("agent_memories")
5853
- .select("content, importance")
5854
- .eq("user_id", userId)
5855
- .eq("agent_id", resolvedAgentId)
5856
- .eq("tier", "L1_focus")
5857
- .gt("decay_score", 0.1)
5858
- .or(`expires_at.is.null,expires_at.gt.${now}`)
5859
- .order("importance", { ascending: false })
5860
- .limit(5),
5861
- supabase.from("agent_memories")
5862
- .select("content, importance")
5863
- .eq("user_id", userId)
5864
- .eq("agent_id", resolvedAgentId)
5865
- .eq("tier", "L4_rule")
5866
- .is("superseded_by", null)
5867
- .order("importance", { ascending: false })
5868
- .limit(10),
5869
- supabase.from("agent_memories")
5870
- .select("content")
5871
- .eq("user_id", userId)
5872
- .eq("agent_id", resolvedAgentId)
5873
- .eq("tier", "L5_persona")
5874
- .order("importance", { ascending: false })
5875
- .limit(3),
5876
- ]);
5877
-
5878
- const l1 = l1Res.data || [];
5879
- const l4 = l4Res.data || [];
5880
- const l5 = l5Res.data || [];
5930
+ const { data: allBrain } = await supabase
5931
+ .from("agent_memories")
5932
+ .select("content, importance, tier")
5933
+ .eq("user_id", userId)
5934
+ .eq("agent_id", resolvedAgentId)
5935
+ .in("tier", ["L1_focus", "L4_rule", "L5_persona"])
5936
+ .gt("decay_score", 0.1)
5937
+ .is("superseded_by", null)
5938
+ .or(`expires_at.is.null,expires_at.gt.${now}`)
5939
+ .order("importance", { ascending: false })
5940
+ .limit(18); // 5 L1 + 10 L4 + 3 L5
5941
+
5942
+ // Client-side split by tier
5943
+ const all = allBrain || [];
5944
+ const l1 = all.filter((m: any) => m.tier === "L1_focus").slice(0, 5);
5945
+ const l4 = all.filter((m: any) => m.tier === "L4_rule").slice(0, 10);
5946
+ const l5 = all.filter((m: any) => m.tier === "L5_persona").slice(0, 3);
5881
5947
 
5882
5948
  if (l1.length === 0 && l4.length === 0 && l5.length === 0) {
5883
5949
  brainCache.set(resolvedAgentId, { text: "", at: Date.now() });