The node model
A Spor node is one markdown file in the graph home’s nodes/ directory:
YAML frontmatter for the structured fields, a short prose body underneath.
Here is a decision from a fictional team building a parcel-tracking product:
---id: dec-carrier-webhookstype: decisionproject: parceltitle: Carrier status updates arrive as signed webhooks, not pollingsummary: We ingest carrier status via signed webhooks with a replay window, because polling every carrier API burned rate limits and lagged by minutes.status: activedate: 2026-05-14edges: - {type: derived-from, to: spec-tracking-events} - {type: supersedes, to: dec-carrier-polling}---
Polling was the first design and it worked until the third carrier. Webhooksgive us push latency and one verification path; the replay window coverscarriers that redeliver. Polling remains only as the reconciliation sweep.One fact per node
Section titled “One fact per node”A node records a single fact. If you find yourself writing “also” a lot, split it — two facts in one node means one of them is invisible to every edge, filter, and queue signal that would otherwise find it. The body stays short, a few paragraphs at most, written for a reader with zero session context.
Ids are typed, kebab-case, and immutable
Section titled “Ids are typed, kebab-case, and immutable”The id must equal the filename minus .md, be kebab-case, and start with
its type’s prefix: dec- for decisions, task- for tasks, issue- for
issues, and so on (the full table is on Node types).
An id never changes once created. Everything else about a node can move —
its status, its edges, even its title — but the id is the stable reference
that edges, briefings, and commit trailers point at.
The summary must stand alone
Section titled “The summary must stand alone”summary is mandatory, and it is the field most consumers see. When Spor
compiles a briefing, most nodes appear at summary resolution; only the nodes
that score highest are shown with their full body. Write the summary as one
or two sentences that carry the fact and the why on their own:
- Weak: “Decision about webhooks.”
- Strong: “We ingest carrier status via signed webhooks with a replay window, because polling burned rate limits and lagged by minutes.”
If the summary only makes sense next to the body, the briefing that shows it without the body will mislead.
Timestamps come from git
Section titled “Timestamps come from git”The graph home is a git repository, and git is the source of truth for
system time. A node’s created_at is the first commit that touched its
file; updated_at is the last. Neither is stored in the node bytes, which
keeps files byte-identical across reads and makes history tamper-evident.
The frontmatter date field is different: it records when the underlying
event happened (the day the decision was made), not when the node was
written. Explicit created_at/updated_at frontmatter is accepted as an
override for graphs whose git history was squashed or rebased, and date is
the last-resort fallback when git has nothing.
Edges live in frontmatter
Section titled “Edges live in frontmatter”Edges are written on the source node as - {type: <edge>, to: <id>} entries.
An edge may point at an id that does not exist yet; the compiler skips it,
and the dangling reference marks a node worth creating — don’t delete it.
An edge may also carry extra flat attributes after to:, such as the
per-assignment profile override on an assigned edge
(- {type: assigned, to: agent-jo-laptop, profile: profile-reviewer}).
Two optional scalar fields connect nodes to work outside the graph:
commits: [parcel-api@1a2b3c4, ...]links a node to the code commits that implement it. Commits are deliberately not nodes — a node per commit would mirrorgit logand drown the curated graph.wake: YYYY-MM-DDparks a queueable node as dormant until the date arrives, the renew-the-certificate shape — see the decision queue.
Completing work needs a durable why: the resolver gate
Section titled “Completing work needs a durable why: the resolver gate”Flipping a task to done or an issue to resolved requires a live inbound
resolves edge from a decision or artifact node. This is the resolver
gate, and it is the node model’s central discipline: the outcome must live
on the graph, where its neighborhood can surface it, instead of evaporating
into a status flip.
A heavyweight closure earns a decision node (the why). A trivial one earns a few-line artifact (what was done, like a commit message). Either satisfies the gate. A task abandoned as won’t-do is exempt — abandoning produces nothing worth recording.
The gate runs at write time on both create and update, so a node can no more
be born done than be flipped there without a resolver. The resolver must
also be in a resolving state: an artifact whose delivery status is still
in-review or approved keeps the task live; merged, released, or no
delivery status resolves it. Which statuses count as resolving is registry
data, so a team can retune the bar by editing a schema node — see
Schemas are nodes.
Attribution
Section titled “Attribution”When a node is written through the Spor server, the server stamps
author: Name <email> and authored_via: mcp|rest|capture|dispatch|gardener
from the authenticated identity. Any author supplied in the payload is
discarded. Locally written nodes may omit both. The authored_via stamp is
the durable machine-vs-human signal: capture marks nodes drafted by the
ingestion path, gardener marks automated sweep findings, dispatch marks
work written by an agent on behalf of its owner.