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>
144 lines
6.9 KiB
Markdown
144 lines
6.9 KiB
Markdown
# KeiSeiKit Architectural Decisions
|
|
|
|
> ADR-style log. Each entry: context → decision → consequences. New entries
|
|
> at the top. Cross-link from `_primitives/_rust/<crate>/README.md` when 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-python` live only in
|
|
`stack-rust`, which only the `systems` stack enables. `git-guard`
|
|
(no-github-push) is opt-in only and pulled by NO stack — a general kit must
|
|
not block a user's normal `git push` to 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 via `kei 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 the `hooks-control` skill; 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 configure` apply on the next `./install.sh`
|
|
(reconfigure re-applies hooks fully but does not remove already-installed
|
|
agent manifests — they are harmless extra `.md` files).
|
|
- `_toml_array` extracted from `lib-profile.sh:profile_members` as 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-pipe` queries the schedule from the pipe-runtime loop (already its
|
|
own scheduler) — it must not have a competing async runner inside the
|
|
store.
|
|
- `cron-wrapper-agent` test harness wants deterministic, blocking lookups
|
|
with no background tasks. A `tokio` runtime 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 `fcntl` locking; 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 `fcntl` durability 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-pipe` cron trigger.
|
|
- **Fail-loud overlap.** If you find yourself porting a feature from one
|
|
to another (e.g. "let `kei-scheduler` also 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
|