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 (35)

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[]
workspaceoptionalstring
llmWireoptionalstring
browserWireoptionalstring
childrenoptionalstring[]
contextWindowoptionalnumber
windowSourceoptionalThresholdSource
originoptionalstring
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
decidedByoptionalstring | null
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

context_cleared

agentIdstring
removedMessagesnumber
tsnumber

playbook_progress

agentIdstring
runIdstring
phase"start" | "step" | "end"
playbookoptionalstring
taskoptionalstring
nodeoptionalstring
nameoptionalstring
provideroptionalstring
modeloptionalstring
stopReasonoptionalstring
resultoptionalstring
detailoptionalstring
tsnumber

playbook_picked

diroptionalstring
nameoptionalstring
noneoptionalboolean
tsnumber

run_step

agentIdstring
runIdstring
runKind"playbook" | "workflow"
phase"run_start" | "step_start" | "step_end" | "run_end"
nameoptionalstring
taskoptionalstring
nodesoptional{ id: string; name?: string; kind: string }[]
nodeoptionalstring
kindoptionalstring
entryoptionalnumber
performeroptional"chat" | "child"
roleoptionalstring
provideroptionalstring
modeloptionalstring
pickedoptionalboolean
inputoptionalstring
inputCharsoptionalnumber
readsoptional{ doc: string; path?: string }[]
roundoptionalnumber
maxRoundsoptionalnumber
statusoptional"done" | "failed" | "stopped"
durationMsoptionalnumber
childIdoptionalstring
childrenoptionalstring[]
outputoptionalstring
outputCharsoptionalnumber
documentsoptional{ doc: string; path: string; change?: "created" | "changed" }[]
labeloptionalstring
erroroptionalstring
stopReasonoptionalstring
resultoptionalstring
detailoptionalstring
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
aiCreditsoptionalnumber
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
thresholdSourceoptionalThresholdSource
contextWindowoptionalnumber
parts{ label: string; chars: number; estTokens: number; text?: string }[]
tsnumber

window_override

tokensoptionalnumber
thresholdnumber
thresholdSourceoptionalThresholdSource
contextWindowoptionalnumber
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

hook_decision

agentIdstring
callIdstring
toolNamestring
eventstring
matcherstring
commandstring
timeoutSecondsnumber
verdictstring
reasonoptionalstring
tsnumber

settings_ignored

keystring
filestring
hintstring
inForceoptionalboolean
inForceFromoptionalstring
keptoptionalstring[]
tsnumber

launch_outcome

namestring
okboolean
upboolean
urloptionalstring
problemoptionalstring
durationMsnumber
tsnumber

no_progress

agentIdstring
detectorstring
countnumber
detailsoptionalstring[]
evidencestring
callIdoptionalstring
tsnumber

progress_intervention

agentIdstring
callIdstring
detectorstring
interventionstring
stoodDownboolean
tsnumber

continuation

agentIdstring
decisionstring
continuationnumber
budgetnumber
openStepsnumber
totalStepsnumber
inputTokensnumber
evidencestring
tsnumber

goal_check

agentIdstring
outcomestring
commandstring
exitCodeoptionalnumber | null
judgestring
outputstring
durationMsnumber
gateWaitMsoptionalnumber | null
evidencestring
tsnumber

images_withheld

agentIdstring
imagesnumber
modeloptionalstring
reasonstring
tsnumber

question_asked

agentIdstring
callIdstring
questionsAskedQuestionWire[]
tsnumber

question_answered

callIdstring
answersstring[]
cancelledboolean
waitMsoptionalnumber
tsnumber

steering_message

agentIdstring
textstring
takenboolean
turnnumber
tsnumber

socket frames (29)

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
askoptionalstring

set_workspace

modeoptionalChooserOption
pathoptionalstring

set_permission_mode

modestring

set_tool_groups_off

groupsstring[]
saveoptionalboolean

set_local_mode

onoptionalboolean
valuesoptionalRecord<string, unknown>
resetoptionalstring[]

set_window_override

tokensnumber | null

question_response

callIdstring
answersstring[]
cancelledoptionalboolean

steering_message

textstring

compact_context

clear_context

start_playbook

dirstring
hashstring
promptstring
picksoptionalRecord<string, { provider: string; model: string }>
askoptionalstring

confirm_playbook

promptstring
diroptionalstring

set_playbook

dirstring | null
noneoptionaltrue

start_workflow

dirstring
filestring
hashstring
inputstring
lang"en" | "de"

start_workflow

namestring
inputstring
lang"en" | "de"
diroptionalstring

wake_session

sessionIdstring

peer_message_decision

msgIdstring
deliverboolean

send_peer_message

tostring
toSessionoptionalstring
textstring

set_goal

outcomestring
checkstring

set_loop

action"start"
promptstring
intervalSecondsoptionalnumber

