Spotlight's agnostic contract is AGENTS.md + skills/*/SKILL.md. Any agent runtime that can (a) read those files, (b) dispatch the 13 verbs to native tools, and (c) spawn sub-agents can run Spotlight.
This doc is the per-runtime wiring guide. Each section covers: how the runtime loads skills, how verbs map, how sub-agents work, and how sensitive mode is enforced.
fetch, search, read-file, write-file, edit-file, list-files, grep-files,
execute-shell, spawn-agent, wait-agent, invoke-skill, query-vault, vault-write
Universal backings (never change):
| Verb | Concrete tool |
|---|---|
fetch, search |
fetch → Crawl4AI via integrations.scraping; search → SearXNG via integrations.search (Firecrawl = optional fallback when FIRECRAWL_API_KEY is set) |
query-vault |
BUN_INSTALL="" qmd query <vault> "<query>" |
vault-write |
obsidian CLI (Obsidian app must be running) |
execute-shell |
native shell subprocess |
read-file, write-file, edit-file |
filesystem (runtime-native) |
list-files, grep-files |
glob + ripgrep (runtime-native) |
Runtime-specific backings (vary):
| Verb | Varies by runtime |
|---|---|
spawn-agent, wait-agent |
pi extension / Hermes delegate_task / Goose recipe / tmux subprocess / SDK call |
invoke-skill |
pi's native skill loader / Hermes SKILL.md injection / Goose recipe prepend / raw prompt concat |
What it is: the repo's harness/flue/ — a Flue app (TypeScript
harness framework built on Pi, by the Astro team) that IS the Spotlight orchestration for every
non-frontier deployment. One harness: the evals exercise the same app the installer deploys
(local test == user experience). It gives the orchestrator native subagents (investigator +
fact-checker in their own child sessions — real verification independence), workspace-discovered
skills from <cwd>/.agents/skills/ with bodies loaded on-invoke, durable sqlite sessions
(resume-on-crash), and threshold conversation compaction summarized by the resident RLM e4b.
The installer writes everything; day-to-day is one command per investigation turn:
spotlight # start the model servers, print usage (no session)
spotlight my-case "Investigate <target>: <what you want to know>" # open a session
spotlight my-case "Approved, proceed." # answer each gate
spotlight-local --stop # stop the llama.cpp servers
spotlight doctor # health checksSessions are durable (harness/flue/data/): the same session-id resumes exactly where it left
off, including after a crash or laptop sleep. Gates are real turns — the orchestrator stops and
waits; you answer by re-running with the same id.
Under the hood, spotlight (shell function) sources $SPOTLIGHT_DIR/.env and runs
spotlight-local, which (a) serves the orchestrator GGUF on :8080 (llama.cpp, --jinja
tool-calling, q8_0 KV + flash-attn, two resident slots --parallel 2 --no-cache-idle-slots,
tier-capped --reasoning-budget), (b) serves the RLM e4b on :8095 when SPOTLIGHT_RLM_GGUF_PATH
exists (--reasoning-budget 0; powers fetch --rlm distillation AND the compaction summarizer),
(c) exports the env, and (d) runs flue run spotlight --id <session> from harness/flue.
Servers stay resident between turns (warm prefix cache).
$SPOTLIGHT_DIR/.env drives the launcher each run:
| Var | Meaning |
|---|---|
SPOTLIGHT_GGUF_PATH |
orchestrator GGUF served on :8080 |
SPOTLIGHT_MODEL_TIER |
12b | 26b | 31b — picks reasoning budget (400/800/1024), compaction profile (fold at ~16k/24.5k/28.5k, keep 4k/6k/8k recent), and the .raw affordance (12b: leads only; 26b+: may selectively read raw) |
SPOTLIGHT_RLM_GGUF_PATH |
e4b GGUF for the RLM; absent = graceful degradation (raw fetches, session-model compaction) |
SPOTLIGHT_REASONING_BUDGET, SPOTLIGHT_COMPACT_AT, SPOTLIGHT_COMPACT_KEEP |
optional overrides of the tier defaults |
To switch tiers: edit SPOTLIGHT_GGUF_PATH + SPOTLIGHT_MODEL_TIER, run
spotlight-local --stop, then launch normally.
harness/flue/src/app.ts registers providers by env: Fireworks (FIREWORKS_API_KEY, GLM-5.2
ZDR) and OpenRouter (OPENROUTER_API_KEY). Select with
SPOTLIGHT_FLUE_MODEL=fireworks/… or openrouter/… and run flue run spotlight the same way —
no llama.cpp, no KV flags, flue-default compaction (the local restrictions fall away
structurally). Any OpenAI-compatible endpoint is one registerProvider block; custom providers
MUST declare contextWindow + maxTokens (else flue sends max_completion_tokens:1 → empty
output).
The FLUE_VERB_ADAPTER (harness/flue/src/lib/roles.ts) maps every contract verb to flue's
native tools and injects the absolute-path rules (PYTHONPATH + venv python for integrations.*,
absolute CASE_DIR). Sub-agents are native (defineAgentProfile + task delegation, own
child sessions). Role instructions come from the same runtime-agnostic agents/<role>.md files
the frontier path uses. Sensitive mode follows the skills (same as other runtimes).
Status: RETIRED as the local Spotlight runtime (2026-07-09) — the Flue-on-Pi harness above replaced it (native subagents + compaction + RLM in one app; the installer no longer writes an opencode launcher for local mode). opencode remains usable as a generic runtime for the skills (the wiring below is kept for reference) and as a cloud runtime choice.
What it is: Terminal-first AI coding agent by Anomaly Innovations (https://opencode.ai). MIT license. Native AGENTS.md, SKILL.md, sub-agents, MCP, and a built-in llama.cpp provider that talks directly to a local llama-server.
brew install opencode # CLI (recommended)
brew install --cask opencode-desktop # Optional GUI app
# or, no Homebrew:
curl -fsSL https://opencode.ai/install | bashopencode discovers SKILL.md files recursively under each of these roots (the binary globs skills/**/SKILL.md; verified on opencode 1.17.5):
- Project:
.opencode/skills/,.claude/skills/,.agents/skills/ - Global:
~/.config/opencode/skills/,~/.claude/skills/,~/.agents/skills/
The leaf directory holding SKILL.md must equal the name: field in the frontmatter (validated); intermediate directories are ignored. So a per-product namespace subdir works: ~/.config/opencode/skills/spotlight/<skill>/SKILL.md is discovered exactly like a top-level <skill>/SKILL.md.
Install Spotlight globally (namespaced under spotlight/, matching the engine's <root>/<product>/<skill> shape):
mkdir -p ~/.config/opencode/skills/spotlight
for skill_dir in /path/to/spotlight/skills/*/; do
name=$(basename "$skill_dir")
ln -sfn "$skill_dir" "$HOME/.config/opencode/skills/spotlight/$name"
doneCreates symlinks for all skills, including spotlight, ingest, monitoring, acquisition-graduation, web-archiving, content-access, epistemic-grounding, shell-safety, osint, investigate, follow-the-money, social-media-intelligence, integrations, and review. Live links — git pull in the spotlight repo updates everything.
AGENTS.md is loaded as Rules (https://opencode.ai/docs/rules/), walked up from cwd to the git worktree. Drop a project AGENTS.md in your investigations directory and opencode picks it up automatically.
Merge into ~/.config/opencode/opencode.json (preserves any other providers you have):
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"llama.cpp": {
"npm": "@ai-sdk/openai-compatible",
"name": "llama-server (local)",
"options": { "baseURL": "http://127.0.0.1:8080/v1" },
"models": {
"qwen27": {
"name": "Qwen3.6-27B Uncensored (local llama.cpp, Q4_K_P)",
"limit": { "context": 262144, "output": 16384 },
"cost": { "input": 0, "output": 0 }
}
}
}
}
}Start with: opencode --model llama.cpp/qwen27 (or use the installer-generated launcher script).
Ollama's OpenAI-compatible endpoint expects chat requests at /v1/chat/completions. Configure opencode with the OpenAI-compatible provider and keep baseURL at the /v1 root:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"ollama": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama (local OpenAI-compatible)",
"options": { "baseURL": "http://127.0.0.1:11434/v1" },
"models": {
"spotlight-gemma4-q4": {
"name": "gemma-4-26B-A4B-it-GGUF (local Ollama)"
}
}
}
}
}Do not use ollama-ai-provider-v2 here. In opencode it calls /v1/chat, which Ollama answers with 404. Do not set baseURL to /v1/chat/completions either; the provider appends the chat completion route itself.
opencode ships native bash, read, write, edit, grep, glob, multi-edit — covers 8 of the 13 verbs directly. The remaining five shell out:
| Verb | Concrete tool |
|---|---|
fetch, search |
integrations.scraping (Crawl4AI) / integrations.search (SearXNG) via bash; Firecrawl optional fallback |
query-vault |
BUN_INSTALL="" qmd query via bash |
vault-write |
obsidian CLI via bash |
invoke-skill |
opencode's native skill tool — agents see available skills and load them on demand |
Native (https://opencode.ai/docs/agents/) — Spotlight's investigator and fact-checker map directly to opencode agent files (markdown manifests with frontmatter). Each agent gets its own context, prompt, and optionally its own model.
Enforce at the agent definition: strip firecrawl (and any external-fetch shell) from the agent's allowed-tools frontmatter. Same pattern as the Claude Code marketplace plugin.
What it is: Minimal TypeScript coding harness by Mario Zechner (https://pi.dev). MIT license. Natively supports AGENTS.md + skills/*/SKILL.md.
Status: SUPERSEDED as a direct runtime (2026-07-09) — pi is now the engine under Flue (the canonical harness above is a Flue app, and Flue is built on Pi), which resolves the old "pi lacks native sub-agents" objection: subagents, compaction, and durability come from the Flue layer. Do not wire pi directly for Spotlight; the notes below are kept for reference.
mkdir -p ~/.pi/agent/skills
ln -sfn /path/to/spotlight/skills ~/.pi/agent/skills/spotlight
pipi recursively walks ~/.pi/agent/skills/ (user) and <cwd>/.pi/skills/ (project) at startup, picking up every SKILL.md it finds — verified in pi-coding-agent/dist/core/skills.js:347-348. Skill names come from each frontmatter, so the symlink above loads all Spotlight sub-skills by name.
AGENTS.md is layered into pi's system prompt from ~/.pi/agent/, parent directories, and the current directory (per pi.dev docs).
pi ships native Read, Write, Edit, Grep, Glob, Bash equivalents. The 13-verb contract maps directly — skills reference verbs by name and pi's model uses its native tools to execute (e.g. execute-shell("firecrawl scrape <url>") becomes a Bash-equivalent call).
pi does not ship built-in sub-agents. Workarounds: a pi-subagent extension if one exists, tmux-spawn a second pi process via RPC mode, or SDK-mode wrapper. For Spotlight's investigator/fact-checker pattern this is awkward — that's why opencode is the recommended local runtime.
Use the pi-llama-cpp extension — a community package by gsanhueza (MIT, v0.4.0 as of May 2026), listed on Pi's official package registry at https://pi.dev/packages/pi-llama-cpp:
pi install npm:pi-llama-cppThe extension auto-detects models on the running llama-server (/models endpoint), supports load/unload/switch via /models slash command, and resolves the endpoint URL from any of:
.pi/llama-server.jsonin the project root —{"url":"http://127.0.0.1:8080"}LLAMA_SERVER_URLenv var~/.pi/agent/settings.json—{"llamaServerUrl":"http://127.0.0.1:8080"}- Default
http://127.0.0.1:8080
Spotlight's installer writes option 3 automatically when you pick Pi as the agent harness — pointing at port 8080 for llamacpp or 11434 for Ollama. Pi's authentication for the local server (if any) lives in ~/.pi/agent/auth.json under provider id llama-server (displayed as "Llama.cpp" in the Pi UI).
Status indicators on the /models browser: 🟢 loaded · 🟡 loading · 🔴 failed · 🔵 sleeping · ⚪ unloaded. The sleeping state requires llama-server --sleep-idle-seconds <n> on the server side.
opencode's native llama.cpp provider does the same model-browsing job via one JSON block in opencode.json — no extension. Either path connects an agent to the same OpenAI-compatible endpoint; pick the agent based on whether you need native sub-agents.
Current Spotlight operator model: unsloth/gemma-4-26B-A4B-it-GGUF on Hugging Face (base Gemma 4 26B A4B — we evaluated a journalism fine-tune but the base outperformed it on tool-use + document OCR). Multimodal (text + vision) VLM MoE — 26B total / 4B active. Native vision for scanned court documents, satellite imagery, and screenshots. Recommended quants:
gemma-4-26B-A4B-it-UD-Q6_K_XL.gguf(~22 GB) +mmproj-BF16.gguf(~1.2 GB) — 48GB+ Macsgemma-4-26B-A4B-it-UD-Q4_K_M.gguf(~18 GB, imatrix-calibrated by Unsloth) +mmproj-BF16.gguf— 24GB+ Macs
Serve via llama-server:
llama-server -m gemma-4-26B-A4B-it-UD-Q6_K_XL.gguf --mmproj mmproj-BF16.gguf \
--port 8081 --ctx-size 16384 --n-gpu-layers 999Set sensitive: true in AGENTS.md frontmatter (or pass as env SPOTLIGHT_SENSITIVE=true). The orchestrator instructs pi to strip fetch/search from each agent's allowed_verbs. Implementation paths:
- Write a pi extension that intercepts tool calls and blocks
Bash(firecrawl …)when the sensitive flag is on - Or rely on the orchestrator's skill instructions to refuse calls in sensitive mode (less defense-in-depth but no code required)
What it is: Production ambient agent on the Mac Mini, loaded via ~/.hermes/config.yaml. Already in use for the Mycroft workflow. See ~/.hermes/config.yaml for the live config.
Add to skills.external_dirs in ~/.hermes/config.yaml:
skills:
external_dirs:
- /path/to/spotlight/skills
# existing kit dirs follow
- ~/buried_signals/kit/mycroft
- ~/buried_signals/kit/sharedRestart Hermes:
launchctl kickstart -k gui/$(id -u)/ai.hermes.gatewayAll Spotlight skills (spotlight, ingest, monitoring, acquisition-graduation, web-archiving, content-access, epistemic-grounding, shell-safety, osint, investigate, follow-the-money, social-media-intelligence, integrations, review) become available by invoke-skill name.
| Verb | Hermes tool |
|---|---|
fetch, search |
shell call to integrations.scraping (Crawl4AI) / integrations.search (SearXNG); Firecrawl optional fallback |
read-file, write-file, edit-file |
Hermes filesystem tools |
execute-shell |
Hermes terminal |
query-vault |
BUN_INSTALL="" qmd query via terminal |
vault-write |
obsidian CLI via terminal |
spawn-agent |
delegate_task() with the agent prompt + iteration_limit |
wait-agent |
delegate_task is synchronous by default; handle = task id |
invoke-skill |
Hermes reads the SKILL.md file and injects into the active prompt |
The orchestrator calls delegate_task with a goal string composed from:
agents/investigator.md(orfact-checker.md) prompt- Mode flag (PLANNING / EXECUTION)
- Project context (VAULT_PATH, PROJECT, CYCLE)
Hermes' delegation block in config.yaml sets per-delegation model and iteration limit. The agent manifest in this repo declares iteration_limit: 80 (investigator) and 50 (fact-checker) — map these to Hermes' max_iterations.
Hermes has a local-gemma skill at ~/buried_signals/kit/mycroft/local-gemma/SKILL.md that routes sensitive tasks to the llama-server on 127.0.0.1:8081 (fine-tuned Gemma 4 E4B journalist model). When Spotlight is invoked with sensitive: true:
- The orchestrator sets the per-delegation model to
local-gemmafor all agent spawns - Hermes routes
fetch/searchverbs to a no-op or error — the agent works from local{CASE_DIR}/research/ - The orchestrator marks findings as "sensitive-mode constrained" at Gate 1
What it is: Block/Square's CLI agent (https://block.github.io/goose/). Ships as a brew/installer package; config at ~/.config/goose/config.yaml. Extensions add capabilities.
This repo is packaged as a Goose extension. Consumers install once; all skills become available.
At the repo root (or a distribution artifact), provide a Goose extension descriptor:
# extension.yaml (Goose extension format)
name: spotlight
version: "1.0"
description: "OSINT investigation system — verified findings, fact-checking, vault ingestion"
type: agent-pack
entry:
agents_md: AGENTS.md
skills_dir: skills/
agent_prompts_dir: agents/
schemas_dir: schemas/
requires:
cli_tools:
- firecrawl # reviewed setup pin: [email protected]
- obsidian # Obsidian app (optional, for vault-write)
- qmd # reviewed setup pin: @tobilu/[email protected]
env_vars:
required: [FIRECRAWL_API_KEY]
optional: [OSINT_NAV_API_KEY, CORE_API_KEY]
recipes:
- id: spotlight-investigate
description: "Start a new OSINT investigation"
entry_skill: spotlight
- id: spotlight-ingest
description: "Archive completed findings to a vault"
entry_skill: ingest(Goose's extension format is evolving; verify the exact YAML shape against the current Goose docs before publishing. The fields above are the semantic contract — adjust key names to match Goose's live schema.)
Once published to a Goose extension registry (or a git URL):
goose extensions install spotlightThis should wire:
AGENTS.mdas the project-context file Goose loads at session start- All skills under
skills/discoverable via Goose's skill-search - Agent prompts in
agents/loadable as recipe variants - Schemas validated automatically against case file writes
| Verb | Goose equivalent |
|---|---|
fetch, search |
Goose runs integrations.scraping (Crawl4AI) / integrations.search (SearXNG); Firecrawl optional fallback |
read-file, write-file, edit-file |
Goose filesystem tools |
execute-shell |
Goose developer-mode shell or restricted subprocess |
spawn-agent |
Goose recipe invocation — spawn a new session with the agent prompt |
wait-agent |
Goose sessions are synchronous; wait for completion |
invoke-skill |
Goose loads SKILL.md into the system prompt |
Each agents/*.md becomes a Goose recipe. The orchestrator skill (spotlight/SKILL.md) invokes recipes for investigator PLANNING, investigator EXECUTION, fact-checker pass. Recipe parameters: PROJECT, VAULT_PATH, CYCLE, INTEGRATIONS.
Goose supports per-session provider routing. When sensitive: true:
- Orchestrator invokes recipes with a local provider binding (OpenAI-compatible endpoint to llama-server on 127.0.0.1:8081 or equivalent)
fetch/searchtool permissions are revoked at session start via Goose's tool allowlist- Evidence must come from
{CASE_DIR}/research/— agent cannot reach the network
What it is: OpenAI's CLI agent (@openai/codex, reviewed setup pin 0.138.0). Reads AGENTS.md natively at session start (same convention as pi). Auth via ChatGPT Plus/Pro/Team, Codex free-tier login, or an OpenAI API key. A quick-start adapter bundle lives in adapters/codex/.
npm install -g @openai/[email protected]
codex login # OAuth via ChatGPT OR set OPENAI_API_KEYPoint Codex at the repo root as its working directory. AGENTS.md is loaded automatically.
| Verb | Codex tool |
|---|---|
read-file, list-files, grep-files |
native file tools (no config needed) |
write-file, edit-file |
native edit tools — require --sandbox workspace-write or higher |
execute-shell |
bash -lc tool — require --sandbox workspace-write or higher |
fetch, search |
execute-shell running python -m integrations.scraping (Crawl4AI) / integrations.search (SearXNG); Firecrawl optional fallback |
query-vault |
execute-shell wrapping BUN_INSTALL="" qmd query |
vault-write |
execute-shell wrapping the obsidian CLI |
invoke-skill |
natively loads skills/{skill}/SKILL.md when referenced |
spawn-agent, wait-agent |
execute-shell spawning a second codex exec subprocess — see below |
On the host, keep Codex's default read-only sandbox and widen with -s workspace-write only when writes are needed. Spotlight no longer ships a Docker setup path; package-supply-chain control is handled by reviewed dependency pins in VALIDATED_DEPENDENCIES.md.
Local RLM/Ollama calls are the exception to ordinary read-only file work:
integrations/rlm/run_rlm.py must reach http://127.0.0.1:11434. If Codex's
sandbox blocks localhost, approve host localhost access or run that RLM call
outside the sandbox before concluding gemma4:e4b is unavailable.
Codex 0.122 has no first-class multi-agent primitive. Spotlight relies on isolation between investigator and fact-checker for the verification guarantee, so we run the sub-agent as a separate codex exec subprocess — each call is a fresh conversation with its own context window. The orchestrator:
- Reads the target agent prompt (e.g.
agents/fact-checker.md) + the skill instructions - Shells out via
execute-shell:
codex exec \
--ephemeral \
--skip-git-repo-check \
--profile fact-checker \
--output-last-message /tmp/fact-checker.out \
"MODE: VERIFY
PROJECT: {project}
VAULT_PATH: {vault}
CYCLE: {cycle}
<contents of agents/fact-checker.md>"- Reads the sub-agent's side-effects from the filesystem (
{CASE_DIR}/data/fact-check.json) — the contract is file-based, not stdout.
--ephemeral keeps the sub-agent session off disk; --profile fact-checker loads the per-agent model + iteration budget from ~/.codex/config.toml (see adapters/codex/config.toml.example). Iteration limits from the agent manifest (iteration_limit: 80 investigator, 50 fact-checker) map to Codex's max_output_tokens + turn budget in the profile.
Codex 0.122 ships a native --oss flag that detects Ollama on 127.0.0.1:11434. Use it instead of a custom [model_providers.*] entry — that route is broken in 0.122 because Codex now requires wire_api = "responses" (see codex#7782) which Ollama and llama-server do not speak.
SPOTLIGHT_SENSITIVE=true codex exec \
--oss \
--local-provider ollama \
--model gemma-4-26B-A4B-it \
--skip-git-repo-check \
"<prompt>"For defence-in-depth, wrap firecrawl in a shell alias that refuses to run when SPOTLIGHT_SENSITIVE=true — otherwise the orchestrator can still fetch external resources via execute-shell.
- Rate-limited ChatGPT free tier will not complete a full investigation (expect ~10-20 turns before the daily cap). Use Plus/Pro or API for production.
- Model default is
gpt-5.4under ChatGPT login; override per profile. spawn-agentvia subprocess shares the OAuth token with the parent — no per-agent auth isolation. Rate limits apply to the sum of orchestrator + sub-agents.- Tool-use falls apart on small models (< ~14B).
llama3.2:3btechnically advertises tools but will not call them — it answers "I don't see the file" instead of invokingread_file. Always target Gemma 4 26B A4B class or better for real runs. [model_providers.*]withwire_api = "chat"is rejected — use--ossfor all local inference.
What it is: Google's CLI agent with activate_skill tool. Reads GEMINI.md (symlink GEMINI.md → AGENTS.md if you want Gemini to see the same contract). Currently not installed on this machine.
Point Gemini at the repo root. Create GEMINI.md as a symlink to AGENTS.md so Gemini's startup loader sees the contract.
Gemini's activate_skill tool maps to invoke-skill. Other verbs map to Gemini's native tools (file I/O, shell, web fetch).
Gemini's sub-agent support is evolving. Until native primitives stabilize, use the same tmux / SDK approach as pi.
Any OpenAI-compatible /v1/chat/completions endpoint can drive Spotlight as long as the host harness (pi, Hermes, Goose, a thin SDK wrapper) supports the agent loop.
| Backing | URL | Use case |
|---|---|---|
| llama-server (llama.cpp) | http://127.0.0.1:8080/v1 |
Lean, Terminal-only — brew install llama.cpp. Default for the installer's local mode. |
| Ollama | http://127.0.0.1:11434/v1 |
CLI-first model manager — brew install ollama, ollama pull <repo>. |
| Exoscale Dedicated Inference | https://exoscale-ci-…/v1 |
Swiss-sovereign hosted inference |
| vLLM | http://localhost:8000/v1 |
High-throughput self-hosted |
The endpoint is configured at the harness layer (pi's models.json, Hermes' provider config, Goose's model settings). The skills in this repo are provider-agnostic — they assume the model can call the verb set; how inference is served is the harness's problem.
Spotlight agents use preferred_model in their manifest frontmatter. For a local fine-tune:
preferred_model:
claude: opus
gemini: gemini-2.5-pro
gpt: gpt-4o
local: gemma-4-26B-A4B-it # current ship — upstream base VLM with native visionThe adapter picks the local entry when the active provider is the local endpoint. If the fine-tune underperforms on methodology design (observed with sub-10B models per the sovereign-inference spec), the orchestrator warns the user and offers to route just the investigator PLANNING step to a stronger hosted model while keeping EXECUTION and fact-checking on the local fine-tune.
When sensitive: true is set in AGENTS.md (or via a runtime command), every adapter MUST strip fetch and search from each agent's allowed_verbs. The enforcement point varies:
| Runtime | Enforcement |
|---|---|
| pi | Extension intercepts tool calls + skill instruction refuses in-mode |
| Hermes | Tool allowlist + local-gemma skill routes to llama-server |
| Goose | Per-session tool allowlist revokes network tools |
| Codex | Native tool allowlist (per Codex config) |
| Gemini | Tool allowlist |
| Local-endpoint wrappers | Orchestrator refuses to call the verb backing; wrapper blocks the shell call |
A sensitive investigation cannot satisfy the "document trail" readiness criterion from external sources. The orchestrator marks the investigation as sensitive-mode constrained at Gate 1, and the Gate 1 summary notes which readiness criteria could not be evaluated live.
sensitive mode and anonymized fetch (Tor) solve different problems:
| Posture | Trigger | Egress | Use when |
|---|---|---|---|
sensitive: true |
AGENTS.md frontmatter / SPOTLIGHT_SENSITIVE=true |
None — fetch/search stripped, research is local-only |
The material must not leave the machine (working with sensitive documents; a frontier model would ship context to a third party) |
| Anonymized fetch (Tor) | SPOTLIGHT_ANONYMIZE_FETCH=true (per-run) or --tor/--no-tor (per-fetch) |
Yes, via Tor — Crawl4AI routes through socks5://127.0.0.1:9050; the operator's IP is hidden from the target |
You must scrape a target-of-investigation without revealing that someone is looking (KTD8 / U7) |
They compose: an investigation can be sensitive (no egress) or anonymized (egress via Tor) or neither — but a sensitive run has no fetch to anonymize, so the two are rarely on together. A Tor-proxied fetch never silently falls back to a direct (de-anonymizing) fetch — on a Tor failure the seam raises and the operator re-runs --no-tor (revealing their IP) or aborts. Tor exits are widely blocklisted, so an anonymized fetch of a hard anti-bot target may simply fail — by design. The Spotlight installer provisions Tor when SPOTLIGHT_TOR=1 (opt-in).
To add a runtime adapter doc:
- Confirm the runtime can read
AGENTS.mdor equivalent project-context file - Map each of the 13 verbs to the runtime's native tools
- Choose a sub-agent pattern (native, tmux, SDK wrapper)
- Choose a sensitive-mode enforcement point
- Write a new section here with the same structure as existing ones
- If the runtime has a distribution format (Goose extension, npm package, Homebrew tap), add a manifest entry at the repo root
All runtimes share the same skill content. The adapter doc is 200–400 lines of mapping and setup — the skills themselves are never rewritten per runtime.