mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
docs(personas): accuracy sweep — spec models, protocol contracts, page corrections
Spec models now describe what the endpoints do: ListPersonasResponse declares the tool_inventory the shelf depends on, both console create models declare persona, CreatePersonaRequest declares org_id, and UpdatePersonaRequest documents the null-vs-absent split (null clears base_prompt/tool_allowlist, null on flags/kinds is ignored). Console OpenAPI regenerated. Protocol contracts match the implementations: update_persona's return covers the no-op case, create_persona's raises-list is complete, and both extended row-shape docstrings gain their tail columns plus the append-only rule. The workstreams.persona comments say slug, not display name. Page corrections from the docs review: personas.md documents the creative_mode-to-writer migration conversion, the mid-session /resume MCP-lever behavior, visibility-based nudge gating, the soft-set prompt-cache cost, and the executive tool list — and drops internal jargon. The changelog entry moves under [Unreleased] with the house breaking-marker style and the auto-conversion note. coordinator-skills and the API tour stop using persona to mean framing; governance, api-reference, sdk, console, tools, and memory pick up the new permission family, endpoints, kwargs, picker, and lever caveats.
This commit is contained in:
+10
-4
@@ -13,9 +13,9 @@ stable, and the experimental line:
|
||||
- **`stable/1.6`** — patch-only (`v1.6.x`)
|
||||
- **`main`** — experimental (next major)
|
||||
|
||||
## [Unreleased — 1.7]
|
||||
## [Unreleased]
|
||||
|
||||
### Personas
|
||||
### Added
|
||||
|
||||
- **Personas** (#683) — a named, reusable bundle attached to a workstream
|
||||
at creation, controlling system-message composition and the capability
|
||||
@@ -31,13 +31,19 @@ stable, and the experimental line:
|
||||
`turnstone --persona <name>`); authored in the console's new
|
||||
Governance → Personas tab (`persona.{create,read,write}` perms,
|
||||
archive-only lifecycle). See `docs/personas.md`.
|
||||
- **BREAKING: `/creative` removed.** The REPL toggle (and its tab
|
||||
|
||||
### Removed
|
||||
|
||||
- **`/creative` removed** *(BREAKING)* — the REPL toggle (and its tab
|
||||
completion) is gone; the `writer` seed persona replaces it — start a
|
||||
session with `turnstone --persona writer` or pick *Writer* in the web
|
||||
pickers. Unlike the old fork, the writer persona composes the full
|
||||
system message, so session context and mandatory prompt policies now
|
||||
apply to prose-only sessions too. The `creative_mode` key in
|
||||
`workstream_config` is no longer read or written.
|
||||
`workstream_config` is no longer read or written. Migration `063`
|
||||
converts existing creative-mode workstreams to the `writer` persona
|
||||
automatically, so they resume as writing sessions rather than as
|
||||
legacy defaults.
|
||||
|
||||
## [1.6.0]
|
||||
|
||||
|
||||
@@ -698,6 +698,42 @@ Each skill summary:
|
||||
|
||||
---
|
||||
|
||||
### `GET /v1/api/personas`
|
||||
|
||||
Returns the enabled personas offered by the workstream-creation pickers.
|
||||
Authenticated for any logged-in user and deliberately gated by **no**
|
||||
`persona.*` permission — selecting a persona at creation is a user
|
||||
action, while the `persona.*` perms gate authoring. Display fields only;
|
||||
the levers (base prompt, tool set, MCP/memory toggles) stay server-side.
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"personas": [
|
||||
{"name": "engineer", "display_name": "Engineer", "description": "The stock interactive workstream: full tools, MCP, and memory.", "applies_to_kinds": ["interactive"], "is_default": true},
|
||||
{"name": "researcher", "display_name": "Researcher", "description": "Answers questions with evidence, read-only. Never modifies anything.", "applies_to_kinds": ["interactive"], "is_default": false}
|
||||
],
|
||||
"total": 2
|
||||
}
|
||||
```
|
||||
|
||||
Each persona summary:
|
||||
|
||||
| Field | Type | Description |
|
||||
|--------------------|--------|------------------------------------------------------------------|
|
||||
| `name` | string | Persona slug (used in the `persona` field on workstream creation) |
|
||||
| `display_name` | string | Human-readable label for pickers |
|
||||
| `description` | string | Short description of the persona's intent |
|
||||
| `applies_to_kinds` | array | Workstream kinds the persona applies to (`interactive` / `coordinator`) |
|
||||
| `is_default` | bool | Whether this is the default persona for its kind |
|
||||
|
||||
> **Note:** For full persona management (create, edit, archive), use the
|
||||
> admin endpoints at `/v1/api/admin/personas` (requires the
|
||||
> `persona.{create,read,write}` permissions).
|
||||
|
||||
---
|
||||
|
||||
### `POST /v1/api/workstreams/{ws_id}/send`
|
||||
|
||||
Sends a user message to a workstream. Spawns a daemon worker thread that calls
|
||||
@@ -895,6 +931,7 @@ All fields are optional. The body can be empty or an empty JSON object.
|
||||
| `auto_approve` | bool | false | Auto-approve all tool calls for this workstream |
|
||||
| `resume_ws` | string | "" | Workstream ID to resume atomically during creation (empty = fresh)|
|
||||
| `skill` | string | "" | Skill name. Applies content (system prompt), model, temperature, reasoning effort, max tokens, auto-approve policy, token budget, and other session config from the skill. Returns 400 if not found or disabled. Ignored when `resume_ws` is set (resumed sessions restore their own skill). |
|
||||
| `persona` | string | "" | Persona slug. Resolved and snapshotted into the workstream at creation; empty selects the kind's default. |
|
||||
| `judge_model` | string | "" | Optional model alias for the judge (overrides default judge model for this workstream) |
|
||||
|
||||
> **Skill behavior:** When `skill` is specified, the skill's content is injected as a system message and its session config fields (model, temperature, auto-approve, token budget, etc.) override system defaults for the new workstream.
|
||||
|
||||
+4
-3
@@ -379,6 +379,7 @@ Breadcrumb: `Cluster > Running` or `Cluster > db-west-04`. Server-side paginated
|
||||
Triggered by the "+ new" header button. A modal dialog with:
|
||||
|
||||
- **Node selector** — dropdown with three targeting modes: "Auto (best available)" picks the node with the most headroom, "General pool (any node)" picks a node with available capacity using round-robin, or a specific node from the list (showing capacity).
|
||||
- **Persona** — optional dropdown listing the enabled personas for the workstream kind. Sets the system-message composition and capability envelope at creation, snapshotted server-side; empty uses the kind's default. Picking one requires no `persona.*` permission.
|
||||
- **Profile** — optional dropdown listing enabled skills. Applies the skill's model, auto-approve policy, token budget, and other behavioral settings at creation time.
|
||||
- **Name** — optional text input. Auto-generated if left empty.
|
||||
- **Model** — optional text input for a model alias from the target node's registry.
|
||||
@@ -396,9 +397,9 @@ The browser maintains a local `clusterState` object that mirrors the cluster sna
|
||||
|
||||
Accessed via the "admin" button in the header (visible when authenticated
|
||||
with `approve` scope). Provides user, API token, channel link, MCP server,
|
||||
and skill management with 18 tabs (Users, API Tokens, Channels, Schedules,
|
||||
Watches, Roles, Policies, Prompts, Judge, Skills, MCP Servers, Usage,
|
||||
Audit, Memories, Models, Nodes, Settings, TLS). See also
|
||||
and skill management with tabs that include Users, API Tokens, Channels,
|
||||
Schedules, Watches, Personas, Roles, Policies, Prompts, Judge, Skills,
|
||||
MCP Servers, Usage, Audit, Memories, Models, Nodes, Settings, and TLS. See also
|
||||
[Governance](governance.md) for the Roles, Policies, Skills, Usage, and
|
||||
Audit tabs, and [Settings](settings.md) for the database-backed
|
||||
configuration editor.
|
||||
|
||||
@@ -366,7 +366,7 @@ deleted.
|
||||
|
||||
- [coordinator-skills.md](coordinator-skills.md) — writing a skill
|
||||
that runs on a coordinator session (orchestrator framing,
|
||||
workflow patterns, `SkillKind` classifier).
|
||||
workflow patterns, `SkillKind` classifier).
|
||||
- [bulk-endpoints.md](bulk-endpoints.md) — the two bulk-shape
|
||||
idioms (`{results, denied, truncated}` vs
|
||||
`{<bucket>, failed, skipped}`) used by `cluster/ws/live`,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Writing a coordinator-specific skill
|
||||
|
||||
Skills are prompt-level personas that steer a Turnstone session
|
||||
A skill is prompt-level framing that steers a Turnstone session
|
||||
toward a narrow task. Most skills target **interactive** sessions —
|
||||
the single-workstream "do this thing" surface where the model wields
|
||||
`bash`, `edit_file`, `web_fetch`, and the rest of the maker toolset.
|
||||
|
||||
A **coordinator skill** is different. It runs on a session whose job
|
||||
is to orchestrate other sessions. The toolset is smaller and
|
||||
narrower, the persona is an orchestrator instead of a maker, and the
|
||||
narrower, the role is an orchestrator instead of a maker, and the
|
||||
success metric is "did the plan resolve" instead of "did the code
|
||||
compile". This doc covers the differences a skill author has to
|
||||
care about.
|
||||
@@ -22,8 +22,8 @@ migration 044 added the column). Three values:
|
||||
|
||||
| `SkillKind` enum | Stored as | Meaning |
|
||||
|-------------------------|-----------------|----------------------------------------------------------------------------|
|
||||
| `SkillKind.INTERACTIVE` | `"interactive"` | Authored for the interactive maker persona (single-workstream "do this"). |
|
||||
| `SkillKind.COORDINATOR` | `"coordinator"` | Authored for the orchestrator persona (delegate, monitor, synthesise). |
|
||||
| `SkillKind.INTERACTIVE` | `"interactive"` | Authored for the interactive maker role (single-workstream "do this"). |
|
||||
| `SkillKind.COORDINATOR` | `"coordinator"` | Authored for the orchestrator role (delegate, monitor, synthesise). |
|
||||
| `SkillKind.ANY` | `"any"` | Either surface (or audience-neutral). Default on create. |
|
||||
|
||||
The `kind` field is a `StrEnum` — drop-in `str` compatible — so DB
|
||||
@@ -96,15 +96,15 @@ for the output. The coordinator stays the orchestrator.
|
||||
|
||||
---
|
||||
|
||||
## Persona differences
|
||||
## Framing differences
|
||||
|
||||
Interactive skills compose on top of `base_interactive.md` — a
|
||||
"maker" persona: get the work done, use the tools, edit the code,
|
||||
"maker" framing: get the work done, use the tools, edit the code,
|
||||
close the loop.
|
||||
|
||||
Coordinator skills compose on top of
|
||||
[`base_coordinator.md`](../turnstone/prompts/base_coordinator.md) —
|
||||
an "orchestrator" persona: decompose, delegate, monitor, synthesise.
|
||||
an "orchestrator" framing: decompose, delegate, monitor, synthesise.
|
||||
The base text is short but sets the tone every coordinator skill
|
||||
inherits:
|
||||
|
||||
@@ -339,7 +339,7 @@ For a new coordinator skill:
|
||||
A full end-to-end test isn't required for every skill; a
|
||||
prepare-step unit test that asserts "given this initial message, the
|
||||
first tool call is X with Y args" is usually sufficient to catch
|
||||
persona drift without a real LLM in the loop.
|
||||
framing drift without a real LLM in the loop.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+8
-3
@@ -13,7 +13,7 @@ The permission model has two layers:
|
||||
|
||||
1. **Scopes** (legacy) — `read`, `write`, `approve`. Checked by `AuthMiddleware`
|
||||
on every request based on URL path classification.
|
||||
2. **Permissions** (granular) — 15 permission strings checked per-endpoint by
|
||||
2. **Permissions** (granular) — named permission strings checked per-endpoint by
|
||||
`require_permission()`.
|
||||
|
||||
**Built-in roles** (seeded by migration 008):
|
||||
@@ -24,7 +24,11 @@ The permission model has two layers:
|
||||
| operator | read, write, workstreams.create, workstreams.close |
|
||||
| viewer | read |
|
||||
|
||||
Custom roles can be created with any subset of the 15 valid permissions.
|
||||
Custom roles can be created with any subset of the valid permissions.
|
||||
The `persona.create` / `persona.read` / `persona.write` family gates
|
||||
persona administration; migration `063` seeds all three onto
|
||||
`builtin-admin`, and any role can be granted them through the standard
|
||||
role and permission-override editors.
|
||||
|
||||
**Auth flow:**
|
||||
1. User logs in (password or API token) → `_load_user_permissions()` aggregates
|
||||
@@ -177,6 +181,7 @@ All under `/v1/api/admin/` (requires `approve` scope + granular permission).
|
||||
| Orgs | 3 (list, get, update) | `admin.orgs` |
|
||||
| Tool Policies | 4 (CRUD) | `admin.policies` |
|
||||
| Skills | 4 (CRUD) | `admin.skills` |
|
||||
| Personas | 4 (list, create, get, edit/archive) | `persona.read` / `persona.create` / `persona.write` |
|
||||
| Schedules | 6 (CRUD + runs) | `admin.schedules` |
|
||||
| Watches | 3 (list, create, cancel) | `admin.watches` |
|
||||
| Usage | 1 (aggregated query) | `admin.usage` |
|
||||
@@ -222,7 +227,7 @@ Both Python and TypeScript console SDKs expose governance methods:
|
||||
- **Privilege escalation prevented**: `admin_assign_role` blocks self-assignment
|
||||
and requires caller to hold a superset of the target role's permissions
|
||||
- **Permission validation**: Role create/update validates permissions against
|
||||
a 15-item allowlist (`_VALID_PERMISSIONS`)
|
||||
the permission allowlist (`_VALID_PERMISSIONS`)
|
||||
- **Self-deletion blocked**: `admin_delete_user` rejects attempts to delete
|
||||
your own account (matching the self-assignment guard on role endpoints)
|
||||
- **Field allowlists**: Storage `update_*` methods filter fields against
|
||||
|
||||
@@ -75,6 +75,11 @@ This means the model always has its most relevant memories available without
|
||||
explicit recall -- but can still use `memory(action='search')` for deeper
|
||||
lookup.
|
||||
|
||||
The persona memory lever gates this pathway: a workstream whose persona
|
||||
turns memory off receives no relevance injection at all -- the steps
|
||||
above run only when memory is enabled for the session. See
|
||||
[Personas](personas.md).
|
||||
|
||||
### Nudges
|
||||
|
||||
The metacognition layer can nudge the model to save memories at appropriate
|
||||
|
||||
+16
-6
@@ -12,9 +12,9 @@ A persona is exactly four levers — no more:
|
||||
| Lever | What it does |
|
||||
|---|---|
|
||||
| **Base prompt** | Replaces the BASE module of the composed system message (`base.md` / `base_coordinator.md`). *Only* BASE: ENV, CONTEXT, TOOLS, and POLICIES keep composing, so mandatory [prompt policies](governance.md) ride on top of every persona. Empty = the kind's stock base. |
|
||||
| **Tool visibility** | Which tools the session advertises. Tri-state: *unrestricted* (tracks tool growth and MCP catalogs), *no tools* (the TOOLS prompt block self-suppresses and zero definitions go on the wire), or an *exact set* of names. Including `tool_search` in a set makes it **soft** — tools the model discovers through search join the visible set; omitting it makes the set **hard** (the search pathway is disabled entirely). |
|
||||
| **Tool visibility** | Which tools the session advertises. Tri-state: *unrestricted* (tracks tool growth and MCP catalogs), *no tools* (the TOOLS prompt block self-suppresses and zero definitions go on the wire), or an *exact set* of names. Including `tool_search` in a set makes it **soft** — tools the model discovers through search join the visible set; omitting it makes the set **hard** (the search pathway is disabled entirely). On commercial providers a soft set costs one prompt-cache re-prime per `tool_search` expansion, since each expansion rewrites the wire tool set and recomposes the prompt. |
|
||||
| **MCP** | Whether the workstream talks to MCP at all. **Session-wide**: off means no MCP tools for the persona's own hands *or* for in-process task agents, no resource/prompt catalogs, and no listener registrations. This lever expresses infrastructure intent, not behavior shaping. |
|
||||
| **Memory** | Whether the persona's **own hands** get memory: recalled-memory injection into the prompt, memory-directed metacognitive nudges, and the `memory` tool. Task agents keep their own envelope, and compaction spill/markers are session mechanics that are never persona-gated. |
|
||||
| **Memory** | Whether the persona's **own hands** get memory: recalled-memory injection into the prompt, memory-directed metacognitive nudges, and the `memory` tool. Task agents keep their own envelope, and compaction spill/markers are session mechanics that are never persona-gated. An exact tool set that hides `memory` also mutes those nudges, and the compaction-resume pointer follows `recall`'s visibility. |
|
||||
|
||||
Visibility is behavior shaping, **not** a security boundary: any tool call
|
||||
that does reach the wire still clears the same approval, judge, and policy
|
||||
@@ -29,6 +29,12 @@ reads only the stamp:
|
||||
|
||||
- **Editing or archiving a persona never changes an existing workstream.**
|
||||
Rehydrate, resume, and post-compaction resume all run from the stamp.
|
||||
A mid-session REPL `/resume` adopts the target workstream's stamp for
|
||||
prompt, tools, and memory; for the MCP lever it can only narrow in
|
||||
place — adopting an MCP-off stamp drops the live MCP surface, while
|
||||
adopting an MCP-on stamp into a session whose persona dropped MCP at
|
||||
construction is refused with an error telling you to reopen the
|
||||
workstream fresh.
|
||||
- A workstream outlives its persona — an archived persona keeps labelling
|
||||
the workstreams stamped with it.
|
||||
- A partial or unparseable stamp is treated as corruption: session
|
||||
@@ -36,7 +42,10 @@ reads only the stamp:
|
||||
envelope the operator never chose.
|
||||
- Workstreams created before personas existed carry no stamp and keep
|
||||
legacy behavior, byte-identical to the `engineer` / `orchestrator`
|
||||
defaults below.
|
||||
defaults below — with one exception: pre-1.7 workstreams that had
|
||||
`creative_mode` set are converted by migration `063` into full
|
||||
`writer` stamps, so they resume as writing sessions rather than as
|
||||
legacy defaults.
|
||||
- Forking (`resume_ws` on create) resumes the source's stamped persona; the
|
||||
fork does not re-resolve.
|
||||
|
||||
@@ -53,7 +62,7 @@ personas existed:
|
||||
| `scribe` | interactive | custom (faithful structuring of given material) | none | off | off |
|
||||
| `researcher` | interactive | custom (evidence-first, read-only) | `read_file`, `search`, `web_fetch`, `web_search`, `recall`, `memory` (hard) | off | on |
|
||||
| `writer` | interactive | custom (creative writing partner — replaces the removed `/creative`) | none | off | on |
|
||||
| `executive` | coordinator | custom (delegate, interrogate plans, judge outcomes) | 10 delegation/inspection tools (hard) | off | on |
|
||||
| `executive` | coordinator | custom (delegate, interrogate plans, judge outcomes) | spawn/inspect/lifecycle tools plus `memory`: `spawn_workstream`, `spawn_batch`, `send_to_workstream`, `wait_for_workstream`, `inspect_workstream`, `list_workstreams`, `list_nodes`, `close_workstream`, `cancel_workstream`, `memory` (hard) | off | on |
|
||||
|
||||
Notes:
|
||||
|
||||
@@ -82,7 +91,8 @@ seeded):
|
||||
startup. `--resume` ignores `--persona` and adopts the resumed
|
||||
workstream's stamp.
|
||||
- **Coordinator spawn**: `spawn_workstream` / `spawn_batch` take a
|
||||
`persona` argument, validated at prep time (children are always
|
||||
`persona` argument, validated when the coordinator prepares the spawn
|
||||
and re-checked by the node that creates the child (children are always
|
||||
interactive-kind). Omitted means the interactive **default** — a child
|
||||
never inherits its parent coordinator's persona. Sub-agents spawned via
|
||||
`task_agent` have no persona parameter at all; they keep their own
|
||||
@@ -91,7 +101,7 @@ seeded):
|
||||
## Authoring (console)
|
||||
|
||||
Personas are managed in the console's **Manage → Governance → Personas**
|
||||
tab. The Service Hatch shelf exposes exactly the four levers plus the kind
|
||||
tab. The admin shelf exposes exactly the four levers plus the kind
|
||||
list, the default marker, and archive. Rules:
|
||||
|
||||
- `name` is an immutable lowercase slug; edit `display_name` instead.
|
||||
|
||||
+2
-2
@@ -69,7 +69,7 @@ Both `TurnstoneServer` (sync) and `AsyncTurnstoneServer` (async) expose:
|
||||
|----------|--------|---------|
|
||||
| **Workstreams** | `list_workstreams()` | `ListWorkstreamsResponse` |
|
||||
| | `dashboard()` | `DashboardResponse` |
|
||||
| | `create_workstream(*, name, model, auto_approve, skill, initial_message, attachments)` | `CreateWorkstreamResponse` |
|
||||
| | `create_workstream(*, name, model, auto_approve, skill, persona, initial_message, attachments)` | `CreateWorkstreamResponse` |
|
||||
| | `close_workstream(ws_id)` | `StatusResponse` |
|
||||
| **Attachments** | `upload_attachment(ws_id, filename, data, *, mime_type=...)` | `UploadAttachmentResponse` |
|
||||
| | `list_attachments(ws_id)` | `ListAttachmentsResponse` |
|
||||
@@ -100,7 +100,7 @@ Both `TurnstoneConsole` (sync) and `AsyncTurnstoneConsole` (async) expose:
|
||||
| | `workstreams(*, state, node, search, sort, page, per_page)` | `ClusterWorkstreamsResponse` |
|
||||
| | `node_detail(node_id)` | `NodeDetailResponse` |
|
||||
| | `snapshot()` | `ClusterSnapshotResponse` |
|
||||
| | `create_workstream(*, node_id, name, model, initial_message, skill)` | `ConsoleCreateWsResponse` |
|
||||
| | `create_workstream(*, node_id, name, model, initial_message, skill, persona)` | `ConsoleCreateWsResponse` |
|
||||
| **Schedules** | `list_schedules()` | `ListSchedulesResponse` |
|
||||
| | `create_schedule(*, name, schedule_type, initial_message, ...)` | `ScheduleInfo` |
|
||||
| | `get_schedule(task_id)` | `ScheduleInfo` |
|
||||
|
||||
@@ -580,6 +580,11 @@ Tool search uses the best available mechanism for each provider:
|
||||
`_exec_tool_search()` runs a pure-Python BM25 index over tool names and
|
||||
descriptions, then expands the matched tools into the visible set.
|
||||
|
||||
A persona with a tool-visibility set overrides this selection: any exact
|
||||
set forces tool search into the client-side BM25 mechanism (tier 3)
|
||||
regardless of provider, and a **hard** set — one whose visible tools omit
|
||||
`tool_search` — disables tool search entirely.
|
||||
|
||||
### Configuration
|
||||
|
||||
Tool search is configured in `config.toml` under the `[tools]` section:
|
||||
|
||||
@@ -7785,6 +7785,12 @@
|
||||
"title": "Skill",
|
||||
"type": "string"
|
||||
},
|
||||
"persona": {
|
||||
"default": "",
|
||||
"description": "Persona slug; resolved and snapshotted at creation, empty = kind default",
|
||||
"title": "Persona",
|
||||
"type": "string"
|
||||
},
|
||||
"resume_ws": {
|
||||
"default": "",
|
||||
"description": "Workstream ID to resume (loads previous conversation)",
|
||||
@@ -8112,6 +8118,12 @@
|
||||
"description": "Optional skill name to apply to the coordinator session.",
|
||||
"title": "Skill"
|
||||
},
|
||||
"persona": {
|
||||
"default": "",
|
||||
"description": "Persona slug; resolved and snapshotted at creation, empty = kind default",
|
||||
"title": "Persona",
|
||||
"type": "string"
|
||||
},
|
||||
"initial_message": {
|
||||
"default": "",
|
||||
"description": "Optional first user message dispatched to the new coordinator session.",
|
||||
@@ -11232,6 +11244,12 @@
|
||||
"default": true,
|
||||
"title": "Enabled",
|
||||
"type": "boolean"
|
||||
},
|
||||
"org_id": {
|
||||
"default": "",
|
||||
"description": "Owning org (informational; capped at 64)",
|
||||
"title": "Org Id",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -11241,7 +11259,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"UpdatePersonaRequest": {
|
||||
"description": "PATCH body \u2014 only fields present in the JSON are applied.\n\nArchive = ``{\"enabled\": false}``; default flip = ``{\"is_default\": true}``\non the successor (storage demotes the incumbent atomically). ``name``\nis immutable; existing workstreams are never affected by edits.",
|
||||
"description": "PATCH body \u2014 absent fields are left unchanged.\n\nExplicit ``null`` is meaningful only on the two resettable fields:\n``base_prompt: null`` clears the override back to the kind's stock\nBASE, and ``tool_allowlist: null`` resets to unrestricted. ``null``\non the boolean flags or ``applies_to_kinds`` is ignored (treated as\nabsent), so a client serializing unset optionals as null cannot\narchive a persona or flip levers by accident.\n\nArchive = ``{\"enabled\": false}``; default flip = ``{\"is_default\": true}``\non the successor (storage demotes the incumbent atomically). ``name``\nis immutable; existing workstreams are never affected by edits.",
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"anyOf": [
|
||||
@@ -11369,6 +11387,17 @@
|
||||
},
|
||||
"title": "Personas",
|
||||
"type": "array"
|
||||
},
|
||||
"tool_inventory": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"description": "Per-kind builtin tool names (plus the synthetic 'tool_search') for the visibility checklist \u2014 derived server-side so clients never hand-mirror the inventory",
|
||||
"title": "Tool Inventory",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -147,6 +147,10 @@ class ConsoleCreateWsRequest(BaseModel):
|
||||
default="", description="Optional first message sent after creation"
|
||||
)
|
||||
skill: str = Field(default="", description="Skill name (replaces default skills)")
|
||||
persona: str = Field(
|
||||
default="",
|
||||
description="Persona slug; resolved and snapshotted at creation, empty = kind default",
|
||||
)
|
||||
resume_ws: str = Field(
|
||||
default="", description="Workstream ID to resume (loads previous conversation)"
|
||||
)
|
||||
@@ -1085,10 +1089,18 @@ class CreatePersonaRequest(BaseModel):
|
||||
applies_to_kinds: list[str] = Field(default_factory=lambda: ["interactive"])
|
||||
is_default: bool = False
|
||||
enabled: bool = True
|
||||
org_id: str = Field(default="", description="Owning org (informational; capped at 64)")
|
||||
|
||||
|
||||
class UpdatePersonaRequest(BaseModel):
|
||||
"""PATCH body — only fields present in the JSON are applied.
|
||||
"""PATCH body — absent fields are left unchanged.
|
||||
|
||||
Explicit ``null`` is meaningful only on the two resettable fields:
|
||||
``base_prompt: null`` clears the override back to the kind's stock
|
||||
BASE, and ``tool_allowlist: null`` resets to unrestricted. ``null``
|
||||
on the boolean flags or ``applies_to_kinds`` is ignored (treated as
|
||||
absent), so a client serializing unset optionals as null cannot
|
||||
archive a persona or flip levers by accident.
|
||||
|
||||
Archive = ``{"enabled": false}``; default flip = ``{"is_default": true}``
|
||||
on the successor (storage demotes the incumbent atomically). ``name``
|
||||
@@ -1108,6 +1120,14 @@ class UpdatePersonaRequest(BaseModel):
|
||||
|
||||
class ListPersonasResponse(BaseModel):
|
||||
personas: list[PersonaInfo]
|
||||
tool_inventory: dict[str, list[str]] = Field(
|
||||
default_factory=dict,
|
||||
description=(
|
||||
"Per-kind builtin tool names (plus the synthetic 'tool_search') "
|
||||
"for the visibility checklist — derived server-side so clients "
|
||||
"never hand-mirror the inventory"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ModelReloadResponse(BaseModel):
|
||||
@@ -1243,6 +1263,10 @@ class CoordinatorCreateRequest(BaseModel):
|
||||
default=None,
|
||||
description="Optional skill name to apply to the coordinator session.",
|
||||
)
|
||||
persona: str = Field(
|
||||
default="",
|
||||
description="Persona slug; resolved and snapshotted at creation, empty = kind default",
|
||||
)
|
||||
initial_message: str = Field(
|
||||
default="",
|
||||
description="Optional first user message dispatched to the new coordinator session.",
|
||||
|
||||
@@ -413,12 +413,15 @@ class StorageBackend(Protocol):
|
||||
|
||||
Returns rows of ``(ws_id, alias, title, name, created, updated,
|
||||
message_count, node_id, state, kind, model_alias, launch_skill,
|
||||
child_count, context_tokens, context_window)`` ordered by updated
|
||||
DESC. The trailing enrichment columns feed the saved-list DTO:
|
||||
``model_alias`` / ``launch_skill`` come from ``workstream_config``;
|
||||
``context_tokens`` is the most recent ``usage_events`` prompt size
|
||||
and ``context_window`` the model's window (the caller divides them
|
||||
for the occupancy ratio); ``child_count`` counts child workstreams.
|
||||
child_count, context_tokens, context_window, project_id, user_id,
|
||||
persona)`` ordered by updated DESC. The trailing enrichment columns
|
||||
feed the saved-list DTO: ``model_alias`` / ``launch_skill`` come
|
||||
from ``workstream_config``; ``context_tokens`` is the most recent
|
||||
``usage_events`` prompt size and ``context_window`` the model's
|
||||
window (the caller divides them for the occupancy ratio);
|
||||
``child_count`` counts child workstreams. New columns MUST keep
|
||||
appending at the tail — a full-arity unpack in session_routes
|
||||
consumes this exact tuple.
|
||||
"""
|
||||
...
|
||||
|
||||
@@ -765,9 +768,10 @@ class StorageBackend(Protocol):
|
||||
Returns a list of SQLAlchemy ``Row`` objects. **Prefer dict access
|
||||
via ``row._mapping[<col>]``**; positional indexing is brittle against
|
||||
future SELECT reorders and against new columns appearing in the
|
||||
tail (the select currently ends with ``user_id, title, alias`` —
|
||||
``title``/``alias`` were appended after ``user_id`` so existing
|
||||
positional fallbacks that index up to row[9] stay valid).
|
||||
tail (the select currently ends with ``user_id, title, alias,
|
||||
project_id, persona`` — appended in that order, so positional
|
||||
fallbacks that index up to row[9] stay valid; new columns MUST
|
||||
keep appending at the tail).
|
||||
"""
|
||||
...
|
||||
|
||||
@@ -2421,11 +2425,17 @@ class StorageBackend(Protocol):
|
||||
def create_persona(self, persona: dict[str, Any]) -> None:
|
||||
"""Create a persona. Requires ``persona_id`` and ``name``; accepts the
|
||||
Python-typed dict shape above (JSON serialization is internal).
|
||||
Raises ValueError on duplicate name or invalid ``applies_to_kinds``."""
|
||||
Raises ValueError on: missing ``persona_id``/``name``, duplicate
|
||||
name, invalid ``applies_to_kinds``, oversized fields (caps live in
|
||||
``_utils.serialize_persona_fields``), or an ``is_default`` persona
|
||||
that is multi-kind or disabled."""
|
||||
...
|
||||
|
||||
def update_persona(self, persona_id: str, **fields: Any) -> bool:
|
||||
"""Update PERSONA_MUTABLE fields. Returns True if the persona exists.
|
||||
"""Update PERSONA_MUTABLE fields. Returns True only when the persona
|
||||
exists AND at least one mutable field was supplied — a no-op call on
|
||||
a real row returns False (check existence separately if you need to
|
||||
distinguish "not found" from "nothing to update").
|
||||
|
||||
Invariants (raise ValueError): a default persona cannot be archived,
|
||||
cannot drop its ``is_default`` flag directly (flip the flag on the
|
||||
|
||||
@@ -109,10 +109,11 @@ workstreams = sa.Table(
|
||||
# constraint (this schema family declares none — see migration 058).
|
||||
# Added in migration 062.
|
||||
sa.Column("project_id", sa.Text, nullable=True),
|
||||
# persona: display name of the persona the workstream was created with
|
||||
# (NULL = pre-persona workstream). Display/forensics only — the full
|
||||
# persona snapshot lives in workstream_config; nothing reads this column
|
||||
# to build a session. Added in migration 063.
|
||||
# persona: SLUG of the persona the workstream was created with (NULL =
|
||||
# pre-persona workstream) — personas.name, not display_name; clients
|
||||
# resolve the display label. Display/forensics only — the full persona
|
||||
# snapshot lives in workstream_config; nothing reads this column to
|
||||
# build a session. Added in migration 063.
|
||||
sa.Column("persona", sa.Text, nullable=True),
|
||||
sa.Column("created", sa.Text, nullable=False),
|
||||
sa.Column("updated", sa.Text, nullable=False),
|
||||
|
||||
Reference in New Issue
Block a user