feat(settings): personal rules added to system prompts#944
Conversation
Personal rules are user-authored directives stored in the existing memories table under a new 'personal_rule' category, scoped per project. They are injected into the system prompt (User Rules > Personal Rules), managed from a new Settings > Rules page, hidden from the Memory page, and exposed to the context recommendation system via the v_memories view so de-facto rules can be promoted to shared context.
🚀 Preview Deployment
Preview will be automatically removed when this PR is closed. |
| if (!content) { | ||
| throw new TRPCError({ code: 'BAD_REQUEST', message: 'Rule content cannot be empty.' }); | ||
| } | ||
| const updated = await memoryQueries.updateUserMemoryContent(ctx.user.id, input.ruleId, content); |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
rule.update and rule.delete delegate to generic memory mutators keyed only by userId + ruleId. These routes are project-scoped features, but the write/delete path does not enforce projectId (or personal_rule category), so a known rule ID from another project can still be modified or deleted.
Impact: project-level isolation for personal rules can be bypassed, enabling cross-project rule tampering.
Reviewed by Cursor Security Reviewer for commit bf4eccc. Configure here.
There was a problem hiding this comment.
3 issues found across 23 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/backend/src/trpc/rule.routes.ts">
<violation number="1" location="apps/backend/src/trpc/rule.routes.ts:39">
P1: Update and delete mutations lack project scoping — they don't pass `ctx.project.id`, so a user could modify or delete personal rules from other projects within the same user scope. Breaks the per-project isolation intent.</violation>
</file>
<file name="apps/backend/src/components/ai/system-prompt.tsx">
<violation number="1" location="apps/backend/src/components/ai/system-prompt.tsx:200">
P3: Missing `key` prop on `ListItem` in `personalRules.map()`. React requires a stable key for list reconciliation; omitting it causes unnecessary re-renders and potential state bugs if the list can change.</violation>
</file>
<file name="apps/backend/migrations-sqlite/0050_add_memories_project_id.sql">
<violation number="1" location="apps/backend/migrations-sqlite/0050_add_memories_project_id.sql:1">
P1: SQLite migration misses `ON DELETE CASCADE` for `memories.project_id`, causing schema/migration mismatch and potentially leaving project-scoped memories undeleted.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| if (!content) { | ||
| throw new TRPCError({ code: 'BAD_REQUEST', message: 'Rule content cannot be empty.' }); | ||
| } | ||
| const updated = await memoryQueries.updateUserMemoryContent(ctx.user.id, input.ruleId, content); |
There was a problem hiding this comment.
P1: Update and delete mutations lack project scoping — they don't pass ctx.project.id, so a user could modify or delete personal rules from other projects within the same user scope. Breaks the per-project isolation intent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/src/trpc/rule.routes.ts, line 39:
<comment>Update and delete mutations lack project scoping — they don't pass `ctx.project.id`, so a user could modify or delete personal rules from other projects within the same user scope. Breaks the per-project isolation intent.</comment>
<file context>
@@ -0,0 +1,62 @@
+ if (!content) {
+ throw new TRPCError({ code: 'BAD_REQUEST', message: 'Rule content cannot be empty.' });
+ }
+ const updated = await memoryQueries.updateUserMemoryContent(ctx.user.id, input.ruleId, content);
+ if (!updated) {
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Rule not found.' });
</file context>
| @@ -0,0 +1,2 @@ | |||
| ALTER TABLE `memories` ADD `project_id` text REFERENCES project(id);--> statement-breakpoint | |||
There was a problem hiding this comment.
P1: SQLite migration misses ON DELETE CASCADE for memories.project_id, causing schema/migration mismatch and potentially leaving project-scoped memories undeleted.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/migrations-sqlite/0050_add_memories_project_id.sql, line 1:
<comment>SQLite migration misses `ON DELETE CASCADE` for `memories.project_id`, causing schema/migration mismatch and potentially leaving project-scoped memories undeleted.</comment>
<file context>
@@ -0,0 +1,2 @@
+ALTER TABLE `memories` ADD `project_id` text REFERENCES project(id);--> statement-breakpoint
+CREATE INDEX `memories_projectId_idx` ON `memories` (`project_id`);
\ No newline at end of file
</file context>
| <Title level={3}>Personal Rules</Title> | ||
| <Span>These rules were defined by the user in their settings. Always follow them.</Span> | ||
| <List> | ||
| {personalRules.map((rule) => ( |
There was a problem hiding this comment.
P3: Missing key prop on ListItem in personalRules.map(). React requires a stable key for list reconciliation; omitting it causes unnecessary re-renders and potential state bugs if the list can change.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/src/components/ai/system-prompt.tsx, line 200:
<comment>Missing `key` prop on `ListItem` in `personalRules.map()`. React requires a stable key for list reconciliation; omitting it causes unnecessary re-renders and potential state bugs if the list can change.</comment>
<file context>
@@ -186,10 +188,21 @@ export function SystemPrompt({
+ <Title level={3}>Personal Rules</Title>
+ <Span>These rules were defined by the user in their settings. Always follow them.</Span>
+ <List>
+ {personalRules.map((rule) => (
+ <ListItem>{rule.content}</ListItem>
+ ))}
</file context>
…-settings-cae6 # Conflicts: # apps/backend/migrations-postgres/meta/0050_snapshot.json # apps/backend/migrations-postgres/meta/_journal.json # apps/backend/migrations-sqlite/meta/0050_snapshot.json # apps/backend/migrations-sqlite/meta/_journal.json




Problem
Closes #936. Users had no way to define personal rules/preferences that shape how nao responds — they could only edit memories the agent already created.
What this adds
User Rules → Personal Rules, layered on top of the global project rules (RULES.md).Implementation notes
Per the issue suggestion, this reuses the existing
memoriestable instead of creating a new one:personal_rule(apps/backend/src/types/memory.ts).project_idcolumn tomemories(sqlite + postgres migrations0050) to scope personal rules to a project.user.getMemoriesnow excludes them) and surfaced only on the new Rules page.ruletRPC router (list/create/update/delete), project-scoped.User Rulessection (system-prompt.tsx).v_memoriesview now also exposesproject_idand includes project-scoped rows soquery_app_db/ the recommendation bot can read personal rules; the recommendation prompt now calls outcategory = "personal_rule"for promotion to shared context.Testing
npm run lint(backend, frontend, shared) ✅npm run -w @nao/backend db:check-migrations✅mainunrelated to this change).