diff --git a/README.md b/README.md index 696d6434..0fafbdfd 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Turnstone gives LLMs tools — shell, files, search, web, planning — and orche - **Cluster dashboard** — real-time view of all nodes and workstreams with console routing proxy - **Intent validation** — LLM judge evaluates every tool call with risk assessments and evidence - **Governance** — RBAC, OIDC SSO, tool policies, skills, usage tracking, audit logs -- **Multi-provider** — OpenAI-compatible APIs (vLLM, llama.cpp, NIM) and Anthropic Messages API +- **Multi-provider** — OpenAI-compatible APIs (vLLM, llama.cpp, NIM), Anthropic Messages API, and Google Gemini - **MCP support** — external tool servers with native deferred loading (Anthropic/OpenAI) or BM25 fallback
@@ -132,7 +132,7 @@ UML diagrams in [`docs/diagrams/`](docs/diagrams/):
## Requirements
- Python 3.11+
-- An OpenAI-compatible API endpoint or Anthropic API key
+- An OpenAI-compatible API endpoint, Anthropic API key, or Google Gemini API key
- Optional: PostgreSQL (`pip install turnstone[postgres]`), Anthropic (`pip install turnstone[anthropic]`)
- [Git LFS](https://git-lfs.com/) for cloning (diagram PNGs)
diff --git a/docs/api-reference.md b/docs/api-reference.md
index 034f216a..f0d6a8df 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -857,6 +857,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). |
+| `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.
@@ -914,6 +915,161 @@ Status code: `400`
---
+### `POST /v1/api/workstreams/{ws_id}/delete`
+
+Permanently delete a saved workstream and all its messages from storage.
+
+**Path parameters:**
+
+| Parameter | Type | Description |
+|-----------|--------|----------------------|
+| `ws_id` | string | Workstream ID |
+
+**Response (success):** `200`
+
+```json
+{"deleted": "a1b2c3d4"}
+```
+
+**Response (not found):** `404`
+
+```json
+{"error": "Workstream not found"}
+```
+
+---
+
+### `POST /v1/api/workstreams/{ws_id}/open`
+
+Load a saved workstream into memory with its original `ws_id`. If the
+workstream is already loaded, returns immediately with `already_loaded: true`.
+
+**Path parameters:**
+
+| Parameter | Type | Description |
+|-----------|--------|----------------------|
+| `ws_id` | string | Workstream ID |
+
+**Response (success):** `200`
+
+```json
+{"ws_id": "a1b2c3d4", "name": "refactor"}
+```
+
+**Response (already loaded):** `200`
+
+```json
+{"ws_id": "a1b2c3d4", "name": "refactor", "already_loaded": true}
+```
+
+---
+
+### `POST /v1/api/workstreams/{ws_id}/title`
+
+Set a workstream title manually. The title is stored as the workstream alias.
+
+**Path parameters:**
+
+| Parameter | Type | Description |
+|-----------|--------|----------------------|
+| `ws_id` | string | Workstream ID |
+
+**Request body:**
+
+```json
+{"title": "JWT Authentication Refactor"}
+```
+
+| Field | Type | Required | Description |
+|---------|--------|----------|------------------------|
+| `title` | string | yes | New workstream title |
+
+**Response (success):** `200`
+
+```json
+{"status": "ok", "title": "JWT Authentication Refactor"}
+```
+
+**Response (conflict):** `409`
+
+```json
+{"error": "That name is already used by another workstream"}
+```
+
+---
+
+### `POST /v1/api/workstreams/{ws_id}/refresh-title`
+
+Regenerate the workstream title via LLM based on conversation content.
+
+**Path parameters:**
+
+| Parameter | Type | Description |
+|-----------|--------|----------------------|
+| `ws_id` | string | Workstream ID |
+
+**Response (success):** `200`
+
+```json
+{"status": "ok"}
+```
+
+---
+
+### `GET /v1/api/admin/settings`
+
+List `interface.*` settings with their current values and sources. Requires
+`read` scope on the server.
+
+**Response:** `200`
+
+```json
+{
+ "settings": [
+ {
+ "key": "interface.close_tab_action",
+ "value": "last_used",
+ "source": "default",
+ "type": "str",
+ "description": "Determines which workstream to switch to after closing a tab."
+ }
+ ]
+}
+```
+
+---
+
+### `POST|PUT /v1/api/admin/settings/{key}`
+
+Update an `interface.*` setting. Only keys in the `interface` section are
+accepted; other keys return `400`.
+
+**Path parameters:**
+
+| Parameter | Type | Description |
+|-----------|--------|-------------------------------------|
+| `key` | string | Setting key (e.g. `interface.theme`) |
+
+**Request body:**
+
+```json
+{"value": "light"}
+```
+
+| Field | Type | Required | Description |
+|---------|------|----------|----------------|
+| `value` | any | yes | New value |
+
+**Response (success):** `200`
+
+```json
+{"status": "ok", "key": "interface.theme", "value": "light"}
+```
+
+**Error:** `400` if the key is not in the `interface` section.
+
+---
+
### `GET /v1/api/watches`
List active watches on this server node. Optionally filter by workstream.
diff --git a/docs/architecture.md b/docs/architecture.md
index 43bc0f4c..1f342377 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -38,6 +38,7 @@ turnstone/
_protocol.py LLMProvider protocol, ModelCapabilities, StreamChunk, CompletionResult
_openai.py OpenAIProvider — OpenAI, vLLM, llama.cpp, any compatible API
_anthropic.py AnthropicProvider — Anthropic Messages API, native streaming, thinking
+ _google.py GoogleProvider — Google Gemini via OpenAI-compat endpoint
__init__.py create_provider() + create_client() factory functions
workstream.py Parallel workstream manager (WorkstreamState, Workstream, WorkstreamManager)
tools.py Tool schema loader (JSON -> OpenAI function-calling format)
@@ -593,6 +594,7 @@ LLMProvider (protocol)
|
+--- OpenAIProvider --- OpenAI, vLLM, llama.cpp, any /v1/chat/completions API
+--- AnthropicProvider --- Anthropic Messages API (native streaming, thinking)
+ +--- GoogleProvider --- Google Gemini via /v1beta/openai/ (extends OpenAIProvider)
```
**Protocol methods:**
@@ -646,6 +648,13 @@ both streaming and non-streaming responses. The `anthropic` SDK is imported
lazily so it remains an optional dependency (`pip install
turnstone[anthropic]`).
+**GoogleProvider** (`_google.py`): extends `OpenAIChatCompletionsProvider` for
+the Gemini `/v1beta/openai/` endpoint. Uses a single default
+`ModelCapabilities` (2M context window, 65K max output tokens,
+`token_param=max_tokens`) since Google updates models frequently. No static
+per-model capability table. Google's endpoint is wire-compatible with the
+OpenAI SDK, so no extra dependency is needed.
+
**Factory functions** (`__init__.py`): `create_provider(name)` returns a
singleton provider instance (thread-safe). `create_client(name, base_url,
api_key)` creates the appropriate SDK client.
@@ -674,6 +683,10 @@ api_key = "sk-..."
model = "gpt-5"
context_window = 400000
+[models.gemini]
+provider = "google"
+model = "gemini-2.5-pro"
+
[model]
default = "local"
fallback = ["claude", "openai"]
@@ -681,7 +694,8 @@ agent_model = "claude"
```
Each `[models.*]` entry produces a `ModelConfig` with a `provider` field
-(default: `"openai"`). Supported values: `"openai"` and `"anthropic"`.
+(default: `"openai"`). Supported values: `"openai"`, `"anthropic"`, `"google"`,
+and `"openai-compatible"`.
An optional `[models.*.capabilities]` sub-table overrides per-model
`ModelCapabilities` flags (useful for local models whose capabilities
cannot be detected programmatically):
diff --git a/docs/console.md b/docs/console.md
index 0f347237..d1b4c367 100644
--- a/docs/console.md
+++ b/docs/console.md
@@ -382,6 +382,9 @@ Triggered by the "+ new" header button. A modal dialog with:
- **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.
+- **Judge Model** — optional text input for the judge model alias (overrides the default judge model for this workstream).
+
+Keyboard shortcuts: Ctrl+Shift+R (refresh title), Ctrl+Shift+E (edit title), Ctrl+Shift+F (fork), Ctrl+Shift+X (delete). Press ? for full shortcut help.
On submit, `POST /v1/api/cluster/workstreams/new` dispatches the creation request. A toast confirms success; the SSE stream delivers the `ws_created` event to update the dashboard.
diff --git a/docs/diagrams/02-package-structure.puml b/docs/diagrams/02-package-structure.puml
index c053952c..51d59551 100644
--- a/docs/diagrams/02-package-structure.puml
+++ b/docs/diagrams/02-package-structure.puml
@@ -25,7 +25,7 @@ package "Entry Points" <