Convergence assistant for knowledge corpora.
Note
Experimental, pre-1.0, and moving fast. anneal is in active development:
the CLI surface, the anneal.dl rule layer, diagnostics, and the on-disk
formats all still change between releases, sometimes without a deprecation
path. It is built and dogfooded in the open — useful today, but not yet
stable. Pin a version if you depend on it, expect rough edges, and read the
CHANGELOG before upgrading.
anneal reads a directory of markdown, turns it into typed facts, evaluates a
Datalog-shaped rule layer against those facts, and exposes a small
agent-friendly surface for orientation, retrieval, convergence work, and
extension.
It is built for disconnected intelligences: agents across sessions with no shared memory that need to recover what matters, read enough context, and leave the corpus more settled than they found it.
The larger idea is convergence. In a large corpus, the useful signal is not just
what can be retrieved. It is what is becoming more precise, which obligations
are getting discharged, which decisions have replaced open questions, and which
paths are falling out of the active frontier. anneal keeps that signal
visible.
Large agent-readable corpora behave like living systems. Files cite each other, supersede each other, encode obligations, accumulate local vocabulary, and move through stages of settledness. Without tooling, every new agent burns context reconstructing the same map.
anneal gives the agent a programmable corpus surface:
anneal context "what should I read before changing the release path?"
anneal schema
anneal describe search
anneal -e '? diagnostic(code, severity, subject, file, line, evidence).'The important move is that corpus structure becomes queryable. Markdown files,
frontmatter, labels, body text, spans, references, snapshots, and project
configuration are all exposed as typed relations. The built-in prelude derives
standard convergence facts and verbs from those relations, and project
anneal.dl files can add local rules and callable @verb declarations without
changing the binary.
The command names are intentionally mnemonic, but they are not the whole tool.
status is the arrival dashboard: aggregate corpus vital signs plus
copy-runnable queries for goal-less orientation and work. context gathers a
retrieval bundle once you can name a goal. schema maps the language, and
describe teaches one relation, primitive, predicate, or verb at a time.
search, read, and handle retrieve evidence. When saved forms are too
broad, anneal -e is the normal way to ask the corpus a precise question. When
a precise question becomes reusable, edit anneal.dl and add an @verb
declaration in the same language.
curl -fsSL https://raw.githubusercontent.com/flowerornament/anneal/master/install.sh | bashThe installer is the primary path for prebuilt binaries. It installs to
~/.local/bin by default, prints the target, URL, and destination before it
writes anything, fails fast on unsupported targets, and stays aligned with the
published release matrix.
Binaries are published for:
aarch64-apple-darwinx86_64-unknown-linux-gnuaarch64-unknown-linux-gnu
By default the installer writes only the binary. To also install the bundled agent skill, pass one or more explicit skill targets:
curl -fsSL https://raw.githubusercontent.com/flowerornament/anneal/master/install.sh | bash -s -- \
--skill-target "$HOME/.agents/skills/anneal" \
--skill-target "$HOME/.codex/skills/anneal"Other useful installer forms:
# Install somewhere else
curl -fsSL https://raw.githubusercontent.com/flowerornament/anneal/master/install.sh | INSTALL_DIR="$HOME/bin" bash
# Same override, passed as a flag
curl -fsSL https://raw.githubusercontent.com/flowerornament/anneal/master/install.sh | bash -s -- --install-dir "$HOME/bin"
# Preview without writing anything
curl -fsSL https://raw.githubusercontent.com/flowerornament/anneal/master/install.sh | bash -s -- --dry-rungit clone https://github.com/flowerornament/anneal.git
cd anneal
cargo install --path . --lockedRun without installing:
nix run github:flowerornament/annealInstall into your profile:
nix profile install github:flowerornament/annealAdd as a flake input:
anneal = {
url = "github:flowerornament/anneal";
inputs.nixpkgs.follows = "nixpkgs";
};For flake inputs that should follow the latest published release instead of
master, pin the moving release branch:
anneal.url = "github:flowerornament/anneal?ref=refs/heads/release";Use the exported Home Manager module for a declarative setup. It installs the binary, writes machine-local user config under XDG paths, and can optionally install the bundled skill into agent-managed skill directories.
{
imports = [
inputs.anneal.homeManagerModules.default
];
programs.anneal = {
enable = true;
settings.state.historyDir = config.xdg.stateHome;
skill.enable = true;
skill.targets = [
".agents/skills/anneal"
".codex/skills/anneal"
];
};
}Repo-owned corpus behavior lives in anneal.dl and should be committed with
the corpus. Home Manager does not write project rules, source discovery, handle
policy, or convergence config; it only installs the binary/skill and writes
machine-local preferences such as history storage under XDG paths.
Use the narrowest surface that answers the next question. The ladder is: arrive, discover the language, retrieve evidence, then write a query when the built-in verbs are too broad.
# Arrive cold
anneal status --format=text
anneal -e '? recent_frontier(h, rank, recency), *handle{id: h, file: file} order by rank asc.' --limit 12 --format=text
anneal -e '? ranked_anchor(h, rank, score, why), *handle{id: h, file: file} order by rank asc.' --limit 12 --format=text
anneal context "find the runtime overview and its current blockers" --format=text
# Discover the language before guessing
anneal describe convergence --format=text
anneal schema --format=text
# Read enough to act
anneal search "runtime overview" --limit 3 --format=text
anneal read docs/runtime-overview.md --budget 4000 --format=text
anneal read docs/runtime-overview.md --span-id 'docs/runtime-overview.md#h/summary' --format=text
anneal handle docs/runtime-overview.md --format=text
anneal handle docs/runtime-overview.md --impact --format=text
# Ask a precise corpus question
anneal -e '? *handle{id: h, kind: "file", status: s}.'
anneal -e '? diagnostic{subject: h}, area_of{h: h, area: "language"}.'
# Work the convergence frontier
anneal status --format=text
anneal -e '? diagnostic{severity: "error", subject: h}.'Runtime commands render readable text at a terminal and JSON/NDJSON when piped.
Use --format=text in pipe-only harnesses when an agent needs prose-like
output. Use --json or --format=json when you want stable machine output.
Run commands from a marked corpus (.design, docs, or anneal.dl) or pass
--root <path> for an explicit ad-hoc scan. If an unmarked directory should
become a corpus, run anneal init --dry-run to inspect the project file before
writing it with anneal init.
Handle
The graph unit of knowledge. A handle may be a file, label, version, or
external reference. Headings are content spans (*span) attached to file
handles. In-repo code references such as lib/app.ex:10-20 use external
handles with external_class = "code" metadata.
Edge
A typed relationship between handles. Common edges include Cites,
DependsOn, Supersedes, Verifies, and Discharges.
Status
The lifecycle state from frontmatter. Project config partitions statuses into
active and terminal sets.
Convergence Lattice
The model that tracks movement from active uncertainty toward terminal
settledness. If an ordering is configured, anneal status can show pipeline
shape: raw ideas becoming drafts, drafts becoming reviewed artifacts, reviewed
artifacts becoming authoritative.
Snapshot
A point-in-time capture of graph state, stored in local anneal history. Snapshot
history powers at("snapshot:last") queries, flow(h, direction),
advancing(h), holding(h), drifting(h), and the status convergence summary.
anneal status records bounded automatic snapshots, coalescing unchanged
consecutive status reads.
Prelude
The built-in standard library of rules, diagnostics, ranking, and verbs. It is
loaded automatically and should not be copied into a project.
anneal status
anneal -e '? recent_frontier(h, rank, recency), *handle{id: h, file: file} order by rank asc.' --limit 12
anneal -e '? ranked_anchor(h, rank, score, why), *handle{id: h, file: file} order by rank asc.' --limit 12
anneal context "goal"
anneal help agentstatus renders aggregate corpus vital signs and prints copy-runnable
orientation/work queries. recent_frontier returns recently changed file
handles for goal-less reading; ranked_anchor returns the durable spine from
the broader anchor relation. Both close with order by rank asc so the list
arrives in reading order — rank 1 first — because order by <expr> [asc|desc]
sorts a query's result at the projection boundary, and order by … --limit N
is a true top-N. context
composes ranked summary-bearing span search, compact span metadata, and graph
neighborhood once you have a goal. Add --read-spans when you want matched
span bodies inline.
help agent prints the bundled agent skill briefing from the installed binary.
The hidden prime alias remains for installed skill loaders and muscle memory.
Useful context flags:
--hits N: number of search winners--budget N: per-hit span selection cap; also caps bodies with--read-spans--depth N: graph distance around winners--include-low-confidence: include lower-confidence search hits--read-spans: include matched span bodies inline
anneal schema
anneal describe search
anneal describe runtime
anneal describe currency
anneal -e '? axis(name, question, oracle, disposition).'
anneal -e '? axis_of(predicate, "recency").'
anneal -e '? search("conformance", h, span, score, reason, field, low).'
anneal -e '? changed_within(h, 7), *handle{id: h, kind: "file", summary: summary}.'
anneal -e '? sources(name, recognizes, capabilities, doc).'Use schema to see queryable relations and signatures. Use describe for one
primitive, predicate, or verb. Use describe runtime for the compact map and
copyable examples. Use describe <axis> when the vocabulary is still blurry:
axis lists the runtime dimensions, and axis_of places predicates on axes
such as relevance, currency, lifecycle, recency, importance, convergence,
structure, obligations, and topic. Corpus-local vocabulary is queryable
directly through relations such as *handle{status: status},
*edge{kind: kind}, *handle{namespace: ns}, and *meta{key: key}. Adapter
information is queryable through sources(name, recognizes, capabilities, doc). Use -e when you need to compose a question directly. When a working
query should become a named project move, edit anneal.dl and add an @verb
declaration.
anneal search "runtime overview" --limit 5
anneal read docs/runtime-overview.md --budget 4000
anneal read docs/runtime-overview.md --span-id 'docs/runtime-overview.md#h/protocol'
anneal handle docs/runtime-overview.md
anneal handle docs/runtime-overview.md --impact
anneal handle docs/runtime-overview.md --lineagesearch ranks content and metadata hits and includes summary for
matched spans. Scores combine lexical strength with lifecycle status,
hub degree, and currency: current operative successors outrank superseded
predecessors, while superseded material stays reachable. Search and context
hits include disposition, status, and age so you can tell current framing from
stale-but-relevant history.
read retrieves bounded content spans for one handle; use --span-id when a
search hit already identified the section you need.
handle shows incoming and outgoing edges grouped by kind and separates
in-repo code references; --impact adds direct and indirect reverse
dependencies before an edit, and --lineage follows file-level Supersedes
chains to show older predecessors, newer successors, and current heads.
Use anneal context "X" when the task is "find the section that defines X";
it returns ranked section hits, span metadata, summary, and graph
neighborhood. Add --read-spans only when inline matched bodies are worth the
extra output. Use grep -rn "X" when you need every literal occurrence with
line numbers. Use anneal -e '? ...' when the question is structural, such as
"which handles match this graph predicate?"
anneal -e '? frontier(h, energy), *handle{id: h, file: file, summary: summary}.'
anneal -e '? area_health(area, grade, files, errors, cross_edges).'
anneal -e '? diagnostic{code: code, severity: "error", subject: h, file: file, line: line}.'
anneal -e '? blocker(h, energy, source), h = "HANDLE".'
anneal -e '? *handle{id: h, file: f}, git_mtime(f, t).'
anneal -e '? authored_age(h, days), *handle{id: h, file: file}.'
anneal -e '? changed_recently(h, band), *handle{id: h, file: file}.'potential exposes raw unsettled-work energy; frontier projects the
highest-energy candidates. area_health grades per-area convergence.
diagnostic{severity: "error", ...} filters to blockers. blocker explains why one
handle is stalled. flow classifies active movement as advancing, holding, or
drifting; settled handles are outside flow by design. authored_age is the
date-backed authored-age oracle used by recency ranking. changed_recently,
changed_within, and git_mtime are lower-authority git-backed change
signals for "what changed?" questions.
The convergence vocabulary lives in the prelude — use describe convergence,
describe potential, describe entropy, describe blocker,
describe recency, describe authored_age, describe changed_recently,
describe changed_within, or describe git_mtime to learn the
joins, then compose with -e. The check command remains as a hidden CI gate
alias for diagnostic{code: code, severity: "error", subject: h, file: file, line: line}.
anneal -e '? *handle{id: h, kind: "file"}.'
anneal -e '? *edge{from: src, to: dst, kind: "DependsOn"}.'
anneal -e '? diagnostic(code, severity, subject, file, line, evidence).'
anneal -e '? frontier(h, energy), *handle{id: h, file: file, summary: summary}.'
anneal -e '? source_of("frontier", file, lines).'
anneal -e '? search("conformance", h, span, score, reason, field, low).' --limit 20
anneal -e '? changed_within(h, 7), *handle{id: h, kind: "file"}, search{query: "conformance", handle: h}.'
anneal -e '? currency_suspect("HANDLE", newer), topic_sibling("HANDLE", newer, shared).'The query language is Datalog-shaped. Stored relations use * prefixes, for
example *handle, *edge, *meta, *content, *span, *config,
*snapshot, and *generation. Derived predicates come from the built-in
prelude, project anneal.dl, and inline query-local rules. anneal -e -
reads a query from stdin, so agents can keep larger questions in scratch files.
Use --limit N while exploring broad predicates.
New corpora work without configuration. anneal loads the built-in prelude,
scans markdown with default adapter settings, infers handle namespaces from
corpus evidence, and runs the standard commands immediately.
Configuration has three layers:
- Built-in prelude: standard rules, diagnostics, ranking, and verbs. It is automatic.
anneal.dl: repo-local corpus semantics: adapter discovery, convergence statuses, terminal/active sets, pipeline ordering, linear namespaces, frontmatter edge mappings, excludes, project rules, and@verbdeclarations.- User config: machine-local preferences at
$XDG_CONFIG_HOME/anneal/config.tomlor~/.config/anneal/config.toml.
Repo config should describe the corpus. User config should describe the machine.
anneal init --dry-run previews a scaffolded anneal.dl. anneal init writes
only when no repo config exists.
source md {
file_extension(".md").
scan_root(".").
scan_exclude(["vendor", "**/README.md"]).
}
config convergence {
ordering(["raw", "draft", "review", "approved", "published"]).
active(["draft", "review", "approved"]).
terminal(["published", "archived", "superseded"]).
asserts_code(["draft", "review", "approved"]).
description("draft", "Under construction; may change substantially").
description("approved", "Settled primary artifact; changes require review").
description("archived", "Superseded or retired; no further changes expected").
}
config handles {
rejected(["SHA", "GPT"]).
linear(["REQ"]).
}
config frontmatter {
field("depends-on", "DependsOn", "forward").
field("superseded-by", "Supersedes", "forward").
}
config impact {
traverse(["DependsOn", "Supersedes", "Verifies"]).
}
config code_path_root {
root(["web", "bin"]).
}
config search_boost {
status("authoritative", 0.08).
status("draft", 0).
hub(0.01).
}
config state {
history_mode("xdg"). # xdg | repo | off
}
Label namespaces are inferred automatically. Use force(["REQ"]) only for a
sparse namespace that should be recognized before it has enough examples. Use
rejected([...]) for false positives and linear([...]) for obligation
namespaces whose labels must be discharged exactly once.
search_boost adjusts ranking calibration without changing what search can
match. Use exact status names for lifecycle boosts and hub(...) for the
bounded per-incoming-edge boost; anneal describe search_boost shows the
queryable config rows.
anneal.dl can also declare rules and project verbs.
# rules run after source facts exist
release_blocker(h, "broken_ref") :=
diagnostic("E001", severity, h, file, line, evidence).
@verb(
name: "release-blockers",
query: "release_row(h, why, milestone) :=
verb_arg(\"milestone\", milestone),
release_blocker(h, why),
*meta{handle: h, key: \"milestone\", value: milestone}.
? release_row(h, why, milestone).",
doc: "Open blockers for the next release.",
output_schema: "{\"h\":\"HandleId\",\"why\":\"String\",\"milestone\":\"String\"}",
args: ["milestone:String"],
capabilities: ["read"]
).
Load order is fixed: config and discovery facts first, source extraction second, prelude and project rules third, evaluation last. Project predicates shadow prelude predicates by name and arity. Project verbs are callable by name:
anneal release-blockers v0.11.0 --format=text
anneal release-blockers --milestone v0.11.0 --explainReusable project moves are plain @verb declarations in anneal.dl:
@verb(
name: "area-diagnostics",
query: "area_diagnostic(h, code, file) :=\n verb_arg(\"area\", area),\n diagnostic{subject: h, code: code, file: file},\n area_of{h: h, area: area}.\n\n? area_diagnostic(h, code, file).",
doc: "Diagnostics in one area.",
output_schema: "{\"h\":\"HandleId\",\"code\":\"String\",\"file\":\"String|null\"}",
args: ["area:String"],
capabilities: ["read"]
).
After that declaration is present in anneal.dl, call it like any other verb:
anneal --root docs area-diagnostics '(root)' --format=textIf a project verb is wrong, edit or remove the @verb(...) block in
anneal.dl.
anneal.toml has been replaced by anneal.dl.
If a corpus still has anneal.toml, run:
anneal init --dry-run
anneal init --forceThe force path writes unified anneal.dl from the currently loaded config and
moves anneal.toml to anneal.toml.legacy.
Pre-0.11.0 [handles].confirmed inventories are not carried forward.
Namespaces are inferred automatically; add force([...]) manually only for
sparse prefixes that need an explicit policy override. If an existing
anneal.dl still contains confirmed(...), anneal init --dry-run previews a
cleaned file and anneal init --force rewrites it without the obsolete
inventory.
Snapshot history is the automatic migration path. When XDG history is enabled,
pre-0.11.0 repo-local .anneal/history.jsonl is copied into XDG state on the
first write so snapshot-based history queries continue.
Older compatibility commands now return teaching recovery messages instead of
running the retired commands. Use the language-first ladder above:
status plus its recent_frontier/ranked_anchor queries to arrive,
context GOAL when the goal is known, schema/describe to discover,
search/read/handle to retrieve, handle --impact for reverse
dependencies, handle --lineage for Supersedes history, and anneal -e for
precise composite questions.
Common replacements:
find TEXT:anneal -e '? *handle{id: h, kind: kind, status: status}, h contains "TEXT".'get H:anneal handle Horanneal read Hmap:anneal -e '? *edge{from: src, to: dst, kind: kind}.'health:anneal statusplusanneal -e '? diagnostic{code: code, severity: severity, subject: h, file: file, line: line}.'diff:anneal -e '? at("snapshot:last") { *handle{id: h, status: old} }, *handle{id: h, status: now}, old != now.'obligations:anneal -e '? undischarged(h), obligation(h), *handle{id: h, file: file, status: status}.'garden:anneal statusplusanneal -e '? frontier(h, energy), entropy(h, source).'orient:anneal status, then the printedrecent_frontier/ranked_anchorqueries; useanneal context "GOAL"once you have a goalimpact H:anneal handle H --impactlineage H:anneal handle H --lineagework:anneal statusoranneal -e '? frontier(h, energy), *handle{id: h, file: file, summary: summary}.'blocked H:anneal handle Horanneal -e '? blocker(h, energy, source), h = "H".'diagnostics:anneal -e '? diagnostic(code, severity, subject, file, line, evidence).'broken:anneal -e '? diagnostic{code: code, severity: "error", subject: h, file: file, line: line}.'oranneal checkareas:anneal -e '? area_health(area, grade, files, errors, cross_edges).'trend:anneal -e '? at("snapshot:last") { *handle{id: h, status: old} }, *handle{id: h, status: now}, old != now.'sources:anneal -e '? sources(name, recognizes, capabilities, doc).'query:anneal -eexplain:anneal -e '...' --explain
Adapters emit stored relations. Rules and queries consume them.
*handle: handle id, kind, status, namespace, file, line, area, summary, identity, revision, generation*edge: typed handle relationship*meta: frontmatter and extracted metadata, includingexternal_class,target_path,target_start_line,target_end_line,target_exists, andtarget_history_statusfor code external handles*content: bounded text chunks*span: addressable content spans*concern: concern-group membership*config: ordered runtime config facts*snapshot: history rows forat(...)*generation: source refresh bookkeeping
Every stored row carries enough identity to distinguish corpus, source, origin, revision, and generation.
The diagnostic catalog tracks local consistency and convergence health.
| Code | Severity | Description |
|---|---|---|
| E001 | Error | Broken reference |
| E002 | Error | Undischarged obligation |
| W001 | Warning | Active handle depends on terminal handle |
| W002 | Warning | Higher lifecycle state depends on lower state |
| W003 | Warning | Missing frontmatter |
| W004 | Warning | Malformed or suspicious frontmatter value |
| W005 | Warning | Lifecycle config gap |
| W006 | Warning | Code-authoritative spec cites missing code |
| I001 | Info | Section reference summary |
| I002 | Info | Multiple discharges on one obligation |
| S001 | Suggestion | Orphaned handle |
| S002 | Suggestion | Reserved; namespaces are inferred automatically |
| S003 | Suggestion | Pipeline stall |
| S004 | Suggestion | Abandoned namespace |
| S005 | Suggestion | Concern group candidate |
The workspace is split by runtime boundary:
crates/
anneal-lang/ private parser, AST, source spans, loader (publish = false)
anneal-core/ runtime, facts, store, evaluator, prelude, verbs, trails
anneal-md/ markdown Source adapter
anneal-cli/ CLI surface over core + adapters
anneal-mcp/ MCP library surface over core + adapters
anneal-core has no markdown-specific code. Adapters implement Source and
emit FactBatch values. Retrieval providers, trail recording, policy checks,
verb projection, axis introspection, and source orchestration are separate
runtime seams so future adapters can target code, host runtimes, issue
trackers, and federated corpora without changing the query language. The
prelude exposes the dimensional map through axis and axis_of; command
surfaces are projections over those runtime predicates rather than a separate
documentation layer.
anneal-lang is a private crate (publish = false) until a second consumer
proves the syntax boundary. anneal-mcp ships as a crate/library surface, not
an installed root-CLI launcher. From a source checkout,
cargo run -p anneal-mcp -- --tools prints the crate-level MCP tool catalog.
MIT