feat(ai-chat): agent roles (admin persona + optional model) #11
Reference in New Issue
Block a user
Delete Branch "feat/ai-agent-roles"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements
docs/ai-agent-roles-plan.md(v1 scope).What
Reusable, workspace-shared agent roles for the built-in AI chat. A role = a named persona (system-prompt
instructions) + emoji/description + optional model override. A new chat is bound to a role at creation; the role applies on every turn (persona + optional model), with a badge in the UI.How
Backend
20260620T120000-ai-agent-roles.ts: addsai_agent_roles(workspace-scoped, soft-delete) +ai_chats.role_id(ON DELETE SET NULL). Additive only.db.d.ts/entity.types.tsare hand-merged (this repo keepsdb.d.tshand-curated; a fullmigration:codegenregenerates all 41 tables and clobbers the partial hand-maintained types — so I ran codegen to confirm the emitted shape, then surgically addedAiAgentRoles+AiChats.roleId+ theDBmap entry).core/ai-chat/roles/CRUD module. CASL:list= any workspace member (for the picker);create/update/delete= admin (Manage Settings, mirroringai-settings/mcp-servers). Every repo query scoped byworkspace_id; validation: non-empty name+instructions, override driver ∈openai|gemini|ollama.buildSystemPrompt(roleInstructions?): role replaces the persona base (admin prompt /DEFAULT_PROMPT), but workspace context +SAFETY_FRAMEWORKare always appended (a role can't drop the safety layer).stream()resolves the role fromai_chats.role_idfor existing chats (never the request body → no per-turn role swap);body.roleIdis honored only at chat creation. Disabled (enabled=false) and soft-deleted roles fall back to the universal assistant.getChatModel(workspaceId, override): a rolemodel_configcan swap the model id / driver; a driver without configured credentials throws 503 with a clear message naming the driver + role, resolved before response hijack (never mid-stream).Client
roleIdsent only on the first message. Role badge (emoji+name) in the chat header + conversation list. Admin "Agent roles" management section in Settings → AI (add/edit/delete via the MCP-form modal pattern). New query hooks +IAiRoletypes; 21 i18n keys.Reasoning / decisions (resolved the plan's open questions)
/api/ai-chat/roles; role replaces persona (so a narrow role like "Proofreader" dominates) while safety stays; soft-delete; 503 (not silent fallback) on a misconfigured override driver — explicitness over surprise; no seed presets and no tool gating (kept v1 tight); badge via JOIN (no denormalization).Review findings & fixes
Ran an adversarial backend review. It explicitly confirmed the three critical scenarios are closed: a non-admin cannot create/edit/delete roles; cross-workspace role access is blocked (all queries
workspace_id-scoped, foreignroleIdresolves to null at bind); and an existing chat's role cannot be swapped per-request (resolved from the stored row). Migration/safety/503 verified correct. Findings fixed:resolveRoleForRequestfiltered onlydeleted_at, so a disabled role kept applying to existing chats. Fixed server-authoritatively (if (!role || !role.enabled) return null), and the chat picker now filters to enabled roles (settings list still shows all for management).ai.service.spec.tsasserting an override on an unconfigured driver throwsAiNotConfiguredExceptionwith the driver+role name.Verification
pnpm --filter server build+pnpm --filter client build— clean.pnpm --filter server test -- ai-chat(17) +ai.service(1) — pass. Prompt spec covers role layering + safety-always-present (incl. a jailbreak instruction).🤖 Generated with Claude Code
20a1780977to24bf0ab18f24bf0ab18fto20a1780977