the reference

read the light.

A spectroscope tells you what light is made of without touching the source. This reference works the same way: every section below is extracted straight from the source tree, and each one names the file it came from. The RunEvents are the spectral lines, a run is the light they add up to, and the JSONL file on disk is the plate it is recorded on.

the facade

Extracted from Spectro.java; the example is the owner-frozen wording from SPECTRO-API.md.

var agent = Spectro.agent()
    .model(Anthropic.opus())
    .tools(Tools.readFile(), Tools.runCommand())
    .workspace(Path.of("/tmp/scratch"));

for (RunEvent event : agent.run("Write hello.py and run it")) {
    System.out.println(event);   // the stream IS the observability
}

the Python port is in design. The JSONL wire is the contract, so a stream recorded by either edition replays in the other.

agent()AgentThe five-lines facade — spectroscope's front door.
panel()FleetPanelThe fleet path — several agents, one merged spectrum — in exactly the facade's shape.
model(LlmProvider provider)AgentOne configured agent: setters chain, #run streams.
tools(Tool... tools)AgentThe tool belt the model may call.
workspace(Path workspace)AgentThe directory the file tools resolve and sandbox against.
systemPrompt(String systemPrompt)AgentReplaces the default unattended system prompt entirely.
run(String prompt)EventStreamRuns one prompt against the agent and hands back the live stream.

RunEvents (19)

The wire contract. Extracted verbatim from spectro-web/src/events.ts — the same union the TypeScript edition and the JSONL files share. The server additionally announces workspace_info on the socket (SessionConnection.java); consumers skip unknown types by contract.

run_start

runIdstring
agentIdstring
parentIdoptionalstring
promptstring
provideroptionalstring
modeloptionalstring
triggeroptionalstring
attachmentsoptionalAttachmentRef[]
tsnumber

turn_start

agentIdstring
turnnumber
tsnumber

text_delta

agentIdstring
textstring
tsnumber

thinking_delta

agentIdstring
textstring
tsnumber

tool_call

agentIdstring
callIdstring
namestring
inputunknown
tsnumber

permission_request

agentIdstring
callIdstring
namestring
inputunknown
tsnumber

permission_decision

callIdstring
allowedboolean
tsnumber

tool_result

agentIdstring
callIdstring
outputstring
isErrorboolean
durationMsnumber
fileChangeoptionalstring
tsnumber

agent_spawn

agentIdstring
parentIdstring
taskstring
tsnumber

compaction

agentIdstring
removedTurnsnumber
summaryCharsnumber
tsnumber

llm_exchange

xidstring
agentIdstring
turnoptionalnumber
kindstring
providerstring
modelstring
transportstring
urlstring
statusoptionalnumber
requestBytesnumber
responseBytesnumber
responseLinesnumber
abortedboolean
fidelitystring
durationMsnumber
tsnumber

usage

agentIdstring
inputTokensnumber
outputTokensnumber
cacheReadTokensoptionalnumber
cacheCreationTokensoptionalnumber
tsnumber

run_end

runIdstring
stopReasonstring
tsnumber

error

agentIdoptionalstring
messagestring
tsnumber

image_generated

agentIdstring
callIdstring
promptstring
providerstring
modelstring
mediaTypestring
blobPathstring
sha256string
tsnumber

context_info

agentIdstring
turnnumber
messagesnumber
estimatedTokensnumber
thresholdnumber
thresholdSourceoptional"override" | "window" | "model" | "fallback"
contextWindowoptionalnumber
parts{ label: string; chars: number; estTokens: number; text?: string }[]
tsnumber

agent_message

fromstring
tostring
rolestring
statestring
textstring
labeloptionalstring
tsnumber

plan

agentIdstring
steps{ text: string; status: string }[]
tsnumber

browser_action

agentIdstring
callIdoptionalstring
cidstring
epochnumber
toolstring
urloptionalstring
okboolean
resultBytesnumber
durationMsnumber
sha256optionalstring
tsnumber

socket frames (10)

Client → server messages on /ws. In the other direction the server sends RunEvent JSON plus socket-only UI frames: workspace_info, provider_info, permission_mode_info, and the fleet frames fleet_roster and fleet_event. Consumers skip unknown types by contract.

