neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
Spor는 AI 비서와 팀원이 이미 정한 결정·규칙·미해결 질문을 바탕으로 작업을 시작하고, 완료된 작업을 그 이유까지 추적할 수 있습니다
Every AI assistant and teammate starts a piece of work with the decisions, rules, and open questions the team already worked out. And you can trace any finished work back to why.
개발 도구JavaScript · ShellGitHub @sporhq온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
Spor — a shared memory substrate for teams and agents. Decisions, their reasons, and the traces they leave.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Spor gives coding agents a memory they can carry from one session to the next.
It keeps track of the useful things that normally disappear into chat history: decisions, rejected approaches, open questions, project norms, tasks, incidents, and the reasons behind them. When a new session starts, Spor briefs your agent with the relevant parts of that history. When the session ends, useful new context can be written back.
The name comes from Norwegian: spor means the track something leaves behind.README 전문 보기 ↗Spor gives coding agents a memory they can carry from one session to the next.
It keeps track of the useful things that normally disappear into chat history: decisions, rejected approaches, open questions, project norms, tasks, incidents, and the reasons behind them. When a new session starts, Spor briefs your agent with the relevant parts of that history. When the session ends, useful new context can be written back.
The name comes from Norwegian: spor means the track something leaves behind.
📖 Full documentation: docs.sporhq.io — concepts, getting started, the CLI, the REST API, the MCP surface, and the hosted product.
Coding agents are good at working inside a single session. The problem is everything before and after that session.
Without shared memory, you keep repeating things like:
Spor gives those facts somewhere to live.
It is not just a pile of notes. Spor stores knowledge as a small typed graph, so it can preserve relationships such as:
That graph can live locally on your machine, or be shared by a team.
At a high level, Spor runs a simple loop:
Brief At the start of a coding session, Spor finds relevant project context and gives it to your agent.
Nudge While you work, Spor can suggest capturing useful discoveries before they vanish.
Distil At the end of a session, Spor can turn the important parts of the transcript into one or two durable graph nodes.
Reuse Future sessions get briefed from the graph instead of starting cold.
You can also ask for things directly, such as a briefing for a task, a correction to stale context, or the next item in the project queue.
A briefing is the packet of context Spor gives your coding agent before it starts work.
It is not a transcript dump. Spor reads the project graph, finds the nodes that look relevant to the current repo and task, and turns them into a short working summary.
A good briefing might include:
For example, before working on auth, a briefing might tell the agent:
Use the token exchange flow from dec-auth-token-exchange.
Do not revive the old session-cookie approach; it was rejected in dec-auth-cookie-rejection.
The current blocker is issue-auth-refresh-race.
Security review notes are in art-auth-review-2026-06.
The point is to give the agent enough memory to start in the right place, without making you paste old notes into every session.
Briefings can be created automatically at session start, but you can also ask Spor directly.
In agent hosts that support tool mentions, use @Spor:
@Spor brief me before I change the auth middleware
@Spor what do we already know about the export pipeline?
@Spor why did we reject the previous queue design?
@Spor what should I avoid touching in this repo?
You can also use the explicit command:
/spor:brief
A briefing is itself stored as a graph node, with links back to the nodes it was compiled from. That means it can be reviewed, corrected, versioned, and rebuilt when the graph changes.
Spor is distributed as an npm package.
npm install -g @sporhq/spor
Requirements:
For local use, Spor runs without:
The package installs two commands:
spor — the human-facing CLIspor-hook — the hook dispatcher used by agent hostsCheck the install with:
spor --help
Install the adapter for the agent host you use:
spor install claude
Other supported hosts include:
spor install codex
spor install gemini
spor install cursor
spor install copilot
spor install opencode
To see what Spor detects on your machine:
spor install
Useful install flags:
spor install --all # install every detected host
spor install --print # show what would change, without changing it
spor install --scope repo
spor install --scope user
--scope user is the default. It installs Spor for you.
--scope repo writes configuration that can be committed with a repository.
Re-running spor install is safe. It refreshes the Spor paths and does not duplicate existing hooks.
After installing the adapter, open your coding agent inside a repo and run:
/spor:onboard
This is the easiest way to start.
Onboarding will:
You can run it again later if setup was interrupted or something feels wrong.
Once Spor is enabled for a repo, you usually do not need to think about it.
At the start of a session, your agent gets a briefing: a short, task-aware summary of the project memory that matters right now. During the session, Spor can surface related context, answer direct questions through @Spor, or suggest captures. At the end, it can distil useful discoveries back into the graph.
In Claude Code, the main commands are:
/spor:spor # the operating manual — load before any graph operation
/spor:brief # get a briefing for a task or area
/spor:correct # fix stale or wrong context
/spor:defer # capture something to return to later
/spor:ask # record a question the graph cannot answer
/spor:next # show the next useful thing to work on
/spor:triage # actively work the queue: dedupe, groom, close readiness gaps
/spor:onboard # first-time setup
/spor:backfill # extend the graph from existing sources
/spor:factory # compile a factory: what has to be true before work counts as done
From the shell, spor status is the first thing to run when something is unclear:
spor status
It shows the current mode, graph, project, server health, and whether Spor is active in the current repo.
Spor can dispatch background work with the right context already attached.
spor dispatch "wire up token rotation in the pipeline"
You can also dispatch from existing graph nodes or from the queue:
spor dispatch issue-86
spor dispatch --from-queue
spor dispatch --backfill
When dispatching a node, Spor briefs the agent with the relevant neighbourhood of the graph. It also avoids obvious duplicate work: if the same node is already being worked on locally, or already claimed in team mode, Spor refuses the duplicate dispatch unless you force it.
To see what would be launched without starting anything:
spor dispatch issue-86 --print
--print is a full diagnostic, not just a prompt preview: it runs the same
resolution path a real dispatch does and reports the effective tenant and the
selector that chose it, the profile and harness, the write posture, the
candidate workspace and whether it is isolated, and a preflight: verdict
saying whether an unattended worker would be refused there and why. It performs
none of a dispatch's side effects — no claim, no child, no worktree, no config
write — and never echoes a credential.
To provide your own prompt wrapper:
spor dispatch issue-86 --template prompt.tpl
Templates can use placeholders such as:
{{brief}}
{{task}}
{{node}}
{{id}}
{{title}}
{{summary}}
{{type}}
{{status}}
{{date}}
{{slug}}
{{dir}}
{{default}}
{{id}}, {{summary}}, {{type}}, {{status}}, and {{date}} come from the
dispatched node's own frontmatter fields (blank in free-text or --backfill
dispatch, where there is no target node).
A dispatched agent runs beyond the launcher's own lifetime — a Claude Code
agent detaches into its own background-agent daemon, a Codex agent into a
supervisor Spor owns (more on that below) — so spor dispatch records every
run it launches and spor runs reports how each one ended:
spor runs
spor runs --node issue-86
Each run resolves to a terminal state — done, failed, vanished (it stopped
mid-turn, or it ended in a way nothing can be attributed to), or failed_launch
— with the reason, and a class that keeps environment failures such as provider
credit exhaustion separate from failures of the work itself. A Claude Code run
that bound a session also carries a pointer to its transcript; a Codex run's
equivalent evidence is its own log instead. One that never bound a session says
so rather than borrowing a transcript from whatever else ran in that checkout.
Terminal records age out after dispatch.runRetentionMs (default 14 days).
That state describes how the process ended. Alongside it every run also
carries its outcome — what the run did to the graph — as exactly one of
resolved, reported, declined, or failed:
| outcome | meaning |
|---|---|
resolved | the graph itself attests completion — a live resolving edge (resolves/answers) onto the target node, or, for a type retired by its own status rather than by an edge, a status that has reached that type's terminal partition. Verified by re-reading the node after the run, never inferred from an exit code and never taken from the agent's own word |
reported | no attestation of completion, but the agent left a final report. An enforced run files it as an artifact node linked to the target (relates-to) and then hands the lease back, so the item returns to the queue carrying the work instead of vanishing into a dead run. report_node_id names the artifact — a filed report always reads reported, enforced or not; the converse does not hold, since an unenforced reported filed nothing |
declined | the agent declared the ITEM wrong rather than the work unfinished, by making the first line of its final report DECLINED: <reason>. An enforced run files the reason as a finding on the target, clears the target's readiness: agent stamp, and hands the lease back — the item goes to triage, never into a gate. The state is the agent's own declaration, so it stands whether or not those three land: an unenforced decline performs none of them, a finding the graph refuses leaves the lease deliberately held, and a refused readiness clear is recorded rather than fatal |
failed | no attestation and no report filed — a launch failure, a crash before any report, an empty one, or a graph that refused the write. terminal_note carries the failure note. An enforced run hands the lease back, except where the report (or a decline's finding) could not be filed at all — see the ordering rule below |
The ordering is the contract: the report is filed before the lease goes back to the pool, so an interrupted run can leave a held lease with the report filed but never a released lease with nothing attached.
Those legs are attempts, not postconditions of the outcome: each records its own
verdict on the run record rather than being implied by the state. report_node_id
/ finding_node_id are present only where the artifact actually landed,
readiness_cleared says whether the stamp was cleared, and lease_released says
whether the server confirmed the handback — a false there is an unconfirmed
handback, not proof the lease is still held, and the remedy reconciles either way
(spor release <id> is idempotent, and a claim someone else now holds answers
409). WORKERS.md §8 is the field-by-field contract.
Enforcement covers supervised launches (Claude Code, Codex, OpenCode, Copilot
CLI — every built-in). A target's TYPE never costs a run its enforcement: both
attestation paths — the resolving edge, and the terminal own-status a
status-only type is retired by — are judged, and a target that either path finds
unfinished takes the same file-then-hand-back route, whichever one judged it.
What does cost it is a posture where nothing could be verified at all: a
legacy native-background run record (from the retired spor dispatch --bg
launch), a free-text dispatch with no target node, and a graph that could not be reached are
classified best-effort and marked terminal_enforced: false — an unenforced run
can never read resolved. Local mode is not excluded: it has no server door to
file a report or hand a lease back through, but it does have a graph, so a local
target that reads attested complete is an enforced resolved, and only its other
outcomes are unenforced. A report is still filed wherever one exists and the
graph is reachable. Filing sits downstream of that verify leg, so a record this
client writes unenforced today carries no report_node_id — but reach for the
artifact by testing that key's presence, not by testing terminal_enforced,
which is the flag for whether to trust terminal_state (WORKERS.md §8 has the
retained-record case that distinction exists for).
spor runs prints the outcome (tagging (unenforced)), the note, and the report
artifact id; spor runs --json carries the same fields on each record.
spor dispatch does one item. spor work does them all: it polls the queue,
takes the items this machine may actually run, dispatches each one under its
routed profile, waits for its terminal state, and goes round again.
spor work # work the whole queue, one run at a time
spor work --project spor --concurrency 2 # two runs in flight, scoped to one project
spor work --once --print # tenant, posture, workspace isolation, gate coverage, candidates — launch nothing
It is pull, not push: nothing schedules a worker, it takes work. That is safe because the claim is a server-held lease with a per-launch nonce — two workers racing for one node end with one claim and one refusal, and a worker that dies drops its lease by lapsing. Capabilities stay machine-local facts and the fleet scheduler stays advisory, so a worker that cannot reach the scheduler degrades to "work the queue with what I have" rather than stopping.
It adds no guards of its own. Every launch goes through the same code path as
spor dispatch --node <id>, so already-resolved, requires: human, a profile
this box cannot satisfy (never substituted), a profile that tries to declare
what to execute, the same-machine duplicate guard, the auto-claim, worktree
isolation and the terminal-state contract all apply exactly as they do one-shot.
Two of those guards exist for the unattended case specifically (WORKERS.md §3.1)
and are checked before the claim: the resolved harness must have a
non-interactive write posture — a Claude Code worker with no
--permission-mode is refused rather than launched into a run where every write
comes back permission-blocked, and preflight never sets a posture for you — and
the candidate workspace must be free of other live writers, which with
dispatch.worktree off means one dispatch at a time per checkout (turn
dispatch.worktree on before running a worker at --concurrency above 1). (A
dispatch.worktreeSetup hook does not turn isolation on; declaring one without
dispatch.worktree is diagnosed, not silently honoured by halves.)
Selection is the same filtered page --from-queue picks its one item from,
minus anything whose derived readiness is human — a worker never claims work
meant for a person — and minus anything already in flight on this machine. An
item that is refused, or whose run ended without resolving it, is remembered
with the reason and retried after --retry-after instead of being re-attempted
on the next poll.
A slot frees when the run record goes terminal and its outcome is settled,
not when a launcher returns — by then the terminal-state contract has filed the
report and released or held the lease. There is no --no-claim here: the lease
is what keeps two workers off one node, so a loop always takes it. Stopping
(SIGINT/SIGTERM, or --once/--max) stops picking up new work; runs
already in flight are detached, keep going, and self-report through spor runs.
Every run is supervised — Claude Code, Codex, OpenCode, Copilot CLI, or a
declared harness — so a slot frees when the run's own supervisor closes its
record; --run-max (default 24 hours) is the backstop that stops following a
run that never goes terminal. (The native claude --bg launch, whose ending
could only be inferred by scraping claude agents --json and the session
transcript, is retired; a legacy record from it is judged from the record alone
and closed an hour after its launch.)
Run it as a service and read it back:
spor work --status # every worker on this box: slots, outcomes, what it is skipping and why
spor work --regate <run-id> --factory <id> # re-judge one refused run after fixing what refused it (no redo)
spor work --status --json
Records live under the machine-local journal; a worker whose process is gone
reads as stale, never as running. The work.* config keys (concurrency,
intervalMs, maxIntervalMs, retryAfterMs, project) let a unit file be a
bare spor work.
A worker with no factory declared runs bare: dispatch, await, repeat. Point it
at a factory definition — a type: factory node in the graph — and its
ordered gate list is enforced, in code, between the claim and the resolve.
spor schema adopt schema-factory # the factory/gate schemas ship as candidates
spor work --factory factory-spor-default # (or set work.factory)
spor work --print --factory factory-spor-default # see the gates without launching anything
Three kinds of gate, written inline in the factory or referenced as shareable
type: gate nodes an org vets once and reuses (the runner cannot tell them
apart):
touches:auth, …); files an
approval item and blocks the resolve until someone answers it.Every gate outcome is written to the graph as a fact linked to the work item, and a factory that does not validate refuses to start the worker rather than letting it run ungated. WORKERS.md §10 is the full contract.
You do not have to hand-write the definition. /spor:factory is the compiler:
it interviews you — product questions if you are the owner, pipeline questions
if you are the engineer — reads your CI config, suites and graph, proposes a
pipeline, and emits the factory, its gates and the profiles they route to as
nodes. It also maintains one from its own telemetry ("why did the last three
fail review"), and seeds a test-writer lane when there is no acceptance suite
to gate on yet. It authors data only; enforcement stays in spor work.
A factory that completes items itself (completion: {by: controller}) keeps
its coordination state in an execution store: one execution per item and
pipeline attempt, pinned to the definition it was opened under, leased with a
fence, moved by an ordered idempotent event log, and completed only at its
declared boundary. On a team server that store is the server's own
(/v1/executions, where a resolving edge into a held item is refused at the
door); in personal mode it is a shape-compatible local store under your Spor
home. spor executions reads either:
spor executions # every execution this box drives
spor executions --node task-x # one item's executions
spor executions exec-4da6d4763543a301 --events
A worker that loses its lease — its box went quiet and another took the execution over — never writes the resolving edge on the strength of its local state; what it recorded while partitioned is replayed idempotently when it reconnects. WORKERS.md §10.15 has the contract.
By default, spor dispatch launches a Claude Code agent in headless print mode
(claude -p --output-format stream-json) under the shared supervisor: the
prompt goes in on stdin, the run's session id and final report are read off its
event stream, the run record goes terminal when the process does, and the
terminal-state contract judges the outcome like any other supervised harness.
This is the only launch mode: the native background session (spor dispatch --bg, claude --bg) is retired — its outcome could only be inferred by
scraping claude agents --json and the session transcript, which shifted with
every Claude Code release. --bg is now refused (run claude --bg yourself
for an attachable session), and a leftover dispatch.claudeLaunchMode: native-background is ignored with a warning. To dispatch under a different coding-agent CLI —
Codex, OpenCode, and GitHub Copilot CLI are also supported — resolve a
profile: a node that bundles a harness, model, and toolset.
spor dispatch issue-86 --profile profile-codex-sol
A profile looks like this:
---
id: profile-codex-sol
type: profile
title: Codex / gpt-5.6-sol
summary: Codex harness running gpt-5.6-sol — general-purpose dispatch profile.
status: active
harness: codex
model: gpt-5.6-sol
mcp: [spor]
---
Profiles are authored deliberately rather than captured from a transcript —
write one yourself with spor put-node, or reuse one your team has published.
An agent node can also carry a default profile (a uses-profile edge), so
its dispatches pick a harness without a flag every time; --profile on the
command line always wins over that default.
Before launching anything, dispatch checks whether this machine can
actually run the resolved profile — is the harness's CLI reachable, are the
right MCP servers reachable, and so on (see spor capabilities). If it can't,
dispatch refuses outright rather than silently falling back to Claude Code; in
team mode it also names any other machine in the fleet that can run it.
Codex-specific flags (--sandbox, --approval-policy) and Claude-specific
ones (--permission-mode, --agent) are mutually exclusive — passing the
wrong one for the resolved harness is a hard error, so a dispatch can't launch
half-configured for the wrong CLI. The one exception: --permission-mode bypassPermissions against a Codex profile has a real Codex equivalent
("run fully unattended"), so instead of erroring it translates to exactly
--sandbox danger-full-access --approval-policy never and prints a loud
warning naming the translation — so an orchestrator or script that passes the
same bypass flag to every dispatch regardless of harness keeps working. The
translation is fixed: an explicit --sandbox/--approval-policy beside the
bypass is a contradiction and is refused, not an override (say the posture in
Codex flags alone if you want a different one). Every other permission-mode
value still hard-errors against Codex.
The harnesses do not all confine a run the same way. Codex dispatch defaults to
--sandbox workspace-write, so its filesystem reach is bounded. OpenCode
(--auto) and GitHub Copilot CLI (--allow-all --no-ask-user) have no
equivalent — a dispatch under either runs with unrestricted tool access,
because an unattended run has no human to answer a permission prompt. Dispatch
those into a worktree or a checkout you are willing to have an agent change.
Naming a launcher explicitly. A dispatched run does not inherit your
interactive shell, so a CLI installed under a prefix that only an interactive
shell sees (a common Homebrew setup) resolves when you check it by hand and
resolves to nothing when Spor launches it. Point Spor at the binary directly
rather than relying on PATH, in ~/.spor/config.json — machine-specific, like
dispatch.repos, so it never belongs in a committable .spor.json (and is
dropped with a warning if it turns up in one):
{ "dispatch": { "bin": { "opencode": "/home/linuxbrew/.linuxbrew/bin/opencode" } } }
SPOR_CLAUDE_CMD / SPOR_CODEX_CMD / SPOR_OPENCODE_CMD / SPOR_COPILOT_CMD
override the same thing per harness and win over the config file. An explicit
launcher is used verbatim and is never quietly swapped for something on PATH;
with none set, the bare name resolves on PATH as before.
Declaring a harness Spor has no adapter for. A profile can name a harness this client ships no adapter for at all — a team's modified Claude Code build, an internal wrapper. The graph carries only the name; the machine binds what that name runs, in the same machine-local config:
{
"dispatch": {
"harness": {
"oxalpha": {
"command": "/opt/ox/bin/ox",
"args": ["run", "--jsonl", "--dir={cwd}", "--model={model}"],
"label": "Ox Alpha",
"report": { "from": "lastText", "text": "message.text" },
"session": "session.id"
}
}
}
}
A profile then selects it with nothing but harness: oxalpha. That split is
the point: a graph write must never define what a machine executes. A
profile carrying a command, args, env or any other launch-defining field
is refused outright rather than honoured, and a machine that never declared the
id refuses the dispatch and leaves the item assigned — so an org can publish a
profile naming oxalpha and only the boxes whose owner bound that id will take
the work. spor capabilities lists a declared harness alongside the built-in
ones, and, in team mode, publishes it to the fleet so re-routing can find it.
The same rule holds for a write anyone else could land in your repo:
dispatch.harness (and dispatch.bin) are dropped with a warning if they
appear in a committable .spor.json, so cloning a repo — or pulling a PR
branch into one — can never choose a command this box will run. They are read
only from your own ~/.spor/config.json (or the global one).
What the declaration may set, and nothing else:
command — the launcher: an absolute path, or a bare name resolved on PATH.args — the argv template. {cwd} becomes the run's directory, {report}
the run's report path, {model} the resolved model. An entry carrying
{model} is dropped whole when no model resolves, so it has to be the
flag itself — --model={model}, or --model=anthropic/{model} — never a
bare value after a separate --model, which would leave that flag to swallow
the next argument. A declaration that gets this wrong is refused, not
launched.label — what spor dispatch and spor runs call it.report — how the run's final message is recovered: "lastText" (the
default) keeps the last string found at the report.text JSON path in the
harness's own event stream, and "file" means the harness writes the report
itself at the {report} path you passed it.session — the JSON path (or paths) carrying the harness's session id, so
the run can be bound to its agent session. Optional; without it the run
simply is not bound.Everything else is fixed, and naming it is an error: a declared harness always
runs under the supervisor below, always takes its prompt on stdin (so a
compiled briefing never lands in a process listing), and always gets its
agent-scoped token as SPOR_TOKEN in the run's environment. A malformed
declaration is refused by name — Spor never falls back to guessing.
Claude Code dispatch detaches into Claude Code's own background-agent daemon —
the launcher exits immediately, and spor dispatch can only reconcile what
happened to it afterwards from the harness's own session transcript. Every
other harness instead runs under a small supervisor Spor itself owns: it
launches the CLI's headless mode in the background, streams its progress into a
private log, and captures the run's final message to a report file. At launch
it prints where everything lives:
run: 3f9a2c1e-... (Codex supervisor running)
log: ~/.spor/journal/dispatch/3f9a2c1e-....log
report: ~/.spor/journal/dispatch/3f9a2c1e-....report.md
session: 019f7a51-...
log is the full JSONL progress stream; report is the run's final message —
the thing to read for "what did it conclude". Both paths, plus the run's
outcome, are also recorded durably and can be looked up later, same as any
other dispatch:
spor runs --node issue-86 # human-readable: state, why, log path
spor runs --node issue-86 --json # add .runs[0].report_path for the final message
By default, Spor can run entirely on your machine.
The graph lives outside your code repositories, under:
~/.spor/
or another directory if $SPOR_HOME is set.
The graph is a normal git repo. Nodes are plain markdown files. This means your project memory has history, diffs, branches, and all the boring-but-useful properties of git.
A local graph is good when:
Because the graph is outside your code repo, context from a branch can survive even if the branch never merges.
Team mode is for sharing one live graph across people and agents.
Join a team graph with an invite token:
spor join spor_pat_...
By default, this points at the hosted Spor service. To use another server:
spor join https://spor.example.com spor_pat_...
You can also configure team mode with environment variables:
export SPOR_SERVER=https://api.sporhq.io
export SPOR_TOKEN=spor_pat_...
In team mode, writes are attributed to the person or agent that made them. The server also handles concurrent writes so teammates do not clobber each other.
Team mode is useful when:
If the team server is unavailable, Spor fails open. It should not block your coding session.
You can also share a graph without running a server.
Create or clone a graph as a normal git repo, then point your code repo at it with a committed .spor marker:
# .spor
repo: my-service
graph: ../my-team-graph
The path is resolved relative to the repo marker. A common layout is:
my-service/
my-team-graph/
Everyone clones both repos side by side. Distilled nodes are written as markdown and can go through your normal pull-request flow.
This is simpler than team mode, but it does not provide live concurrent writes, hosted isolation, or question routing.
Spor stores small, typed nodes.
Examples include:
Each node is a markdown file with frontmatter and a short body. Nodes can link to other nodes using typed edges.
A simplified decision node looks like this:
---
id: dec-export-csv-format
type: decision
project: meridian
title: Bulk export uses CSV with a stable column order
summary: CSV is the first supported bulk export format because customers already use spreadsheet-based workflows.
status: active
date: 2026-06-09
edges:
- {type: derived-from, to: spec-export-schema}
- {type: supersedes, to: dec-export-json-only}
---
We chose CSV first because it works with the tools customers already use.
JSON export is still possible later, but it is no longer the first format.
See GRAPH.md for the full graph format.
Spor reads configuration from several places. More specific settings win over broader ones.
Precedence order:
SPOR_SERVER.spor.json$SPOR_HOME/config.json~/.config/spor/config.jsonA repo can opt in with .spor.json:
{
"enabled": true
}
Installing Spor does not automatically enable every repo you open. A repo is inactive until it has a .spor or .spor.json marker, or until you enable Spor globally.
This avoids leaking side-project context into a team graph by accident.
To enable Spor in the current repo:
spor enable
To check what mode and config are active:
spor status
To validate config:
spor validate
Never commit a team token into .spor.json. Use the environment, user config, or global config for secrets.
Spor can make small model calls for two things:
You can turn either off:
export SPOR_DISTILL=0
export SPOR_NUDGE=0
You can also point them at a custom backend:
export SPOR_DISTILL_CMD=/path/to/distiller
export SPOR_NUDGE_CMD=/path/to/classifier
The backend contract is simple: prompt on stdin, response on stdout.
Spor records model usage under:
$SPOR_HOME/journal/llm-calls/
To inspect spend:
spor cost
spor cost --since 2026-06-01
spor cost --json
Spor hooks are designed to fail open. If something goes wrong, your agent session should continue; you may just get less context.
For a health check, run:
spor-hook doctor
It reports things like:
If captures are stuck because the team server was unavailable, drain the outbox with:
spor drain
Update the npm package:
npm install -g @sporhq/spor
Then refresh installed adapters:
spor upgrade
For a specific host:
spor upgrade claude
To preview changes:
spor upgrade --print
This matters because some hosts cache plugins or hook definitions. Updating the npm package alone may not refresh what the agent has already loaded.
spor status will show when a loaded plugin is stale.
GRAPH.md — graph format, node types, edges, and schema behaviourAPI.md — REST and MCP server contractQUEUE.md — queue, capture, routing, and workflow detailsWORKERS.md — the worker protocol: claim/brief/work/report/resolve, lease
semantics, agent identity, and the terminal-state contract — for
implementing a Spor factory worker without this clientadapters/ — host-specific adapter notesCONTRIBUTING.md — contributing guideSECURITY.md — security policySpor is licensed under Apache-2.0. See LICENSE and NOTICE.
“Spor” and “sporhq” are project trademarks. The Apache license grants rights to the code, not to the marks. See TRADEMARKS.md for details.
Contributions are welcome under inbound = outbound Apache-2.0.
To cancel a factory execution still owned by another worker or machine, use the explicit person door:
spor release <node-id> --execution <execution-id> --force --reason "Why this execution must stop"
This requires a person credential on a server. Agent credentials cannot use the
command, including an agent owned by that person. Authorization uses the existing
tenant graph boundary; it does not introduce a separate project ACL. In local
mode, the graph home's Git email must bind to a person node and dispatch.agent
must be unset. That local check trusts filesystem/configuration access; it is not
cryptographic proof of a human at the keyboard.
The command inspects the exact execution, then releases it only if its observed
state is unchanged. The journal records the person, time, reason, prior owner and
fence. Late writes from the old worker are refused; release does not mark the
work complete. The execution acknowledgement precedes graph hold cleanup, and
cleanup never clears a different execution's hold. No local dispatch record is
required. If the response is lost or graph cleanup fails, a credential-bound
cleanup intent stays in journal/person-force-release under the user config
home. Repeat the same explicit command and reason to recover. Receipt recovery
only reads the authoritative acknowledgement and retries exact graph cleanup;
it never submits a force release unattended.
Local execution mutations use a filesystem lock across processes. A live writer keeps its lock regardless of elapsed time; a verifiably dead process can be recovered. Malformed ownership or an abandoned breaker fails closed. Stop all local execution writers before manually removing such a lock from the execution item index directory; ordinary execution reads never repair or delete it.
Each tagged release includes an installable plugin.zip GitHub Release asset.
Build it locally with npm run package:chatgpt (Python 3 required). See the
ChatGPT adapter for packaging and connection details.