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

6.8 KiB
Raw Blame History

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:

{
  "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
{
  "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):

{ "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:

{
  "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 §15.3 for the grant system. Personas use grants to control who can see and use them beyond their base scope.