solstone Developer Guide #
This file is the developer guide for the solstone repository. Read it before writing code.
Audience:
- Coders (cwd = repo root, editing
solstone/observe/,solstone/think/,solstone/convey/,solstone/apps/,solstone/talent/,tests/) — you're in the right place. - Cogitate talents (cwd =
journal/, running inside the live system) — your journal-side entry issolstone/talent/journal/SKILL.md, installed intojournal/.claude/skills/journal/andjournal/.agents/skills/journal/alongside thesolrouter skill. The runtime contract you operate under — tools, reads vs writes, finalization, access tiers, and what is not in your context — isdocs/COGITATE.md. - Operators debugging a running system — see
docs/DOCTOR.md.
For the journal-side runtime entry point, see journal/AGENTS.md.
CLAUDE.md and GEMINI.md at the repo root are symlinks to this file.
1. Start here #
Read, in order, when you enter the repo for a coding task:
- This file through §8 — the invariants must be in working memory before your first edit.
docs/SOLCLI.md— the CLI routing map. Publicsol/solstonecommands are native Rust authority entries;solstone/think/sol_cli.pyis now thejournalhost dispatcher.solstone/think/top.py(first ~100 lines) — the interactive TUI. Ties callosum + supervisor + service status together in one vantage point. Good "oh, this is how it connects" moment.- The area you're about to touch:
- User-visible feature or
sol call <app> <verb>→solstone/apps/<name>/native/{authority.toml,command.rs}+solstone/apps/<name>/routes.py+solstone/apps/<name>/templates/. - Think pipeline →
solstone/think/<module>.py+ its tests. - AI talent prompt or behavior →
solstone/talent/<name>.md(+ optional.pypost-hook). - Capture / observe →
solstone/observe/<module>.py.
- User-visible feature or
- Run
sol(no args) — prints current journal status + grouped command list. Orients you to live state. make devormake sandboxwhen you need a running stack to iterate against.
If you cannot state in one sentence which module owns the data your change touches, stop and re-read §7 L2 (the domain ownership table). Writing to a domain from the wrong module is how we got the 14 layer violations the April 2026 audit catalogued.
2. Repo map #
| Dir | Purpose | Go here when | Depth doc |
|---|---|---|---|
solstone/think/sol_cli.py |
Journal-host CLI dispatcher — service/universal COMMANDS and ALIASES for the journal script |
adding a journal <cmd> or host-only Python command |
docs/SOLCLI.md |
solstone/observe/ |
Multimodal capture — screen, audio, transcribe, describe, sense, transfer | capture-side bugs, new input modalities | docs/OBSERVE.md |
solstone/think/ |
Post-processing core — cortex, talent, callosum, indexer, entities, facets, activities, scheduler, heartbeat, supervisor | anything downstream of capture; most coder work lives here | docs/THINK.md, docs/CORTEX.md, docs/COGITATE.md, docs/CALLOSUM.md |
solstone/convey/ |
Web app framework — app discovery, routing, bridge | layout / framework-level UI changes | docs/CONVEY.md |
solstone/apps/ |
Convey apps — each self-contained (native/ authority + Rust command, routes.py, templates/) |
adding a user-facing feature, a sol call <app> verb, a UI surface |
docs/APPS.md (required reading before modifying solstone/apps/) |
solstone/talent/ |
AI talent configs (markdown prompts + optional .py post-hooks) + installed router skills (sol, journal); app fragments feed generated router references |
defining or tuning a talent; updating router guidance | solstone/talent/journal/SKILL.md, docs/PROMPT_TEMPLATES.md |
core/ |
Rust wave-0 workspace — thin solstone-core bin plus library-first adapter crates |
Rust scaffold, gates, or Python→Rust porting doctrine | docs/PORTING.md |
scripts/ |
Repo maintenance scripts — check_layer_hygiene.py |
tooling that guards the codebase; wired into make install-checks, not reached by the frozen make ci |
channel adapters: docs/CHANNEL_ADAPTERS.md |
tests/ |
Pytest suites + tests/fixtures/journal/ mock journal |
writing tests; debugging flakiness; make dev / make sandbox use fixtures as the journal |
docs/testing.md |
tests/js/ |
JavaScript harnesses driven by Python node tests | testing browser scripts without a real browser | docs/testing.md |
docs/ |
All longform documentation | reference lookups; never your first stop | §10 below |
journal/ |
The live journal (user data). Git-ignored content; checked-in template (AGENTS.md, skills symlinks) |
rarely as a coder — modify solstone/think/, solstone/apps/, or solstone/talent/, not journal data |
solstone/talent/journal/SKILL.md |
Top-level dirs intentionally not in the table: .venv/, scratch/, logs/, tmp/, observers/, routines/, skills/ — not active coder surfaces.
3. Mental model #
The pipeline: observe (capture) → JSON transcripts in journal/chronicle/YYYYMMDD/ → think (analyze) → SQLite index + derived artifacts → convey (web UI) and sol call CLIs.
Think is the center. observe feeds it raw material; convey + apps render its outputs; talent prompts + cortex run AI against it; indexer makes it searchable. A change in solstone/think/ usually ripples outward.
Key concepts, priority-ordered:
- Journal — the on-disk record rooted at
journal/in the repo. Every day is ajournal/chronicle/YYYYMMDD/directory. Segments (timestamped capture windows) are anchored to creation/modification time, not content "about" time.get_journal()fromsolstone.think.utilsis the single source of truth for journal path resolution; trust it unconditionally. Source-checkout installs inheritSOLSTONE_JOURNALfrom the managed bash wrapper at~/.local/bin/sol; packaged installs (uv tool install solstoneorpipx install solstone) installsoldirectly at~/.local/bin/soland rely onget_journal()to resolve the default journal location; tests use the autouse fixture; sandboxes set it explicitly. Application code must not set it itself (see §8). - Talents — AI processors (markdown prompt + optional Python post-hook). Each has a config in
solstone/talent/<name>.mdwith frontmatter that declares hooks, priority, model, and output. Cortex spawns them as subprocesses. - Callosum — Unix-socket JSON message bus at
journal/health/callosum.sock. Real-time event distribution across services (tract+event+ payload). If components need to talk asynchronously, they talk through callosum. - Cortex — process manager for talent runs. Listens on callosum (
tract="cortex",event="request"), spawnspython -m solstone.think.talentssubprocesses, writes<talent>/<ts>_active.jsonlthen renames to<talent>/<ts>.jsonlon completion, broadcasts all events back through callosum. Readdocs/CORTEX.mdbefore modifying talent execution. - Facets — project/context scopes (
work,personal, …). Group related entities, activities, and relationships. Facet data lives underjournal/facets/<facet>/. - Entities — tracked people / projects / tools. Extracted from transcripts and accumulated across time. Canonical records in
journal/entities/<slug>/entity.json. - Activities — scheduled or observed "things that happen" (meetings, deadlines, anticipated events). Per-facet JSONL at
journal/facets/<facet>/activities/<day>.jsonl. Sources:anticipated(fromsolstone/talent/schedule.md),user(manual),cogitate(talent-inferred). - Indexer — reads journal state, builds SQLite + FTS5 index. Never mutates source data (§7 L6). Rerunning on unchanged data is a no-op.
- Supervisor — top-level process manager. Starts/restarts services, talks to callosum.
journal supervisor/journal start.
4. The sol CLI #
Two surfaces:
sol <command>— native access commands declared undersolstone/think/native/<command>/authority.tomland implemented by the matching Rustcommand.rs(e.g.,sol import,sol chat).journal <command>— host/service commands fromsolstone/think/sol_cli.py(e.g.,journal think,journal supervisor,journal heartbeat).ALIASESprovides shorthand compound commands (journal start→journal supervisor,journal up/down→journal service up/down).doctoris universal:sol doctorchecks CLI usability;journal doctorchecks journal-host health.sol call <app> <verb>— native app commands declared undersolstone/apps/<app>/native/and generated into the native aggregate inventory. The only remaining Python subtree is the finite private compatibility path forsol call journal; seedocs/PORTING.mdfor the inventory and removal criterion.
Adding a top-level sol command: add a native authority under solstone/think/native/<command>/ and wire the Rust handler into the generated native inventory path. Use solstone/think/native/chat/ and solstone/think/native/import/ as the current patterns.
Adding a sol call sub-verb: update solstone/apps/<app>/native/authority.toml, implement the handler in command.rs, and regenerate the native inventory.
sol call journal export is the CLI entry for portable journal ZIPs; read-only archive validation lives in solstone/think/importers/journal_archive.py.
Run sol (no args) for live status plus the full grouped command list.
5. Make commands #
Verified against Makefile. Grouped by use.
Install #
| Target | When to use |
|---|---|
make install |
First setup and whenever pyproject.toml or uv.lock changes. Creates .venv/, syncs deps, runs make skills. |
make speakers-analyze-helper |
Reinstall the published speakers-analyze helper into .venv after a source-checkout uv sync prunes it. Normally run by make install; useful after manual syncs. |
make skills |
Regenerate generated router references, then rewrite the sol + journal router skill symlinks into journal/. (make install depends on this; rarely run alone.) |
make update |
Upgrade all deps to latest, regenerate uv.lock. Expect test churn. |
make update-prices |
Refresh genai-prices model-cost data when adding a new provider model or when pricing tests fail. |
make clean |
Remove build artifacts, caches, and the skill symlinks. Does not touch .venv/. |
make clean-install |
Nuke .venv/ and .installed, then reinstall. Recovery path when the venv is wedged. |
Run the stack #
| Target | When to use |
|---|---|
make dev |
Start the full stack (supervisor + callosum + sense + cortex + convey) against tests/fixtures/journal/, no observers, no daily processing. Primary inner-loop for UI work. Ctrl-C to stop. |
make sandbox |
Ephemeral background sandbox: copies fixtures to a temp journal, starts supervisor in the background, waits for readiness, writes .sandbox.pid / .sandbox.journal. Pair with verify targets below. Always follow with make sandbox-stop. |
make sandbox-stop |
Terminate the backgrounded sandbox and clean up state files. |
Format, lint, test #
| Target | When to use |
|---|---|
make / make all / make build |
Build the native Rust workspace, excluding the Python-provisioned ONNX crate pair during the conversion freeze. |
make format |
Format the Rust workspace with Cargo fmt; modifies Rust source. |
make format-check |
Cargo fmt dry-run (cargo fmt --all -- --check); one of the Rust-only CI checks. |
make test |
Alias for make check-rust-test: Rust workspace tests only, excluding the ONNX crate pair from host coverage. |
make test-cov |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make test-integration |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make test-release |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make release-checks |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make test-performance |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make test-app APP=<name> |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make test-only TEST=<path-or-pattern> |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make coverage |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make watch |
Frozen for the Rust-conversion effort; fails immediately. See docs/PORTING.md#rust-conversion-freeze. |
make ci |
Rust-only gate: check-rust-fmt, MSRV, clippy, Rust tests, iOS canary, and Rust dependency policy. |
make verify |
Alias for make ci during the Rust-conversion freeze. |
make install-checks |
Directly runnable full Python-and-Rust preflight chain (format, ruff, layer hygiene, and related checks); no longer called by ci or verify. |
make check-layer-hygiene |
Run scripts/check_layer_hygiene.py alone. Useful when iterating on an L1–L2 violation flagged by make install-checks. |
During the Rust-conversion freeze, use the narrowest applicable
make check-rust-* target, then make ci as the settled Rust gate. The focused
Python Make targets are frozen; run the Python suite directly when it is needed.
Do not rerun an unchanged failure merely to seek green.
Verification against a running sandbox #
| Target | When to use |
|---|---|
make verify-api |
Start a sandbox, run tests/verify_api.py against its convey port, stop the sandbox. API-regression check. |
make update-api-baselines |
Same, but update the baseline fixtures instead of failing on diff. Run after intentional API changes. |
Service management (systemd / launchd) #
.venv/bin/journal setup is the source-checkout runtime install path after make install; it installs or refreshes the source-checkout wrappers, installs the Claude Code skill when Claude is configured, and starts the background service on port 5015 by default. After the first run, the wrappers at ~/.local/bin/sol and ~/.local/bin/journal let you use sol and journal from anywhere. Use journal service <install|start|stop|restart|status|logs> for manual service operations.
| Target | When to use |
|---|---|
make service-logs |
Tail the installed service's logs. |
Other #
| Target | When to use |
|---|---|
make pre-commit |
Install pre-commit hooks (optional). |
make versions |
Print versions of Python, uv, and key deps. Diagnostic. |
Release rail #
Rust-conversion freeze: this rail's documented entry points are frozen:
scripts/release.shrefuses unconditionally in every mode, and the Make release targets fail immediately. Seedocs/PORTING.md#rust-conversion-freeze.
The rest of this section documents the retained release implementation for post-freeze restoration. These commands do not reach that implementation while the freeze is active.
DESTRUCTIVE: bash scripts/release.sh --candidate is fresh construction; before
policy or build work it first runs make release-checks, then deletes prior raw
build/dist outputs and that version's stale payload/evidence. It verifies the
expected source commit and lock state, gathers target evidence through
configured build/proof-host channels, pair-promotes payload and evidence, and
prints canonical local readiness JSON. This is candidate evidence only, not
publication authorization. Advisory acquisition is a separate operator
operation documented in scripts/release_advisory_policy.py.
If retained dist/release-candidate/<version> or target/release-evidence/<version> bytes already exist, --candidate refuses before cleanup unless the operator sets RELEASE_CANDIDATE_DISCARD_RETAINED=<version> for an unpublished version or RELEASE_CANDIDATE_DISCARD_PUBLISHED_TAG=<version>+<tag> for a tagged version; undeterminable retained state is not authorizable.
Every candidate attempt generates a fresh proof challenge and requires, for each
configured native target, a challenge-bound nvattest receipt alongside the
unchanged install/smoke receipt. Before the ledger is written and before any
proof host is contacted, the rail extracts the nvattest authority from the
candidate's own root wheels, requires every target's extraction to agree
byte-for-byte, and materializes the locked support-wheel set declared by
uv.lock, verifying each wheel's bytes against the lock. Challenge, authority,
and that exact support set are then handed to every target lane. Two attempts
over identical version, source, and payload inputs differ only in the challenge,
the receipts bound to it, and observation timestamps.
The retained support declarations recorded in the ledger are the lock-derived
declarations captured when the candidate was cut. Disagreement with the release
lock therefore fails at cut time rather than being re-litigated later, which is
why a retained candidate keeps validating after an unrelated dependency bump.
That durability is specific to dependency and lock drift. It does not extend
to the retained ledger's own shape: changing a registered top-level or nested
ledger key set breaks candidates that were already cut. The retained-ledger
schema registry and frozen fixture tests make that visible; read
docs/release-evidence-contract.md before changing the registry, and publish any
outstanding retained candidate before merging such a change.
bash scripts/release.sh --recover <version> <source-commit> is
retained-byte-only, read-only validation. It preserves retained payload and
evidence, revalidates them, and reports retained-candidate-valid for v2
evidence or retained-pre-nvattest-candidate-valid for registered
pre-nvattest v1 evidence; publication requires v2. It never rebuilds, refreshes
advisories, contacts hosts, installs wheels, reads authentication, or uses the
network.
For v2 evidence, recovery re-derives the nvattest authority from the retained candidate wheel bytes rather than reading it from the ledger, requires every target's extraction to agree, and holds that extraction against the ledger's authority payload, its byte digest, and every receipt's installed authority. It re-hashes the retained support wheels against the ledger declarations and against every receipt, and binds the challenge and the candidate identities through both receipt classes. Because the anchor is the retained wheel bytes, a forgery that rewrites the ledger and every receipt to agree with itself is still rejected. Recovery reads no release lock, no clock, and no network, and writes nothing on either the success or the failure path.
bash scripts/release.sh --dry-run-linux validates the Linux structural plan
only. It emits no ready payload, manifest, ledger, proof, or clean-source claim.
Candidate readiness means candidate payload, ledger, retained support wheels, and both per-target receipt classes — install/smoke and nvattest — are locally consistent for the retained bytes. Proofs do not authorize publication and do not prove external distribution. Live SPP composite acceptance, real remote lanes, and production URL reach are VPE post-ship work; this rail does not attest them.
Aggregate release publication is the retryable delivery step for an already
finalized retained candidate. make publish-release RELEASE_DIR=<retained ready dir> runs scripts/release_publish.py --mode production: it revalidates retained candidate bytes, uploads the canonical PyPI
artifact set, verifies PyPI digests, tags v<version> at the retained source
commit, and records a GitHub Release witness. The independently versioned
solstone-journal-models wheel and sdist may be reused when the package index
already holds that ledger-declared version and its canonical archive manifests
match the retained archives; reused model archives are removed from twine upload
and train-owned exact-digest verification, then the model index is rechecked
before tag/witness. The GitHub Release witness still receives the full retained
asset list. make publish-release-test RELEASE_DIR=<retained ready dir> is
TestPyPI upload+verify only; it does not validate changelog or tag readiness and
does not invoke git or gh.
Publication additionally refuses when the retained candidate's nvattest authority does not bind to the current checkout's canonical authority. That prerequisite runs immediately after retained-candidate revalidation and before the mode branch, so production and test alike fail with no index, upload, tag, or witness call attempted. The repair is to publish from a checkout whose authority matches the retained candidate, or to cut a new candidate; the retained bytes are never rewritten to satisfy the check.
The no-arg release path, --test, make release, and make release-test stay
locked out. They fail before any token, transport, build-host, git, upload, tag,
or hosted-release seam is reached. Do not add publication behavior to this
candidate rail.
Transparency publication is the separate retryable evidence step after delivery;
it never gates delivery. It is soft-gated while inactive: set
TRANSPARENCY_ACTIVATED=1 (or make TRANSPARENCY_ACTIVATED=1 <target>) to reach
the real implementation; see docs/PORTING.md#rust-conversion-freeze.
make publish-transparency RELEASE_DIR=<retained ready dir> is env-driven: operator endpoints, bucket,
credentials, minisign key paths, and archive channel come from
TRANSPARENCY_* env vars, while the public base defaults to
https://transparency.solstone.app.
RELEASE_DIR must resolve to dist/release-candidate/<version>/; the
corresponding rail evidence is derived from target/release-evidence/<version>/.
The evidence directory is inventory-exact within its retained ledger schema
version: a missing entry and an unexpected extra both fail closed. Current v2
evidence holds ledger.json, one install/smoke receipt per configured native
target under proofs/, one challenge-bound nvattest receipt per the same
targets under nvattest/, and the retained locked support wheels under
support/; registered pre-nvattest v1 evidence holds only ledger.json and
the install/smoke receipts under proofs/. After the prerequisite is published,
the single post-finalization tombstone verification record is the only optional
extra allowed for either schema version.
The public layout is fixed:
releases/<product>/v/<version>/ledger-entry.json,
ledger-entry.json.minisig, the byte-identical companion manifests, and the
byte-identical native proof receipts of both classes are immutable create-only
objects. Install/smoke receipts publish under their target name; nvattest
receipts publish under <target>.nvattest.json, mapped from their retained
sibling directory at a single point in the publisher.
releases/<product>/ledger.jsonl is mutable and derived.
releases/<product>/latest.json.minisig and latest.json are the signed pointer
pair, written in that order.
The archive channel retains the full candidate artifact bytes; the public
surface carries evidence only.
Transparency attests what was released, that the public version history is
immutable, and that history is publicly reconstructible; it does not attest that
binaries provably match source. A version key is one-shot and permanent. If a
version entry is uploaded but the pointer is not updated, rerun the publisher to
reuse the staged bytes and finish the mutable pointer. If a different object is
locked at a version key, the recovery is cutting the next version; a locked-zone
object can never be replaced.
Latest-pointer writes deliberately use an immediately re-fetched ETag as
If-Match, even though fetch-time pointer ETag presence is also asserted.
make resign-transparency-pointer refreshes only the signed latest pointer's
signed_at and valid_until; it does not require RELEASE_DIR and does not
change chain_length, tip_sha256, or version.
The in-repo append-only head witness is transparency-head-log.jsonl.
It is JSONL with no header row and is committed before the first publish.
The public minisign trust-anchor filename is solpbc-transparency-1.pub,
served at releases/keys/solpbc-transparency-1.pub; rotation increments the
numeric suffix and uses cross-signed successor files. TRANSPARENCY_MINISIGN_PUB
sets only the local verification path and may use any local filename.
Don't use #
| Target | Why not |
|---|---|
make uninstall |
Disabled by design. Use journal service uninstall, sol skills uninstall, and python -m solstone.think.install_guard uninstall for installed user artifacts, or make clean-install to rebuild the local dev env. |
6. Testing quickstart #
- Rust gate:
make/make all,make ci,make test,make verify, andmake buildoperate only on the nativecore/Cargo workspace during the Rust-conversion freeze.make ciruns formatting, MSRV, clippy, Rust tests, the iOS canary, and Rust dependency policy. - Python suite: pytest files remain
test_*.pywithtest_*functions, shared fixtures intests/conftest.py, and the fixture journal attests/fixtures/journal/. The autouseset_test_journal_pathfixture is unchanged; tests that write, scan, or rebuild journal/index state must usejournal_copyor a smallertmp_pathjournal (see §8). Run this suite directly with barepytestwhen needed.tests/andsolstone/apps/*/tests/are unchanged, but the former Python Make rails, includingmake test-app,make test-only, and the othermake test-*targets, now fail with the freeze diagnostic. - Marked Python tests: integration, performance, and release tests remain in the suite and can be selected with bare pytest as needed; their former Make rails are frozen. Live product verification still uses
make sandbox. - After editing
solstone/convey/orsolstone/apps/:journal restart-conveyto reload code in a running stack. - Runtime artifacts:
make devwrites them into the fixtures journal, wheretests/fixtures/journal/.gitignorecovers them.make sandboxuses an ephemeral copy and leaves only its.sandbox.pidand.sandbox.journalstate files untilmake sandbox-stopremoves them. - Test invariants, not snapshots. A test asserts what must hold in every valid state of the system — not what happens to be true today. Never pin a test to hand-edited prose (CHANGELOG / README / docs), to a value the system is designed to change (a version, a date, a growing count), or to a transient state. The tell: if doing the correct next thing — cut a release, rename a label, graduate a shipped changelog entry — turns the test red, the test is wrong, not the system. And test the code that produces a fact, never the rendered text about it. (A
[Unreleased]-pinned changelog test was exactly this anti-pattern — its pass condition required the release process to not run; removed 2026-05-30.)
Full depth: docs/testing.md.
7. Layer hygiene — required reading (L1–L9) #
Why this lives here. A codebase-wide audit in April 2026 found 14 layer-hygiene violations in solstone/think/ and solstone/apps/. Infrastructure modules (indexer, importers, schedulers) were silently writing domain state; CLI read-verbs were mutating; get-prefixed functions were creating records on miss. These invariants encode the rules the audit distilled, so the same landmines don't get re-planted. They're inlined here because a one-click-away invariant is a routinely-skipped invariant.
The low-bar grep enforcement is scripts/check_layer_hygiene.py, wired into make install-checks and not reached by the frozen make ci. Known audit-flagged files are allowlisted with audit-reference TODOs; the allowlist shrinks as remediation bundles ship.
L1 — Layer boundaries are load-bearing #
Each module family has a declared responsibility. Infrastructure modules (indexer, importer, scheduler, search, graph, stats) may write only their own output artifacts. They may not create, modify, or delete domain state (entities, facets, observations, activities, events, chronicle day content). If an infrastructure module needs to trigger a domain mutation, it emits a callosum event or invokes a sol call <domain> <verb> subprocess — never writes domain state directly.
L2 — Domain write ownership #
Each domain has exactly one write-owning module (or one tightly-scoped family of modules). No other module may call atomic_write, json.dump, open("w"), Path.write_text, unlink, rmtree, etc. on that domain's on-disk state.
| Domain | Write-owning module(s) |
|---|---|
Entities (entities/*/entity.json, entities/*/*.npz) |
solstone/think/entities/journal.py + solstone/think/entities/relationships.py + solstone/think/entities/saving.py + solstone/think/entities/merge.py + solstone/think/entities/voiceprints.py + solstone/apps/speakers/owner.py + solstone/apps/speakers/routes.py |
Entity history content (entities/*/history/{events,prepared,private}/**) |
solstone/think/entities/history.py is the sole writer of history events, prepared staging, and private merge payloads. Whole-entity deletion by entity owners removes history/ only as part of removing entities/<id>/. |
Owner voice candidate (awareness/owner_candidate.npz) |
solstone/apps/speakers/owner.py |
Speaker discovery clusters (awareness/discovery_clusters.json, awareness/discovery_clusters.resolved.json) |
solstone/apps/speakers/discovery.py |
Speaker candidate pool (awareness/speaker_candidates.json) |
solstone/apps/speakers/candidate_tracker.py |
Speaker identify operation ledger (speakers/identify-operations.jsonl) |
solstone/think/speaker_identify_operations.py |
Entity resolution ambiguities (entities/ambiguities.jsonl) |
solstone/think/entities/ambiguities.py |
Entity merge candidates (entities/review-candidates.jsonl) |
solstone/think/entities/review_candidates.py |
Facet review candidates (facets/review-candidates.jsonl) |
solstone/think/facet_review_candidates.py |
Speaker review candidates (speakers/review-candidates.jsonl) |
solstone/think/speaker_review_candidates.py |
Speaker candidate-pair review candidates (speakers/candidate-pair-review-candidates.jsonl) |
solstone/think/speaker_candidate_pair_review_candidates.py |
Speaker discovery cluster dismissals (speakers/cluster-dismissals.jsonl) |
solstone/think/speaker_cluster_dismissals.py |
Speaker keep-separate assertions (speakers/keep-separate.jsonl) |
solstone/think/speaker_keep_separate.py |
Facets (facets/*/facet.json, facets/*/relationships/) |
solstone/think/facets.py + solstone/apps/facets/* (if/when created) |
Observations (observations.jsonl) |
solstone/think/entities/observations.py |
Activities (facets/*/activities/*.jsonl) |
solstone/think/activities.py |
Timeline (chronicle/<day>/timeline.json, chronicle/**/<seg>/timeline.json, root timeline.json) |
solstone/apps/timeline/maintenance.py + solstone/apps/timeline/talent/segment_summary.py |
Per-segment sense outputs (chronicle/**/<seg>/talents/{sense.json,facets.json,speakers.json,density.json,change.json,activity.md,sense.md}) |
solstone/think/sense_splitter.py |
_solstone_processing records on header-only native describe/transcribe outputs (chronicle/**/<seg>/{screen,*_screen,audio,*_audio}.jsonl) |
Primary, automatic: sense re-entry via should_reenter_analysis_output in solstone/observe/processing_record.py — a record-less screen output is re-attempted and the handler determines the verdict. Operator bulk tool: solstone/think/backfill_processing_records.py, which stamps a guessed state=empty and is CLI-only, for marker-less, chunk-less legacy fleets; it declines anything carrying a marker or an existing record (SKIP_MARKER / SKIP_HAS_RECORD, unchanged). |
Awareness (awareness/current.json, awareness/YYYYMMDD.jsonl) |
solstone/think/awareness.py |
Awareness activity state (awareness/activity_state.json) |
solstone/think/thinking.py |
Identity (identity/*.md, identity/history.jsonl audit log) |
solstone/think/identity.py |
Todos (facets/*/todos/*.jsonl) |
solstone/apps/todos/todo.py |
Chronicle chat stream (chronicle/**/chat/<seg>/chat.jsonl) |
solstone/convey/chat_stream.py |
Day talent-output accumulator (chronicle/<day>/talents/<name>.jsonl) |
solstone/think/day_accumulator.py |
Talent provenance sidecars (chronicle/<day>/health/talent-provenance/**) |
solstone/think/talent_provenance.py |
Config (config/journal.json) |
solstone/think/journal_config.py |
Schedules (config/schedules.json) |
solstone/think/schedule_config.py |
Push devices (config/push_devices.json) |
solstone/think/push/devices.py |
Local inference operational telemetry (health/local-inference/YYYYMMDD.jsonl) |
solstone/think/providers/local_admission.py |
Active-brain state (health/brain.json, health/brain-fingerprint.key, health/brain-refresh.lease) |
solstone/think/providers/brain_state.py |
Provider install status records and proof cache (health/providers/{local,parakeet}.json, health/providers/{local,parakeet}.proof-cache.json) |
solstone/think/providers/install_state.py + solstone/think/providers/artifact_proof.py |
Provider install leases (health/providers/{local,parakeet}.lease) |
solstone/think/providers/install_lease.py |
Provider runtime health and retry-token records (health/providers/runtime/{local,parakeet}.json, health/providers/runtime/{local,parakeet}.retry-token.json, health/providers/runtime/{local,parakeet}.operation.lock) |
solstone/think/providers/runtime_health.py |
Native speakers-analyze install generation (health/speakers-analyze/install-generation.json, health/speakers-analyze/install-generation.lock) |
solstone/think/speakers_analyze_installation.py |
Provider artifact manifests (cache/providers/**/.solstone-provider-manifest.json, cache/providers/local/mlx/**/*.manifest.json) |
solstone/think/providers/artifact_proof.py |
nvattest appraiser cache (cache/providers/nvattest/**) |
solstone/think/providers/nvattest_install.py |
Media offload ledger (health/offload/<YYYYMMDD>.jsonl) |
solstone/think/offload_ledger.py |
Parakeet server placement record (health/parakeet-cpp.placement) |
solstone/think/providers/parakeet_server.py |
Hosted backup binding (backup/hosted/binding.json) |
solstone/think/backup/hosted.py |
Convey config (config/convey.json) |
solstone/convey/config.py + solstone/think/facets.py |
Chat config (config/chat.json) |
solstone/apps/chat/config.py |
Speaker labels (chronicle/**/talents/speaker_labels.json) |
solstone/apps/speakers/attribution.py |
Speaker corrections (chronicle/**/talents/speaker_corrections.json) |
solstone/apps/speakers/attribution.py |
Stream identity (chronicle/**/<seg>/stream.json marker + streams/<name>.json state) |
solstone/think/streams.py |
Observer ingest manifest (chronicle/**/<seg>/ingest.json) |
solstone/apps/observer/utils.py |
Link service state (link/ca/cert.pem, link/ca/private.pem, link/ca-staging/**, link/nonces.json, link/authorized_clients.json, link/state.json including optional locked_at, link/tokens/account.json, link/totp.json) |
solstone/think/link/ca.py + solstone/think/link/establish.py + solstone/think/link/nonces.py + solstone/think/link/auth.py + solstone/think/link/paths.py |
Chronicle day content (chronicle/YYYYMMDD/**) |
The capturing module (observer, importer) per its declared outputs |
Index (SQLite, indexer/*) |
solstone/think/indexer/* |
Observer registry and sync history (apps/observer/observers/*.json, apps/observer/observers/*/hist/*.jsonl) |
solstone/apps/observer/utils.py |
Import ingest/resolve staging (imports/**) |
solstone/apps/import/ingest.py + solstone/apps/import/resolve.py + solstone/apps/import/facet_ingest.py + solstone/apps/import/journal_sources.py — HTTP-ingest + resolve staging state, plus the remote-ingest bundle under imports/<id>/. journal_sources.py owns only its create_state_directory imports/ initializers; its source registry is app-storage. Import-bundle and sync-cursor content under imports/<id>/ and imports/<backend>.json is written by solstone/think/importers/{utils,cli,shared,sync}.py (local/CLI import flows + sync cursor), and solstone/think/importers/plaud.py installs streamed imported audio onto imports/<id>/<name> via journal_io's install_file primitive, as importer declared outputs (L7). |
Health import local state (imports/health-dedupe.sqlite, imports/_approvals/**, Apple Health imports/<id>/{raw,normalized,content_manifest.jsonl,manifest.json}) |
solstone/think/importers/{apple_health,health_dedupe,pre_save_gate,shared}.py — Apple Health save-mode reads the approval artifact before any write, installs raw synthetic/approved sources under the import bundle, writes normalized shards under imports/<id>/normalized/, and keeps record dedupe in imports/health-dedupe.sqlite. The pre-save gate is read-only; creation of _approvals/** remains a separately approved setup action. |
Operational-log/cache pruning and root task-log compaction — deletion/compaction only, across the dated allowlist (chronicle/<day>/health/*.{log,jsonl}, top-level talents/<talent>/<epoch_ms>.jsonl run logs + talents/<YYYYMMDD>.jsonl day indexes, tokens/, health/local-inference/, awareness/, config/actions/, facets/*/logs/, apps/observer/observers/*/hist/, .cache/cogitate-history/) plus root task_log.txt epoch lines older than the same retention window |
solstone/think/log_retention.py — prunes derived/operational artifacts by retention age and rewrites only root task_log.txt during compaction, preserving recent and unparseable lines. Routes its own deletes/compaction; does not write through each domain's owner. |
If you're about to write to a domain from a module not in this table, stop and route through the owner.
Native sol call <app> <verb> handlers keep the HTTP boundary. Each
journal-data command is declared by an app-local native authority and reaches the
journal through the generated native HTTP client. The positive native-sol
inventory, coverage, architecture, and conformance gates enforce that boundary.
The only Python sol call subtree is the finite sol call journal
compatibility path documented in docs/PORTING.md.
L3 — Naming is a contract #
Function and CLI-subcommand verbs signal read vs. write intent.
Read verbs (functions and CLI subcommands): load_*, get_*, read_*, scan_*, list_*, show_*, find_*, match_*, resolve_*, query_*, lookup_*, status_*, check_*, validate_*, discover_*, format_*, render_*, extract_*, parse_*, view_*, inspect_*, info_*, describe_*, search_*.
A read-verb function must not mutate on-disk state. No exceptions for caches. No exceptions for "create on miss."
If a function needs create-on-miss semantics, split it:
entity = load_entity(eid) or create_entity(eid, ...)
This makes the write visible at every call site.
Write verbs are the ones allowed to write — choose the right one: save_, create_, add_, insert_, append_, attach_, delete_, remove_, update_, rename_, move_, promote_, merge_, seed_, consolidate_, bootstrap_, backfill_, dispatch_, record_, ingest_, import_, rebuild_.
L4 — CLI read-verbs are read-only #
CLI subcommands with read verbs (list, show, status, get, search, find, check, validate, discover, inspect, info, describe, read, view) must not write to journal domain state under any flag combination. If a command needs a write path, split it into two commands — a read-verb reader and a write-verb writer.
L5 — Write-verb defaults #
CLI subcommands with write verbs default to safe.
- Preferred: no default mutation; an explicit
--commit(or--apply) flag is required to perform the write. - Acceptable alternative:
--dry-rundefaulting toFalseonly if the subcommand name is unambiguously a write verb AND the command's primary user journey is the write (e.g.,sol call entities create).
"Bootstrap", "backfill", and "resolve-names" are not unambiguous — default them to dry-run.
L6 — Indexers never mutate source data #
An indexer's job is to build indexes from source-of-truth data. Indexers may not mutate the source data they read. Re-running journal indexer --rescan on an unchanged journal must be a no-op for domain state.
L7 — Importers only write to imports/ #
Importers write source material to imports/ and the raw-content areas of chronicle/. They may not create or modify entities, facets, observations, or other cross-cutting state. If an importer needs to create an entity for deduplication, it calls a domain-owned seed_entity() function in solstone/think/entities/ that surfaces the write explicitly.
L8 — Hooks have declared outputs #
Post-processing hooks (solstone/think/hooks.py, solstone/talent/*.py hook functions) declare every path they will write in their frontmatter. The hook runner validates that all actual writes match the declaration. Writes outside the declared set fail loudly — raise at runtime; assert in tests.
L9 — Event handlers are idempotent #
Any function that handles a callosum event, a scheduled tick, or a supervisor-started automation is idempotent w.r.t. on-disk state. Append-only history records dedupe by a natural key (usually (day, segment) or (day, segment, ts)). Before adding a write to an event handler, ask: "what happens if this event fires twice?"
8. Coding invariants #
The rules above govern where code lives. The rules below govern how code behaves. They exist because we got burned.
-
No backwards-compatibility shims. All code that depends on this project lives in this repository — never add fallback aliases, re-exports for moved symbols, deprecated-parameter handling, or legacy support code. When renaming or removing something, update every usage directly. For journal data-format changes, write a migration script (see
docs/APPS.mdformaintcommands); do not add a compatibility layer. Cogitate agents default to adding shims; resist this. -
Trust
get_journal()unconditionally.get_journal()fromsolstone.think.utilsis the single source of truth for journal path resolution. For source-checkout installs, the managed bash wrappers at~/.local/bin/soland~/.local/bin/journalsetSOLSTONE_JOURNALbefore invoking the matching venv binary; packaged installs useuv tool install/pipx installand rely onget_journal()for default-journal resolution; tests use the autouse fixture; Makefile sandboxes set it explicitly. Application code, agent prompts, subprocess environments, and service files must not setSOLSTONE_JOURNALthemselves. To rewrite the wrapper's embedded path usejournal config journal <path>. Seedocs/environment.md. -
SPDX header on every source file. All Python (and other source) files begin with:
# SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc(
//for JavaScript.) Markdown, text, and prompt files don't need it. -
Fail loudly, not silently. Raise specific exceptions with clear messages; use the
loggingmodule, notprint. Validate inputs at module boundaries. A silent swallow in production costs days of forensics — an error at the boundary is free. -
Trust internal code. Don't add defensive validation for things internal callers can't violate. Validate at system boundaries (user input, external APIs, imported files) — not between modules you control.
Generic software principles (DRY, KISS, YAGNI, single responsibility, small focused commits) apply; see docs/coding-standards.md for the full list.
9. File headers, naming, dependencies #
- SPDX header as above — mandatory on source code files.
- Naming: modules / functions / variables
snake_case; classesPascalCase; constantsUPPER_SNAKE_CASE; private members_leading_underscore. Full table indocs/coding-standards.md. - Imports: prefer absolute (
from solstone.think.utils import get_journal), grouped stdlib → third-party → local, one per line. - Type hints on function signatures.
- Dependencies: managed by uv.
pyproject.tomlis authoritative;uv.lockis committed;make installsyncs;make updaterefreshes. - Python 3.12+.
10. Commit hygiene #
- Small, focused commits with descriptive messages.
- Validate each commit with focused checks appropriate to its diff. The full
make cigate belongs on the final tree before merge or release, not before every intermediate commit. - Run
gitcommands directly — notgit -C— you're already in the repo. - Don't commit runtime artifacts written under
tests/fixtures/journal/bymake dev/make sandbox(.gitignorecovers them; verify withgit statusanyway).
11. Where to go deeper #
Bare links don't motivate clicking. Each entry below says when you actually need the doc.
| Doc | When to read |
|---|---|
docs/APPS.md |
Required before modifying solstone/apps/ — pattern catalog for Convey apps, hook-idempotency guidance, Typer sub-app conventions, maint commands for data migrations |
docs/THINK.md |
Understanding the think-layer pipeline (importers, indexer, segment/stream processing) |
docs/CORTEX.md |
Modifying talent execution, cortex lifecycle, talent process management |
docs/COGITATE.md |
The cogitate talent runtime contract — cwd/workspace, the sol-CLI-authoritative journal access, raw-read bound, access tiers, finalization, disallowed assumptions, and the in-context preamble constant. Read before authoring/editing a talent prompt. |
docs/CALLOSUM.md |
Adding a new tract/event, debugging message flow |
docs/CONVEY.md |
Framework-level web changes (as opposed to an individual app) |
docs/OBSERVE.md |
Capture-side work: new modalities, transcription, sensing |
docs/SOLCLI.md |
Adding a new sol <cmd> or sol call <app> <verb> |
docs/PORTING.md |
Python→Rust porting doctrine, Rust workspace layering, and wave-0 lockstep/gate rules |
docs/PROMPT_TEMPLATES.md |
Modifying talent prompt format or frontmatter |
docs/PROVIDERS.md |
Three-lane provider architecture: active-brain resolution, local/BYO/confidential lanes, and honest no-fallback failure semantics |
docs/testing.md |
Test structure, fixtures, debugging test isolation |
docs/environment.md |
Journal path resolution, managed-wrapper behavior, service install details, and SOLSTONE_JOURNAL rules |
docs/CHANNEL_ADAPTERS.md |
Release channel adapter config, scrub-gate expectations, and operator-safe placeholders |
docs/release-evidence-contract.md |
Required before changing the retained release ledger schema registry — why such a change breaks already-cut candidates, what schema_version does and does not tolerate here, and the frozen-fixture rule |
docs/journal-format-contract-maintenance.md |
Changing a committed journal at-rest format (observer ingest envelopes, stream.json, audio.jsonl, screen.jsonl) — schema floor vs producer-local requirements, and which relaxations are safe |
docs/coding-standards.md |
Full naming conventions, ruff config, dep-management details — reference for everything not promoted into this file |
docs/project-structure.md |
Canonical directory layout; resolving "where does this file go" debates |
docs/DOCTOR.md |
Diagnostics and debugging a running system |
docs/SCREEN_CATEGORIES.md |
Screen-understanding classifier taxonomy (observe side) |
docs/VENDOR.md |
Vendor-level integrations |
docs/design/ |
Per-subsystem design docs |
docs/JOURNAL.md |
Breadcrumb only — redirects to solstone/talent/journal/SKILL.md, the progressive-disclosure journal-layout reference |
solstone/talent/journal/SKILL.md |
Journal layout, vocabulary, and sol call journal CLI (loaded by cogitate talents on demand via skills) |
solstone/talent/journal/references/cli.md |
Full sol call journal reference, including Talent CLI Boundaries (which infrastructure commands cogitate talents must not call) |
The live journal also carries journal/AGENTS.md as its runtime-facing breadcrumb.
docs/BACKLOG.md and docs/ROADMAP.md are product-planning docs — not coder reading.
12. What this file is NOT #
- Not a runtime guide for cogitate talents. Runtime CLI restrictions on talents live in
solstone/talent/journal/references/cli.md§ Talent CLI Boundaries. If you're tuning what a talent can or cannot call, look there, not here. - Not the journal-layout reference.
solstone/talent/journal/SKILL.md+ itsreferences/is the cogitate-audience entry point. This file describes how those commands are implemented, not which ones talents can't call. - Not an operations manual. For debugging a live system see
docs/DOCTOR.md; for setup and service lifecycle, see INSTALL.md (owner install), CONTRIBUTING.md (developer install),journal setup, andjournal service.