AGENTS.md@apps/lemon_core · git:20261013.efb1938 · 2026-10-13 · sha256 711aa7848d350689
AGENTS.md@apps/lemon_core git:20261013.efb1938A
Immutable. This exact content never changes and is served at /api/v1/blob/711aa7848d350689.
# LemonCore contributor guide Core owns shared contracts, config, secrets, Store, Bus and lifecycle services. It has no umbrella dependencies. Browser/media/LSP drivers live in their own apps; optional consumers register at runtime. See [README](README.md), [configuration](../../docs/config.md) and [architecture boundaries](../../docs/architecture_boundaries.md). ## Config and profiles - `Config.Modular` is the canonical implementation; `Config` is its facade. Precedence is environment > project `.lemon/config.toml` > user `~/.lemon/config.toml`. Cached reads and explicit reload are distinct. - Config defines defaults, not current session/chat policy. Use `PolicyStore` for runtime overrides; do not add a second app-env policy plane. - Platform channel modules resolve/validate their own `[gateway.<id>]` sections through the registered config behavior, rather than growing core vendor code. - Deprecated config sections fail with migration guidance, not silent fallback. Preserve unknown TOML keys/comments when changing a selected profile table. - `ProfileStore` is metadata/filesystem service, not a runtime orchestrator. Validate IDs before deriving paths, reject symlink/special homes, serialize writes per config path and atomically replace in the same directory. - Profile deletion requires exact-ID confirmation and moves the home to trash before config removal. Exports omit sessions, memory, binaries, artifacts and secret-like paths; redact the selected text/config/skill files. ## Secrets and redaction - Secrets use AES-256-GCM with per-secret salt/HKDF. Master keys are base64 32-byte values; raw strings need explicit legacy opt-in. Master-key rotation goes through `Secrets.KeyRotation` only: preview token bound to the exact record set, fail closed on any undecryptable record, and records stay readable by the next-loaded key at every step (staged ciphertext fallback). - Master-key lookup is Keychain, env, then private key file. On non-Keychain hosts, the launcher normalizes env from canonical `~/.lemon/secrets_master_key`. Preserve 0600 permissions. See [resolution contract](../../docs/security/secrets-and-keychain.md). - Secret resolution is encrypted store, explicitly enabled external sources in priority/id order, then env. A source error stops resolution; only a successful missing result proceeds. `env_fallback: false` disables both fallback kinds. Bootstrap auth uses `resolve_local/2` to prevent recursion. - External source programs are argv-only, supervised and bounded in time/output. Never copy their stdout/stderr into errors, logs or diagnostics. Cache defaults off and is bounded/process-local. - `Secrets.EnvCatalog` owns operator import/check names; `Env` declarations are release-dependent runtime configuration, not that credential catalog. - Secret lists/status/tests expose presence and safe metadata, never values or prefixes. Reads update usage metadata, not the secret's modification time. - Use the shared redaction boundary for secret-bearing text/maps. Redaction does not replace domain allowlists, output limits or authorization checks. ## Persistence and lifecycle - Domain stores are ordinary modules over the generic Store table API. Shared callers use their owner wrapper, not ad-hoc table access or domain methods on `LemonCore.Store`. Agent workspace stores belong in `lemon_agent`; channel indices belong in `lemon_channels`. - Operator session list/search/stats/export/prune uses `SessionLifecycle`. Reads are bounded/redacted; stats preserve exact totals with capped dimensions. Delete requires exact-key confirmation and verified completion; prune requires the preview's exact candidate token and millisecond cutoff. - Distinguish strict persistence failures from absence when enforcing durable uniqueness, replay fences or destructive confirmation. A missing-looking compatibility read is not evidence that a mutation is safe to repeat. - SQLite decode failures remain explicit corruption errors. Preserve atom/tuple/ struct keys through the backend codec; do not substitute lossy JSON encoding. - Expired chat state is deleted by compare-and-delete so a concurrent refresh survives. Run finalization remains serialized and idempotent with bounded recent-run indexing, not a lifetime-growing set. - Backup restore verifies the complete bundle before mutation. Credentials are opt-in; overwrite permission binds manifest digest and expanded destination. See [backup contract](../../docs/user-guide/backups.md). ## Runtime and event boundaries - `RouterBridge` lets producers call registered router behavior without a compile dependency. Success requires a non-empty binary run ID. Unregistered means unavailable; a configured mutation that raises/exits/throws/times out or returns malformed acknowledgement is `:outcome_unknown`, even for `:noproc`. - Unknown mutation outcomes may already have side effects: retain fixed identity and reconcile; never retry without independent idempotency. A definite returned error promises no mutation. Abort/keepalive `:ok` acknowledges dispatch, not synchronous application by the target process. - Query failures stay unavailable/query-failed, never silently idle/empty. Logs and errors exclude callbacks, arguments, stacktraces and raw exception reasons. - Exec approval resolve/cancel is atomic, first-writer-wins. A losing transition installs no grants. Timeout broadcasts resolution so clients clear pending UI. Audit payloads contain hashes/IDs/decisions, not commands or prompt text. - Bus envelopes use millisecond wall-clock timestamps. In-memory dedupe TTL uses monotonic time; durable idempotency uses wall-clock time. Do not mix clocks. ## Verification and packaging From the umbrella root: `mix test apps/lemon_core/test`. Use existing isolated store/testing helpers; test crash/restart and concurrent mutation boundaries with explicit synchronization. Restore global config/env. Core is one of five package targets. Repository 0.2.0 is not yet published; keep package metadata self-contained and optional dependencies optional. `scripts/verify_packages` exercises consumers of actual tarballs. See [package status](../../docs/platform-split.md).