user_message

textstring
attachmentsoptional{ mediaType: string; dataBase64: string }[]

permission_response

callIdstring
allowedboolean
rememberoptionalboolean
persistoptionalboolean

abort

set_image_provider

providerstring

set_thinking

enabledboolean

set_reasoning

mode"on" | "off" | "default"
effortoptionalstring

set_provider

providerstring
modeloptionalstring

set_workspace

modeoptional"random" | "default" | "set"
pathoptionalstring

set_permission_mode

modestring

question_response

callIdstring
answersstring[]
cancelledoptionalboolean

REST endpoints (73)

Extracted from the @GetMapping/@PostMapping/@DeleteMapping annotations in spectro-server.

POST /api/analyze

One analysis call, streamed as NDJSON.

AnalyzeController.java

GET /api/analyze/engine

The opt-in run analysis (card 294): an imported run's compact digest goes to the configured provider ONCE, on an explicit click, and the model's reading comes back as a stream.

AnalyzeController.java

GET /api/browser/status

Where the browser pane goes, and whether one exists — the two things the web UI needs in order to draw the browser segment honestly.

BrowserViewportController.java

POST /api/browser/viewport

The rectangle the browser segment reserved, in window CSS pixels.

BrowserViewportController.java

GET /api/bundles

The starter-bundle endpoints.

BundleController.java

GET /api/bundles/{id}

GET /api/bundles/{id}?build=gradle — the rendered file set (copy-paste).

BundleController.java

POST /api/bundles/{id}/scaffold

POST /api/bundles/{id}/scaffold {dir, build} — write the bundle's files into dir.

BundleController.java

GET /api/claude/transcripts

Read-only browser for the Claude Code transcript store under ~/.claude/projects.

ClaudeTranscriptsController.java

GET /api/claude/transcripts/content

The listing itself, fence already passed.

ClaudeTranscriptsController.java

GET /api/claude/transcripts/facts

A JSON body already built, at a given status.

ClaudeTranscriptsController.java

GET /api/claude/transcripts/folders

GET /api/claude/transcripts/folders: which of a session's three folders are really on disk.

ClaudeTranscriptsController.java

POST /api/claude/transcripts/folders/open

POST /api/claude/transcripts/folders/open: show one of them.

ClaudeTranscriptsController.java

GET /api/claude/transcripts/gists

What a click on this row would fetch, weighed now.

ClaudeTranscriptsController.java

POST /api/claude/transcripts/gists

POST /api/claude/transcripts/gists: write the missing ones.

ClaudeTranscriptsController.java

GET /api/claude/transcripts/run

GET /api/claude/transcripts/run: one whole recorded run, as texts.

ClaudeTranscriptsController.java

GET /api/claude/transcripts/sidecars

The agents beside one session, named.

ClaudeTranscriptsController.java

GET /api/config

The active LLM backend for the header + the Lab map: the boot config's provider and model (the same layers the socket builds its agent from).

SessionsController.java

GET /api/context

Whether a value is the shape its setting takes.

SessionsController.java

GET /api/docker/status

Reports whether Docker is usable on this machine, so the Observability settings can offer the right next step instead of a command that cannot work.

DockerStatusController.java

POST /api/explain

The LLM-backed explain (card 62): the Text tab POSTs a bounded, readable digest of the viewed run (the same feed it renders) and gets back a streamed INTERPRETATION — what the agent was trying to do, why each step followed, where it hesitated, how it recovered.

ExplainController.java

GET /api/file

The tree/content root for a request: THAT session's resolved workspace, and nothing else.

WorkspaceController.java

GET /api/files

Phase 5: the workspace panel's backend — a read-only, sandboxed view of the agent's working directory.

WorkspaceController.java

GET /api/fleet

The fleet's REST face (block C): the roster fold and per-node ring replay.

FleetController.java

POST /api/fleet/nodes

