A fresh install now activates only the safety pack; discipline hooks and agents are opt-in via an onboarding step (step 6) or `kei configure`. "People don't need Rust-only" — they pick their own stack. - _primitives/hook-packs.toml: SSoT mapping pack -> hooks, stack -> packs + agent groups. safety always on; evidence/observability/epistemic/ orchestration/git-guard/stack-rust opt-in. rust-first/no-python only under the systems stack; git-guard (no-github-push) opt-in only, pulled by no stack. - lib-profile: extract generic _toml_array (reused by lib-packs); profile_members becomes a thin wrapper (no behavior change). - lib-packs: pack/stack/agent resolvers + selection loader. - lib-hooks: filter_snippet_by_packs (install-time allowlist) + prune_kit_hooks (reconfigure removes deselected kit hooks, keeps foreign ones); activate_hooks rewired to prune + filter + merge. No custom settings.json fields (/doctor safe). - lib-agents: install_manifests filters by stack agent set (empty = install all). - onboarding: pick_stack step (reuse _onb_read_choice), persists stack_profile + enabled_packs to onboarding.toml; i18n STR_* added. - bin/kei configure -> scripts/kei-configure.sh (re-pick without reinstall); install stamps ~/.claude/.kei-kit-dir. - numeric-claims-guard: money regex no longer matches shell positionals ($1..$9); requires decimal / unit / 2+ digits / tilde. Real money + time still caught. - gate one-liner added to 8 discipline hooks (runtime toggle via hooks-control). Verified end-to-end (scratch HOME): fresh=safety only; evidence pack adds numeric+citation; systems stack wires rust-first + 14 base/systems agents (no data-science/swift); reconfigure-shrink prunes kit hooks but keeps a foreign hook; settings schema clean; assembler golden 3/3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.9 KiB
KeiSeiKit Architectural Decisions
ADR-style log. Each entry: context → decision → consequences. New entries at the top. Cross-link from
_primitives/_rust/<crate>/README.mdwhen a decision is crate-local.
2026-05-25 — Opt-in hook packs + stack profiles (public-prep posture)
Context
The kit force-activated every hook via settings-snippet.json, including the
author's personal research discipline (numeric-claims evidence markers,
no-downgrade, citation-verify, rust-first / no-python). For a public,
general-audience kit that is presumptuous — users bring their own stack and do
not need a Rust-only policy or evidence-marker enforcement by default.
Decision
- Posture: safety hooks on by default; all discipline packs opt-in. Packs:
safety(always),evidence,observability,epistemic,orchestration,git-guard,stack-rust. SSoT =_primitives/hook-packs.toml. - Stack profiles (minimal / web / ml / systems / mobile) pull a set of
discipline packs AND an agent set.
rust-first/no-pythonlive only instack-rust, which only thesystemsstack enables.git-guard(no-github-push) is opt-in only and pulled by NO stack — a general kit must not block a user's normalgit pushto github. - Mechanism: install-time filter of the snippet by selected packs
(
filter_snippet_by_packs) + prune of kit-owned hooks on reconfigure (prune_kit_hooks, foreign hooks preserved). Selection persists to~/.claude/config/onboarding.toml; re-runnable viakei configure. - Non-interactive /
--yes/ CI default = minimal (safety + cosmetic only), all agents (back-compat for power users).
Consequences
- Gate wiring (
_lib/gate.sh) added to the 8 highest-friction discipline hooks for runtime toggling via thehooks-controlskill; remaining cosmetic/event hooks deferred (install-time filtering already gives "off by default", so the runtime gate is a convenience, not a correctness requirement). - Agent-set changes via
kei configureapply on the next./install.sh(reconfigure re-applies hooks fully but does not remove already-installed agent manifests — they are harmless extra.mdfiles). _toml_arrayextracted fromlib-profile.sh:profile_membersas the shared one-line-array TOML reader (no new dependency).
2026-04-28 — Three scheduling abstractions in workspace
Context
After Hermes import (P4.2 kei-cron-scheduler) the KeiSeiKit workspace
contains three scheduler-like primitives. A naive audit reads this as
duplication; in practice each occupies a distinct layer of the stack and
removing any one would break a downstream consumer. This ADR documents the
boundary so a future reader does not consolidate them by mistake.
The three primitives
| Crate | Storage | Concurrency | Owns runner? | Canonical use |
|---|---|---|---|---|
kei-scheduler |
rusqlite (sync, metadata-only) |
sync | no | per-call queryable schedule index |
kei-cron-scheduler |
JSON-on-disk + fcntl advisory lock |
tokio async |
yes | Hermes parity (/schedule parser + cron loop) |
kei-pipe cron triggers |
embedded in pipe TOML | driven by pipe runtime | depends on pipe | pipeline-level cron embedded in a pipe definition |
Decision
Keep all three. Do not consolidate. Each abstraction encodes a different ownership contract and a different blast radius on failure.
Rationale, primitive by primitive
kei-scheduler — synchronous metadata-only store
Synchronous rusqlite schedule store. Stores cron expression, next-run
timestamp, owner, payload pointer. Does not dispatch — the caller asks
"what should I run between t and t+Δ" and the caller is responsible for
execution.
This separation matters because two callers want exactly that contract:
kei-pipequeries the schedule from the pipe-runtime loop (already its own scheduler) — it must not have a competing async runner inside the store.cron-wrapper-agenttest harness wants deterministic, blocking lookups with no background tasks. Atokioruntime would fight the harness.
A SQLite-backed metadata store is the smallest abstraction that satisfies
both callers. Any tokio infrastructure inside this crate would force its
contract on the harness and break determinism.
kei-cron-scheduler — async runner for Hermes parity
Async tokio-based runner. JSON-on-disk persistence (one file per job),
fcntl advisory lock to keep multiple binaries from racing the same job
file, owns its own loop, supports interval + standard 5-field cron. This
is the surface imported from Hermes (HERMES-MIGRATION-PLAN P4.2) — the
contract is "set-and-forget recurring scheduler with the runner inside the
crate."
A SQLite-only store like kei-scheduler cannot satisfy this contract:
- File-per-job is the unit of
fcntllocking; a single SQLite file would serialise all locks through the SQLite write mutex. - The runner is part of the public surface — Hermes callers expect to hand the crate a job and walk away. Splitting the runner into a separate crate would re-litigate the contract on every consumer.
kei-pipe cron triggers — pipeline-level cron embedded in a pipe
Pipes (KeiSei pipeline definitions, TOML) can declare a cron trigger inline. The pipe runtime evaluates the trigger as part of the pipe's own state machine, alongside event triggers, file-watch triggers, and HTTP triggers. The cron trigger is not a separate scheduler — it is a trigger source within the pipe runtime, which is itself the scheduler.
Re-implementing this on top of kei-cron-scheduler would either (a)
duplicate the pipe runtime's lifecycle into the cron crate, or (b) split
a single pipe's triggers across two runtimes, which loses the atomic
"trigger-fired-and-pipe-started" guarantee the pipe runtime provides.
Consequences
- Choosing the right primitive for a new caller. Decision tree:
- Need a recurring background runner with
fcntldurability and minimal blast radius if a single binary crashes? →kei-cron-scheduler. - Need a queryable index of "what should I run", with execution owned
elsewhere? →
kei-scheduler. - Trigger is one of many in a pipe definition, lives next to the data
flow, dies with the pipe? →
kei-pipecron trigger.
- Need a recurring background runner with
- Fail-loud overlap. If you find yourself porting a feature from one
to another (e.g. "let
kei-scheduleralso dispatch"), STOP — that is the No-Patching/No-Overlay smell from the umbrella rules. Add the feature to the right primitive instead, or write a new one. - Audit signal. A future audit may flag "three schedulers" as a code smell. This ADR is the canonical answer; link here from any review comment that surfaces the question again.
References
_primitives/_rust/kei-scheduler/_primitives/_rust/kei-cron-scheduler/_primitives/_rust/kei-pipe/(cron trigger source)HERMES-MIGRATION-PLAN.md§P4.2 — Hermes parity import