Sixteen-finder review (4 dimensions x 4 subsystem slices) + adversarial verify: 16/16 findings confirmed, all fixed. Majors: - The schedule preview (and create/update via _compute_next_run) 500'd on syntactically-valid-but-impossible cron dates: croniter.is_valid passes '0 0 30 2 *' but get_next raises CroniterBadDateError. One _next_cron_runs helper now owns construction + the guard for both paths; the preview answers its 200/valid:false contract, and next[] is one shape (the cron branch now carries the UTC offset the 'at' branch always had). - The batch-delete results view tore down (exit delete mode, refresh the stale list) only via the footer Close — header ✕ / Escape / backdrop left deleted rows on screen and the mode stuck. The teardown moved onto the dialog's onClose (gated by a resultsShown flag so a pre-delete cancel keeps the selection), and Close just closes. - The model shelf's Server-compatibility section was permanently invisible: the one hidden-attr element still toggled via style.display, which cannot beat .hatch [hidden] !important — openai-compatible operators could never reach server type / API surface / extra-body. Now .hidden like its siblings. Minors: shelves prune detached entries when a pane closes mid-edit (state map + Escape-listener leak); the capabilities autofill gains the _schPreviewSeq stale-response guard; the rename dialog focuses its input before select() (select() does not move focus per spec — Enter landed on the ✕); .mcp-install-source-label becomes the fourth protected label component; nine write-only shelf-handle vars dropped; one alert region gets one name; orphaned .modal-col-heading CSS, the stale toast z-index rationale, a dangling divider comment, and a comment chasing the renamed _submitRoleShelf all cleaned. Regression tests pin the Feb-30 preview, the create-path guard, and the uniform next[] shape.
Turnstone
Self-hosted, local-first orchestration for tool-using AI agents. Give LLMs real tools — shell, files, search, web — and run them across your own cluster with direct HTTP routing and interactive interfaces. Your code, your models, your data stay on hardware you control: no telemetry, no phone-home.
Named after the Ruddy Turnstone (Arenaria interpres) — a shorebird that flips stones to discover what's hiding underneath.
Release Tracks
| Track | Install | Docker | Description |
|---|---|---|---|
| Stable | pip install turnstone |
ghcr.io/turnstonelabs/turnstone:stable |
Production-grade. Bugfixes only. |
| Experimental | pip install turnstone --pre |
ghcr.io/turnstonelabs/turnstone:experimental |
New features. May have rough edges. |
See docs/releasing.md for the full release process.
What it does
Turnstone gives LLMs tools — shell, files, search, web, planning — and orchestrates multi-turn conversations where the model investigates, acts, and reports.
- Local-first & private — runs entirely on hardware you control, with no telemetry and no phone-home. Point it at local models (vLLM, llama.cpp, Ollama) or commercial APIs you hold the keys to — your prompts and data never transit a third party you didn't choose.
- Bring your own models — OpenAI-compatible APIs (vLLM, llama.cpp, NIM), the Anthropic Messages API, and Google Gemini, mixed freely per role
- Interactive sessions — terminal CLI or browser UI with parallel workstreams
- Cluster dashboard — real-time view of every node and workstream, with a rendezvous routing proxy
- Intent validation — an LLM judge (your model) grades every tool call with a risk assessment and evidence before it runs
- MCP support — external tool servers with native deferred loading (Anthropic/OpenAI) or BM25 fallback
- Team controls when you need them — optional RBAC, SSO, tool policies, and audit logs, all stored in your own database
Quickstart
pip install turnstone
# Terminal REPL
turnstone --base-url http://localhost:8000/v1
# Browser UI
turnstone-server --port 8080 --base-url http://localhost:8000/v1
# Cluster dashboard
turnstone-console --port 8090
For PostgreSQL (recommended for production):
export TURNSTONE_DB_BACKEND=postgresql
export TURNSTONE_DB_URL="postgresql+psycopg://user:pass@localhost:5432/turnstone"
turnstone-server --port 8080 --base-url http://localhost:8000/v1
Docker
One-line install — autodetects Ubuntu/Debian, Fedora/RHEL, Arch, and WSL, installs git + Docker if missing, generates secrets, and starts the stack:
curl -fsSL https://raw.githubusercontent.com/turnstonelabs/turnstone/main/run.sh | bash
Or, if you already have Docker, clone the repo and run it yourself:
docker compose up
That builds one image and brings up a full local cluster — PostgreSQL, console,
Caddy, channel gateway, and 10 server nodes — with no .env required (it ships
with insecure dev defaults). Open the dashboard at https://localhost:8443 (Caddy
serves it over TLS with its own local CA — trust it once). Nodes boot without an
LLM; add model backends from the console UI.
For production (released images from ghcr.io, real secrets required), use the
bundled stack: docker compose -f turnstone/deploy/compose.yaml up.
See QUICKSTART.md for the bootstrap wizard and docs/docker.md for Docker configuration.
Programmatic (SDK)
from turnstone.sdk import TurnstoneServer
with TurnstoneServer("http://localhost:8080", token="tok_xxx") as client:
ws = client.create_workstream(name="demo")
result = client.send_and_wait("Analyze the error logs", ws.ws_id, auto_approve=True)
print(result.content)
Tools
Built-in tools for shell, files, search, web, memory, notifications, and autonomous sub-agents — plus external tools via MCP with native deferred loading. See docs/tools.md for the full reference and docs/mcp-registry.md for MCP configuration.
Architecture
Single-node: Client → Server (direct HTTP + SSE). No external dependencies beyond the database.
Multi-node: Client → Console (rendezvous routing proxy) → Server nodes. The console picks the target node for each workstream via rendezvous (HRW) hashing over the live service registry — pure function of (ws_id, live_nodes), no stored bucket state, deterministic across readers. A node join or drop only re-routes the keys that score highest on the affected node.
| Component | Purpose |
|---|---|
turnstone |
Terminal CLI (REPL) |
turnstone-server |
Web UI + REST API + SSE events |
turnstone-console |
Cluster dashboard + routing proxy + admin panel |
turnstone-channel |
Channel gateway (Discord and Slack adapters) |
turnstone-admin |
User/token management CLI |
turnstone-eval |
Eval harness for prompt/tool optimization |
turnstone-bootstrap |
LLM-guided setup wizard |
Diagrams
UML diagrams in docs/diagrams/:
| Diagram | Description |
|---|---|
| System Context | Components and external dependencies |
| Package Structure | Python modules and dependency graph |
| Core Engine | SessionUI, ChatSession, LLMProvider |
| Conversation Turn | Message lifecycle through the engine |
| Tool Pipeline | Prepare / approve / execute |
| Workstream States | State machine transitions |
| Console Data Flow | Dashboard data collection |
| Deployment | Docker Compose topology |
| Auth | JWT, scopes, login flows |
| Channels | Discord / Slack adapters + routing |
| Judge | Intent validation pipeline |
| OIDC | SSO authorization code flow |
Documentation
| Topic | Link |
|---|---|
| Configuration reference | docs/settings.md |
| API reference | docs/api-reference.md |
| Docker deployment | docs/docker.md |
| Intent validation (judge) | docs/judge.md |
| Governance & RBAC | docs/governance.md |
| OIDC SSO | docs/oidc.md |
| TLS / mTLS | docs/tls.md |
| Channel integrations | docs/channels.md |
| Console dashboard | docs/console.md |
| Eval harness | docs/eval.md |
| Tools reference | docs/tools.md |
| MCP integration | docs/mcp-registry.md |
Requirements
- Python 3.11+
- An OpenAI-compatible API endpoint, Anthropic API key, or Google Gemini API key
- Optional: Discord / Slack channel integrations (
pip install turnstone[discord,slack]) - Git LFS for cloning (diagram PNGs)
Community
Questions, ideas, or want to show what you're building? Join us on Discord: discord.gg/Nh3bWMacaq.
License
Business Source License 1.1 — free for all use except hosting as a managed service. Converts to Apache 2.0 on 2030-03-01.