Spawn a readonly fleet node (block 3b) — the RCE-sensitive surface, gated to the teeth: LOCAL-ORIGIN (loopback remote address AND a localhost Host — the Host check is what stops DNS rebinding, which a loopback check alone does not), JSON-only (consumes makes a browser form POST a 415), OPT-IN (SPECTRO_ALLOW_SPAWN, default off), a spawn cap, and the node is FORCED readonly (see {@link NodeSpawner}).

FleetController.java

GET /api/fleet/{node}/events

Per-node replay from the hub ring — bounded by the ring, and saying so.

FleetController.java

POST /api/fleet/{node}/gate

Answer a parked permission gate on a fleet node (block 4): dispatch a ctl{gate} carrying the operator's verdict to the node over the hub.

FleetController.java

POST /api/fleet/{node}/message

Carry the operator's words to a fleet node that stays (card 166's server leg) — the verb that turns a node you can only stop into one you can talk to.

FleetController.java

POST /api/fleet/{node}/stop

Stop a running fleet node (block 3): dispatch a ctl{stop} to it over the hub.

FleetController.java

GET /api/governing-numbers

The numbers that govern a run, read out for the settings room — card 357.

GoverningNumbersController.java

GET /api/health

The health probe the desktop shell polls before loading the UI.

SessionsController.java

POST /api/images/copy-to-workspace

Copies a generated image from the global content-addressed store (~/.spectro/images/.) into a session's workspace — the gallery's "copy to workspace" button.

ImageCopyController.java

GET /api/images/{file}

A dedicated client for the model-list probes with FINITE connect + read timeouts.

SessionsController.java

GET /api/jobs/state

The scheduler's job-state map (the same data `spectroscope cron status` prints).

SessionsController.java

GET /api/leveling

The ladder over REST: read the state, report a visited surface, switch mode, restart, and tick a criterion by hand.

LevelingController.java

POST /api/leveling/mode

Switches how much of the ladder is doing work in this home.

LevelingController.java

POST /api/leveling/reset

Restarts the ladder from the dark frame, keeping the chosen mode.

LevelingController.java

POST /api/leveling/tick

Marks a criterion by hand.

LevelingController.java

POST /api/leveling/visit

Reports that a surface was shown, which is how acts the event stream cannot see reach the ladder.

LevelingController.java

GET /api/local-model/catalog

The built-in provider's model endpoints, behind the picker's chooser dialog: GET /api/local-model/catalog (every offered model, its download state and whether this machine can hold it), GET /api/local-model/status (polled for progress) and POST /api/local-model/download (starts the fetch).

LocalModelController.java

POST /api/local-model/download

Start one model's download (idempotent per model; two different models may download concurrently).

LocalModelController.java

GET /api/local-model/status

Current download/presence state of one model — polled by the dialog for its progress bar.

LocalModelController.java

GET /api/logs

The doctor page's live server log (card 85): the rolling ~/.spectro/logs/spectroscope.log the shared logback.xml writes, served as a tail plus cheap incremental deltas — the client polls with the returned byte offset and receives only what was appended since.

LogsController.java

GET /api/models

Curated fallbacks — used when the live model APIs are unreachable.

SessionsController.java

GET /api/models/capabilities

The capability sibling of /api/models (card 88): what one (provider, model) pair honestly supports for reasoning control, as the {@link ReasoningCapability} record — the same record the providers gate their wire fields on, so the picker and the request path share ONE truth.

ModelCapabilityController.java

POST /api/onboarding/key

Save an API key from the onboarding UI — LOCAL browsers only.

SessionsController.java

GET /api/otlp/probe

The doctor's OTLP check: is the configured exporter endpoint reachable and does it accept our auth?

OtlpProbeController.java

POST /api/pick-workspace

Opens the NATIVE folder chooser on the machine spectroscope runs on and returns the picked absolute path.

WorkspacePickController.java

GET /api/sessions

The REST endpoints alongside the socket.

SessionsController.java

GET /api/sessions/live

The REST twin of the live_sessions socket frame (card 212).

LiveSessionsController.java

DELETE /api/sessions/{id}

Session ids as the store mints them (yyyyMMdd-HHmmss-uuid8) plus the test/CLI-friendly general shape — never a path, never a dot.

