save_rule
Save mandatory rules that apply to every session, bypassing scoring and decay. Use for absolute requirements like 'ALWAYS do Y' or 'NEVER do X'.
Instructions
Save a mandatory rule that will ALWAYS be followed in every session. Rules bypass scoring and decay — they are injected into every conversation, every time. Use for absolute requirements like "NEVER do X" or "ALWAYS do Y".
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| content | Yes | The rule to enforce (e.g., "NEVER use -latest model aliases", "ALWAYS apply changes to both class-chat.php AND class-maas.php") | |
| subject | No | Short label for the rule, e.g. "model_aliases", "companions_parity" | |
| scope | No | global = applies to all projects, project = only this codebase | project |
| category | No | Category for organization: frontend, backend, security, general, etc. | general |
| tags | No | Optional tags for grouping rules | |
| project_id | No | Project identifier override (auto-detected from CLAUDE_PROJECT_DIR or git remote if omitted) |
Implementation Reference
- src/tools.ts:293-347 (handler)The handler function for the save_rule tool. It registers the tool on the MCP server with Zod schema for inputs (content, subject, scope, category, tags, project_id). The handler creates a memory with memory_type='rule' and importance=10, calls storage.saveMemory(), resets debt, and returns the result with merge suggestions.
server.tool( 'save_rule', 'Save a mandatory rule that will ALWAYS be followed in every session. Rules bypass scoring and decay — they are injected into every conversation, every time. Use for absolute requirements like "NEVER do X" or "ALWAYS do Y".', { content: z .string() .min(5) .max(10000) .describe('The rule to enforce (e.g., "NEVER use -latest model aliases", "ALWAYS apply changes to both class-chat.php AND class-maas.php")'), subject: z .string() .max(100) .default('') .describe('Short label for the rule, e.g. "model_aliases", "companions_parity"'), scope: z .enum(['global', 'project']) .default('project') .describe('global = applies to all projects, project = only this codebase'), category: z .string() .max(50) .default('general') .describe('Category for organization: frontend, backend, security, general, etc.'), tags: z .array(z.string().max(30)) .max(5) .optional() .describe('Optional tags for grouping rules'), project_id: z .string() .max(200) .optional() .describe('Project identifier override (auto-detected from CLAUDE_PROJECT_DIR or git remote if omitted)'), }, async ({ content, subject, scope, category, tags, project_id }) => { try { const projectId = project_id || detectProjectId(); const body: Record<string, unknown> = { content, memory_type: 'rule', category, subject, importance: 10, // Rules are always max importance scope, project_id: projectId, }; if (tags && tags.length > 0) body.tags = tags; const result = await storage.saveMemory(body); resetDebt(); return wrapResult(annotateMergeSuggestion(result)); } catch (error) { return wrapError(error); } } ); - src/tools.ts:296-326 (schema)Zod input schema for save_rule: content (string, 5-10000 chars), subject (string max 100), scope (enum global/project), category (string max 50), tags (array of strings max 5), project_id (optional string).
{ content: z .string() .min(5) .max(10000) .describe('The rule to enforce (e.g., "NEVER use -latest model aliases", "ALWAYS apply changes to both class-chat.php AND class-maas.php")'), subject: z .string() .max(100) .default('') .describe('Short label for the rule, e.g. "model_aliases", "companions_parity"'), scope: z .enum(['global', 'project']) .default('project') .describe('global = applies to all projects, project = only this codebase'), category: z .string() .max(50) .default('general') .describe('Category for organization: frontend, backend, security, general, etc.'), tags: z .array(z.string().max(30)) .max(5) .optional() .describe('Optional tags for grouping rules'), project_id: z .string() .max(200) .optional() .describe('Project identifier override (auto-detected from CLAUDE_PROJECT_DIR or git remote if omitted)'), }, - src/tools.ts:293-347 (registration)The save_rule tool is registered via server.tool('save_rule', ...) in the registerTools function (line 193), which is called from both index.ts (line 109) and http-server.ts (line 176).
server.tool( 'save_rule', 'Save a mandatory rule that will ALWAYS be followed in every session. Rules bypass scoring and decay — they are injected into every conversation, every time. Use for absolute requirements like "NEVER do X" or "ALWAYS do Y".', { content: z .string() .min(5) .max(10000) .describe('The rule to enforce (e.g., "NEVER use -latest model aliases", "ALWAYS apply changes to both class-chat.php AND class-maas.php")'), subject: z .string() .max(100) .default('') .describe('Short label for the rule, e.g. "model_aliases", "companions_parity"'), scope: z .enum(['global', 'project']) .default('project') .describe('global = applies to all projects, project = only this codebase'), category: z .string() .max(50) .default('general') .describe('Category for organization: frontend, backend, security, general, etc.'), tags: z .array(z.string().max(30)) .max(5) .optional() .describe('Optional tags for grouping rules'), project_id: z .string() .max(200) .optional() .describe('Project identifier override (auto-detected from CLAUDE_PROJECT_DIR or git remote if omitted)'), }, async ({ content, subject, scope, category, tags, project_id }) => { try { const projectId = project_id || detectProjectId(); const body: Record<string, unknown> = { content, memory_type: 'rule', category, subject, importance: 10, // Rules are always max importance scope, project_id: projectId, }; if (tags && tags.length > 0) body.tags = tags; const result = await storage.saveMemory(body); resetDebt(); return wrapResult(annotateMergeSuggestion(result)); } catch (error) { return wrapError(error); } } ); - src/tools.ts:289-347 (helper)The save_rule handler uses storage.saveMemory() as a helper, setting memory_type='rule' and importance=10 to create mandatory rules. It also calls resetDebt() and annotateMergeSuggestion() helpers.
// ─── 1b. save_rule ────────────────────────────────────── // Rules are mandatory memories that ALWAYS surface in every session. // They bypass scoring, decay, and relevance filtering. server.tool( 'save_rule', 'Save a mandatory rule that will ALWAYS be followed in every session. Rules bypass scoring and decay — they are injected into every conversation, every time. Use for absolute requirements like "NEVER do X" or "ALWAYS do Y".', { content: z .string() .min(5) .max(10000) .describe('The rule to enforce (e.g., "NEVER use -latest model aliases", "ALWAYS apply changes to both class-chat.php AND class-maas.php")'), subject: z .string() .max(100) .default('') .describe('Short label for the rule, e.g. "model_aliases", "companions_parity"'), scope: z .enum(['global', 'project']) .default('project') .describe('global = applies to all projects, project = only this codebase'), category: z .string() .max(50) .default('general') .describe('Category for organization: frontend, backend, security, general, etc.'), tags: z .array(z.string().max(30)) .max(5) .optional() .describe('Optional tags for grouping rules'), project_id: z .string() .max(200) .optional() .describe('Project identifier override (auto-detected from CLAUDE_PROJECT_DIR or git remote if omitted)'), }, async ({ content, subject, scope, category, tags, project_id }) => { try { const projectId = project_id || detectProjectId(); const body: Record<string, unknown> = { content, memory_type: 'rule', category, subject, importance: 10, // Rules are always max importance scope, project_id: projectId, }; if (tags && tags.length > 0) body.tags = tags; const result = await storage.saveMemory(body); resetDebt(); return wrapResult(annotateMergeSuggestion(result)); } catch (error) { return wrapError(error); } } );