This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/ICD/personas.md
2026-03-12 19:34:48 +00:00

244 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Personas
A **Persona** is the unit of access control and AI identity: system
prompt + model + provider config + KB bindings + grant scoping. Users
interact with Personas, not raw provider configs. Admins curate which
Personas are available; team admins create team-scoped Personas.
Three scopes, three route namespaces, one domain:
| Scope | Routes | Who manages |
|-------|--------|-------------|
| Personal | `GET/POST/PUT/DELETE /personas` | The user |
| Team | `GET/POST/PUT/DELETE /teams/:teamId/personas` | Team admin |
| Global (admin) | `GET/POST/PUT/DELETE /admin/personas` | Platform admin |
All three return the same Persona object shape:
```json
{
"id": "uuid",
"name": "Code Reviewer",
"handle": "code-reviewer",
"description": "Reviews code for bugs and style",
"icon": "🔍",
"avatar": "data:image/png;base64,...",
"base_model_id": "claude-sonnet-4-20250514",
"provider_config_id": "uuid|null",
"system_prompt": "You are a senior code reviewer...",
"temperature": 0.7,
"max_tokens": 4096,
"thinking_budget": null,
"top_p": null,
"scope": "personal|team|global",
"owner_id": "uuid|null",
"created_by": "uuid",
"is_active": true,
"is_shared": false,
"memory_enabled": false,
"memory_extraction_prompt": null,
"grants": [],
"kb_ids": [],
"created_at": "...",
"updated_at": "..."
}
```
`grants` and `kb_ids` are loaded from junction tables, not stored on the
persona row. All nullable fields (`provider_config_id`, `owner_id`,
`temperature`, `max_tokens`, `thinking_budget`, `top_p`,
`memory_extraction_prompt`) are omitted from JSON when null.
`handle` is auto-generated from `name` on creation if not provided
(e.g. "Code Reviewer" → "code-reviewer"). Used for @mention routing.
### Personal Personas
Gated by `allow_user_personas` policy.
```
GET /personas → { "data": [...] }
POST /personas ← { "name", "base_model_id", ... }
PUT /personas/:id ← partial update
DELETE /personas/:id
```
**Auth:** JWT required. `POST` requires `persona:create` permission.
`PUT`/`DELETE` require `persona:manage` permission and ownership check
(`scope == "personal"` and `owner_id == caller`).
### Team Personas
```
GET /teams/:teamId/personas → { "data": [...] }
POST /teams/:teamId/personas ← same shape
PUT /teams/:teamId/personas/:id ← partial update
DELETE /teams/:teamId/personas/:id
```
**Auth:** JWT required. Team membership for `GET`, team admin for
`POST`/`PUT`/`DELETE`. All mutating endpoints verify the persona belongs
to the team in the URL path (`scope == "team"` and `owner_id == teamId`).
### Admin (Global) Personas
```
GET /admin/personas → { "data": [...] }
POST /admin/personas ← same shape
PUT /admin/personas/:id
DELETE /admin/personas/:id
```
**Auth:** Admin JWT required (middleware enforced).
### Persona KB Bindings
A Persona can have Knowledge Bases bound to it. When a channel uses
that Persona, the bound KBs are automatically scoped into `kb_search`
tool calls. Users never see or manage the underlying KBs directly —
they talk to the Persona.
**Get bindings:**
```
GET /personas/:id/knowledge-bases → { "data": [KB objects] }
GET /teams/:teamId/personas/:id/knowledge-bases → { "data": [...] }
GET /admin/personas/:id/knowledge-bases → { "data": [...] }
```
**Set bindings** (replace all):
```
PUT /personas/:id/knowledge-bases
PUT /teams/:teamId/personas/:id/knowledge-bases
PUT /admin/personas/:id/knowledge-bases
```
```json
{
"kb_ids": ["kb-uuid-1", "kb-uuid-2"],
"auto_search": {
"kb-uuid-1": true,
"kb-uuid-2": false
}
}
```
`auto_search`: when `true`, the KB is always searched (prepended to
context). When `false`, it's available via the `kb_search` tool but
not auto-injected.
Team-scoped KB binding endpoints verify the persona belongs to the
team in the URL path before reading or writing bindings.
### Persona Avatars
Avatars are stored as `data:image/png;base64,...` data URIs in the
`avatar` column. Upload accepts a JSON body (not multipart):
```json
{ "image": "data:image/png;base64,..." }
```
The image is decoded, resized to 128×128 PNG, and stored.
```
POST /personas/:id/avatar ← { "image": "base64..." }
DELETE /personas/:id/avatar
POST /admin/personas/:id/avatar ← same
DELETE /admin/personas/:id/avatar
POST /teams/:teamId/personas/:id/avatar ← same (verify team ownership)
DELETE /teams/:teamId/personas/:id/avatar
```
**Auth:** Personal avatar routes verify the caller owns the persona
(`scope == "personal"` and `owner_id == caller`). Admin routes are
protected by admin middleware.
### Persona Tool Grants
Control which tools a persona can use during completions. The completion
handler applies tool grants as a second-pass allowlist.
```
GET /personas/:id/tool-grants → { "data": ["web_search", "kb_search", ...] }
PUT /personas/:id/tool-grants ← { "tool_names": ["web_search", "calculator"] }
```
Admin equivalents:
```
GET /admin/personas/:id/tool-grants
PUT /admin/personas/:id/tool-grants
```
Team equivalents (verify persona belongs to team):
```
GET /teams/:teamId/personas/:id/tool-grants
PUT /teams/:teamId/personas/:id/tool-grants
```
When a workflow version is published, persona tool grants at that moment
are frozen into the version snapshot.
### Persona Groups
Saved roster templates. A persona group is a named set of personas that
can be stamped onto new conversations.
```
GET /persona-groups → { "data": [...] }
POST /persona-groups ← { "name", "description" }
GET /persona-groups/:id → group with members
PUT /persona-groups/:id ← partial update
DELETE /persona-groups/:id
```
**Members:**
```
POST /persona-groups/:id/members ← { "persona_id", "is_leader": false }
DELETE /persona-groups/:id/members/:memberId
```
`is_leader`: the leader persona responds when no @mention is present in
a group channel. Only one leader per group. Setting a new leader
automatically clears the existing one.
**Persona Group Object:**
```json
{
"id": "uuid",
"name": "Support Team",
"description": "...",
"owner_id": "uuid",
"scope": "personal",
"team_id": null,
"members": [
{
"id": "uuid",
"group_id": "uuid",
"persona_id": "uuid",
"is_leader": true,
"sort_order": 0,
"persona_name": "...",
"persona_handle": "...",
"persona_avatar": "..."
}
],
"created_at": "...",
"updated_at": "..."
}
```
**Note:** Persona groups are currently personal-scope only. The `scope`
and `team_id` fields exist in the schema but `POST` hardcodes
`scope = 'personal'`. Team-scoped persona groups are a future item.
### Resource Grants
See [teams.md](teams.md) §15.3 for the grant system. Personas use grants
to control who can see and use them beyond their base scope.