# Contributing to Sjel Sjel accepts changes that improve the reusable public shell. Personal data and deployment state stay in a private overlay. The same boundary covers credentials, private host details, and operator-specific policy. ## Before writing code Name the consumer and the outcome. An interesting technology without a concrete Sjel consumer remains an idea, not an implementation commitment. Use GitHub Issues for concrete work that a maintainer or contributor can test or complete. Name the consumer, outcome, and acceptance check. Use `type:idea` for experiments that may conclude not to build. Open work that is not yet an issue lives in [`PLAN.md`](PLAN.md) and the area plans in [`docs/plans/`](docs/plans/). Move enduring behavior into code, tests, or the owning docs. Comments explain local invariants, not dated decisions. Git keeps the history. Before external code or adopted design influence enters the tree, record its canonical source in `upstreams.toml`. Record the license and verdict there too, then state precisely what Sjel adopts. Record no version: every dependency tracks its upstream's latest release ([Patch first](#patch-first)). ## Work on one change Until `PLAN.md` is done, every change goes straight to `main`. Branches, worktrees and pull requests start when the plan is done, independently of release tags. Rules: `Packs/harness/skills/sjel/references/on-changes.md`. Keep the diff inside one coherent boundary. Put reusable code and doctrine in Sjel, and use synthetic fixtures for data-shaped tests. Never copy an active overlay or a secret value into public work. Keep workstation paths and private logs out of commits and GitHub text, including screenshots and test failures. Run `tools/doctor` before editing and record unrelated or machine-only failures separately. A fresh source checkout does not need a real private overlay for CI; repository tests use synthetic configuration where a machine contract is required. ## Validate the changed boundary Run `sjel check` (`tools/ci-local fast`) before pushing; the pre-push hook runs it too. It checks formatting in affected Rust workspaces, publication hygiene and affected cheap repository gates. Tests, Clippy and builds run in CI rather than being repeated in the push hook. `SJEL_SKIP_PUSH_CHECKS=1` skips formatting and selected gates for one push, but does not skip secret scanning or publication hygiene. Inspect the diff, `git diff --check` and `git status --short` before committing. Do not describe a skipped or unavailable check as passing. ### On the go On battery, the runtime profile defers local compilation, Clippy and test suites. `tools/cargo-hermetic`, the CLI rebuild in `tools/lib/sjel-cli.sh`, `tools/ci-local run` and the pi command-policy extension refuse a start with the reason; nothing already running is killed. Profile and exceptions: `crates/runtime/README.md`. One command may proceed: ~~~sh SJEL_ALLOW_LOCAL_BUILD=1 tools/cargo-hermetic test -p sjel-runtime --locked ~~~ Fast editing checks still run: formatting, publication hygiene and cheap repository gates compile nothing. A checkout with no built `sjel-cli` cannot consult the profile, says so, and admits the work; a `cargo` command typed outside these tools is also outside the guard. ### Change-aware CI `tools/ci-plan.ts` selects verification inputs for local checks and Actions. Rust selection includes package owners, runtime-path readers and reverse dependencies. Bun and UI selection follows imports and package dependencies; unresolved runtime inputs widen coverage. Repository gates declare their input boundaries in `tools/lib/ci-inputs.ts`. Every plan retains publication hygiene. `tools/ci-run.ts` executes selected tests, gates and package builds. A main push compares against the last successful ancestor run of its workflow, not the previous push. Failed or cancelled pushes therefore remain in the next comparison. A missing base, planner change or selection failure selects full verification. Actions summaries record the base, selected and skipped surfaces, reasons and recent full-verification history. The CI workflow runs full verification daily at 03:23 UTC, on published releases and on manual dispatch. Distribution preparation calls full CI at the caller revision before building artifacts. `tools/release` requires a successful full-verification result for the exact commit; an ordinary selective success is insufficient. Security and CodeQL retain weekly full scans because advisory databases and query packs change independently of source files. Successful local format checks can be reused when checkout contents, the command and modeled toolchain inputs match. Publication checks, tests and builds are never reused as format receipts. `tools/ci-local fast --full` bypasses reuse; it still defers tests and compilation to CI. `bun tools/ci-plan.ts --base origin/main --local` prints the current selection without running it. ### Rust Rust packages are members of the root `Cargo.toml` workspace and share the root `Cargo.lock`. Keep each package's direct dependencies in its own manifest, and add no nested lockfile. The full set, from the repository root: ~~~sh cargo fmt --all --check cargo clippy --workspace --all-targets --all-features --locked -- -D warnings cargo test --workspace --locked ~~~ These use the channel and components `rust-toolchain.toml` names, as CI does. Do not answer a lint with a workspace-wide allowance. A narrow allowance goes beside the affected item and names the invariant that makes the lint inapplicable. `cargo test` needs no database service and no environment variable. The database-backed suites share the module name `db_tests::`, and each test opens its own temporary SQLite file: ~~~sh cargo test --workspace --locked -- db_tests:: ~~~ `tools/cargo-hermetic` isolates the local overlay, writable data and build output, and refuses release builds. For a run with less memory and contention: ~~~sh CARGO_BUILD_JOBS=2 RUST_TEST_THREADS=2 tools/cargo-hermetic test --workspace --locked ~~~ Temporary test files live inside the run's sandbox and are removed after Cargo exits, including failed tests. Build output stays outside that sandbox in a reusable cache keyed by checkout. Nested invocations retain the resolved cache rather than adding another checkout suffix. An inherited cache that cannot create and remove artifact directories falls back to the shared temporary cache; an explicit `--target-dir` is refused instead of silently redirected. Reuse the default cache for ordinary verification rather than creating a target directory per command. ### Other checks - `tools/maintenance --json` inventories tracked maintenance inputs offline. Add `--unused`, `--freshness` or `--licenses` for optional read-only analyzers. Missing requested coverage exits 2; license inventory covers the current Rust host target and is not legal approval. - `sjel gates` and `sjel test` replay CI's repo gates and Bun job from the workflow file. `tools/ci-local list` shows which jobs can be replayed. - A package that `tools/discover-ui-packages` reports: run `bun run check`, and `bun run build` where it declares one. CI runs both. - A manifest change: run `tools/generate-architecture.sh`, then `tools/check-architecture-fresh.sh`. - A test that needs a platform resource (`/dev/full`, a container runtime) uses `skippable` from `tools/lib/test-support.sh`. It reports omitted coverage locally and fails in CI. A performance claim names a probe this repository can re-run: an ignored test beside the code it measures, or a script under `tools/`. ### Pitfalls - **`SJEL_DB_PATH` isolates the database only.** Vault projections (`comms`, `finance`, `trips`) resolve their roots from overlay configuration. When you test against a copy, also export `SJEL_TRIPS_EXPORT_ROOT`, `SJEL_FINANCE_DECISIONS_ROOT` and a scratch `SJEL_COMMS_CONFIG`. - **Do not `cargo build --release` in a worktree that shares the root `target/`.** That build overwrites `target/release/`, the binary `tools/service-runner.sh` supervises. Build debug binaries there, or set an isolated `CARGO_TARGET_DIR`. ## Open the pull request Open a draft pull request first. State the outcome, then bound the exact scope. List every completed validation command with its result and name the known limits. Keep separable follow-ups out of the pull request rather than widening it. A pull request lands as a squash merge: one coherent change per commit on `main`, easy to review or revert. Security findings follow [SECURITY.md](SECURITY.md); never report one in a public issue or pull request. ## License and sign-off The project is licensed under the GNU Affero General Public License, version 3 only ([LICENSE](LICENSE)). Anyone who runs a changed version as a service for other people must offer those people its source. Code that `Packs/` vendors from other projects keeps its own license; each such Pack reproduces the upstream notice in its `LICENSE` file. A contributed commit carries a Developer Certificate of Origin sign-off ([developercertificate.org](https://developercertificate.org/)): a `Signed-off-by:` line that states you have the right to submit the change under this license. `git commit -s` adds it. A sign-off does not transfer copyright: a contributor keeps it in their change. A later license change therefore needs the consent of every contributor whose code remains. Releases before 2026-09-26 were published under the MIT license and stay available under it. --- # Engineering doctrine The rules below govern every change to this repository. Detailed procedures live in the linked technical documents; each rule has one owner. Code comments and READMEs link to that owner. ## Product rules Every change keeps to these. Rules 1, 4 and 5 describe the target behaviour; the README's [State](README.md#state) table shows how much of it is built. 1. A task runs on the best model the device can reach: its own Apple model, then the Mac's, then deterministic rules. The result shows which one answered. 2. A paired device's key admits its requests. Every connection type carries the same signed requests. 3. Sjel detects what the devices can do and chooses the defaults. Other options are under Advanced. 4. C2 data leaves the owner's devices only end-to-end encrypted, with keys on those devices, or pseudonymized. A cloud request carries only the fields its task needs (Staab et al.); ambient metadata, protocol headers, and extraneous history are stripped. A test proves each path. A failing test closes the path. 5. Summaries and labels appear without a confirmation. Confirmations protect only while they are rare enough to be read (Akhawe and Felt); frequent prompts cause habituation and click-through. A reversible change applies by itself only after a frozen test set shows it is reliable. A change that leaves Sjel, or cannot be undone, asks. The prompt rate on routine flows is measured and kept at zero. 6. Screens and assistant tools come from the typed data. 7. A statement about the system cites the file, command or measurement that proves it. ## Start here Sjel supports macOS and Linux. The guided installer detects the platform, creates or connects a private deployment overlay, writes the local machine manifest, and leaves existing state alone. Follow [the shell-only installation workflow](website/content/operations/index.md#install-a-shell-only-node) for prerequisites, source setup, capability selection, authentication, build, startup and reboot verification. The installer reports missing tools rather than installing them silently. Contributors who only need source and tests can skip operating setup and start at [Before writing code](#before-writing-code). ## Operating values These are design tests, not aspirations. A value only counts when it changes a placement, contract, install, or review decision. 1. **Lean runtime, growing monorepo.** A fresh install enables a small useful core. The repo may hold far more — optional capabilities, Packs, public reference projects — and none of that may become a runtime prerequisite. 2. **One feed, many views.** Capabilities keep domain ownership, but publish typed events with provenance, time, confidence and state. The app sorts and connects those events; it does not become another data silo. 3. **Data and mechanism never blur.** Sjel owns public code, schemas, renderers and explicitly public first-party datasets. The active private overlay owns private content, secrets, machine configuration and history. Both sides use the same bounded contracts: data may select an allow-listed behavior, never become executable code. 4. **Evidence before automation.** Every surfaced claim keeps its source, evidence boundary and decision state. Agents rank and explain; anything irreversible still goes through the capability contract, and leaves a trace. 5. **Security is continuous observation.** An audit gate is a start, never the property itself. Dependencies track their upstream's latest release ([Patch first](#patch-first)). Bun/npm packages maintain a 24-hour resolution window to mitigate rapid publish-compromise attacks. Egress, access and agent touches stay observable; telemetry lives in the private overlay. 6. **Replaceable edges, stable contracts.** Agent harnesses, model providers, visual renderers and deployment substrates are adapters. Adopt first and record why. Re-open the call when its stated flip condition comes true. 7. **Self-hosted by default, distributed when earned.** One home-server deployment should serve phone and desktop clients. Kubernetes, WASM or a native shell enter only for a measured portability, isolation or performance need. 8. **English is the shared surface.** Sjel-authored interfaces, documentation, prompts, summaries, explanations and errors default to English. Source material and explicitly locale-specific capabilities may remain multilingual; accepting multilingual input must not silently change Sjel's output language. ### Principles cited by number Code and READMEs cite "Principle N" by the number the frozen PRD gave it. Those principles bind like the values above: - **Principle 1, cheapest mechanism first.** Logic, then algorithms, then ML, then AI. A rule beats a model. Each capability names the lowest rung that does its job and justifies a higher one. - **Principle 2, privacy by data class.** What the data is decides its route, not whoever wrote the call site. See [Data classes](#data-classes). - **Principle 3, one fact, one owner.** Each field is maintained by hand or derived, and has a declared owner. A fact stated in two places without one is drift. - **Principle 5, evidence before automation.** Operating value 4. - **Principle 8, human-readable at rest.** Markdown, JSON, YAML and plaintext journals are the ground truth. A database is a rebuildable index, never the only copy. - **Principle 9, cut what stopped earning its place.** A folder, field, capability or rule that stopped earning its place is removed, not documented around. When it conflicts with another principle, this one wins. ## Feed, Scouting, and Obsidian boundaries “One feed” means one general observation stream, not one opportunity engine. A feed item is anything the operator may need to notice: a security advisory, system or package update, change in a watched repository, news item, useful article, scholarship, hackathon, event, or a future source that fits the same typed observation contract. Each item keeps its source, observed time, evidence link, kind and decision state. The originating capability keeps any domain-specific payload. `comms` currently supplies the feed store, manual link ingest and mail triage. A new source belongs in the general feed when its first job is awareness or reading. Mail triage uses deterministic rules with an optional local model fallback over unresolved threads. It reads the sender's domain, redacted subject, and preview, refusing Secret mail before prompt assembly. Proposals that would raise a mail's data class are held for operator review. Personal relevance is a Feed annotation, not a new ownership boundary. Comms scores items against explicitly configured focus notes, storing matches separately and labeling whether the comparison was semantic or lexical. Sorting combines focus fit, upcoming travel, freshness, and operator keep/dismissal history. Ranking inputs re-evaluate from stored matches rather than re-prompting models. `/feed/[id]` provides a unified dynamic reader across all item kinds. The app shows passive intake and active discovery as two views of one `/feed` workspace. That is a UI integration and never a database merge: each capability keeps its own contract and provenance. Narrower by design, `scouting` searches for and scores opportunities such as scholarships, hackathons, events, calls for papers, or travel deals against an interest profile. Obsidian holds learning notes only: what `teach` and `practice` write, and subject knowledge (`PLAN.md`, Decisions, 2026-11-08). It is not a Sjel database. Operational records live in each capability's store; notes that belong to a record live in `projects`. The markdown adapters in `comms` and `scouting` read any configured directory and no longer point into the vault ([`storage.md`](docs/plans/storage.md#vault-holds-learning-only)). Integrations never mutate another capability's notes by implication. Lineage: Sjel's public-core and private-overlay separation draws inspiration from upstream AI-operator patterns (see `upstreams.toml` [lifeos]). ## Architecture and ownership ### Public core and private overlays Sjel contains public code, schemas, renderers, and engine logic. Private data, vault contents, backups, and deployment configuration live in the active overlay. See [website/content/architecture/security.md](website/content/architecture/security.md). Mechanism stays in Sjel; instance inventory and values stay in the overlay. `tools/check-publication-hygiene.sh` gates against committed workstation paths and deployment markers. ### What the installer owns in an agent harness Agent configuration directories (`~/.claude`, `~/.codex`, `~/.pi`) belong to the operator. Installer authority is strictly additive: - `tools/claude-code-config/claude-code-config` merges Sjel baseline settings into `settings.json` without removing or clobbering existing operator keys. Refuses unparseable files rather than overwriting. - Drift is detected and reported via `sjel claude check`. - Packs are offered rather than force-linked during installation. ### What survives a harness swap A harness is an adapter (operating value 6), so a swap must keep everything below: | Must survive | Where it lives | |---|---| | The operator's preferences and principles | `crates/operator-profile` | | Long-term memory | The vault | | Skills and know-how | `Packs/` | | Tool contracts | `schemas/` and capability manifests | | Conversation history | Agent state outside the harness | | Scheduled jobs | Sjel, never the harness | A swap may lose the chat UI, the exact prompt format and provider-specific caching. ### Integrate-first topology Personal and self-authored components integrate into Sjel by default. Standalone repositories are reserved for independent products with distinct lifecycles, external collaborators, or distinct overlays. Integration occurs via declared contracts and state mounts. ### Three architectural nouns Sjel organizes all code into three nouns: - **Spine**: The core runtime and developer surface: root manifests, `schemas/`, `tools/` (scripts), `crates/` (shared crates, apps and tools), the `sjel-status` gateway and the apps in `apps/`. - **Capability**: Autonomous domain units declared via `service.toml` (Rust crates under `crates//`, supervised third-party software and scripts under `services//`). - **Pack**: Modular agent know-how and workflows under `Packs//`. See [website/content/architecture/index.md](website/content/architecture/index.md) for full topology and boundary rules. Shared Rust code belongs in `crates/`; third-party services in `services/`; contracts belong in `schemas/`; operator tools belong in `tools/`. Do not introduce unstructured `utils/` catch-alls. ### Schemas and dependency direction `schemas/` defines shared contracts: import them, never redefine them. Dependencies point downward: Packs -> Spine -> Capabilities -> Libraries (`libs/`). Capabilities interact with other capabilities via loopback HTTP contracts and declared schemas, never via direct database mutation. The apps are presentation: they read and write through capability contracts and own no domain state. ## Dependencies and build ### Upstream-first Choose in this order: adopt, contribute upstream, configure or adapt existing source, then build. Decide explicitly whether Sjel consumes an upstream product or maintains an adaptation. Use the upstream installer for a product Sjel only consumes. A locally maintained adaptation belongs in Sjel when its behavior and integration are part of Sjel's work; preserve provenance, licenses, the reviewed upstream revision and the reasons for local changes in its owning README. Review upstream changes selectively and preserve local behavior during upgrades. Discovery does not authorize adoption. Offer unselected extensions and skills as optional upstream installs; do not copy or enable them merely because they were found. Conversely, an explicit decision to maintain source locally is authorization to improve it here, not a demand to replace it with a stock installation. The harness Pack records the current ownership choices. ### Cargo and bun are the build path `cargo` builds and tests the Rust workspace: one root workspace, one `Cargo.lock`, with each member manifest managing its own direct dependencies. A service manifest declares its `target/release` binary, and `tools/service-runner.sh` compiles it on demand. `bun` powers TypeScript tooling and UI bundles. Freshness of generated artifacts is enforced via `tools/check-architecture-fresh.sh`. Operator machinery is unified in `tools/sjel-cli` and `tools/storage` in Rust for fast parallel execution, with lightweight wrapper scripts maintaining stable paths. Build artifacts are ephemeral. `sjel storage target` bounds debug-to-release ratio (`target/debug` within 3× of `target/release`), verified by `tools/doctor`. Space is reclaimed via `sjel storage reclaim --repo`. ### Implementation languages and intelligence New backend logic defaults to Rust. Choose the lowest rung that can solve the problem and justify every move upward: heuristic, algorithm, classical machine learning, local AI, cloud AI. A model is not a substitute for a deterministic baseline it has not beaten. ### One manifest per concern Configuration has one owner per concern (see [website/content/capabilities/manifest.md](website/content/capabilities/manifest.md)): | Manifest | Owns | | --- | --- | | `sjel.toml` | Shared platform defaults and the shipped overlay fallback; never machine-specific state | | `sjel.local.toml` | This checkout's active overlay location; gitignored and written by the installer | | `upstreams.toml` | External code and adopted influence: url, verdict, license and why. No version — every entry tracks its upstream's latest | | `toolchain.toml` | Host executables Sjel commands assume, with requiredness, scope and install hints | | `systems.toml` | Systems, services and projects that have a role in the setup | | `/config/machine.toml` | OS, container runtime, enabled capabilities and state mounts for this machine. An overlay owning several machines uses `/config/machines/.toml` instead, selected by `sjel.local.toml` or the hostname | | `/config/deployment.env` | facts true of the whole deployment rather than one machine or one capability — the home timezone, and `SJEL_INBOUND_TOKEN_FILE`, the reference to the shared secret every capability server authenticates inbound requests against (`libs/sjel-server/README.md`). Declared once because several capabilities need it and independent copies drift silently (`schemas/deployment.env.example`) | `tools/lib/toml.sh` is the parser for single-line scalar and array fields. More complex TOML goes through the shared Bun parser; no caller grows another partial parser or duplicates manifest data. Event sources follow the same pattern: declared configuration, never values spread through code. Host requirements are scoped, not global. `toolchain.toml`'s `needed_by` says where an entry applies: absent means every machine, `workflow:` means only when that workflow is asked about, and `capability-field:` derives from the enabled set so the requirement follows the capability when it moves host. A runtime node is therefore never told to install a scanner it has no path to invoke, and a tool that is out of scope is reported as `n/a` naming what would pull it in rather than omitted. Run the scoped check after changing what a machine is enabled to do, and run it for a workflow before invoking one: ```sh tools/toolchain-check # this machine: core + enabled capabilities + runtime tools/toolchain-check --workflow restore # before a restore, not after it holds a service down ``` ### State mounts record reality Sjel does not relocate an adopted tool's data directory. The active machine manifest records where the tool really persists data, its class, sync policy and direction. Backup and monitoring walk that registry. Dotfiles and shell configuration are state mounts with injection direction, not special cases. ## Security and data ### Dependency verdicts and provenance Every consumed external dependency gets a verdict in `upstreams.toml` first. **No entry, no entry.** The manifest records, per upstream, the URL, the verdict, the licence and the `why` that argues for it — four fields and nothing else. The consuming README also records what Sjel adopted: runtime, idea, architecture, algorithm, code or asset. A local clone or archive path is never durable provenance. **The register records no version.** Every dependency tracks its upstream's latest release: `services/host-patch` moves brew, uv and rustup nightly, `services/container-refresh` moves the images daily, Dependabot moves the Cargo and Bun manifests, and the workflows install `latest`. An audit date belongs in the `why`. | Question | Answered by | |---|---| | Is there a verdict, a licence and a reason? | `upstreams.toml` itself, read by a human at review time | | Is a newer release out? | Dependabot version updates — `.github/dependabot.yml`, one grouped pull request per ecosystem per day; Cargo and actions use 0 days, Bun a one-day window | | Is a locked dependency known-vulnerable? | Dependabot alerts and security updates, and `osv-scanner` in `.github/workflows/security.yml` and in `tools/audit` | | Is software installed outside this checkout known-vulnerable? | `tools/audit`'s second `osv-scanner` pass: the full global npm tree as a CycloneDX SBOM, plus each installed crate's own published `Cargo.lock` (the resolution `cargo install --locked` uses). Nothing else covers it — Dependabot reads lockfiles in this repository, and `osv-scanner`'s `directory` plugin extracts nothing from installed software | | Is there a CVE in a declared capability image? | `grype registry::` in `security.yml`, weekly. Findings are report-only and land in the run summary; the job goes red only if it discovered no image to scan. No container runtime and no local pull | | Is there a secret in this repository? | GitHub secret scanning, with push protection — and `gitleaks` in `tools/audit`, which reads history the push protection never saw | | Is there a secret in the private overlay? | `tools/audit` alone. GitHub charges for secret scanning on a private repository, and this repository's CI cannot reach the overlay, so this is the one scan that has to be local | | Is there a flaw in the code as written? | CodeQL — `.github/workflows/codeql.yml`, `security-extended` over rust, javascript-typescript, python and actions | | Is a host package behind? | `sjel update` — every class of software installed outside this checkout, with the owner of each named and the stale ones listed. `services/host-patch` moves `brew`, `uv` and `rustup` daily; `sjel update apply` moves the two classes nothing else owns (`cargo install`ed crates, global npm packages) | | Is a running container behind its channel? | Nothing asks. `services/container-refresh` pulls every declared image every day and recreates what moved | `upstreams.toml` is documentation. No script enforces or reads its fields, except `tools/self`, which publishes each name and verdict into `self.json`. A human reads a verdict before adopting a dependency. **Two gaps.** No scanner covers shell: CodeQL has no shell extractor, and `tools/` is the largest hand-written surface. A malicious release is covered unevenly: CI runs Socket's scanner over each Bun package that `tools/discover-ui-packages` finds, and every other install path relies on its ecosystem's own controls and on Bun's lifecycle-hook blocking. The scanner's free mode does not replace the age window; see [Patch first](#patch-first). ### Patch first Take upstream patches under the [patch policy](website/content/operations/index.md#patch-first), including the Bun/npm age window, update ownership, receipts, and accepted risks. ### Secrets Secret values live in Vaultwarden, never in this repository or an overlay note. A capability may materialize a real value only into its gitignored runtime environment because the service consumes plaintext environment variables. Creating or changing one requires the user to run `tools/setup-secret.sh` interactively after explicit, specific approval. A general “continue” is not authorization. A pre-Vaultwarden bootstrap exception must be argued when a real need exists; none exists today. ### Data classes Follow the [data-class and model-admission policy](website/content/architecture/security.md#data-classes). The shared admission functions own the gate; labels and callers must agree with it. ### Backups before migrations No data migration starts without a tested 3-2-1 backup. Follow the [recovery rehearsal](website/content/architecture/storage.md#backups-before-migrations) before changing live state. Delivery receipts alone do not prove recovery. ## Tooling conventions ### Portable shell Shell scripts remain compatible with macOS Bash 3.2. Do not use associative arrays, `mapfile`, `readarray` or another Bash 4-only feature unless the script owns and verifies a newer runtime. ### Language tooling Use `uv` for Python and `bun` for TypeScript. Do not add `pip`, `npm` or bare `node` commands to Sjel code, launchers or documentation. The runtime choice belongs in `toolchain.toml`; an external package consumed as code also belongs in `upstreams.toml`. ### Capabilities are data A container-backed capability is a `service.toml` consumed by the shared service runner and watchdog, not a new lifecycle script. Before adding a command, ask whether the behavior belongs in the manifest. A user-facing command shipped by one capability lives with and is named after that capability; implementation helpers stay off `PATH`. ### Dynamic paths and current facts Resolve repository, overlay and platform facts through `tools/lib/paths.sh` and `tools/lib/platform.sh`. Do not duplicate absolute paths or personal directory names. The only unavoidable bootstrap is the initial `SJEL_ROOT` shell setting before any shared resolver can be found. Ports, enabled capabilities, health, issue state, test counts, graph size and machine state come from manifests and tools at runtime. Prose may keep historical measurements and constants that are part of an argument, but it must not claim a changing count that no gate verifies. ### Public CLI `sjel` is the public command interface for humans and agents. Run `sjel help` to discover operations and `sjel search ` to narrow the current capability and Pack surface without an installed agent skill. Repository policy lives in `AGENTS.md`; command help and capability or Pack contracts own operational detail. There is deliberately no separate CLI reference. `docs/sjel-cli.md` was one, and every row of it restated something `sjel help` already prints — a command table, the harness names, the discovery instruction `AGENTS.md` carries verbatim. A second copy of a generated surface is the kind of doc that rots first and is believed longest. `tools/install.sh` owns the installation contract: it links `~/.local/bin/sjel` to the tracked launcher, never overwrites a non-Sjel command at that path, and reports the exact shell-path action when `~/.local/bin` is absent from `PATH`. ## Releases ### The release line The public spine versions with SemVer, not CalVer. A tag is `vMAJOR.MINOR.PATCH` with all three components present — `tools/release` refuses any other shape — and `tools/lib/version.sh` orders tags with `sort -V`. - **major** — a public contract or architectural shape changes such that an existing overlay needs edits to keep working. - **minor** — a capability or operator-visible feature lands. - **patch** — fixes only. Judge a change against those three before opening a pull request. A contract change that ships as a patch is what breaks an overlay on an update it was told was safe. Deployment overlays stay untagged. An overlay is deployment state rather than a public release line, and its commit identity stays independent of the Sjel version it runs against. ### Cutting and consuming a release Tags are created through `tools/release`, never by hand. It gates on a clean tree, on `main`, not behind `origin/main`, and a successful CI run on the exact commit, then generates notes from the manifest-aware delta in `tools/lib/delta.sh` — the same view `tools/update.sh` shows a consumer, so release notes and the incoming preview cannot drift apart. A checkout moves along the line with `tools/update.sh`: fetch, fast-forward, re-run `tools/doctor`. It never resets, rebases or force-pushes; a checkout that is both ahead and behind is left alone with instructions rather than silently repaired. ### Getting onto the line `bootstrap.sh` clones a release and hands off to `tools/install.sh`. The `usage` profile uses `--depth 1 --branch `; `development` keeps full history. Fetch the bootstrap by a chosen release tag rather than executing a moving branch. `tools/bootstrap.test.sh` exercises both clone shapes, installer handoff, refusals and truncated downloads against a local test remote. A usage checkout can become a development checkout without reinstalling. Restore the branch fetch specification before deepening the tag-only clone: ~~~sh git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' git fetch --unshallow origin ~~~ Both profiles retain the source tree: tools resolve `SJEL_ROOT` from it. The bootstrap chooses the clone shape; it does not provide prebuilt services or complete authentication and startup. **A release tarball instead of a clone was rejected.** `tools/update.sh` updates by fetch and fast-forward. A tarball would require a second update mechanism, including removal of files deleted upstream. Historical clone measurements do not establish the current installation size; use the [footprint method](website/content/operations/index.md#installation-footprint). Source checkouts retain Git for release identity, updates and repository metadata. The consumer distribution target is now prebuilt apps and a minimal node, with optional capability packages; it must not require Git or build tooling on the user's machine. `distribution.json` and the release catalogue carry the packaged runtime's identity. The remaining acceptance probes live in [#235](https://github.com/larsboes/Sjel/issues/235), and [release preparation](website/content/operations/index.md#release-preparation-and-later-testing) does not establish that the source-free installation is ready. The agent-readable install page is the primary route and the one-liner is the terminal alternative. Sjel's install contains real decisions — overlay location, container runtime, capability selection, secrets — and an agent walking those with a permission gate per step beats a script asking the same questions blind. Both land in the same `tools/install.sh` prompts. ## Packs and agent harnesses ### Harness-neutral Packs `Packs//pack.toml` plus `skills/` is the neutral source for a togglable agent bundle. Harness adapters translate that source into their own installation format; harness-specific metadata never changes the neutral manifest or forks the canonical `SKILL.md`. ### Proportionate approvals and delegation Repeated approvals for the same routine operation are a policy defect to investigate, not a reason to keep asking. Inspect the matched rule and actual side effects. Prefer a narrow, reviewed permission for the intended command and scope, with regression cases for harmful variants. Do not disable a guard, broaden an entire tool, or weaken secret, destructive, publication or data-class boundaries to reduce prompt count. Distinguish authorization cards from product decisions that still require the user. The command-policy cases and the agent plan own policy changes and their prompt-rate evidence. Delegate only when an independent investigation, parallel implementation or verification justifies its startup, context and reconciliation cost. Use direct tools for known targets; do not repeat a delegated search in the parent. Before expanding delegation, and after failed or redundant runs, compare usable findings and avoided work with elapsed time and reported usage. Reduce agents or work inline when delegation did not help. Change canonical harness instructions when repeated waste points to an instruction defect; retain independent review where it provides evidence the author cannot supply. No fixed agent count substitutes for this judgment, and no per-agent report is required for routine successful delegation. ### Owned agent components Skills, agents, prompt workflows, hooks and harness plugins must be maintained as Sjel-owned components. Upstream packages are reference material, not ready-made runtime installations. Adapt only a justified behavior into its existing owner, or deliberately maintain a reviewed adaptation with a local contract and checks. Renaming or copying a package does not establish ownership. Upstream changes are reviewed inputs, never automatic replacements for local work. Preserve attribution and every applicable license obligation, including notices and source requirements. Attribution alone is insufficient. Stop an adoption when permission or license compatibility is unresolved. Runtime libraries and host applications remain dependencies; this rule governs the agent components they supply, not ownership of every underlying tool. The adoption procedure and migration boundaries live in [`Packs/harness/README.md`](Packs/harness/README.md#upstream-adoption). ### Public skills Skills stay public and contain no host, IP address, key, personal path or private preset. They resolve instance values from the active overlay at runtime. ### Skills drive capabilities A skill is a thin workflow over capability code and contracts. It may teach discovery and safe operation; it does not embed a second implementation or a static copy of the capability registry. ### Adapter-owned deployment Each harness adapter owns its wiring and may remove only installations it can prove it owns. Codex deployment is materialized and drift-checked; destination edits are never overwritten silently. Other adapters may use a different mechanism, but no installed copy becomes the editing source. Claude Code uses the same profile-aware deployment path as the other harnesses: `tools/harnesses` and `tools/packs-claude`. There is no separate native marketplace catalogue. The tracked `.claude/settings.json` contains project hooks and disables automatic memory; it is not a Pack distribution channel. ### Pack documentation and attribution Every Pack has a README and SPDX license field. Adapted material names its canonical upstream, license and adopted influence. Preserve required notices and nearby lineage comments where Sjel's changes would otherwise obscure origin. ## Documentation ### Documentation stays owned and current Keep one README per capability or Pack, the root README, manifests, schemas and code. A new document earns its place only when a workflow or executable consumer needs it. Do not add hand-maintained status, session or fleet files; `tools/doctor`, Git and the self-model already own that state. Capability and Pack READMEs explain what the thing solves, its verdict, tradeoffs, provenance and honest experience. A README may precede implementation when defining the capability is itself the current work. The [technical docs](website/content/index.md) route readers by task: understand the architecture, build a capability, or operate an installation. Keep commands and interface contracts in their references; explain entry points and invariants beside the owning code. This organization draws on [TigerBeetle's documentation](https://docs.tigerbeetle.com/) and [rust-analyzer's architecture guide](https://rust-analyzer.github.io/book/contributing/architecture.html), recorded as documentation influences in `upstreams.toml`. User docs live in `website/content/` and are published by `website/build` (see [website/README.md](website/README.md)). `docs/` holds contributor material: `docs/plans/`. Report vulnerabilities through the private route in [SECURITY.md](SECURITY.md), never through a public issue. ### Document size and modularity Aim for fewer than 500 lines per hand-maintained document. At 500–800 lines, review whether it still serves one reader task. Keep new documents below 800 lines. Existing longer documents may stay while they are cleaned up; avoid further growth by removing duplication or extracting a cohesive section when that section changes. Apply this through review before introducing a CI limit. Generated files and vendored upstream documentation are outside this limit. Split by subject and ownership, with a short entry point linking to the details. Move a rule to one canonical location and update its references; do not copy it into several documents. Use existing READMEs and `website/content/` sections before adding another guide. Keep `AGENTS.md` a short bootstrap that routes to the shared rules and relevant skills. A separate repository-rules file earns its place only when it replaces an existing body of rules and simplifies navigation. ### Generated architecture `ARCHITECTURE.md` is generated from manifests and the tracked tree. Never edit it directly or maintain a second diagram that can drift. Change its source or generator, regenerate with `tools/generate-architecture.sh`, and verify with `tools/check-architecture-fresh.sh`. ### Decisions live with their owner Implemented decisions live in code; reasoning needed to change them lives beside it. Rejected alternatives use `## Considered and declined` in the affected README; capability-wide choices use `## Why this shape: `. Doctor validates that structure and any declared absent paths. Repository-wide doctrine lives here. Do not create a detached decision-log directory. ### Scratch is not documentation `to-integrate/` is ignored pre-git scratch and may never be cited by permanent files. Unjudged external leads belong in the owning README or relevant `website/content/` section. When a lead becomes a concrete experiment someone can run, track it in a `type:idea` issue. Adopted dependencies and influences pass through `upstreams.toml` and the consuming owner. ### Quarries and one-way migration Legacy tooling and LifeOS-mono archives are quarries, not dependencies. Migrate material into Sjel deliberately, redact it, verify the new owner, and leave the source until the user explicitly approves removal. Never bulk-import their history or treat an archive location as permanent provenance. ### Issues and plans Use GitHub Issues for discrete work that someone can test or complete. State the consumer, outcome, and acceptance check. `type:idea` marks an experiment that may end without an implementation. Issues remain open to outside reports as well. [`PLAN.md`](PLAN.md) is the one ordered plan: it orders the work by priority and records the decisions that span areas. Each area has one file in [`docs/plans/`](docs/plans/), with its state, open work and done-when checks. A plan is not a source of truth. Code and tests own implemented behavior. Shipped interfaces belong in the owner's technical manual and are verified by its doctor or self-test. ## Placement guide | Adding | Goes in | Boundary | | --- | --- | --- | | Bounded domain, external system or data store | `capabilities//` | Register an upstream verdict before consuming external code | | User-facing command for one capability | `capabilities//` | The shell discovers commands by capability name | | UI serving one capability | `capabilities//ui/` | Serve it through that capability's HTTP surface | | Shared code with no domain | `libs//` | Requires a second consumer and its own crate in the Cargo workspace | | Shared contract | `schemas/` | Import it; do not redefine it | | Agent workflow | `Packs//skills//` | Public and runtime-configured through the overlay | | System or project Sjel connects to | `systems.toml` | Private URLs go in the overlay extension | | Host executable required by Sjel | `toolchain.toml` | Add `upstream = ` only when it is also consumed code | | Machine fact or private state | Active overlay | Resolve it dynamically; never copy it into public prose | | Shared operator logic | `tools/lib/` | Source it from every caller | | Technical manual & specs | `docs/` | Bounded interface specifications and architecture diagrams | | Interesting unjudged lead | Owning README or relevant `docs/` section | Track a runnable experiment in a `type:idea` issue; apply the provenance gate before adoption | | Concrete work someone can test or do | GitHub Issue | State the consumer, outcome, and acceptance check | | Open work that is not yet an issue | The area plan in `docs/plans/` | Give it a done-when check. Move lasting truth to code, tests, or owner docs once built | ## Control surface The native app in `apps/gpui` is the visible form of the gluing layer, on the Mac and the iPhone. It shows capability status and setup, and every capability's own pages. It is presentation only: every action goes through the same Rust services and manifests (`sjel.toml`, `upstreams.toml`) that the CLI and agents use. A rule the Mac and the phone share lives in a capability or in `apps/gpui/core`. ## Layout | Path | Holds | |---|---| | `sjel.toml` | Sjel manifest: platform name, the release-tag pattern, default overlay root. Tracked and shared, so nothing machine-specific lives here | | `sjel.local.toml` | this machine's overlay root, and optionally which of that overlay's machines this is. Gitignored, one per machine, written by `tools/install.sh` (`sjel.local.toml.example` is the template) | | `/config/machine.toml` | this machine's identity: os, container runtime, enabled capabilities, state-mount registry. One file per machine once an overlay holds more than one, under `config/machines/` | | `/config/deployment.env` | this deployment's shared facts, resolved by `libs/sjel-config` and `libs/sjel-server` for every capability that needs one. A capability may still override, but may not silently disagree | | `profiles.toml` | named Pack sets (`tools/packs-codex use `). Tracked and shared: a profile says which Packs belong together, while which machine deploys them stays in the overlay | | `upstreams.toml` | every external project: url, verdict, license, why | | `README.md` | what Sjel is, where it stands, and how to start | | `CONTRIBUTING.md` | how to change Sjel, and the engineering doctrine | | `PLAN.md`, `docs/plans/` | priority order, and one plan per area with its open work | | `website/content/` | architecture, capability contracts and operations manuals, published as the public site | | `AGENTS.md` | minimal cross-harness bootstrap that routes assistants into the `sjel` skill. Every harness, Claude Code included, reads it. There is no `CLAUDE.md`. | | `ARCHITECTURE.md` | generated snapshot of capabilities/Packs/upstreams/systems — never hand-edited, see `tools/generate-architecture.sh`. State mounts are machine-local and deliberately absent; `tools/doctor` reports those | | `capabilities//` | one dir per capability: curated README + its code as it lands | | `apps/` | `gpui`, the native app for Mac and iPhone, and `mac`, the Swift menu bar and CloudKit relay. Both reach capabilities through the `sjel-status` shell; neither owns domain data | | `libs//` | spine-owned shared code with no domain of its own — statically linked into capability binaries at compile time, own crate in the Cargo workspace from day one | | `schemas/` | shared contracts; import, never redefine | | `tools/` | operator tools: `install.sh`, `capability.sh` (enable, disable, resolve `requires`), `update.sh`, `doctor`, `audit` (gitleaks and osv-scanner), `storage` (disk classes from the overlay policy, `target` and `reclaim --repo`), `generate-architecture.sh`, `agent-integrations.sh`, and the `sjel-cli` behind `sjel` | `ARCHITECTURE.md`'s tables and its Mermaid dependency graph (Packs → capabilities they drive → the upstream image each declares) are derived straight from `sjel.toml`/`upstreams.toml`/`systems.toml`/`Packs/*/pack.toml` — that's the live, generated view. Don't hand-edit it, and don't hand-maintain a second diagram somewhere else that it could drift from. `tools/check-architecture-fresh.sh` catches drift (fails if a manifest changed and nobody regenerated); `tools/generate-architecture.sh` fixes it. Every capability dir carries a **curated, opinionated README**: what this solves, the verdict and why-not-the-alternatives, links worth having, and honest personal experience including mistakes (`upstreams.toml`'s `[local-llm]` entry is the style bar this is measured against). The README can exist long before any code does; a capability may BE its README. The layering below is the fixed conceptual shape and won't change when a capability is added or removed — for what's actually wired right now, see `ARCHITECTURE.md`'s generated graph instead. ```mermaid flowchart TB subgraph HARNESS["agent harnesses (swappable)"] H["Claude Code · Codex · opencode · pi"] end subgraph SJEL["Sjel (public shell)"] A["apps/gpui (Mac and iPhone)"] M["sjel.toml + upstreams.toml"] S["schemas/"] T["tools/ (install, doctor, audit, generate-architecture)"] CAP["capabilities/ (domain owners)"] SH["sjel-status (shell)"] L["libs/ (spine-owned shared code)"] A -->|HTTP| SH SH -.->|proxies + starts on demand| CAP L --> CAP end subgraph OVERLAY["selected deployment overlay (private)"] P["config values · vault · databases · memory · backups"] end H --> SJEL SJEL -.->|runtime injection only| OVERLAY SJEL -->|contracts + state mounts| EXT["separate projects & services"] ```