SessionsController.java

GET /api/sessions/{id}/browser-wire

The read side of the browser record (card 204): the sidecar the {@link BrowserWireRecorder} writes under ~/.spectro/browser-wire/, served three ways — the whole file as a download, a bodiless ledger the replay view scrubs, and one action's two lines on demand.

BrowserWireController.java

GET /api/sessions/{id}/browser-wire/action/{cid}

One action's two recorded lines, parsed: the drill-in behind a scrubber step.

BrowserWireController.java

GET /api/sessions/{id}/browser-wire/index

The bodiless ledger the replay view scrubs: one object per action in file order, the call and its result paired by cid while STREAMING the file.

BrowserWireController.java

GET /api/sessions/{id}/events

The events of one session as JSON — the graph tab replays exactly this.

SessionsController.java

GET /api/sessions/{id}/export

Export one stored session as its RAW JSONL — the mirror of the existing import, so a session can leave the machine and come back byte-identical.

SessionsController.java

GET /api/sessions/{id}/llm-wire

The read side of the backend-to-LLM record (card 184): the sidecar file the {@link LlmWireRecorder} writes under ~/.spectro/llm-wire/, served three ways.

LlmWireController.java

GET /api/sessions/{id}/llm-wire/exchange/{xid}

One exchange's two recorded lines, parsed: the drill-in behind an index row.

LlmWireController.java

GET /api/sessions/{id}/llm-wire/index

The bodiless ledger: one object per exchange in file order, request and response lines paired by xid while STREAMING the file; bodies and stream lines are read past, counted and never returned, because one sidecar can be orders of magnitude bigger than the session it records.

LlmWireController.java

GET /api/settings

The settings API — the read side.

SettingsController.java

GET /api/settings/allowlist

GET /api/settings/allowlist: the auto-approve allowlist READ OUT — every entry with the tier it now carries, per scope and folded (card 199, criterion 4).

SettingsController.java

GET /api/settings/env

GET /api/settings/env: the operator settings the UI may save, and what they are set to now.

SessionsController.java

POST /api/settings/env

POST /api/settings/env: save one operator setting to ~/.spectro/.env.

SessionsController.java

GET /api/settings/hooks

One layer's raw autoApprove array, read out through the gate's own parser.

SettingsController.java

PUT /api/settings/local

PUT /api/settings/local?session=: applies a partial patch to the session's workspace .spectro/settings.local.json (machine-local, gitignored — {@link SettingsWriter#patch} ensures the .gitignore entry) and answers the fresh session-moment view.

SettingsController.java

PUT /api/settings/project

PUT /api/settings/project?session=: applies a partial patch to the session's workspace .spectro/settings.json and answers the fresh session-moment view.

SettingsController.java

PUT /api/settings/user

One layer's raw hooks array, read out through the core's own record.

SettingsController.java

GET /api/skills

The settings page's skill manager (card 90): list the skills of both roots (user ~/.spectro/skills and project /.spectro/skills) — including DISABLED ones the loader hides — toggle a .disabled marker, and delete a user-root skill (the seeding ledger keeps it from returning).

SkillsController.java

POST /api/skills/install

The shelf, with installed recomputed per request — the index itself is built once, since the catalogue rides inside the artifact and cannot move.

SkillsController.java

POST /api/stt/model/download

Fetch the pinned model.

SttController.java

GET /api/stt/status

What speech-to-text needs, whether it is here, and the one half of it this app can honestly fetch (card 184 leg 2b).

SttController.java

POST /api/transcribe

The web face of voice input: the browser records with MediaRecorder, converts the recording to 16 kHz mono WAV itself, and POSTs that here; the server runs the SAME {@link Transcriber} the CLI uses.

TranscribeController.java

POST /api/translate

One translation run, streamed as NDJSON — a provider call per passage.

TranslateController.java

GET /api/translate/engines

Translating an imported session (owner 2026-07-27, reading an incident-response transcript written in Ukrainian): the reader hands over the READABLE passages of the record and gets each one back in the language they picked, from either the built-in local model or the configured cloud provider.

TranslateController.java

modules (7)

From settings.gradle.kts, plus the two npm toolchains.

spectro-core gradle module

The harness SDK: agents, tools, the permission gate, the typed RunEvent stream. Plain Java 21; Spring appears only as its HTTP client.

spectro-cli gradle module

The ./spectro launcher's engine: run, REPL, doctor, cron, sessions, resume (picocli).

spectro-server gradle module

One Spring Boot app: WebSocket + REST faces, serves the built web UI from its jar.

spectro-mcp-notes gradle module

A worked MCP server example (stdio JSON-RPC) to copy from.

spectro-orchestrator gradle module

The fleet: BusEnvelope choreography over one bus, every lane a full core agent, one merged EventStream.

spectro-web npm toolchain

The browser cockpit (React 19 + Vite): chat, Spectrum, trace, graph, text and Lab over one WebSocket; builds into the server's static resources.

spectro-desktop npm toolchain

The Electron shell: supervises the boot jar as a child process (not a gradle module).

tools (10)

The public catalog on the Tools facade (Tools.java), wire names cross-checked against StandardTools.java; the permission gate fronts every one of them.

Tools.readFile()read_fileTool factories that read as plain names — the frozen facade's tool vocabulary: .tools(Tools.readFile(), Tools.runCommand()).
Tools.writeFile()write_file@return write_file — create or overwrite a workspace file
Tools.editFile()edit_file@return edit_file — exact-match replacement inside a workspace file
Tools.listDir()list_dir@return list_dir — a workspace directory listing
Tools.glob()glob@return glob — filename patterns over the workspace
Tools.grep()grep@return grep — content search over the workspace
Tools.runCommand()run_command@return run_command — a shell command in the workspace (permission-gated)
Tools.viewImage()view_image@return view_image — shows the model an image from the workspace
Tools.viewFile()view_file@return view_file — shows the model a document from the workspace
Tools.all()@return the full standard belt, in registration order

environment (26)

SPECTRO_* keys found in spectro-core. Precedence: defaults < env < user settings < project settings < workspace settings < flags.

SPECTRO_ALLOW_LOCALHOSTsee the SpectroConfig javadoc
SPECTRO_BASE_URLOverride the provider endpoint (local proxies, compat servers).
SPECTRO_CHROMEPath to the Chrome binary the vision tools drive.
SPECTRO_HEADLESS_MCPsee the SpectroConfig javadoc
SPECTRO_IMAGE_MODELImage generation backend override.
SPECTRO_IMAGE_PROVIDERsee the SpectroConfig javadoc
SPECTRO_LLAMACPP_BASE_URLsee the SpectroConfig javadoc
SPECTRO_LMSTUDIO_BASE_URLsee the SpectroConfig javadoc
SPECTRO_LOG_LEVELWire logging: info by default.
SPECTRO_MAX_RETRIESProvider retry budget.
SPECTRO_MODELModel name for the selected provider.
SPECTRO_OLLAMA_BASE_URLsee the SpectroConfig javadoc
SPECTRO_OTLP_BASIC_AUTHsee the SpectroConfig javadoc
SPECTRO_OTLP_ENDPOINTsee the SpectroConfig javadoc
SPECTRO_PATH_EXTRAsee the SpectroConfig javadoc
SPECTRO_PROMPT_CACHINGsee the SpectroConfig javadoc
SPECTRO_PROVIDERLLM backend: anthropic, ollama or any OpenAI-compatible server.
SPECTRO_SEARXNG_URLsee the SpectroConfig javadoc
SPECTRO_STT_LANGUAGEsee the SpectroConfig javadoc
SPECTRO_STT_MODELSpeech-to-text model for /voice.
SPECTRO_STT_PROVIDERsee the SpectroConfig javadoc
SPECTRO_THINKINGsee the SpectroConfig javadoc
SPECTRO_TOOL_INPUTsee the SpectroConfig javadoc
SPECTRO_TOOL_NAMEsee the SpectroConfig javadoc
SPECTRO_TOOL_RESULTsee the SpectroConfig javadoc
SPECTRO_WORKSPACEsee the SpectroConfig javadoc