set_loop

action"stop"

get_session_items

graph_ask

questionstring

REST endpoints (123)

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}

The catalog for a given stamp.

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

POST /api/codegraph/start

POST /api/codegraph/start: starts graphify in the background against the session's resolved folder.

CodeGraphController.java

GET /api/codegraph/status

"Build code graph" (card 472): graphify run as a background job of the harness against a session's working folder, and its graph.html served to the internal browser.

CodeGraphController.java

GET /api/codegraph/view

GET /api/codegraph/view: the session folder's graph.html, for the internal browser, under {@link #SANDBOX}.

CodeGraphController.java

GET /api/commands

The chat commands for the web (card 554, owner option A): the web chat's /help, its slash picker and the Skills and Commands page are built from this answer, and the terminal REPL prints the same list from the same resource.

CommandsController.java

GET /api/config

Card 379: whether rtk resolves where the agent's shell looks, and which version answered.

SessionsController.java

GET /api/context

Why a value is not the shape its setting takes, or null when it is.

SessionsController.java

GET /api/copilot/account

The Copilot sign-in sheet's endpoints (card 495).

CopilotAccountController.java

POST /api/copilot/account/cancel

POST /api/copilot/account/cancel: stops a waiting sign-in.

CopilotAccountController.java

POST /api/copilot/account/sign-in

POST /api/copilot/account/sign-in: starts a device flow, or takes the Copilot CLI's stored sign-in when the method is cli and the CLI has one.

CopilotAccountController.java

POST /api/copilot/account/sign-out

POST /api/copilot/account/sign-out: deletes the stored tokens, or the choice of the CLI's sign-in.

CopilotAccountController.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/port-check

Card 585: both hub settings as the server read them, the port in use, and why the hub differs from what was written.

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}

The Copilot runtime's own model list (card 496), in its order.

SessionsController.java

GET /api/jobs/state

The scheduler's job-state map (the same data `spectroscope cron status` prints), plus the code graph builds of this server process under codegraph: (card 472), whose status is running while they build.

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

LM Studio's own listing, read for one model; unknown when it does not answer.

ModelCapabilityController.java

GET /api/models/tool-use

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/playbooks

The playbook routes: the known folders and the pin per workspace, loading a registered folder, and copying a bundled playbook into a folder the owner picked.

PlaybookController.java

PUT /api/playbooks/active

PUT /api/playbooks/active {workspace, dir} : pins a registered folder to a workspace.

PlaybookController.java

POST /api/playbooks/bundled/{id}/copy

POST /api/playbooks/bundled/{id}/copy {dir} : writes a shipped playbook into the folder and registers it.

PlaybookController.java

GET /api/playbooks/contents

One of the card 523 routes: the fence, the registered folder, then the work.

PlaybookController.java

GET /api/playbooks/draft

The largest draft body the check reads.

PlaybookController.java

POST /api/playbooks/draft

POST /api/playbooks/draft?dir=&workspace= with the draft as the body : the editor view of the body, checked as a load would check it.

PlaybookController.java

GET /api/playbooks/export

The largest body POST /api/playbooks/import reads: a bundle of 24 MB in JSON, twice over, for base64 and escaped text.

PlaybookController.java

PUT /api/playbooks/file

PUT /api/playbooks/file?dir=&workspace= with {"baseHash", "playbook"} : saves the draft as the folder's playbook.json in the canonical form.

PlaybookController.java

DELETE /api/playbooks/file-at

DELETE /api/playbooks/file-at?dir=&path= : removes one text file of a registered folder, never a folder and never playbook.json, LICENSE or PROVENANCE.md at the root.

PlaybookController.java

POST /api/playbooks/folders

POST /api/playbooks/folders {dir} : registers a folder that holds a playbook.json.

PlaybookController.java

POST /api/playbooks/import

A JSON answer on a route whose body is a stream: Spring writes a {@link StreamingResponseBody} whatever the status, so a refusal is serialized here.

PlaybookController.java

POST /api/playbooks/import-hooks

POST /api/playbooks/import-hooks {dir, replace?, baseHash?, workspace?} : writes the hooks of the user settings into hooks/hooks.json, copies the scripts their commands name from the home into hooks/, writes the project folder (the workspace, else the launch folder) as {project}, leaves out a hook that names a dot folder of the home (card 538), and lists the file in contents.hooks.

PlaybookController.java

POST /api/playbooks/import-plugin

POST /api/playbooks/import-plugin {dir, baseHash?, plugin:{root?, marketplace, name, version, kinds:[skills|agents|commands|hooks]}} : copies the chosen kinds of a listed plugin into the folder (card 526).

PlaybookController.java

POST /api/playbooks/import-skills

POST /api/playbooks/import-skills {dir, baseHash?, items:[{source, root?, pack?, name?]}} : copies installed or catalogue skills into the folder's skills// and lists the pack in contents.skills.

