Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .konjo/scripts/dry_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,28 @@ def find_duplicates(
return violations


def _write_empty_report(args: argparse.Namespace) -> None:
"""Write a zero-violation report when there's nothing to scan.

``--changed-only``/``--staged-only`` return early before the normal
report-writing path when there's no scan target at all — a real,
legitimate outcome (a docs-only PR), not an error. Without this, a
caller that unconditionally reads ``--report``'s output file (as
``konjo-gate.yml``'s DRY check step does) crashes on a missing file
instead of seeing the true, empty result.
"""
if not args.report:
return
report = {
"duplicates": [],
"count": 0,
"threshold": args.threshold,
"min_lines": args.min_lines,
"scanned": 0,
}
Path(args.report).write_text(json.dumps(report, indent=2))


def main() -> int:
parser = argparse.ArgumentParser(description="Konjo DRY Checker")
parser.add_argument("--root", default=None, help="Repo root (default: git toplevel)")
Expand Down Expand Up @@ -259,12 +281,14 @@ def main() -> int:
if not scan_targets:
if not args.json_out:
print("[dry-check] No staged source files to check.")
_write_empty_report(args)
return 0
elif args.changed_only:
scan_targets = _changed_files(root, extensions, args.base_ref)
if not scan_targets:
if not args.json_out:
print("[dry-check] No changed source files to check.")
_write_empty_report(args)
return 0
else:
scan_targets = all_files
Expand Down
65 changes: 65 additions & 0 deletions LEDGER.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,71 @@ expensive to silently re-litigate in a later sprint. One entry per sprint,
newest first. Not a changelog (that's `CHANGELOG.md`) — this is *why*, not
*what*.

## Mutation-Hunt-Live-Trigger -- pin bump confirmed fixed; a separate, real blocker found

Part D of the combined sprint. Gated on Part A (kiban `KIBAN_REF` bump to `v1.14.0`,
confirmed live at all four pin sites on `main` post-merge) and Part B/C (this
session's own PR). Both merged (`f5c55ce`, `2a4babc`); triggered the real
`mutation-hunt` `workflow_dispatch` job against `lopi-ratelimit` as instructed — the
PF-3/PF-0b baseline's own known-non-trivial target (11 real surviving mutants out of
51 tested in the full-crate baseline). No version bump — verification run, not a
code change.

### Run 1 (`31950963613`): a real, self-inflicted argument bug, not a pin problem

Dispatched with `diff_base_ref: 97c75a2` (short SHA of the commit immediately before
`crates/lopi-ratelimit` was created, chosen so `--in-diff` scopes the whole crate —
881 lines, matching the baseline's real target rather than an empty self-diff).
Failed instantly: `fatal: couldn't find remote ref 97c75a2` — the job's own
`git fetch origin "$diff_base_ref"` needs a name GitHub can resolve as a ref, and a
short SHA isn't one. Not a job bug; a dispatch-input mistake on my part.

### Run 2 (`31951537127`): the pin bump is confirmed fixed

Re-dispatched with the full 40-character SHA
(`97c75a20dec1e87f4556d41ce044ec964f327e93` — GitHub resolves a full commit SHA as a
fetchable ref even off the default branch). This time the job ran for real: kiban
`v1.14.0` cloned clean, `bin/kiban-mutation-hunt` exists and imported (the exact gap
`v1.8.0` had — confirmed fixed, this was the whole point of Part D), `cargo-llvm-cov`
generated coverage for `lopi-ratelimit`, and the loop **started and found a real
target**:

```
round 1 [uncovered_item]: surviving=None killed=n/a truncated=False tokens=0 cost=$0.0000 clean_tree=ok
total_tokens: 0 clean_tree_failures: 0
terminated: generation_failed gate_pass: False
```

### A second, separate, real blocker: `ANTHROPIC_API_KEY` does not reach this job

`tokens=0 cost=$0.0000` on a `generation_failed` termination means the loop never
successfully called the model at all — round 1's own env dump shows
`ANTHROPIC_API_KEY: ` (empty). This is not the pin problem Part D set out to test;
it's a second, independent gap. Checked before concluding: this repo's other
`ANTHROPIC_API_KEY` consumer, the `review` job (`G5 · Adversarial Review`), is
explicitly designed to soft-fail-safe on a missing key (`_load_anthropic()` raises
`ImportError`, caught and treated as a WARNING, exit 0) — so `G5`'s own "success" on
every run so far is not evidence the secret is actually configured; it succeeds
identically either way. `kiban-mutation-hunt` has no such soft path — a missing key
surfaces immediately and honestly as `generation_failed`, which is the correct
failure mode, just not the one this verification run was chasing.

**Not fixed here** — adding or checking a GitHub Actions repository secret is outside
what this session can do (no admin access to repo settings, and it shouldn't be
guessed at rather than confirmed by whoever manages that secret). Flagged as the real
remaining blocker before `mutation-hunt` is live-runnable for its actual purpose:
finding and fixing real surviving mutants, not just proving the pin resolves.

### Verdict

**Part D's own question — "does the pin bump make the job runnable end-to-end
against `v1.14.0`?" — is answered yes, confirmed live, not assumed.** Every step up
through model invocation succeeded: clone, script existence, coverage generation,
loop start, real mutant target identified. The job still can't complete a full round
today, but for a reason Part D didn't set out to test and doesn't touch: the
`ANTHROPIC_API_KEY` secret isn't reaching this job. `NEXT_SESSION_PROMPT.md` carries
this forward as the next blocker to clear.

## Telegram-Gateway-Non-Goal — `claude/telegram-bot-overhaul-8iJpe` closed, the gateway question is settled

Sprint P4 ("close the loop"), Phase 3 branch triage. `claude/telegram-bot-overhaul-8iJpe`
Expand Down
18 changes: 18 additions & 0 deletions NEXT_SESSION_PROMPT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ the `lopi` repo. Newest first.

---

## Next Session, after Mutation-Hunt-Live-Trigger (Part D verification run)

Read `LEDGER.md`'s `Mutation-Hunt-Live-Trigger` entry first. The `KIBAN_REF` pin
bump to `v1.14.0` is confirmed fixed by a real live dispatch against
`lopi-ratelimit`: kiban clones clean, `bin/kiban-mutation-hunt` exists and runs,
coverage generates, the loop starts and correctly identifies a real uncovered
mutant. **What's still blocking a full round:** `ANTHROPIC_API_KEY` doesn't reach
the `mutation-hunt` job — round 1 terminated `generation_failed` at `tokens=0
cost=$0.0000`. This is a GitHub Actions repository-secret question, not a code
question — needs whoever manages this repo's Actions secrets to confirm the secret
exists and is named exactly `ANTHROPIC_API_KEY`, then re-dispatch. Once that's
sorted, re-run with `crate: lopi-ratelimit`, `diff_base_ref:` a **full 40-character
commit SHA** (a short SHA fails `git fetch origin <ref>` — confirmed live, not a
guess) — `97c75a20dec1e87f4556d41ce044ec964f327e93` (the commit immediately before
`lopi-ratelimit` was created) still gives the whole-crate diff scope this needs.

---

## Next Session, after Sprint P4 ("close the loop," `[0.45.0]`)

Sprint P4 verified, merged, and shipped two parked sprints (`0.43.0` Planner/Executor
Expand Down
26 changes: 20 additions & 6 deletions docs/LOOP_ENGINEERING_ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
---
decays: state
verified-against: 2b7aa29
verified-date: 2026-08-04
verified-against: d00b87e
verified-date: 2026-08-16
---

# The Pentad — Loop Engineering Completion Roadmap

Verified against: `2b7aa29` · 2026-08-04 (re-verified; Sprint P2b's commit volume
Verified against: `d00b87e` · 2026-08-16 (re-verified; the combined P3a-closeout/
Collision-Oracle/RepoProfile-parity/mutation-hunt/branch-triage sprint (plus Sprint
P4's own merge and branch-triage work) crossed the 20-commit cap. Two real citation
drifts found and fixed this time, not just commit volume: the Collision-Oracle-Build
sprint's `AgentRunner` wiring (a `with_collision_oracle` builder method, two new
`lopi_oracle` imports) shifted `with_skills` from `runner/builder.rs:92` to `:94`,
and the same sprint's `seed_collision_alerts` addition to `gather_seed` shifted
`seed_skills` from `runner/seed.rs:210-241` to `:230-244` (`record_skill_activation`
moved further, to `:330-343`, from other changes in this window) — both corrected
above, confirmed by reading the current file, not assumed from a line-count delta.
Content unchanged: `with_skills`, `seed_skills`, and `record_skill_activation` still
do exactly what this doc describes. Every other cited file either didn't change in
this window or changed only in ways this doc doesn't cite specific lines into.

Prior banner (`2b7aa29` · 2026-08-04, re-verified; Sprint P2b's commit volume
(review-pipeline sections 1/3/4 plus a parallel Oracle-Preflight sprint's merge) pushed
this past the 20-commit cap again. One real citation drift found and fixed this time,
not just commit volume: Sprint P1 (Planner/Executor split) added a `tool_profile` field
Expand Down Expand Up @@ -118,7 +132,7 @@ new `no_progress_limit`/`isolation` fields); `src/main.rs:50,299` → **`:50,268
(`McpServe`, file shrank); `web/mod.rs:273` → **`:288`** (route registration);
`pool/run_loop.rs:338` → **`:380`** (`setup_worktree` call site, pushed down by the
new autonomy/isolation resolution block); `runner/mod.rs:329` (`with_skills`) moved
entirely to **`runner/builder.rs:92`** (file-size split); and
entirely to **`runner/builder.rs:94`** (file-size split); and
`crates/lopi-remote/src/lib.rs:1-10`'s description named `telegram`/`egress` modules
that have since been deleted (crate is now `whatsapp`-only at `:1-19`) — the
underlying verdict (no `Connector` trait, no durable outbound queue) is unchanged.
Expand All @@ -133,7 +147,7 @@ Legend: 🟢 solid · 🟡 partial · 🔴 missing.
|-------|--------|-------------|--------------|
| **Automations** | 🟢 | `lopi-orchestrator` (`scheduler.rs`, `schedule_manager.rs`) cron; `lopi-webhook` CI-failure → task with HMAC verify; per-schedule autonomy L1–L4; run-history persistence | `crates/lopi-webhook/src/github.rs:36-60` — no delivery-id **dedup**, no **dead-letter queue**, triage is synchronous, no schedule-change audit trail. `crates/lopi-core/src/template.rs:44` has a generic `{name}`-hole templating primitive and `Task::from_template` (`crates/lopi-core/src/task.rs:475-480`) exists, but neither is called outside tests — event-payload templating is unwired scaffolding, not shipped |
| **Worktrees** | 🟢 | **Real `git worktree` isolation, shipped and wired.** `crates/lopi-git/src/worktree.rs:36-217` (`WorktreeManager` add/add_detached/prune/list/gc) with RAII `Drop` cleanup (`worktree.rs:295-330`); `crates/lopi-orchestrator/src/pool/worktree.rs:25-50` (`setup_worktree`) puts each task in its own detached worktree when `IsolationMode::Worktree` is set (`crates/lopi-core/src/loop_config.rs:38-44`), with per-worktree `CARGO_TARGET_DIR` (`worktree.rs:266-277`); `crates/lopi-git/src/rebase.rs:27-75` (`rebase_onto`/`rebase_onto_default`) rebases onto a moved default branch and maps conflicts to `TaskStatus::Conflict` (wired at `crates/lopi-agent/src/runner/finalize.rs:243-264`, `rebase_before_pr` — line drift from the Sprint G verification-gate work touching this file); GC exposed via `lopi worktree gc`/`list` (`src/worktree_commands.rs:18-51`) | Isolation mode defaults to `Branch`, not `Worktree` — a repo must opt in via `.lopi/loop.toml`. No mid-run snapshot |
| **Skills** | 🟢 | **Runtime skill engine, shipped and wired.** `crates/lopi-skill/src/registry.rs:17-93` (`SkillRegistry::load_from_dirs`, dup-name validation) parses `SKILL.md` frontmatter into a typed registry; `crates/lopi-agent/src/runner/builder.rs:92` (`with_skills` — moved out of `runner/mod.rs` since the last verification, file-size split) and `crates/lopi-agent/src/runner/seed.rs:210-241` (`seed_skills`/`record_skill_activation`) inject matching skills into the planning prompt and record activation | Lesson→skill promotion is **partial**: `crates/lopi-skill/src/promote.rs:37-60` (clustering) and `promoter.rs:40-60` (drafts to `.lopi/skills-pending/`, human-approval gate) exist and are reachable via `src/skill_commands.rs:64`, but drafting is a fixed string template, not "via a sub-agent" as originally scoped, and nothing triggers it automatically — it's a manual CLI-only path today |
| **Skills** | 🟢 | **Runtime skill engine, shipped and wired.** `crates/lopi-skill/src/registry.rs:17-93` (`SkillRegistry::load_from_dirs`, dup-name validation) parses `SKILL.md` frontmatter into a typed registry; `crates/lopi-agent/src/runner/builder.rs:94` (`with_skills` — moved out of `runner/mod.rs` since the last verification, file-size split) and `crates/lopi-agent/src/runner/seed.rs:230-244` (`seed_skills`; `record_skill_activation` moved to `:330-343`) inject matching skills into the planning prompt and record activation | Lesson→skill promotion is **partial**: `crates/lopi-skill/src/promote.rs:37-60` (clustering) and `promoter.rs:40-60` (drafts to `.lopi/skills-pending/`, human-approval gate) exist and are reachable via `src/skill_commands.rs:64`, but drafting is a fixed string template, not "via a sub-agent" as originally scoped, and nothing triggers it automatically — it's a manual CLI-only path today |
| **Plugins & connectors** | 🟢 | **MCP client + server, shipped and wired — both directions.** `crates/lopi-mcp/src/client.rs:36-65` + `config.rs:19-37` (`[[mcp.servers]]` in `.lopi/loop.toml`) + `bridge.rs:21-49` (merges discovered tools into `lopi-tools::ToolRegistry`) is the consuming side; `crates/lopi-mcp/src/server.rs:18-80` wired at `src/mcp_commands/mod.rs:117-243` exposes `lopi_submit_task`/`lopi_get_task`/`lopi_cancel_task`/`lopi_list_tasks`/`lopi_get_logs`/`lopi_get_agent_dag`/`lopi_get_stats` as MCP tools over stdio (`McpServe` registered at `src/main.rs:50,268`) — more surface than the original sprint scoped | `crates/lopi-remote/src/lib.rs:1-19` is now down to a single hardcoded `whatsapp` module — Sprint S10 Phase 4 removed the `telegram` transport entirely (the iOS/macOS app covers that use case now; the `TaskSource::Telegram` variant itself survives as a durable persisted enum, see `LEDGER.md`), and the `egress` allowlist module cited here previously has also since been deleted. Neither removal changes the verdict: **no `Connector` trait exists anywhere in the crate, no durable outbound queue.** The original claim ("connectors are hardcoded singletons") still holds, just with one fewer singleton than when this was last checked |
| **Sub-agents** | 🟢 | **Maker/checker split, shipped and wired.** `crates/lopi-agent/src/verifier.rs:196-199` — `VerifierAgent::new` defaults `isolated: true`; `resolve_verifier` (`verifier.rs:47`) forces a different model than the maker; test `isolated_prompt_excludes_the_maker_plan` moved to the new `crates/lopi-agent/src/verifier_tests.rs:54` when Sprint G split verifier's tests into their own file — still asserts a maker's plan text never reaches the verifier's prompt | No parallel task decomposition: `crates/lopi-core/src/successor.rs:1-27` is a depth-capped (3) **sequential** one-hop successor chain, not a sub-task DAG dispatched through `AgentPool`. Earned-trust auto-promotion exists as an isolated, tested state machine (`crates/lopi-core/src/earned_trust.rs:31-101`) but has zero callers outside its own module — not wired into `schedule_manager.rs`, not persisted |
| **Memory / state** | 🟡 | `lopi-memory` SQLite (patterns, lessons, audit, schedules); `CLAUDE.md` + rules; `LoopConfig` → `.lopi/loop.toml`. Stall detection exists in a narrower form than originally claimed missing: `StopReason::NoProgress` (`crates/lopi-core/src/stop_reason.rs:27-28`) + `ProgressGate` (`crates/lopi-agent/src/runner/progress.rs:20-55`) halts on score-delta stagnation | Still genuinely open: no `AgentEvent::ProgressStall` variant (only a string-convention reason), no per-loop external markdown state file (Ralph), no `VISION.md` intent anchor |
Expand Down Expand Up @@ -249,7 +263,7 @@ the standing Three-Wall gates; only sprint-specific acceptance is spelled out.
file+line, never silently.

**Sprint 2.2 — Relevance injection into the loop**
- **Status: ✅ DONE.** `crates/lopi-agent/src/runner/builder.rs:92` (`with_skills` — moved out of `runner/mod.rs` since the last verification, file-size split); `crates/lopi-agent/src/runner/seed.rs:210-241` (`seed_skills`/`record_skill_activation`). Activation is recorded through the generic audit trail rather than a dedicated `lopi-memory/src/store/skills.rs` (that file doesn't exist) — functionally equivalent, different location than originally scoped.
- **Status: ✅ DONE.** `crates/lopi-agent/src/runner/builder.rs:94` (`with_skills` — moved out of `runner/mod.rs` since the last verification, file-size split); `crates/lopi-agent/src/runner/seed.rs:230-244` (`seed_skills`; `record_skill_activation` moved to `:330-343`). Activation is recorded through the generic audit trail rather than a dedicated `lopi-memory/src/store/skills.rs` (that file doesn't exist) — functionally equivalent, different location than originally scoped.
- **Goal:** The right skills enter the planning prompt automatically.
- **Deliverables:** trigger-match (keyword now, embedding-ready interface) →
inject skill body into `AgentRunner` context; per-task **activation record**
Expand Down
19 changes: 16 additions & 3 deletions docs/ops/PANIC_AUDIT.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
---
decays: state
verified-against: 2b7aa29
verified-date: 2026-08-04
verified-against: d00b87e
verified-date: 2026-08-16
---

# Panic audit — the trustworthy count, and why grep couldn't give it to you

Verified against: `2b7aa29` · 2026-08-04 (re-verified; Sprint P2b's commit volume
Verified against: `d00b87e` · 2026-08-16 (re-verified; the combined P3a-closeout/
Collision-Oracle/RepoProfile-parity/mutation-hunt/branch-triage sprint (plus Sprint
P4's own merge and branch-triage work) crossed the 20-commit cap again. Re-confirmed
live, not assumed: re-ran the exact cited deny-flag command
(`cargo clippy --workspace --all-targets --all-features -- -D warnings
-D clippy::unwrap_used -D clippy::expect_used -D clippy::panic -D clippy::todo
-D clippy::unimplemented -D clippy::dbg_macro -D clippy::print_stdout
-D clippy::print_stderr -W clippy::cognitive_complexity`) against the current
workspace — clean, covering every crate this window's sprints added or touched,
including the new `lopi-oracle` crate and its `AgentRunner` wiring
(`crates/lopi-agent/src/runner/collision_seed.rs`). This entry's own diff
(`LEDGER.md`/`NEXT_SESSION_PROMPT.md` only) touches no production Rust.

Prior banner (`2b7aa29` · 2026-08-04, re-verified; Sprint P2b's commit volume
(review-pipeline sections 1/3/4 plus a parallel Oracle-Preflight sprint's merge) pushed
this past the 20-commit cap, not the zero-unwrap claim losing accuracy. Re-confirmed
live, not assumed: ran the exact cited deny-flag command workspace-wide (`cargo clippy
Expand Down
Loading
Loading