Straylight — the open-source, self-hosted gated AI agent (the gate is the only path to tools).
Project description
Straylight
Straylight is an open-source, self-hosted, gated AI agent — a general-purpose, OpenAI-compatible assistant (à la Claude Code / a Hermes agent) where the Strahl Prism information-flow gate is the only path to tools.
You prompt it; it works automatically — multi-step tool use (read files, search, run shell, edit code) to a result. The difference from any other agent: every tool call is authorized by the gate before it runs, so untrusted data (a fetched web page, a file's contents, command output) can't drive a sensitive action. Coverage is 100% by construction, and it fails closed — if the gate is unreachable, nothing runs.
Quick start
Needs two things running: an OpenAI-compatible model (a tool-capable one — e.g. Qwen2.5 via Ollama or vLLM) and a Prism gate.
uv sync
# point at your model + gate (or pass --model_url / --gate_url as flags)
export STRAYLIGHT_MODEL_URL=http://localhost:11434/v1 # Ollama's OpenAI endpoint
export STRAYLIGHT_MODEL=qwen2.5
export STRAYLIGHT_GATE_URL=http://localhost:8000
export STRAYLIGHT_API_KEY=sk_live_... # if your gate requires a key
uv run straylight # interactive REPL (bare command == `chat`)
uv run straylight run "list the python files and summarise the largest one"
uv run straylight tools # show the toolset + which are active
In the REPL: streamed Markdown answers, each tool call and gate PERMIT / DENY shown inline. /reset
starts a fresh conversation, /tools lists tools, /exit quits, Ctrl-C stops the current turn.
Models
straylight drives any OpenAI-compatible endpoint, so it works with the leading open-source models —
Qwen 2.5/3, Llama 3.1/3.3, Mistral/Mixtral, DeepSeek, Hermes, and Gemma 3. straylight models lists them; the
only model-specific thing that matters is the tool-call parser, which the serve scripts pick automatically.
(Gemma 3 tool calling uses vLLM's pythonic parser — the serve script auto-fetches its chat template — and is
less robust than Qwen/Llama; Gemma 2 has no native tool calling. Pair Gemma with STRAYLIGHT_STREAM=false if
its tool calls don't stream cleanly.)
uv run straylight models # the supported models + how to serve each
# Easiest — Ollama (OpenAI-compatible on :11434/v1, straylight's default STRAYLIGHT_MODEL_URL):
scripts/serve-ollama.sh qwen2.5:7b # pulls + serves; prints the env to export
# Self-hosted — vLLM (auto-selects the tool-call parser by family):
LOCAL_LLM_PORT=8020 scripts/serve-model.sh meta-llama/Llama-3.3-70B-Instruct
export STRAYLIGHT_MODEL_URL=http://localhost:8020/v1 STRAYLIGHT_MODEL=Llama-3.3-70B-Instruct
Hosted OSS (OpenRouter / Together / Fireworks) also works — just set STRAYLIGHT_MODEL_URL +
STRAYLIGHT_API_KEY to theirs. If a model's tool calls don't stream cleanly, set STRAYLIGHT_STREAM=false
(a non-streaming fallback). The agent needs a tool-calling-capable model — that's the only requirement.
How it works
The loop is a generator: the model streams, proposes tool calls, the gate authorises each, permitted ones execute, and the loop repeats until the model answers.
prompt ─► model (stream) ─► proposed tool calls ─► [ Prism gate ] ─► permitted run / denied blocked ─► loop
│
strahl SDK · POST /v0/analyses
agent.py— the gated loop, emitting typedevents.py(streamed text, tool calls, verdicts, results) so the CLI (and, later, aservemode) render the same core without touching loop logic.tools.py— the toolset (web_fetch,read_file,list_files,glob,grep,write_file,edit_file,bash,run_python), each carrying an IFC label (requires/produces) that is the policy the gate enforces. Filesystem/shell tools are confined to a workspace;web_fetchblocks non-public hosts (SSRF guard).config.py— onepydantic-settingsobject (env /.env/ CLI flags).cli.py— the Rich REPL + one-shotrun.
The gate boundary — HTTP only
Straylight reaches the gate only over HTTP, through the published strahl
SDK (POST /v0/analyses). It depends on the SDK for the labeled-transcript types and the gate call, and
never imports the gate's decision code or the GPU attribution service — that stays behind the HTTP
boundary, which is what keeps the gate tamper-proof and this subtree cleanly open-source. The agent tier
holds no GPU; both GPU workloads (the model, the gate's attribution) sit behind APIs.
straylight ──strahl SDK──► analysis ──► attribution (the Prism gate; GPU)
└────────/v1/chat/completions──────► model (the LLM; GPU)
Configuration
All via env (prefix STRAYLIGHT_, .env supported) or CLI flags; legacy STRAHL_* names still work.
| setting | env | default |
|---|---|---|
| model endpoint | STRAYLIGHT_MODEL_URL |
http://localhost:11434/v1 |
| model name | STRAYLIGHT_MODEL |
local |
| gate URL | STRAYLIGHT_GATE_URL |
http://localhost:8000 |
| gate API key | STRAYLIGHT_API_KEY |
— |
| workspace (tool root) | STRAYLIGHT_WORKSPACE |
cwd |
| max tool steps | STRAYLIGHT_MAX_STEPS |
25 |
| toolset subset | STRAYLIGHT_ENABLED_TOOLSETS |
all (read,files,code,safe presets) |
Development
uv run ruff format --check . && uv run ruff check . && uv run ty check . && uv run python -m pytest -q
Tests are hermetic — a fake gate + fake model exercise the permit/deny/fail-closed paths, the streaming
parser, the toolset, and config; no network, model, or gate required. (Use python -m pytest: the bare
pytest launcher misses the path-based editable strahl install.)
Design docs
The design docs live in docs/. Start with architecture — the map: the
module tree, end-to-end data-flow traces, and the invariants that make straylight gated. Then the subsystems:
trust-model (how provenance labels are synthesized on a bare filesystem),
toolset (the tools and the policy on each), and memory (persistent,
injection-safe local memory). A browsable site builds with uv run mkdocs serve (adds an auto-generated API
reference from the docstrings).
Conventions
uv, Python 3.14, src/ layout, hatchling, ruff (line-length 120), pytest. Depends on strahl (the SDK).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file straylight_agent-0.1.0.tar.gz.
File metadata
- Download URL: straylight_agent-0.1.0.tar.gz
- Upload date:
- Size: 133.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f39743c97d3ff8b668f1a40577d053a33db7981e3d4fa4201405ee713e4b9351
|
|
| MD5 |
0256c397113f2c101b977aa85af75bec
|
|
| BLAKE2b-256 |
aec0bf2fb137ab59f9d5c4247ee6b0f5365a52d043bbba8852841a085a1451c4
|
Provenance
The following attestation bundles were made for straylight_agent-0.1.0.tar.gz:
Publisher:
straylight-agent-publish.yml on strahl-labs/strahl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
straylight_agent-0.1.0.tar.gz -
Subject digest:
f39743c97d3ff8b668f1a40577d053a33db7981e3d4fa4201405ee713e4b9351 - Sigstore transparency entry: 2256627811
- Sigstore integration time:
-
Permalink:
strahl-labs/strahl@7fece52fe57697919be6f4d305c251cbdea8bba4 -
Branch / Tag:
refs/tags/straylight/v0.1.0 - Owner: https://github.com/strahl-labs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
straylight-agent-publish.yml@7fece52fe57697919be6f4d305c251cbdea8bba4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file straylight_agent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: straylight_agent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 48.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b4e39b32ee0ec111ff9cbc7cb353316dcc7c2321c01b6d6032569fbbb1323488
|
|
| MD5 |
fe80685e8ee06b505589adf28c40e612
|
|
| BLAKE2b-256 |
d1d6bfde5fff122adcd269025c46150cde10a6d5bf492e1a2cff1b5bc36a2788
|
Provenance
The following attestation bundles were made for straylight_agent-0.1.0-py3-none-any.whl:
Publisher:
straylight-agent-publish.yml on strahl-labs/strahl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
straylight_agent-0.1.0-py3-none-any.whl -
Subject digest:
b4e39b32ee0ec111ff9cbc7cb353316dcc7c2321c01b6d6032569fbbb1323488 - Sigstore transparency entry: 2256627813
- Sigstore integration time:
-
Permalink:
strahl-labs/strahl@7fece52fe57697919be6f4d305c251cbdea8bba4 -
Branch / Tag:
refs/tags/straylight/v0.1.0 - Owner: https://github.com/strahl-labs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
straylight-agent-publish.yml@7fece52fe57697919be6f4d305c251cbdea8bba4 -
Trigger Event:
push
-
Statement type: