* feat(kb): apply-worker to auto-land approved proposals (stage 2 automation)
Event-driven worker that turns a HUMAN-approved kb_stage proposal into canonical
state, so an approval in Telegram surfaces in Leo's identity without Leo applying
its own work.
- Fires only on status='approved' (never pending_review) -> proposer != applier
holds; the human approval stays the trigger. No auto-approve anywhere.
- Reuses scripts/apply_proposal.py verbatim as the sole apply path (same txn,
rowcount=1 guard, FK stamp). Connects as the narrow kb_apply role, never
superuser, never inside the hermes harness.
- Render hook (--render-cmd / KB_APPLY_RENDER_CMD) is inert until the SOUL
renderer (PR2) is deployed; applying still works, rendered SOUL just lags.
- Ships INERT: report-only unless --enable / KB_APPLY_WORKER_ENABLED=1. systemd
oneshot service + 5min timer, both shipped disabled.
- 10 unit tests; candidate query validated read-only vs prod (0 applyable today).
* fix(kb): apply-worker --max-per-tick cap + poison-pill retry ceiling
Fixer draft-exit items:
- --max-per-tick=1 (default): an enabled worker lands applies one-at-a-time
and observably instead of draining the whole approved queue in one tick.
- --max-attempts=3 ceiling with a persisted failure-count state file: a
deterministically-failing approved proposal is treated as a poison pill and
skipped after N consecutive failures, instead of retrying every tick forever.
State persists on disk because the worker runs oneshot per timer tick.
Both are inert until the worker is enabled; it still ships disabled.
* feat(kb): apply_proposal engine to land approved proposals into canonical
Stage 2 of the KB apply pipeline (approve -> APPLY -> render -> surface).
Turns an approved kb_stage.kb_proposals row into canonical public.* rows and
flips the ledger to 'applied' in one verified transaction.
- Connects as the narrow kb_apply role (never superuser): writes only
strategies, strategy_nodes, claim_evidence, claim_edges + kb_proposals ledger.
Enforces "agents propose, do not self-apply" at the DB boundary.
- Per-type handlers: revise_strategy (versioned strategy + node replace),
add_edge, attach_evidence (requires existing source_id; source minting is
intentionally out of scope for kb_apply's grants).
- Strict apply_payload contract (v1); freeform eval packets are normalized
upstream, not applied directly.
- --dry-run prints exact SQL; idempotent (refuses non-approved / already-applied);
transactional with an in-txn DO-block invariant check that rolls back on failure.
- Unit tests cover SQL builders, validation, dispatch, and status guards.
* fix(kb): rowcount=1 apply guard + real applied_by FK stamp
Closes the three draft-exit review items on the apply engine:
- Ledger flip now runs in a DO block asserting exactly one 'approved'
row moved to 'applied' (GET DIAGNOSTICS row_count). Closes the
concurrent double-apply race — load_proposal (read) and the flip
(write) are separate statements, so a row lock cannot span them; only
one concurrent apply can match status='approved', so rowcount=1 is the
authoritative guard. A loser RAISEs and the whole txn rolls back.
- applied_by_agent_id is stamped as a real FK resolved from public.agents
by handle, defaulting to the kb-apply service agent — no more NULL FK,
no backfill needed.
- scripts/kb_apply_prereqs.sql: one-time superuser bootstrap — inserts the
kb-apply service-agent row (kb_apply never gets INSERT on agents), grants
kb_apply SELECT on public.agents, and ensures the one-active-strategy
unique index (idempotent; already present on prod).
18/18 unit tests pass.
* fix(kb): hard-resolve applied_by handle, RAISE on NULL FK
Resolve applied_by into a variable and assert NOT NULL before the ledger
flip, instead of an inline subselect that silently stamps a NULL
applied_by_agent_id on an unresolved handle. Since the FK is ON DELETE SET
NULL, a bad handle (typo/unseeded agent) was a legal silent NULL -- the
perpetually-NULL FK we eliminated. Unresolved handle now hard-fails ->
rollback. Non-default --applied-by (operator, future drafters) is the path
that goes through the lookup and could strand NULL.
Adds graph schema prerequisite plus research-eval schema/docs/tests for Leo tool-use benchmarks and x402 research telemetry. Validated by full local pytest and green CI.
* Wire Leo Telegram x402 smart research
* Suppress token-bearing Telegram HTTP logs
* Keep Telegram typing visible during Leo proxy calls
* Allow Leo Telegram social research spend cap
* Route contextual Leo research prompts to smart research
* Generalize Leo smart research intent routing
* Resume Leo smart research from paid work orders