PlaybookController.java

GET /api/playbooks/import-sources

GET /api/playbooks/import-sources?dir=&workspace= : what can be put into a registered folder (card 523): the installed skills by root label and pack, the catalogue skills by id and the hooks of the user settings, each hook with left set when the import leaves it out.

PlaybookController.java

GET /api/playbooks/load

GET /api/playbooks/load?dir=&workspace= : a registered folder, loaded.

PlaybookController.java

POST /api/playbooks/new-agent

POST /api/playbooks/new-agent {dir, name, type, skill?, description?} : writes agents/.md from the template and lists it in contents.agents.

PlaybookController.java

POST /api/playbooks/new-command

POST /api/playbooks/new-command {dir, name} : writes commands/.md from the template and lists it in contents.commands.

PlaybookController.java

GET /api/playbooks/plugin-sources

GET /api/playbooks/plugin-sources?dir= : the Claude Code plugins of this home (card 526), from the plugin cache and the local plugins of each marketplace under ~/.claude/plugins, each by its root label, marketplace, name and version, with the count of each kind it carries, whether it carries MCP servers and whether it has a licence.

PlaybookController.java

GET /api/playbooks/read

GET /api/playbooks/read?dir=&path= : one text file of a registered folder with its sha256.

PlaybookController.java

POST /api/playbooks/remove-content

POST /api/playbooks/remove-content {dir, kind, path} : takes the entry out of contents and removes the files under its path, never a file outside the kind's folder and never playbook.json.

PlaybookController.java

GET /api/playbooks/start-preview

GET /api/playbooks/start-preview?dir=&workspace= : the confirmation of a run (card 482).

PlaybookController.java

GET /api/playbooks/tree

The largest body PUT /api/playbooks/write reads: a text of 1 MB in JSON, where an escaped control character takes six bytes.

PlaybookController.java

PUT /api/playbooks/write

PUT /api/playbooks/write {dir, path, text, baseHash} : writes one text file of a registered folder through a temp file and an atomic move.

PlaybookController.java

GET /api/providers

The provider registry on the wire.

ProvidersController.java

POST /api/providers/check

POST /api/providers/check: runs a bounded check and returns the rows.

ProvidersController.java

GET /api/run-document

The bytes of one file whose path a route has already checked, typed for the preview: the answer /api/file and /api/run-document share once their own checks have passed.

WorkspaceController.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

PATCH /api/sessions/{id}

Card 445: renames or pins one stored session.

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}/bundle

Export one stored session as a bundle (card 473): a zip with the session file, its llm wire, its browser wire and its child session files, each entry the file on disk byte for byte.

SessionsController.java

GET /api/sessions/{id}/events

The config for a session's recorded provider and model; the server's own config when the session recorded none.

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 request order, request and response lines paired by xid.

LlmWireController.java

GET /api/sessions/{id}/messages

Card 503: the agent messages of one stored session, as the peer_message frames the page folds into its message blocks, read from its .messages.jsonl under ~/.spectro/messages.

SessionsController.java

GET /api/sessions/{id}/playbook-runs

The playbook runs of one session (card 482): a row per run read from the sidecar, and the graph file of a run.

PlaybookRunsController.java

GET /api/sessions/{id}/playbook-runs/{run}/graph

GET /api/sessions/{id/playbook-runs/{run}/graph}.

PlaybookRunsController.java

POST /api/sessions/{id}/title/suggest

Card 445, criterion 8: asks the session's own provider and model for a title, once, from its first prompt.

SessionsController.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/skills/install-set

Copy several catalogue skills into the user root in one request, all or nothing (card 410: a pack's install button).

SkillsController.java

POST /api/skills/remove-set

Take several catalogue skills out of the user root in one request, all or nothing (card 410: a pack's remove button).

SkillsController.java

GET /api/spectrolyzr

The Spectrolyzr routes: the catalog the wizard offers, a preview of the files one set of choices renders, and the generate route that writes them.

SpectrolyzrController.java

POST /api/spectrolyzr/generate

POST /api/spectrolyzr/generate with archetype, language, addons, name, dir, playbookDir: writes the project and, with the playbook add-on, the playbook folder, and pins the latter to the former.

SpectrolyzrController.java

GET /api/spectrolyzr/preview

GET /api/spectrolyzr/preview: the files one set of choices renders, each with its reason, and the test and check commands.

SpectrolyzrController.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

POST /api/workspace/reveal

The folder chip's two server actions (card 462): show a live session's working folder in Finder, and open Terminal in it.

WorkspaceOpenController.java

POST /api/workspace/terminal

POST /api/workspace/terminal: opens Terminal in the session's working folder.

WorkspaceOpenController.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 (27)

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_COPILOT_CLIENT_IDsee the SpectroConfig javadoc
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