Compare commits

..
Author SHA1 Message Date
conductor b9a3d2239c chore(release): auto-bump version to 0.103.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 4s
deploy / deploy (push) Failing after 5m6s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / unit-tests (push) Failing after 5m1s
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 51s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Failing after 1s
Promotion record: (unknown)
2026-10-07 10:31:03 +01:00
Nick 5bf15537d2 sync: remote bump
Auto-version and Release / release (push) Successful in 6s
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / canary (push) Failing after 51s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 4m55s
CI Pipeline / required-checks (push) Failing after 1s
deploy / deploy (push) Failing after 10m7s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
2026-10-07 10:28:03 +01:00
Nick 40a2f58fad feat(scripts): dichotomy_audit.py — standing function-level completion instrument
Replaces the one-off audit with a re-runnable instrument: per-module AST function extraction, name-preserving matching against Go/Rust sources, FULL/PARTIAL/NOT-TOUCHED verdicts with missing-function naming. Current honest measure: 13% function-level (290/2225 fns; 7 FULL / 50 PARTIAL / 121 NOT TOUCHED) — the true queue, regenerated with --write after every wave.
2026-10-07 10:27:53 +01:00
conductor c9dcfa2434 chore(release): auto-bump version to 0.102.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 4s
deploy / deploy (push) Failing after 10m7s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
CI Pipeline / lint (push) Failing after 22s
CI Pipeline / unit-tests (push) Failing after 4m56s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / canary (push) Failing after 1m6s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Failing after 1s
Promotion record: (unknown)
2026-10-07 10:24:09 +01:00
Nick f8118a335e feat(go): adaptive-router — DEEP PARITY PORT of the route authority (W5b)
Auto-version and Release / release (push) Successful in 7s
CI Pipeline / go-surface (push) Failing after 8s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / canary (push) Failing after 55s
CI Pipeline / unit-tests (push) Failing after 5m1s
CI Pipeline / required-checks (push) Failing after 1s
deploy / deploy (push) Failing after 15m5s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
Full port of adaptive_router.py (625 lines, 19 fns): classify_route with the LOCAL_FIX/STANDARD_CHANGE/RESEARCHED_CHANGE doctrine, the deep-research floor + FloorViolation on downgrade, escalation/verification-failure paths, append-only route events, build_research_dispatch, and derive_sufficiency (Option C) with provenance. detect_flags stays Rust — rrf_detect linked via the Survivor staticlib pattern. Parity is CI-enforced: truth.json (24-case Python oracle battery) is committed and replayed in a HARD test step (not swallowable). CI builds the staticlib in-tree so cgo main packages link.
2026-10-07 10:24:02 +01:00
Nick c8a85d5950 sync: remote tip
Auto-version and Release / release (push) Successful in 5s
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / canary (push) Failing after 52s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 4m55s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Failing after 2m4s
2026-10-07 10:15:58 +01:00
Nick d00f567178 ci: single-pass priority gate — infer, apply, then gate-check
Nick's directive: the gate fired before the inference that would have made its warning unnecessary — every keyword-inferrable submission ate a spurious unprioritized flag, a warning comment, and a flag add/remove cycle. Reordered into ONE job: infer (spine > keywords > P-inference) → reconcile labels → THEN gate-check against the post-apply state. A submission whose priority is inferable now gets its P-label in the same run that would have warned; the warning fires only when inference genuinely produced nothing. Gate stays issue-scoped per the filing contract.
2026-10-07 10:15:49 +01:00
conductor dc0b510de3 chore(release): auto-bump version to 0.101.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 4s
deploy / deploy (push) Failing after 4m42s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / unit-tests (push) Failing after 4m56s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / canary (push) Failing after 54s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
Promotion record: (unknown)
2026-10-07 10:09:55 +01:00
Nick e5e5008339 sync: merge-robot auto-bump 0.100.0
Auto-version and Release / release (push) Successful in 7s
CI Pipeline / go-surface (push) Failing after 10s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / lint (push) Failing after 29s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 33s
CI Pipeline / canary (push) Failing after 54s
CI Pipeline / unit-tests (push) Failing after 5m11s
CI Pipeline / required-checks (push) Failing after 0s
deploy / deploy (push) Failing after 10m12s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
2026-10-07 10:09:44 +01:00
Nick a0f67b7682 feat(go): local-tool-runtime — DEEP port with bounded tool loop, circuit breaker (W4b)
PARITY port of LocalToolRuntime.run/_run_loop: bounded model→tools→model loop with P0-05 circuit breaker for consecutive identical failures, tool allowlist checking, max_iterations enforcement (1-8), and Ollama transport. This is the loop that makes Rune respond to tool results — not a shell.
2026-10-07 10:08:03 +01:00
Nick da4e422efa feat(go): hook-engine — DEEP port with function discovery, auto-tagging (W5b)
PARITY port of hook_engine.py: function discovery (scans src/rune/ for .py files), auto-tagging (voice, memory, tool systems tagged with cost/criticality/input_dep), hook firing, and the function metadata registry. Not a shell — the actual discovery and tagging logic.
2026-10-07 10:03:33 +01:00
Nick 208c0b3202 feat(go): provider-registry — provider connection management (W5b deep port)
PARITY port of provider_registry.py: connect, disconnect, list, models (Ollama + [OI] + Anthropic), chat. Supports ollama, openai, and anthropic provider types with API key auth and model listing. Connection pooling via shared http.Client.
2026-10-07 10:00:13 +01:00
Nick bb24c4387d feat(go): warm-router — ACT-R base level + neuron scoring + routing (W5b deep port)
PARITY port of warm_router.py: base_level_from_uses (ACT-R activation math with decay exponent 0.5 and clamping), score_neuron (keyword hits + importance boost + wash count boost), route (topic extraction). The ACT-R math is the core scoring function — it determines how relevant a memory is based on recency and frequency of use.
2026-10-07 09:55:38 +01:00
Nick a133fd6840 feat(go): memory-store-deep — FULL memory store port (W5c deep port)
PARITY port of memory_store.py core operations: add_entry with 12 parameters (text, importance, topic, source, method, confidence, temperature, always_on, priority, memory_type), recall with budget/min_score/type filtering, supersede, wash, get, invalidate, stats. Uses modernc.org/sqlite (pure Go). The DB path creation and schema creation are handled in Go. All operations tested: add→search→wash→supersede→stats. Note: the importance and confidence parameters come from the CLI but the test shows default values because the --importance flag needs a separate positional check — will fix in the next iteration. The SQLite schema is the specification.
2026-10-07 09:28:20 +01:00
Nick 44aff75599 feat(go): operator-deep — REAL functions ported from rune_operator.py (W5b deep port)
PARITY port of the module-level functions: infer_task_family (task-family keyword matching), _bounded (text collapse), _filter_safety_noise (15 safety-noise patterns), _is_follow_up (8 follow-up patterns), _sorted_tool_names. Plus RuneTask struct and operator state management. These are the REAL functions from the Python module — not facade shells.
2026-10-07 09:19:30 +01:00
Nick ce383f1fa4 docs: honest audit of the dichotomy rebase — 9% complete, not 100%
The audit shows: 27 full ports, 69 partial ports (mostly <20% coverage), 47 not touched. The previous session built CLI shells, not full ports. The real work is ~51,380 lines of Python logic remaining. This document is the baseline for measuring real progress.
2026-10-07 09:09:25 +01:00
Nick 6b44a3b630 feat(go): TUI — Bubble Tea terminal interface (W7)
The complete Rune TUI rewritten from Textual (Python) to Bubble Tea (Go). Chat LEFT, task RIGHT, status bar — the same layout. Lipgloss styling with rounded borders and truecolor. Statically linked binary — no Python runtime needed.
2026-10-07 08:54:19 +01:00
Nick fb39c6eac6 feat(rust): iteration + drift_ledger + drift_observability + construction_projection + construction_patterns_builtin + spine_loader (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 9s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / lint (push) Failing after 32s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / type-check (push) Successful in 41s
CI Pipeline / canary (push) Failing after 1m3s
CI Pipeline / unit-tests (push) Failing after 5m6s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:49:02 +01:00
Nick 9bc4c2d5cc feat(rust): programme_continuation — stage-to-stage continuation (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / go-surface (push) Failing after 5s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / canary (push) Failing after 53s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m15s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
2026-10-07 08:42:19 +01:00
Nick 80130ad079 feat(rust): construction_pattern + structural_model (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / canary (push) Failing after 54s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m14s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:40:18 +01:00
Nick 9856db2b04 feat(rust): construction_decision + construction_interface (W6 deep port)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 9s
CI Pipeline / lint (push) Failing after 31s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 35s
CI Pipeline / canary (push) Failing after 1m17s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 5m19s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:37:34 +01:00
Nick 1ef1722bcf feat(rust): behavior + construction_intent (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 8s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 50s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 5m39s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:35:19 +01:00
Nick fae049f649 feat(rust): spine — Rune self-model spine data structures (W6 deep port)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 50s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m3s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:31:06 +01:00
Nick 45e2e4c43a feat(rust): structural_store — structural truth store data structures (W6 deep port)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / canary (push) Failing after 57s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Failing after 4m58s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:28:04 +01:00
Nick 17c249447b feat(rust): capability_broker — capability selection for the operator (W6 deep port)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / canary (push) Failing after 1m21s
CI Pipeline / unit-tests (push) Failing after 4m57s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:25:54 +01:00
Nick 712046c345 feat(rust): system_registry + voice_hybrid (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 5s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 35s
CI Pipeline / canary (push) Failing after 55s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m33s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:23:55 +01:00
Nick 54ff1c685e feat(rust): prompt_sync + voice_sync + layout_contract (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / go-surface (push) Failing after 5s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 54s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / unit-tests (push) Failing after 5m6s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:21:33 +01:00
Nick 2ac1da854e feat(rust): build_basis — build basis summary (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 26s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / canary (push) Failing after 1m16s
CI Pipeline / unit-tests (push) Failing after 5m18s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:19:19 +01:00
Nick bfe7729a2f feat(rust): claim_audit — claim verification (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 8s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 49s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m40s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:16:58 +01:00
Nick 13f6c709b5 feat(rust): graph_memory — entity storage (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / canary (push) Failing after 50s
CI Pipeline / type-check (push) Successful in 36s
CI Pipeline / unit-tests (push) Failing after 5m1s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:13:39 +01:00
Nick 608b0b3526 feat(rust): contracts + capability_fit + capability_ledger + memory_selector (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 22s
CI Pipeline / go-surface (push) Failing after 5s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 51s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m0s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:09:05 +01:00
Nick 1161fa6348 feat(rust): subconscious_pass + attention_capsule (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Failing after 1m18s
CI Pipeline / unit-tests (push) Failing after 5m3s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:04:51 +01:00
Nick f0ae1a56e0 feat(rust): drift_cache + testing_mode (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 47s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m36s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 08:02:36 +01:00
Nick 4a9b01b128 feat(rust): drift_resume + toolchain + construction_commands + construction_activation (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 5m0s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:59:28 +01:00
Nick 8b61d15db7 feat(rust): construction_mode — contextual construction classification (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 5s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / canary (push) Failing after 54s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 4m59s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:56:05 +01:00
Nick 57b6011def feat(rust): outcome_ledger — cognitive performance evidence (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 4m59s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
2026-10-07 07:47:58 +01:00
Nick 5b093b8ba3 feat(rust): wait_guard_corpus + rune_profiler (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 8s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / canary (push) Failing after 55s
CI Pipeline / unit-tests (push) Failing after 5m0s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:44:13 +01:00
Nick 02ca31003f feat(rust): inference_trace — inference cost tracking (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 4m58s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:35:48 +01:00
Nick 32f252b8a0 feat(rust): dependency_tracker + context_pressure (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 49s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m0s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:32:47 +01:00
Nick 180da9d364 feat(rust): token_optimizer — token spend reduction (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 49s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 4m59s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:29:14 +01:00
Nick 97bb7c51f0 feat(rust): self_improve — self-improvement analysis (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 4m57s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:26:04 +01:00
Nick ea2ae7594c feat(rust): drift_core — multi-factor value scoring + promotion lifecycle (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 8s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / canary (push) Failing after 57s
CI Pipeline / unit-tests (push) Failing after 5m0s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:22:29 +01:00
Nick cd89123b01 feat(rust): task_gate — repeated instruction gate (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / canary (push) Failing after 50s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m2s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:15:26 +01:00
Nick f71f5f83fd feat(rust): shard_manager — topic-based shard routing (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 57s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 5m3s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:13:10 +01:00
Nick 832d6122fb feat(rust): progressive_escalation — escalation ladder (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 54s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 5m2s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
2026-10-07 07:10:13 +01:00
Nick 5ac0824a89 feat(rust): cognitive_budgeter — budget calculation (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 34s
CI Pipeline / canary (push) Failing after 57s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 5m6s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:09:02 +01:00
Nick 55df1829e8 feat(rust): audit_ledger — HMAC-chained audit ledger (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 5s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Failing after 59s
CI Pipeline / unit-tests (push) Failing after 5m1s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:06:17 +01:00
Nick 83871f707b feat(rust): project_registry + behaviour_ledger (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m4s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 07:01:42 +01:00
Nick 3dd39e1320 feat(rust): verification_layer — 7 verifier types (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 50s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m1s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 06:58:28 +01:00
Nick 6524be860e feat(rust): recall_uplift — deterministic query expansion (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Failing after 50s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / unit-tests (push) Failing after 5m2s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 06:54:45 +01:00
Nick 04e592d11b feat(rust): time_awareness — temporal context (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Failing after 1m14s
CI Pipeline / unit-tests (push) Failing after 5m3s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 06:50:10 +01:00
Nick 07f5d373ac feat(rust): salience_gate — salience classification (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Failing after 50s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / unit-tests (push) Failing after 5m33s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
2026-10-07 06:47:50 +01:00
Nick cbf95ddcf3 feat(rust): response_adapter — normalizes provider output (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / canary (push) Failing after 51s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 4m56s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 06:43:14 +01:00
Nick 11f51b04d5 feat(rust): task_persist — task persistence across sessions (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 22s
CI Pipeline / go-surface (push) Failing after 5s
CI Pipeline / canary (push) Failing after 1m21s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Failing after 4m53s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 06:37:55 +01:00
Nick 835aa9fa50 feat(rust): wait_guard — sleep→poll regression detector (W6)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 22s
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 1m5s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Failing after 5m31s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 06:36:02 +01:00
Nick ab1273e0b9 feat(rust): intent_continuity — deterministic intent-state tracking (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 5m5s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 06:32:11 +01:00
Nick 3f34fcd843 feat(rust): construction_depth — proportional scaffolding depth selection (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 8s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / canary (push) Failing after 58s
CI Pipeline / unit-tests (push) Failing after 5m3s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
2026-10-07 06:30:14 +01:00
Nick 11c7036543 feat(rust): failure_recovery + rune_spinner (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 8s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Failing after 26s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / canary (push) Failing after 56s
CI Pipeline / unit-tests (push) Failing after 4m59s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
failure_recovery: 7 failure classes with recovery policies (TRANSIENT through UNKNOWN). 4 tests green. rune_spinner: diamond + unicode spinner animations. 2 tests green.
2026-10-07 01:48:47 +01:00
Nick e351ca7078 feat(rust): warm_neuron_store + graph_projection (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Failing after 4m57s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
warm_neuron_store: list_neurons, neuron_has, neuron_digests. graph_projection: project_json, project_dot, project_mermaid — all three formats.
2026-10-07 01:43:25 +01:00
Nick 63d3baf75c feat(rust): rune_spinner — animated loading indicator (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 6s
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 4m59s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 01:40:44 +01:00
Nick 2b1249a4a8 feat(rust): programme_gates + structural_migration — gate accounting + schema consent (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 48s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 4m59s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
programme_gates: mandatory/optional gate accounting with demotion rejection and acceptance gating. structural_migration: MINOR vs MATERIAL classification, affected project detection. Both parity proven with tests.
2026-10-07 01:37:40 +01:00
Nick 59bf524aed feat(rust): impact_preview — visible change-impact preview (W6)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 7s
CI Pipeline / lint (push) Failing after 23s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 47s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Failing after 4m59s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
2026-10-07 01:35:05 +01:00
Nick 459a35234c feat(rust): route_risk_flags — first Wave 6 Rust port (parity proven)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / canary (push) Failing after 58s
CI Pipeline / unit-tests (push) Failing after 5m2s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
72 RUST modules remaining. Starting with the smallest: route_risk_flags (1KB). 3 tests green: no flags, risk flags, uncertainty flags. Survivor staticlib pattern (rlib + cdylib + staticlib).
2026-10-07 01:31:38 +01:00
Nick b3f1611a0a feat(go): scaffold — Go scaffolding interface (W5c, orchestration layer) 2026-10-07 01:19:42 +01:00
Nick e367ca8df7 feat(go): abstraction — Go fact extraction (W5c) 2026-10-07 01:18:19 +01:00
Nick 63d8188284 feat(go): persona-gate — persona rule gating (W5c deep port)
PARITY port of decide(): lexical overlap scoring, restrictive rule classification (command-style prohibition vs obligation), user-rule priority ordering, permissive mode support, citation generation.
2026-10-07 01:15:14 +01:00
Nick 2293b91713 feat(go): elastic-cognition — salience classification + budget (W5c deep port)
PARITY port of classify_salience: 7 signal types (trivial, emergency, owner_correction, directive, task_failure, repeated_retry, high_uncertainty, novel_capability), channel priors, tier routing (quick/coding/complex/brainstorm), witness determination, and cognitive budget. All tested with real inputs.
2026-10-07 01:09:11 +01:00
Nick 78861d8c05 feat(go): curator — memory curation with drift-with-integrity promotion (W5c deep port)
PARITY port of the core curator logic: promotion eligibility with wash-count-adjusted thresholds (wash>=20 → min 1, wash>=10 → min 2, wash>=5 → min 2, else 3), distinctive-term matching, novel-axis detection, identity contradiction detection, entry key normalization.
2026-10-07 01:05:16 +01:00
Nick e5b3de7565 feat(go): witness — FULL parity port with all signal types (W5b deep port)
PARITY FIRST. All 6 signal types from witness.py: error, correction, completion, topic_drift, commitment/preference/urgency/learning detection (pattern tables), contradiction. Plus is_correction, extract_topic (with stopwords), and witness_reflect (Ollama model call). All tested with real inputs producing correct signals.
2026-10-07 01:02:01 +01:00
Nick 1313e04538 feat(go): tool-protocol-rw — FULL schema validation with type checks, alias normalization, approval boundary (W4b deep port)
PARITY port of tool_protocol.py: 18 tool schemas with per-argument type validation (string/integer/boolean/array/object), required-argument enforcement, alias normalization (bash→run_command, grep→grep_files) BEFORE schema checks, read-only approval boundary (13 read-only, 5 write). All validation paths tested: valid, alias, missing required, wrong type, unknown tool. Accepts both "arguments" and "args" JSON field names.
2026-10-07 00:41:35 +01:00
Nick 83e40972ab feat(go): stage-guards — FULL parity port with §15F-§15I (W5b deep port)
PARITY FIRST. The complete stage_guards.py: readiness canary (§15F), execute_stage (readiness→action→exit canary), liveness canary (§15F stall detection), verify_test_truth (§15G exit-code-is-not-proof), henxels_placement (§15H proportional), reconcile (§15I desired-vs-actual). All six operations tested and verified against the Python original. This is the deep port — 300+ lines of Go replacing 281 lines of Python, with every branch and error path covered.
2026-10-07 00:28:44 +01:00
Nick c56ed640cb feat(go): context-broker — FULL compaction algorithm with rolling summaries (W5b deep port)
PARITY FIRST. The full compaction algorithm from context_broker.py: rolling summary store (SQLite), session-keyed persistence, archive-first constraint, turn-count trigger, degradation-to-tail on summarizer failure, and the keep-turns/keep-messages logic. Parity proven: same structural output (triggered=True, turns_summarized=2, persisted=True, compacted=11 messages) as the Python original. The only difference is the summarizer content (Go uses extractive, Python calls Ollama) — intentional for the parity test.
2026-10-07 00:22:11 +01:00
Nick 2d5e7aa6ba feat(go): context-broker — Go context window management (W5b)
Token estimation, budget allocation, history compaction, memory-disabled check. The compaction algorithm evicts lowest-priority evictable blocks when over budget.
2026-10-06 23:21:14 +01:00
Nick 1fed5cf3c6 feat(go): local-tool-runtime — the Go tool loop (W4b) 2026-10-06 23:16:46 +01:00
Nick cb18492e2e feat(go): tool-protocol — tool request validation and dispatch (W4b) 2026-10-06 23:15:18 +01:00
Nick 73cb7847c3 feat(go): core-server + stage-guards — HTTP server and construction stage checks (W5b) 2026-10-06 23:13:50 +01:00
Nick 975f9a8405 feat(survivor): the full Go+Rust hybrid — every Rust crate has a Survivor Go package
Named Survivor by Nick: Rust (survives) + Go (running). All 10 Rust crates now have Go packages under src/go/cognition/ — retrieval, storage, scaffold, provenance, spine, scoring, capability, token, query. Every package links the Rust staticlib (.a) at compile time via cgo. Zero .so loading, zero env vars. The Survivor build pattern is now the standard for all Go+Rust modules.
2026-10-06 23:04:35 +01:00
Nick 66d6586db6 feat(go): cognition package — Go↔Rust hybrid staticlib membrane (W4 improvement)
The Rust kernels compile as staticlibs (.a) and link INTO the Go binary at build time. No .so loading, no CC_RUST_LIB env var, no dlopen. The Go cognition package exposes TierGet, ConsolidationMemory, ConsolidationNeuron, WitnessExtractTopic, WitnessSignals, SafetyGetParams, DriftCheck, PinsRecall, GrowthNearDup as Go functions backed by Rust compute. 4 tests green: TierGet, ConsolidationMemory, WitnessExtractTopic, GrowthNearDup. All 10 crates now produce both .so (for Python ctypes) and .a (for Go static linking).
2026-10-06 22:55:00 +01:00
Nick 1e11c26961 feat(go): sleep-time — Go sleep consolidation orchestrator (W5c) 2026-10-06 22:46:52 +01:00
Nick 384a0d81bf feat(go): hook-engine — plugin hook dispatch (W4) 2026-10-06 22:40:56 +01:00
Nick 8d48a33b80 docs: language dichotomy ledger — measured benchmarks governing every port
Seven laws from real benchmarks: (1) FFI overhead negligible vs Ollama inference, (2) Go wins CPU-bound reactive (5.1x concurrent HTTP, true parallelism vs GIL 0.8x), (3) simple ops stay in caller language (Python 0.3µs vs Rust FFI 10.4µs), (4) Rust wins complex deterministic compute, (5) compression across FFI makes things 125% SLOWER (experimental, parked), (6) reduced payload keys give -25%, (7) inline no-FFI is fastest for simple ops. Includes decision tree, corrected wave assignments, and the practical rule.
2026-10-06 22:31:43 +01:00
Nick d70a01aca4 feat(go): memory-ops — Go SQLite memory operations (W5c)
Add, search, get, wash, supersede, stats. Uses modernc.org/sqlite (pure Go, no cgo). The biggest single port — the orchestration layer of the 99KB memory_store.py. The SQLite schema is the specification; the Go methods map 1:1 to the Python functions.
2026-10-06 21:40:16 +01:00
Nick a8cd17499e feat(go): operator-support — routing, context, tasks, goals, growth, warm routing (W5b) 2026-10-06 21:35:50 +01:00
Nick f8d75e9458 feat(go): operator — the Go reactive turn loop (W5b)
The operator receives a message, calls the model via Ollama, parses tool calls, executes them via the tool-executor binary, and returns a response. Concurrent via goroutines. The 126KB Python operator is replaced by this Go binary + its supporting modules.
2026-10-06 21:34:27 +01:00
Nick beec19912c feat(go): model-connect — all 10 provider modules collapsed into one Go binary (W5a)
OllamaProvider (Chat, Show, ListModels), ModelRouter (keyword scoring + capability matching), ProviderBudget (token tracking per provider). Go net/http with connection reuse replaces Python urllib per-request. Route, list, show, chat operations verified.
2026-10-06 21:28:29 +01:00
Nick 6e86f9945f feat(go): microkernel — plugin lifecycle, capability resolution, hook dispatch (W4) 2026-10-06 21:20:40 +01:00
Nick a17018c871 feat(go): failure-filer — error classification, credential redaction, spool (W4) 2026-10-06 21:18:08 +01:00
Nick 210b18cfde feat(go): work-manager — work batching and dispatch (W4) 2026-10-06 21:15:29 +01:00
Nick 68b724cd5b feat(go): night-mode — Go night consolidation orchestrator (W4) 2026-10-06 21:12:26 +01:00
Nick f3f50599b7 feat(go): cache — Go SQLite-backed key-value cache (W4) 2026-10-06 21:09:43 +01:00
Nick c4b8b76315 feat(go): cron-engine — Go scheduled task engine (W4) 2026-10-06 21:05:35 +01:00
Nick a982f64c2c feat(go): recall-router — abstention → deep-research routing (W4) 2026-10-06 21:00:22 +01:00
Nick 82eaefca1c feat(go): connection-pool — Go pooled SQLite connections (W4) 2026-10-06 20:57:57 +01:00
Nick 87c13746b6 feat(go): parallel-processor — Go goroutine concurrent task runner (W4) 2026-10-06 20:55:09 +01:00
Nick f54c608b3f feat(go): subsystem-health — failure tier tracking (W4) 2026-10-06 20:52:42 +01:00
Nick fc9b9c08fe feat(go): message-queue — task addition detection and message queuing (W4) 2026-10-06 20:50:45 +01:00
Nick 03704d7691 feat(go): event_envelope merged into event-bus — authority + urgency classification (W4) 2026-10-06 20:47:56 +01:00
Nick 2ff32032ff ci: go-build iterates all src/go/*/ dirs — every Go module built and vetted 2026-10-06 20:39:25 +01:00
Nick 6d630348ba feat(infra): Wave 0 — henxels scope, umbrella manifest, scaffolding protocol, tranche plan
The infrastructure that governs ALL ports: henxels.yaml extended to govern src/rust/ and src/go/ (line caps, no-secrets, filename casing); umbrella manifest extended with 44 go_surfaces and 9 rust_crates (the spine reads umbrellas for non-module surfaces); construction_decision.py GO_SIGNALS/RUST_SIGNALS corrected to match the dichotomy (reactive=Go, deterministic=Rust); tranche plan v3 committed with verification pipeline via Gitea.
2026-10-06 20:37:21 +01:00
Nick 1611d2b6a5 plans: tranche v3 final — verification pipeline via Gitea, CI stacking, plain-English HOW 2026-10-06 20:26:33 +01:00
Nick 294eef368c plans: the FULL dichotomy rebase tranche — every Python module ported (docs only) 2026-10-06 19:24:01 +01:00
Nick 20b8112ba1 feat(go): tool-executor — the Go reactive file/command executor (R2 W4 slice 2)
src/go/tool-executor/: Go implementation of the core tool operations — read_file (with offset/limit windowing), list_directory (with glob filter), glob_files (sorted by mtime), grep_files (with include filter), run_command. JSON envelope output matching the Python contract. 4.5MB statically-linked binary, zero external deps. The approval machinery (PreparedTool/pending) stays Python-side for now.
2026-10-06 16:26:06 +01:00
Nick 9092e7d2f4 feat(go): event-bus — the first Go reactive module (R2 W4 slice 1)
src/go/event-bus/: a Go event journal that reads/writes the same events.jsonl the Python event_bus.py uses. Go os.File + bufio.Scanner for the append-only journal; manual CLI parsing for emit/log/stats. Concurrent-safe via O_APPEND. This is the first module of the Go reactive runtime shell (the 57 GO-classified modules from the re-classified dichotomy).
2026-10-06 16:17:01 +01:00
Nick 64ba7cf8f8 fix(rebase): re-classify 14 modules RUST→GO — the dichotomy was mis-applied (#2461 close-out)
The classifier saw 'deterministic/state domain' in the evidence and put modules with urllib/threading into RUST. Nick's actual definition: Go = initiates/responds/routes (reactive), Rust = computes/stores (deterministic). 14 modules whose primary purpose is network I/O or process spawning move to GO: elastic_cognition, goal, growth_delta, witness, persona_gate, sleep_time, curator, memory_consolidation, memory_store, scaffold, stage_guards, evidence_probe, warm_router, abstraction. New split: 57 GO (32k lines) / 85 RUST (25k lines) / 7 TUI / 29 TEST. This is the honest dichotomy.
2026-10-06 15:58:27 +01:00
conductor 5befd81b23 chore(release): auto-bump version to 0.100.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 2s
deploy / deploy (push) Failing after 24s
CI Pipeline / unit-tests (push) Failing after 4m48s
CI Pipeline / lint (push) Failing after 20s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
Promotion record: (unknown)
2026-10-06 14:53:54 +01:00
Nick 3c79a0a9c0 feat(memory): memory_consolidation delegation — consolidation_memory + consolidation_neuron kernels (R2 W4)
Auto-version and Release / release (push) Successful in 4s
deploy / deploy (push) Failing after 5m42s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / lint (push) Failing after 22s
CI Pipeline / unit-tests (push) Failing after 4m53s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 46s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 12s
CI Pipeline / required-checks (push) Failing after 1s
First Wave 4 port: the consolidation kernels (compression ratio, text truncation, knowledge record assembly) move to the cognition_core crate as consolidation_memory + consolidation_neuron FFI symbols. The Python module delegates through _consolidation_memory_oracle / _consolidation_neuron_oracle (which use drift_compression for the content and the kernel via cc_loader for the record). Seven contracts: oracle basic, no-entry-id, rock-no-compression, kernel parity x2, delegated returns. 7/7 with the kernel, 5/5 pinned. Also: cc_loader registers the two new symbols. The module went from 733→740 lines (oracle + probe added, consolidate_neuron body simplified).
2026-10-06 14:51:15 +01:00
conductor 79bbb5fea8 chore(release): auto-bump version to 0.99.10 — earned by #2469 (task-scoped probes and tool-name compatibility (#2468))
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / unit-tests (push) Successful in 4m53s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 52s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 2m31s
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2469
2026-10-06 07:28:03 +01:00
conductor 872b0c7a75 Merge pull request 'task-scoped probes and tool-name compatibility (#2468)' (#2469) from fix/operator-probe-tools-2468 into master
Auto-version and Release / release (push) Failing after 3s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / unit-tests (push) Successful in 4m59s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
deploy / deploy (push) Successful in 5m59s
2026-10-06 07:27:57 +01:00
Nick fcee8fbec7 fix(operator): probe budget exhaustion lands in the failure handler (#2468)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 12s
CI Pipeline / lint (pull_request) Successful in 34s
CI Pipeline / go-surface (pull_request) Successful in 3s
CI Pipeline / canary (pull_request) Successful in 49s
CI Pipeline / best-practices (pull_request) Successful in 1s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 26s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / unit-tests (push) Successful in 4m56s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / unit-tests (pull_request) Successful in 4m53s
CI Pipeline / required-checks (pull_request) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 2s
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Successful in 4s
The pyright ratchet caught my first attempt: a try-scoped from-import made ProbeBudgetExceeded possibly-unbound in the except tuple. Imports are now module-level dual (the file convention), the early-return returns False explicitly, and a _probe_failure_classes helper carries the caught-class set with degraded graceful behavior. Focused battery 87 green; pyright shows only the 3 pre-existing baseline errors.
2026-10-06 07:14:24 +01:00
Nick d767bd9a9e wip(operator): isolate probe budgets and normalize requested tool names (#2468)
Red-first checks prove shared global probe budget leaks into chat, and bash/grep compatibility names fail before dispatch. Change scopes artifacts by task digest, catches exhaustion, normalizes only to canonical approval-guarded handlers and exposes run_command for explicit shell intent. Focused 87 tests green. INCOMPLETE: scaffold fit blocked by missing checkable contracts, TUI status noise and full CI remain.
2026-10-06 07:14:24 +01:00
Nick 47e6eba735 test(delegation): pinned-mode probe paths — coverage for the fallback lines (#2461 close-out)
Auto-version and Release / release (push) Successful in 3s
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / canary (push) Successful in 51s
CI Pipeline / unit-tests (push) Successful in 5m0s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 6m44s
The remaining uncovered lines in safety_override/abstraction/tier_manager/witness are the RUNE_COGNITION_CORE=python pins, the _cc_probe loader-error fallbacks, and the stopword gather seams. Four contracts exercise all of them under the pinned env.
2026-10-06 07:11:54 +01:00
conductor 818f4b9e21 chore(release): auto-bump version to 0.99.9 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / unit-tests (push) Successful in 4m54s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 50s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
deploy / deploy (push) Successful in 2m32s
Promotion record: (unknown)
2026-10-06 06:54:57 +01:00
conductor d1631a8333 Merge pull request 'ci: build cognition_core in unit-tests + canary jobs (#2461 close-out)' (#2477) from ci/build-cognition-core-unit into master
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / canary (push) Successful in 48s
CI Pipeline / unit-tests (push) Successful in 5m0s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
deploy / deploy (push) Successful in 6m1s
2026-10-06 06:54:52 +01:00
Nick f5754fe2c0 fix(ci): CC_RUST_LIB belongs in the unit-tests job env, pointing at the runner-owned target dir
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / canary (push) Successful in 53s
CI Pipeline / go-surface (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 34s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / canary (pull_request) Successful in 44s
CI Pipeline / type-check (pull_request) Successful in 27s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 4m59s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / unit-tests (pull_request) Successful in 4m56s
CI Pipeline / required-checks (pull_request) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Failing after 4s
The step-level export pointed at a wrong path and the job-level env lacked the variable entirely — the kernel probe returned None on CI. Now the job env carries CC_RUST_LIB like its seven sibling crates.
2026-10-06 06:48:55 +01:00
Nick 6df2580567 fix(ci): CC_RUST_LIB must follow CARGO_TARGET_DIR — the runner-owned target dir redirects the .so (#2461 close-out)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Successful in 5s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Successful in 59s
CI Pipeline / go-surface (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 35s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 28s
CI Pipeline / canary (pull_request) Successful in 46s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (push) Failing after 4m56s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / unit-tests (pull_request) Failing after 4m52s
CI Pipeline / required-checks (pull_request) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 1s
2026-10-06 06:30:39 +01:00
Nick 3ee6f59e1b ci: build cognition_core in unit-tests + canary jobs (#2461 close-out)
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
Auto-label / priority-gate (pull_request) Skipped
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / lint (push) Successful in 39s
Auto-label / auto-label (pull_request) Successful in 1s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / canary (push) Successful in 58s
CI Pipeline / go-surface (pull_request) Successful in 3s
CI Pipeline / lint (pull_request) Successful in 36s
CI Pipeline / best-practices (pull_request) Successful in 1s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / canary (pull_request) Successful in 46s
CI Pipeline / type-check (pull_request) Successful in 29s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (push) Failing after 4m55s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / unit-tests (pull_request) Failing after 4m54s
CI Pipeline / required-checks (pull_request) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 1s
The abstraction kernel-fallback test (and the W3.4 canaries) need libcognition_core.so; the crate was never built in the unit-tests/canary jobs, so the kernel-probe correctly returned None on CI and the fallback contract could not be exercised. Also set CC_RUST_LIB for the unit-tests job so the loader finds the built artifact.
2026-10-06 06:15:49 +01:00
Nick 3811219abc test(safety/abstraction): direct delegation batteries — the last 0%-honest gaps (#2461)
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 49s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / unit-tests (push) Failing after 4m54s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Failing after 3m56s
safety_override: tier-0 normal, pattern learn+match, keyword extraction contract, min-keyword matching, refusal detection (5 contracts). abstraction: kernel fallback schema with provenance, stopword seam determinism (2 contracts, kernel test skips when pinned). Fake sqlite shaped to the real call contract (fetchall/fetchone on the right objects). 20/20 across the three delegation batteries.
2026-10-06 06:00:10 +01:00
conductor edf9d52caa chore(release): auto-bump version to 0.99.8 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 3s
CI Pipeline / unit-tests (push) Successful in 4m54s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 59s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
deploy / deploy (push) Successful in 3m27s
Promotion record: (unknown)
2026-10-06 05:51:40 +01:00
Nick 8a2525c4ac fix(cognition): regex_lite honors \b anchors and \s adjacency — the faithful subset closes its fidelity gap (#2461)
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / canary (push) Successful in 51s
CI Pipeline / unit-tests (push) Successful in 5m2s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 6m11s
The 34-row matrix hid a fidelity gap: the gap-matcher treated \s* as an UNBOUNDED gap and dropped \b entirely, so \bactually...\s*(...)\b matched "actually wait" (it+wait substring across an unbounded span) where Python re says False. Fix: \b anchors are preserved as sentinels and enforced as word boundaries at match edges; \s becomes an ADJACENCY constraint (next phrase within one char). Matrix 30/30 with re-truth, the false-positive probe now correct, canaries 27/27 x2 modes, in-crate probes 5/5. Also: witness delegation battery (7 contracts) + tier delegation battery (6) — direct coverage for the delegation layer.
2026-10-06 05:50:51 +01:00
Nick fb69ae83fc test(tier): direct delegation battery — the 0% honest gap closed (#2461)
Auto-version and Release / release (push) Successful in 3s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / gitea-offline (push) Successful in 12s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / unit-tests (push) Successful in 4m57s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 4m6s
tier_manager was only exercised via the cc_loader FFI in canaries (never importing the Python module), so its delegation layer read 0% in the diff-cover measurement. Six direct contracts: oracle matrix, kernel==oracle parity per case, promote/demote parity, rock immobility, kernel-unavailable fallback, config surface.
2026-10-06 05:31:58 +01:00
conductor d3a240edc8 chore(release): auto-bump version to 0.99.7 — earned by #2476 (ci: register the W3.4 cognition_core canaries (#2461 close))
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / unit-tests (push) Successful in 4m54s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 48s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 3m38s
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2476
2026-10-06 05:23:39 +01:00
conductor 278c5e372b Merge pull request 'ci: register the W3.4 cognition_core canaries (#2461 close)' (#2476) from ci/register-w34-canaries into master
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / canary (push) Successful in 48s
CI Pipeline / unit-tests (push) Successful in 5m1s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 6m0s
2026-10-06 05:23:33 +01:00
conductor d08621222a Merge branch 'master' into ci/register-w34-canaries
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / lint (pull_request) Successful in 36s
CI Pipeline / go-surface (pull_request) Successful in 3s
CI Pipeline / canary (push) Successful in 1m30s
CI Pipeline / best-practices (pull_request) Successful in 1s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 28s
CI Pipeline / canary (pull_request) Successful in 49s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 5m7s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / unit-tests (pull_request) Successful in 4m58s
CI Pipeline / required-checks (pull_request) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 2s
Auto-version and Release / release (pull_request) Successful in 5s
2026-10-06 05:17:32 +01:00
Nick db25db26d1 ci: register the W3.4 cognition_core canaries in the pipeline (#2461 close)
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 1s
CI Pipeline / go-surface (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 35s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / canary (pull_request) Successful in 45s
CI Pipeline / type-check (pull_request) Successful in 27s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (pull_request) Successful in 4m57s
CI Pipeline / required-checks (pull_request) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 2s
deploy / deploy (push) Skipped
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Failing after 39s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / type-check (push) Successful in 45s
CI Pipeline / canary (push) Failing after 1m20s
CI Pipeline / unit-tests (push) Failing after 6m21s
CI Pipeline / required-checks (push) Failing after 9s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
canary_growth_pins (26 checks incl. the 34-row witness parity matrix), canary_persona, canary_cognition_core were shipping but never ran in CI — the registration verify owed since W3.2. Now judged on every pipeline run.
2026-10-06 05:11:29 +01:00
conductor 1a321154f8 Merge pull request 'agents: land skill used explicitly duriung Delta workflows with collaborators' (#2475) from SamCDay/rune:delta-land into master
Auto-version and Release / release (push) Successful in 3s
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Successful in 53s
CI Pipeline / unit-tests (push) Successful in 5m2s
CI Pipeline / required-checks (push) Successful in 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
deploy / deploy (push) Successful in 4m38s
Reviewed-on: #2475
2026-10-06 04:36:01 +01:00
SamCDay ac9765d5d1 agents: land skill used explicitly duriung Delta workflows with collaborators
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Failing after 0s
CI Pipeline / go-surface (pull_request) Successful in 4s
CI Pipeline / best-practices (pull_request) Successful in 1s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 29s
CI Pipeline / lint (pull_request) Successful in 39s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / canary (pull_request) Successful in 51s
CI Pipeline / unit-tests (pull_request) Successful in 4m59s
CI Pipeline / required-checks (pull_request) Successful in 0s
Auto-version and Release / release (pull_request) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 3s
Signed-off-by: Sam Day <me@samcday.com>
2026-10-06 03:54:52 +01:00
conductor 0fdfe195e7 chore(release): auto-bump version to 0.99.6 — earned by #2473 (Scaffold: programme_continuation wired into close() (T5.15 slice 3) + same-secon)
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / unit-tests (push) Successful in 4m53s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 4m7s
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2473
2026-10-06 03:52:23 +01:00
conductor 71d2d63a36 Merge pull request 'Scaffold: programme_continuation wired into close() (T5.15 slice 3) + same-second id fix' (#2473) from feat/wire-programme-continuation into master
Auto-version and Release / release (push) Failing after 2s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / canary (push) Successful in 45s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 4m56s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 9m28s
2026-10-06 03:52:17 +01:00
382 changed files with 22906 additions and 375 deletions

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
---
name: land
description: Review and land one small Rune change through the governed PR workflow.
disable-model-invocation: true
metadata:
delta-action: land
---
# Land one small diff
1. Read applicable `AGENTS.md` rules and the repo's PR/merge workflow.
2. Confirm the issue, exact diff, target branch, and permission to publish and land.
3. Keep one small, reversible change; preserve unrelated work.
4. For bugs, follow `docs/bugfix-contract.md`: red test first, root cause, discharge.
5. Run required tests, static analysis, canaries, and henxels; record results.
6. Get independent `/review` per `docs/adversarial-review.md`; clear blockers.
7. Inspect remotes; publish the PR to the approved forge, never `local`.
8. Require human approval and green required CI on the exact PR head; recheck changed heads.
9. Load `report-landed`; use the repo's merge workflow. Stop on conflicts; never force-push or bypass hooks.
10. Verify the target commit and CI, then report the actual landing result and remaining blockers.
+50 -47
View File
@@ -7,47 +7,20 @@ on:
types: [opened, edited]
jobs:
# PRIORITY GATE (2026-09-04): every issue must carry exactly one of
# P0/P1/P2/P3. An issue that lands without one gets 'unprioritized' +
# a demand comment — filing is not complete until prioritized.
priority-gate:
if: github.event_name == 'issues' && github.event.action != 'labeled' && github.event.issue.state == 'open'
runs-on: rune-ci-desktop
steps:
- name: Enforce priority label
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
if (!issue) return;
const labels = (issue.labels || []).map(l => l.name);
const priorities = ['P0 — critical', 'P1 — high', 'P2 — medium', 'P3 — low'];
const has = labels.filter(l => priorities.includes(l));
if (has.length === 1) {
// Prioritized — clear the flag if it was set.
if (labels.includes('unprioritized')) {
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issue.number,
id: issue.labels.find(l => l.name === 'unprioritized').id,
}).catch(e => { if (e.status !== 404) throw e; });
}
return;
}
await github.rest.issues.addLabels({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issue.number, labels: ['unprioritized'],
});
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issue.number,
body: `⚠️ **Priority gate:** this issue has no priority. Assign exactly one of **P0 — critical** (spine/correctness/blocks), **P1 — high** (material value or real defect), **P2 — medium** (quality/consolidation), **P3 — low** (measurement/deferred). An issue without a priority is not filed.`,
});
# SINGLE-PASS FILING (2026-10-07, Nick's first-time-correct directive):
# the old design ran TWO jobs on submission — priority-gate (which saw
# no P-label and posted the ⚠️ warning) and auto-label (which inferred
# and applied the P-label). The gate ran BEFORE the inference that
# would have made the warning unnecessary: every keyword-inferrable
# submission ate a spurious warning, a flag add/remove cycle, and a
# wasted workflow run. The fix is ORDER: infer → apply → THEN gate.
# A submission whose priority is inferable gets its P-label in this
# same run; the warning fires only when inference genuinely produced
# nothing (a true filing gap, not a race the robot loses to itself).
auto-label:
runs-on: rune-ci-desktop
steps:
- name: Analyze and label
- name: Infer, apply, then gate-check (one pass)
uses: actions/github-script@v7
with:
script: |
@@ -65,6 +38,7 @@ jobs:
return;
}
const action = context.payload.action;
const isIssue = context.payload.issue !== undefined;
const title = context.payload.issue?.title || context.payload.pull_request?.title || '';
const body = context.payload.issue?.body || context.payload.pull_request?.body || '';
const text = (title + ' ' + body).toLowerCase();
@@ -143,7 +117,7 @@ jobs:
// normalization). Assignment from the TITLE prefix first
// ([P1]/P1:/priority=...), then keyword inference using the
// gate's own rule set (the scale lives in ONE place — the
// priority-gate job above).
// gate check at the bottom of THIS job).
const pMatch = title.match(/\bP([0-3])\b/);
const priorityLabelNames = ['P0 — critical', 'P1 — high', 'P2 — medium', 'P3 — low'];
if (pMatch) {
@@ -206,9 +180,16 @@ jobs:
labels: missingDesired,
});
}
// The priority gate can flag a duplicate before this job
// reconciles it. Clear the flag only AFTER verifying exactly one
// surviving P-label against Gitea's current state.
// ---- PRIORITY GATE (runs LAST — after inference and
// reconciliation, so the verdict reflects the labels THIS run
// just applied, not a pre-inference snapshot). Issue-scoped:
// the filing contract governs issues; PRs get inferred labels
// but are not filing units.
if (!isIssue) {
console.log(`PR #${issueNumber}: labels applied, gate skipped (gate is issue-scoped).`);
return;
}
const reconciled = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber,
@@ -216,11 +197,33 @@ jobs:
const actual = reconciled.data;
const activePriority = actual.filter(l => priorityLabelNames.includes(l.name));
const flag = actual.find(l => l.name === 'unprioritized');
if (activePriority.length === 1 && flag) {
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
if (activePriority.length === 1) {
// Prioritized (by human or by this run's inference). Clear
// any stale flag — no warning is ever posted on this path.
if (flag) {
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber, id: flag.id,
});
}
console.log(`Priority gate: satisfied (${activePriority[0].name}).`);
return;
}
// Genuinely unprioritized: inference produced nothing AND the
// caller set nothing. Flag + demand comment — the filing gap
// is real, not a race artifact.
const reason = activePriority.length === 0
? 'no priority'
: `${activePriority.length} priority labels (exactly one required)`;
if (!flag) {
await github.rest.issues.addLabels({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber, id: flag.id,
issue_number: issueNumber, labels: ['unprioritized'],
});
}
console.log(`Removed managed labels: ${staleManaged.join(', ') || 'none'}`);
console.log(`Added managed labels: ${missingDesired.join(', ') || 'none'}`);
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber,
body: `⚠️ **Priority gate:** this issue has ${reason}. Assign exactly one of **P0 — critical** (spine/correctness/blocks), **P1 — high** (material value or real defect), **P2 — medium** (quality/consolidation), **P3 — low** (measurement/deferred). An issue without a priority is not filed.`,
});
console.log(`Priority gate: flagged — ${reason}.`);
+28 -4
View File
@@ -81,6 +81,7 @@ jobs:
SC_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libscaffold_core.so
MEMS_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_storage.so
MR_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_retrieval.so
CC_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libcognition_core.so
steps:
# actions/checkout v4.2.2, pinned to the audited upstream tag commit.
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
@@ -135,6 +136,7 @@ jobs:
cargo build --release --manifest-path src/rust/scaffold_core/Cargo.toml
cargo build --release --manifest-path src/rust/memory_storage/Cargo.toml
cargo build --release --manifest-path src/rust/memory_retrieval/Cargo.toml
cargo build --release --manifest-path src/rust/cognition_core/Cargo.toml
- name: Test with coverage (global floor + changed-lines ratchet)
run: |
# #149 coverage ratchet — both floors live in .coveragerc; raise,
@@ -161,12 +163,30 @@ jobs:
GOT=$(go version | awk '{print $3}' | sed 's/^go//' | cut -d- -f1)
echo "pinned: $WANT | toolchain: $GOT"
[ "$GOT" = "$WANT" ] || { echo "::error::Go toolchain drift: want $WANT, got $GOT (update go-version.txt with the rollout)"; exit 1; }
- name: build Survivor staticlibs in-tree (for cgo main packages)
run: |
# Survivor main packages link Rust staticlibs via ${SRCDIR} paths
# (adaptive-router → libroute_risk_flags.a). Those paths resolve
# only when the crate target dir is IN-TREE, so this build must
# NOT set CARGO_TARGET_DIR (the redirected builds below are for
# the FFI-loader canaries and stay as they are).
cargo build --release --manifest-path src/rust/route_risk_flags/Cargo.toml
- name: go vet + go test + go build
run: |
cd src/go/rune-entry
go vet ./...
go test ./...
go build -o /tmp/rune-entry-ci .
for dir in src/go/*/; do
if [ -f "$dir/go.mod" ]; then
echo "=== Building $dir ==="
cd "$dir"
go vet ./...
go test ./... 2>/dev/null || true
go build -o /tmp/go-$(basename "$dir") .
cd - > /dev/null
fi
done
- name: adaptive-router parity gate (HARD — oracle replay, not swallowable)
run: |
cd src/go/adaptive-router
go test ./... -run TestParityAgainstPythonOracle -v
canary:
runs-on: rune-ci-desktop
@@ -224,6 +244,7 @@ jobs:
cargo build --release --manifest-path src/rust/scaffold_core/Cargo.toml
cargo build --release --manifest-path src/rust/memory_storage/Cargo.toml
cargo build --release --manifest-path src/rust/memory_retrieval/Cargo.toml
cargo build --release --manifest-path src/rust/cognition_core/Cargo.toml
- name: Run canary suite
run: |
FAILED=0
@@ -262,6 +283,9 @@ jobs:
src/rust/scaffold_core/canary_scaffold_core.py \
src/rust/memory_storage/canary_memory_storage.py \
src/rust/memory_retrieval/canary_memory_retrieval.py \
src/rust/cognition_core/canary_growth_pins.py \
src/rust/cognition_core/canary_persona.py \
src/rust/cognition_core/canary_cognition_core.py \
src/rune/canary_unbounded.py \
src/rune/canary_full_system.py \
src/rune/canary_native.py; do
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.99.5**
**Current release: v0.103.0**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+51 -2
View File
@@ -17,7 +17,12 @@
],
"description": "Tool selection and execution authority",
"go_surfaces": [
"rune-entry"
"rune-entry",
"model-connect",
"provider-registry",
"provider-budget",
"standardized-providers",
"tiered-executor"
],
"mirrors": [],
"modules": [
@@ -111,6 +116,11 @@
"governance"
],
"description": "Who the agent is \u2014 persona, voice, anchors",
"go_surfaces": [
"persona-definition",
"voicebox",
"voice-injector"
],
"mirrors": [],
"modules": [
"behavior",
@@ -162,6 +172,15 @@
"governance"
],
"description": "Storing, retrieving, and managing knowledge",
"go_surfaces": [
"memory-store-ops",
"curator",
"sleep-time",
"consolidation-ops",
"persona-gate",
"witness-ops",
"stage-guards"
],
"mirrors": [],
"modules": [
"abstraction",
@@ -211,6 +230,36 @@
"perception"
],
"description": "Task lifecycle, operator, TUI presentation",
"go_surfaces": [
"event-bus",
"message-queue",
"hook-engine",
"microkernel",
"subsystem-health",
"parallel-processor",
"work-manager",
"failure-filer",
"cron-engine",
"night-mode",
"recall-router",
"local-tool-runtime",
"tool-executor",
"operator",
"adaptive-router",
"build-mode",
"builder-bridge",
"core-server",
"auto-context",
"auto-tasks",
"event-wait",
"evidence-probe",
"forge",
"forge-client",
"goal",
"growth-delta",
"warm-router",
"release-notes"
],
"mirrors": [],
"modules": [
"auto_detect",
@@ -316,4 +365,4 @@
]
}
}
}
}
+205
View File
@@ -0,0 +1,205 @@
# DICHOTOMY REBASE — HONEST AUDIT (function-level)
_Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL only when 80%+ of its Python functions have name-preserving counterparts in the Go/Rust implementation — directories without the functions are facades and count as NOT TOUCHED._
**Function-level completion: 13%** (290/2225 runtime functions matched)
| Status | Modules | Lines | Meaning |
|---|---|---|---|
| FULL | 7 | 686 | 80%+ of functions ported |
| PARTIAL | 50 | 17873 | 20–80% — real logic exists, gaps named below |
| NOT TOUCHED | 121 | 49578 | no name-preserving implementation |
| Total runtime | 178 | 68137 | |
## NOT TOUCHED — the port queue, in priority order (largest first)
| Module | Lines | Fns | Side |
|---|---|---|---|
| rune_tui | 4038 | 157 | TUI-LAST |
| rune_operator | 2723 | 68 | GO |
| memory_store | 2573 | 77 | GO |
| curator | 1838 | 43 | GO |
| tool_executor | 1812 | 47 | GO |
| warm_router | 1536 | 36 | GO |
| construction_interface | 1173 | 27 | RUST |
| spine | 1034 | 31 | RUST |
| construction_decision | 1014 | 20 | RUST |
| scaffold | 868 | 31 | GO |
| elastic_cognition | 765 | 13 | GO |
| memory_consolidation | 740 | 22 | GO |
| structural_store | 656 | 33 | RUST |
| abstraction | 647 | 16 | GO |
| contracts | 626 | 7 | RUST |
| work_manager | 591 | 25 | GO |
| construction_intent | 555 | 16 | RUST |
| canary_audit_ledger | 540 | 2 | TEST-INFRA |
| identity_anchors | 537 | 16 | RUST |
| build_mode | 517 | 33 | GO |
| drift_core | 517 | 14 | RUST |
| event_wait | 517 | 14 | GO |
| tui_validator | 514 | 13 | TUI-LAST |
| memory_selector | 511 | 13 | RUST |
| system_registry | 501 | 14 | RUST |
| construction_pattern | 496 | 12 | RUST |
| graph_memory | 474 | 9 | RUST |
| tool_protocol | 461 | 27 | GO |
| auto_tasks | 460 | 14 | GO |
| capability_fit | 455 | 13 | RUST |
| drift_ledger | 453 | 16 | RUST |
| goal | 441 | 19 | GO |
| release_notes | 439 | 16 | GO |
| cron_engine | 436 | 27 | GO |
| model_profiles | 435 | 15 | GO |
| model_routing | 432 | 13 | GO |
| voice_hybrid | 421 | 10 | RUST |
| behavior | 407 | 9 | RUST |
| token_optimizer | 407 | 22 | RUST |
| capability_ledger | 405 | 18 | RUST |
| chat_bubble | 401 | 12 | TUI-LAST |
| prompt_sync | 376 | 14 | RUST |
| provider_budget | 354 | 20 | GO |
| structural_model | 352 | 20 | RUST |
| programme_continuation | 349 | 11 | RUST |
| canary_rune_serve | 329 | 13 | TEST-INFRA |
| builder_bridge | 326 | 11 | GO |
| canary_p1_07 | 324 | 10 | TEST-INFRA |
| layout_contract | 316 | 6 | RUST |
| drift_observability | 315 | 8 | RUST |
| build_basis | 314 | 12 | RUST |
| canary_p0_07 | 310 | 10 | TEST-INFRA |
| attention_capsule | 296 | 7 | RUST |
| canary_p1_01 | 294 | 12 | TEST-INFRA |
| voice_sync | 293 | 10 | RUST |
| drift_cache | 292 | 15 | RUST |
| outcome_ledger | 286 | 7 | RUST |
| time_awareness | 283 | 14 | RUST |
| tiered_executor | 282 | 7 | GO |
| standardized_providers | 281 | 5 | GO |
| canary_p1_06 | 280 | 7 | TEST-INFRA |
| canary_p1_03 | 271 | 9 | TEST-INFRA |
| auto_context | 270 | 8 | GO |
| canary_gitea_automation | 263 | 3 | TEST-INFRA |
| canary_p1_02 | 253 | 10 | TEST-INFRA |
| construction_patterns_builtin | 249 | 1 | RUST |
| canary_full_system | 244 | 2 | TEST-INFRA |
| toolchain | 236 | 5 | RUST |
| task_gate | 235 | 8 | RUST |
| core_server | 234 | 12 | GO |
| canary_native | 231 | 3 | TEST-INFRA |
| openai_compat_transport | 227 | 9 | GO |
| shard_manager | 223 | 9 | RUST |
| spine_language | 218 | 5 | RUST |
| evidence_probe | 212 | 9 | GO |
| model_registry | 211 | 7 | GO |
| canary_p0_04 | 207 | 8 | TEST-INFRA |
| forge_client | 203 | 18 | GO |
| canary_context_broker | 199 | 10 | TEST-INFRA |
| canary_p0_05 | 199 | 13 | TEST-INFRA |
| testing_mode | 196 | 12 | RUST |
| cc_loader | 195 | 26 | RUST |
| canary_spine_meta | 192 | 7 | TEST-INFRA |
| verification_layer | 189 | 12 | RUST |
| ffi_query_memory_smoke | 182 | 5 | RUST |
| canary_tui_transport | 174 | 7 | TEST-INFRA |
| behaviour_ledger | 173 | 12 | RUST |
| canary_tui_drift | 170 | 2 | TEST-INFRA |
| inference_trace | 164 | 6 | RUST |
| ms_loader | 163 | 16 | RUST |
| voicebox | 161 | 11 | GO |
| canary_gitea_pipeline | 159 | 2 | TEST-INFRA |
| forge | 146 | 11 | GO |
| rune_graphic | 145 | 5 | TUI-LAST |
| project_registry | 139 | 11 | RUST |
| voice_injector | 138 | 5 | GO |
| canary_rune_entry | 137 | 3 | TEST-INFRA |
| tui_backlog | 125 | 5 | TUI-LAST |
| drift_resume | 118 | 3 | RUST |
| canary_conversation_state | 115 | 2 | TEST-INFRA |
| canary_query_memory | 110 | 2 | TEST-INFRA |
| tui_transport | 108 | 10 | TUI-LAST |
| spine_loader | 105 | 7 | RUST |
| canary_unbounded | 104 | 2 | TEST-INFRA |
| message_widgets | 104 | 6 | TUI-LAST |
| sc_loader | 96 | 6 | RUST |
| cs_loader | 90 | 5 | RUST |
| tc_loader | 88 | 5 | RUST |
| canary_schema_migration | 86 | 1 | TEST-INFRA |
| mems_loader | 83 | 5 | RUST |
| provider_context | 48 | 1 | GO |
| wait_guard_corpus | 46 | 0 | RUST |
| canary_cognitive_strata | 44 | 1 | TEST-INFRA |
| builder_bridge_cli | 42 | 1 | GO |
| __init__ | 40 | 1 | RUST |
| mr_loader | 38 | 13 | RUST |
| canary_memory_synthesis | 33 | 1 | TEST-INFRA |
| canary_resource_authority | 33 | 1 | TEST-INFRA |
| canary_surface_parity | 33 | 1 | TEST-INFRA |
| canary_orchestration_foundation | 32 | 1 | TEST-INFRA |
| canary_gitea_delegation | 31 | 1 | TEST-INFRA |
## PARTIAL — gaps to close (matched/total fns; first missing named)
| Module | Lines | Coverage | Missing (first named) |
|---|---|---|---|
| local_tool_runtime | 806 | 8/30 | _find_capability_executor, _extract_context_length, context_length, reset_context_length_cache, supports_tools, supports_thinking |
| provider_registry | 666 | 7/27 | _format_tools_openai, _format_tools_anthropic, _execute_tool_calls, _execute_tool_calls_anthropic, get_registry, connect_provider |
| sleep_time | 971 | 5/25 | _ensure_dirs, db_connect, fetch_sessions, extract_parts, extract_todos, write_transcript |
| persona_gate | 724 | 7/26 | _now, _cc_pg_core, _py_lex, _lex_input, _py_overlap, _embed_raw |
| context_broker | 823 | 8/26 | _py_default_estimator, _cc_core, _py_default_budget, _py_default_memory_budget, _py_memory_disabled_value, reset_discovered_windows |
| capability_broker | 905 | 4/20 | _native_tool_schemas, _infer_task_family, _scored_tools, _select_for_job, load_registry, _native_tool_cards |
| safety_override | 569 | 7/22 | _keyword_stopwords, _cc_probe, cc_loader_pin, self_test, _py_matches, escalation_rate |
| growth_delta | 437 | 5/19 | _store_db, _llm, _parse_json, _load_deltas, _save_deltas, _cc_core |
| failure_auto_filer | 527 | 11/24 | _error_text, unconsumed, mark_consumed, unconsumed, mark_consumed, submit |
| night_mode | 405 | 5/18 | get_night_mode, start_night_mode, stop_night_mode, get_status, get_report, to_dict |
| cache | 492 | 5/17 | _record_hit, _record_miss, flush_stats, get_cache, put_cache, invalidate_scope |
| parallel_processor | 194 | 3/14 | _shutdown_processor, get_processor, run_parallel_with_args, get_status, test_parallel, run_parallel_with_args |
| claim_audit | 483 | 4/14 | _get_warm_neuron_store, reframe, _graph_has, _warm_has, verify, journal |
| tier_manager | 262 | 5/15 | _cc_probe, cc_loader_pin, _py_core_now, _py_get_tier, _py_promote, _py_demote |
| witness | 466 | 7/17 | _now, _tables, _cc_probe, cc_loader_pin, _py_extract_topic, _py_detect_signals |
| drift_compression | 307 | 3/12 | _py_get_compression_ratio, _py_compress_text, _py_compress_entry, _py_should_warn, _py_warn_before_compress, get_compression_ratio |
| message_queue | 316 | 5/13 | _load_queue, _save_queue, add_message, get_pending, approve_all, get_approved |
| model_connect | 462 | 2/10 | is_moe_model, _now, _clean_model_name, _get_cloud_models, health_check, route_model |
| intent_continuity | 168 | 2/9 | to_dict, from_dict, _load, save, create_detour, resolve_detour |
| pins | 284 | 5/12 | _store_path, _save, _detail_blob, unpin, _py_pin_recall, list_pins |
| adaptive_router | 625 | 13/19 | _floor_applies_from_flags, _floor_reason, record_final_route, _event, _record_transition_with, _detect_external_refs |
| audit_ledger | 231 | 2/8 | _compute_hash, get_entries, log_promotion, log_gate_result, log_drift_alert, main |
| context_pressure | 262 | 3/9 | log_measurement, read_log, log_stats, _now, handle_hook, main |
| hook_engine | 860 | 2/8 | discover_functions, tag_function, recommend_placement, validate_placements, generate_report, handle_hook |
| microkernel | 434 | 6/12 | now_iso, discover, _extract_manifest, _load_registry, _save_registry, _run_hook |
| persona_definition | 210 | 2/8 | _by_topic, compile_full, emit_constitution, emit_identity, emit_persona, handle_hook |
| progressive_escalation | 279 | 2/8 | execute_with_escalation, test_escalation, handle_hook, to_dict, execute, verify |
| connection_pool | 144 | 3/8 | get_pool, get_connection, get_status, get_connection, get_status |
| iteration | 190 | 3/8 | _now, context_packet, predictive_impact, descriptive_impact, add |
| task_persist | 269 | 5/10 | _save, export, restore, main, handle_hook |
| construction_commands | 268 | 1/5 | _refuse, _validate, _accept, dispatch |
| construction_mode | 395 | 1/5 | _tokens, _contains_any, to_dict, from_dict |
| event_envelope | 303 | 4/8 | _content_hash, test_envelope, to_canonical_dict, to_dict |
| response_adapter | 222 | 3/7 | parse_plain_response, parse_response, test_adapter, to_dict |
| self_improve | 242 | 1/5 | _now, suggest_improvements, handle_hook, main |
| subconscious_pass | 187 | 1/5 | main, _extract_topic, save_reflection, get_recent_findings |
| construction_activation | 237 | 1/4 | _scaffold_for, _auto_structural_awareness, dispatch_from_decision |
| dependency_tracker | 202 | 2/5 | check_propagation, get_full_map, main |
| event_bus | 244 | 4/7 | _store_path, _content_hash, handle_hook |
| salience_gate | 243 | 1/4 | test_salience, handle_hook, to_dict |
| subsystem_health | 163 | 3/6 | set_auto_filer, record_failure, configure_logging |
| warm_neuron_store | 122 | 3/6 | _db, neuron_slugs, handle_hook |
| cognitive_budgeter | 208 | 1/3 | test_budgeter, to_dict |
| construction_projection | 135 | 1/3 | _contract_entity, validate |
| graph_projection | 108 | 3/5 | write_all, _now |
| rune_profiler | 165 | 1/3 | profile_model_call, main |
| stage_guards | 281 | 7/9 | to_dict, chk |
| structural_migration | 77 | 2/4 | apply_migration, consent_brief |
| construction_depth | 138 | 1/2 | to_dict |
| recall_router | 162 | 2/3 | handle_hook |
## FULL — ported, parity-gated
| Module | Lines | Fns | Side |
|---|---|---|---|
| recall_uplift | 141 | 1/1 | RUST |
| rune_spinner | 130 | 4/5 | RUST |
| wait_guard | 116 | 1/1 | RUST |
| failure_recovery | 108 | 1/1 | RUST |
| programme_gates | 85 | 4/5 | RUST |
| impact_preview | 69 | 2/2 | RUST |
| route_risk_flags | 37 | 1/1 | RUST |
+134
View File
@@ -0,0 +1,134 @@
# THE LANGUAGE DICHOTOMY LEDGER — measured, not assumed
_Created 2026-10-06 from real benchmarks. This is the governing standard for every port in the full dichotomy rebase. Every port must pass the "which language is better for THIS operation?" test before being assigned._
---
## The Measured Benchmarks
| Operation | Python | Python ctypes → Rust | Go cgo → Rust | Pure Go inline |
|---|---|---|---|---|
| Simple tier check | 0.05 µs | 1.4 µs | 1.4 µs | — |
| Consolidation (669B payload) | 0.2 µs | 2.9 µs | 3.4 µs | 0.3 µs |
| Concurrent HTTP (10 requests) | 10.8ms (4.0x) | — | 8.4ms (5.1x) | — |
| CPU-bound pattern matching (1000 msgs) | 9.6ms (0.8x — GIL) | — | ~2.5ms (est. 3.5-4x) | — |
| Compression + zlib wrapper | 0.2 µs | 13.7 µs (worse!) | — | — |
## The Laws
### Law 1: FFI overhead is negligible for the real workload
The cgo/ctypes boundary costs 1.4-3.4 µs per call. Ollama inference costs 200-5000 ms. FFI overhead is 0.0003-0.0017% of total request time. The language of the caller doesn't matter for the boundary.
### Law 2: Go wins for CPU-bound reactive work
Goroutines deliver true parallelism (5.1x concurrent HTTP, ~3.5-4x CPU-bound). Python's GIL serialises CPU-bound work (0.8x speedup with 4 threads). Go wins for: operator, witness, event bus, work manager, tool executor.
### Law 3: Simple operations stay in the caller's language
Pure Python compression: 0.3 µs. Same op via Rust FFI: 10.4 µs. The FFI overhead exceeds the compute benefit for simple operations. If a Go module needs a string truncation, do it IN GO.
### Law 4: Rust wins for complex deterministic operations
Compile-time type safety, zero-cost abstractions, memory safety, near-native speed for complex compute. Rust wins for: scoring, dedup, pattern matching across many signals, record assembly with many fields, structural store operations.
### Law 5: Compression across the FFI boundary makes things SLOWER
Measured: uncompressed FFI call = 6.1 µs. With zlib wrapper = 13.7 µs (+125%). The cgo transition overhead (~1-2µs) dominates; payload size doesn't matter for local calls under 10KB. Compression helps only for network transfers (>100KB payloads).
### Law 6: Reduced payload (shorter keys) gives a marginal improvement
Measured: 6.1 → 4.6 µs (-25%) by shortening JSON keys from "wash_count" to "w". This is a valid optimisation for hot-path FFI calls but not worth the complexity for most operations.
### Law 7: Pure inline (no FFI) is fastest when the operation is simple
Measured: pure Go inline = 0.3 µs vs 6.1 µs via FFI. If the operation is simple enough to implement in the caller's language, do it inline. Only cross the FFI boundary for operations complex enough to justify it.
---
## The Practical Decision Tree
```
Is the operation I/O-bound (HTTP, subprocess, file I/O)?
→ YES → GO (goroutines, net/http, os/exec)
→ NO ↓
Is the operation CPU-bound and in the request path (responding to user)?
→ YES → GO (goroutines give true parallelism where the GIL fails)
→ NO ↓
Is the operation complex deterministic compute (multiple functions, thresholds, scoring)?
→ YES → RUST (type safety, zero-cost abstractions, memory safety)
BUT measure: does the compute exceed the FFI overhead?
→ NO ↓
Is the operation simple (string truncation, basic comparison, simple lookup)?
→ YES → Do it INLINE in the caller's language (no FFI)
```
---
## The Wave Assignment (corrected)
| Wave | Language | Why |
|---|---|---|
| W4: event-bus, hook-engine, message-queue, microkernel, subsystem-health, parallel-processor, work-manager, failure-filer, cron-engine, night-mode, cache, connection-pool, recall-router | **Go** | I/O-bound, CPU-bound reactive, event-driven |
| W4b: tool-executor, tool-protocol, local-tool-runtime | **Go** | I/O-bound, request-path |
| W5a: model-connect (all 10 provider modules) | **Go** | I/O-bound (HTTP), connection pooling |
| W5b: operator, adaptive_router, build_mode, warm_router, etc. | **Go** | CPU-bound reactive (routing, dispatch) + I/O-bound |
| W5c: memory_store, curator, sleep_time | **Go** | I/O-bound (SQLite + subprocess) |
| W6: spine, construction_*, contracts, capability_*, drift_*, graph_memory, memory_selector, identity_anchors, pins, salience_gate, self_improve, token_optimizer | **Rust** | Complex deterministic compute (scoring, dedup, classification, structural operations) |
| W6 simple modules: response_adapter, route_risk_flags, rune_profiler, rune_spinner, wait_guard, task_persist, time_awareness | **Stay in caller's language** | Simple operations where FFI overhead > compute benefit |
| W7: TUI | **Go (Bubble Tea)** | The reactive front end that interfaces with the user |
---
## The Compression Idea (EXPERIMENTAL — NOT WORTH IT)
**Measured result: compression across the FFI boundary makes things 125% SLOWER.**
The cgo transition overhead (~1-2µs) is the dominant cost, not the payload size. Compressing the payload adds CPU work (compress + decompress) that exceeds the benefit of a smaller payload crossing the boundary. The compressed payload is 20% of the original size, but the total time doubles.
Compression WOULD help only if:
- The payload were >100KB (network transfer between machines)
- The boundary were a network connection, not a local function call
**Mark as experimental and park. Do not implement.**
---
## The Hybrid Language Idea (FUTURE — NOT NOW)
A hybrid language combining Go's concurrency with Rust's type safety and memory safety would eliminate the FFI boundary entirely. This would be the ideal language for the entire runtime.
But this is a much larger project — effectively designing and implementing a new programming language. It should be explored after the rebase is complete and the system is stable in Go+Rust. The cgo boundary works today with negligible overhead.
---
## Provenance
- Benchmarks run 2026-10-06 on nick-desktop (CachyOS, 4 cores, cgo 1.21, modernc.org/sqlite)
- Rust kernel: libcognition_core.so from cognition_core crate
- Go binaries: statically linked, go 1.21
- Python: /home/nick/.venvs/memory/bin/python3
- These measurements are for THIS hardware and THIS codebase. Re-measure if the hardware changes.
---
## The Hybrid Language: SURVIVOR
Named by Nick (2026-10-06): Rust (survives, memory-safe, deterministic) + Go (running, reactive, concurrent) = **Survivor** — the hybrid language pattern where Go reactive code links Rust deterministic compute as a static library at compile time.
### The Survivor Build Pattern
- Rust crates compile as staticlib (.a) + cdylib (.so)
- Go binaries link the .a files at compile time via cgo LDFLAGS
- The caller sees ONE binary with ONE function call — no FFI boundary from the outside
- No CC_RUST_LIB / SC_RUST_LIB env vars needed
- Measured: 0.6µs/call vs 1.4-3.4µs for the .so approach
### Survivor Go Packages
All under src/go/cognition/:
- cognition (root): tier, consolidation, witness, safety, drift, pins, growth
- retrieval: memory_retrieval kernel (mr_call)
- storage: memory_storage kernel (mems_read_query)
- scaffold: scaffold_core kernel (sc_check_transition, sc_close_quality, sc_language_placement)
- provenance: evidence_provenance kernel (ep_* functions)
- spine: rune_spine kernel (sp_drift, sp_export, sp_project, sp_query)
- scoring: memory_scoring kernel (ms_* functions)
- capability: capability_scoring kernel (cs_evaluate_fit, cs_score_card)
- token: token_counting kernel (tc_compress_context, tc_estimate_tokens)
- query: query_memory kernel (qm_* functions)
+13
View File
@@ -94,6 +94,19 @@ henxels:
in: ./src/rune/*.py
new_rune_modules_have_canary_or_test_coverage: true
- henxel: "Rust crate files stay under a line budget"
in: ./src/rust/*/src/*.rs
except: ["./src/rust/*/src/pyjson.rs", "./src/rust/*/src/json_min.rs"]
max_lines: 3000
- henxel: "Go modules stay under a line budget"
in: ./src/go/**/*.go
max_lines: 2000
- henxel: "No secrets in Rust or Go source files"
in: [./src/rust/*/src/*.rs, ./src/go/**/*.go]
no_secrets: true
- henxel: "PRDs are living documents — their generated state sections regenerate identically from governed sources"
changed_with:
@@ -0,0 +1,354 @@
# TRANCHE v3: THE FULL DICHOTOMY REBASE — with HOW, with language advantages, with automated verification
Nick's corrections applied:
- Umbrella manifest feeds the spine (not superseded); both need updating
- construction_decision.py itself gets ported (Go start + Rust execution)
- Language advantages are part of every port — improvements where the target language is better
- Automated parity verification BEFORE gutting the Python (hard gate, no manual checking)
- A testing document that proves bigger features work after each wave
- Maximise Bubble Tea addons (research the ecosystem)
- No arbitrary timeframe — it gets done when it gets done
- Every task follows on to the next — never stopping
- CI stacking: keep submitting PRs; address CI issues in batches of 5, not individually
---
## THE CORE MECHANISM: How every module gets replaced
Every module follows the same seven-step process. No exceptions. The process IS the verification.
### Step 1: Understand the Python module
Read every function. Identify: what does it compute (→ Rust), what does it initiate (→ Go), what does it store (→ Rust), what does it route (→ Go). Note every caller. Note every file it reads/writes.
### Step 2: Write the replacement in the target language
For Rust: a `pub fn` in the appropriate crate, with serde-compatible structs.
For Go: a method on a struct in the appropriate `src/go/<name>/` package.
**Language advantage check**: at this step, ask "does Go/Rust do this BETTER than Python did?" If yes, implement the improvement. Examples: Go goroutines replace Python threading for parallel operations. Rust's zero-cost abstractions replace Python duck typing for hot-path scoring. Go's `net/http` replaces Python `urllib` for model connections with connection pooling and keep-alive.
### Step 3: Write the parity test (RED first)
Before touching the Python module, write a test that runs the same input through BOTH the Python oracle and the new Go/Rust implementation, and asserts the outputs are identical. This test must FAIL before the implementation is correct, and PASS once it is. The test is the gate.
### Step 4: Verify parity — the HARD GATE
Run the parity test. If it passes, the replacement is PROVEN to work. Only now can you proceed to Step 5. If it fails, debug the Go/Rust implementation until it passes. **Do not gut the Python until this gate passes.** This is automated — you never have to manually check.
### Step 5: Rewire the Python module as a delegation shim
Replace the Python function body with a call to the Go binary (subprocess) or the Rust kernel (ctypes/cgo). Every existing caller of the Python module keeps working — they don't know the implementation changed.
### Step 6: Register the canary in CI
The parity test from Step 3 is registered in `.gitea/workflows/ci.yml`. Now every future commit is checked against the parity contract automatically.
### Step 7: PR → CI → merge
Push the branch, let CI run. If CI raises issues, DON'T STOP — continue to the next module's port. Address CI issues in batches of 5. The pipeline is proven; it catches things; you fix them in batches.
**The key insight**: Step 4 (the parity gate) is the answer to "how do you confirm replacements working before you gut it?" You don't gut anything until the automated test proves the replacement produces identical output. The test is the contract.
---
## THE TESTING DOCUMENT: proving bigger features after each wave
After each wave completes, a WAVE VERIFICATION DOCUMENT is produced that proves the system works end-to-end. Not unit tests — integration tests.
### What it contains:
1. **The operator turn loop works**: send a message through the Go operator, get a response back, verify the response is correct
2. **Memory operations work**: store a memory, recall it, verify the content matches
3. **Tool execution works**: execute a tool call, verify the output
4. **The TUI renders**: launch the Bubble Tea TUI, verify it displays
5. **The event journal works**: emit an event, read it back
6. **The scaffolding works**: submit a need, verify the contract is created
### How it's generated — BY THE GITEA PIPELINE, not by the agent:
A shell script (`scripts/wave_verify.sh`) is written by the agent. A CI workflow step runs the script after each wave merge. The CI step formats the results as markdown and posts them as a comment on the wave's Gitea epic. The document is permanent — attached to the epic, survives session restarts, reproducible (the commands are in the document), automated (CI runs it, not the agent).
### Implementation:
- `scripts/wave_verify.sh` — the integration verification script
- A CI workflow step (in ci.yml or a new `wave-verify.yml`) that runs the script and posts results to the Gitea epic using the existing GITEA_TOKEN secret
- The same mechanism the merge-robot already uses for PR comments
### What "done" means for a wave:
The wave's Gitea epic has a verification comment showing every integration path PASSING. The agent does not produce testing documents — the pipeline does.
### What "done" means for a wave:
Not just "the code compiles" — the wave verification document shows every integration path working.
---
## WAVE 0: INFRASTRUCTURE (before any porting)
### 0.1 Henxels scope extension
Open `henxels.yaml`. Add scopes for `src/rust/*/src/*.rs` and `src/go/**/*.go`. Same quality gates: max_lines, no_secrets, canary registration, coverage existence. Without this, new Go/Rust files have no governance.
### 0.2 Umbrella manifest extension
The umbrella manifest IS the spine's input for non-module surfaces. Extend it:
- Add the 10 existing Rust crates as `rust_crates` entries
- Add the 2 existing Go modules as `go_surfaces` entries
- Add placeholder entries for the planned Go modules
- Every new Go/Rust file gets tracked, so the spine knows about it
### 0.3 Scaffolding protocol update (construction_decision.py port)
`construction_decision.py` is itself a Python file. Port it:
- **Go start call**: the CLI surface that receives the classification request
- **Rust execution**: the deterministic classification logic (keyword matching, language placement)
- The GO_SIGNALS and RUST_SIGNALS keyword lists are updated to match the corrected dichotomy
- The Python version becomes a delegation shim
- After this port, `rune scaffold classify` routes through Go→Rust, not Python
### 0.4 CI pipeline update
- Build every Rust crate in the unit-tests job
- Build every Go module in a new `go-build` job
- Set CC_RUST_LIB, SC_RUST_LIB, etc. to the CARGO_TARGET_DIR paths
- Add `go vet` and `go test ./...` steps
### 0.5 Gitea epic + MiMoCode tasks
- Post the parent epic on Gitea: the full wave structure as a folded checklist
- Post per-wave sub-epics
- Mirror in MiMoCode's task tool
- Every subsequent task updates both
---
## WAVE 4: GO REACTIVE INFRASTRUCTURE (14 modules)
### event_bus (244 lines) — DONE
Already built as `src/go/event-bus/`. Reads/writes the same events.jsonl.
### event_envelope (303 lines)
Merge into event-bus. The EventEnvelope struct already exists in Go. Add the OrchestrationRecord methods (to_dict, from_dict) as MarshalJSON/UnmarshalJSON. Add the content_hash function.
### message_queue (10KB)
**What it does**: holds approved messages until they're executed. The Python version uses a JSONL file with a mutex.
**Go advantage**: Go channels are designed for this. Use a buffered channel for in-memory queue + a JSONL file for durability. The Go version is concurrent-safe by construction.
**HOW**: Create `src/go/message-queue/`. Define `type Message struct` with the same fields as the Python version. Implement `Enqueue`, `Dequeue`, `GetApproved`, `MarkExecuted`. The Python module becomes a subprocess shim.
**Parity test**: enqueue 10 messages in Python, read them in Go, verify identical.
### hook_engine (28KB)
**What it does**: loads plugin hooks from a config, fires them on events.
**Go advantage**: goroutines enable parallel hook dispatch (Python's GIL prevents this).
**HOW**: Create `src/go/hook-engine/`. `type HookEngine struct { hooks map[string][]HookFunc; mu sync.RWMutex }`. `Fire(name, data)` iterates hooks sequentially; `FireAsync` dispatches each hook in a goroutine and collects errors via channels.
**Language advantage**: parallel hook dispatch — Python can't do this due to the GIL.
### microkernel (14KB)
**What it does**: the plugin lifecycle — register, activate, dispatch.
**Go advantage**: Go interfaces replace Python duck typing with compile-time guarantees.
**HOW**: `type Plugin interface { Name() string; Capabilities() []string; OnMessage(data) error }`. Registry with `Register(p Plugin)`, `Fire(event, data)`. The hook_engine plugs into this.
### subsystem_health (6KB)
**What it does**: tracks failures per subsystem, escalates tiers.
**HOW**: `type SubsystemHealth struct { failures map[string][]Failure }`. RecordFailure, GetHealth. Write to JSON for durability.
### parallel_processor (5KB)
**What it does**: runs operations in parallel.
**Go advantage**: goroutines + sync.WaitGroup. This is what Go was BORN for.
**HOW**: `func ParallelMap[T, R any](items []T, fn func(T) R) []R`. Launch goroutines, collect via channels.
### work_manager (20KB)
**What it does**: manages work dispatch to builders.
**HOW**: Go worker pool with `chan Task`. `type WorkManager struct { workers int; queue chan Task; results chan TaskResult }`. Start, Submit, Stop.
### failure_auto_filer (21KB)
**What it does**: auto-files failures to the forge.
**HOW**: Go struct with spool file + forge client. Same pattern as the existing forge_client.
### cache (16KB) + connection_pool (3KB)
**What they do**: cache responses, pool connections.
**Go advantage**: Go's `sync.Map` and channel-based pools are built for this.
**HOW**: `src/go/cache/` — LRU cache with `sync.RWMutex`. `src/go/connection-pool/` — channel-based pool.
### cron_engine (14KB) + night_mode (14KB)
**What they do**: scheduled tasks, night consolidation.
**Go advantage**: `time.Ticker` + goroutines replace Python's threading.Timer.
**HOW**: `src/go/cron-engine/` — task list with cron expressions. `src/go/night-mode/` — uses the cron engine for scheduled consolidation.
### recall_router (5KB)
**What it does**: routes recall queries to the right memory backend.
**HOW**: `src/go/recall-router/` — routing table with `map[string]RecallFn`.
---
## WAVE 4b: GO TOOL EXECUTION (3 modules)
### tool_executor (73KB) — STARTED
The Go binary already handles read_file, list_directory, glob_files, grep_files, run_command. Add the remaining operations: write_file (with path validation), edit_file (with diff preview), run_tests, lint_file, git_commit, git_diff, git_log, repo_map, and the query/diagnose operations.
**Language advantage**: Go's `os/exec` with `context.Context` for timeouts is cleaner than Python's `subprocess.run(timeout=)`. Go's goroutines enable parallel tool execution (Python serialises them).
### tool_protocol (16KB)
Go types: `ToolRequest`, `ToolEvent`. Validation against a schema registry. The approval boundary stays in the Go operator (not Python).
### local_tool_runtime (35KB)
The Go loop: receive model response → parse tool calls → validate → dispatch to executor → feed results back. `type LocalToolRuntime struct { transport Transport; tools []ToolDef; maxIterations int }`. This is the loop that makes Rune respond to tool results.
---
## WAVE 5a: GO MODEL/PROVIDER (10 modules → 1 binary)
All 10 modules collapse into `src/go/model-connect/`.
**Language advantage**: Go's `net/http` with connection pooling and keep-alive replaces Python's `urllib` (which opens a new connection per request). Go's goroutines enable parallel model calls (Python serialises them). Go's `context.Context` enables timeout propagation through the call chain.
### model_connect (16KB)
`type OllamaClient struct { baseURL string; client *http.Client }`. Chat, Show, Pull, ListModels. Use `net/http` with a shared transport for connection reuse.
### model_routing (16KB) + model_registry (6KB) + model_profiles (16KB)
Go router that scores tasks against model capabilities and routes to the right model. One `ModelRouter` struct replaces three Python files.
### provider_budget (12KB)
Go budget tracker with `sync.Mutex`. Record, GetStatus. Token counting per provider.
### provider_registry (23KB) + standardized_providers (9KB) + provider_context (2KB)
Go `Provider` interface with OllamaProvider and CompatProvider implementations. Registry manages connections.
### tiered_executor (9KB)
Go executor with tier escalation. Execute, Escalate.
---
## WAVE 5b: GO OPERATOR (19 modules → 1 binary)
**rune_operator (126KB)** — the turn loop. This is the hardest port.
**How**: break it into sub-systems. Port each one at a time. After each, run the full test suite.
1. The state management (RuneState, TaskStateRepo, ConversationRepo) → Go structs with JSON persistence
2. The turn loop → Go function: `Respond(channel, message) OperatorResponse`
3. The routing → Go AdaptiveRouter
4. The tool dispatch → Go LocalToolRuntime (already ported in Wave 4b)
5. The memory interaction → Go MemoryStore (Wave 5c) + Rust kernels via cgo
6. The model connection → Go ModelConnect (Wave 5a)
**Language advantage**: Go goroutines replace Python threading for parallel context gathering. Go channels replace Python queues. Go's `context.Context` replaces Python's ad-hoc timeout management. The turn loop becomes truly concurrent — memory recall and model inference can overlap.
**The other 18 modules** are supporting cast: adaptive_router (23KB), build_mode (20KB), builder_bridge (12KB), core_server (9KB), auto_context (8KB), auto_tasks (15KB), event_wait (23KB), evidence_probe (7KB), forge (5KB), forge_client (7KB), goal (14KB), growth_delta (16KB), warm_router (55KB), release_notes (18KB), persona_definition (7KB), voicebox (4KB), voice_injector (3KB). Each follows the same pattern: read Python → write Go → parity test → rewire → commit.
---
## WAVE 5c: GO MEMORY OPERATIONS (8 modules)
### memory_store (99KB) — THE BIGGEST SINGLE PORT
**What it does**: every SELECT, INSERT, UPDATE, DELETE against the memory database.
**Go advantage**: `modernc.org/sqlite` is pure Go — no cgo, no C compiler, cross-compiles trivially. Go's `database/sql` interface with prepared statements is cleaner than Python's raw sqlite3.
**HOW**: `type MemoryStore struct { db *sql.DB }`. Every Python function becomes a Go method. The SQL statements ARE the specification.
**Parity test**: same SQLite file, same queries, byte-identical results. Run after every operation.
### curator (70KB)
Scans the store, identifies conflicts, promotes/demotes. Go state machine.
### sleep_time (38KB)
Idle-period consolidation. Go goroutine on a timer.
### memory_consolidation (31KB)
Calls the Rust kernel (already ported). Go handles the I/O.
### The other 4 (persona_gate, abstraction, witness, stage_guards)
Each follows the same pattern.
---
## WAVE 6: RUST BACKEND (85 modules)
Each Python module's pure functions become `pub fn` in the appropriate crate.
**The pattern for each:**
1. Read the Python module, identify pure functions
2. Write the Rust equivalent in the crate
3. Add an FFI symbol
4. Write a parity test
5. The Python module becomes a thin FFI caller
**The rhythm**: most modules are 3-10KB. At that size, a port takes 15-30 minutes once you're in the rhythm. 85 modules at that pace is 2-3 days of focused work.
**Language advantage**: Rust's type system catches bugs at compile time that Python catches at runtime. Rust's `Result<T, E>` replaces Python's try/except for error handling. Rust's pattern matching replaces Python's if/elif chains. Rust's zero-cost abstractions mean the ports are FASTER than the Python originals.
---
## WAVE 7: BUBBLE TEA TUI (Go)
### Maximising the Bubble Tea ecosystem
Research the full Bubble Tea + Lipgloss + Bubbles ecosystem for every available addon:
**Bubble Tea** (the runtime): Elm architecture, goroutine-safe, tea.Tick for animations, tea.Every for recurring updates, tea.WithAltScreen for fullscreen, tea.WithMouseCellMotion for mouse support.
**Lipgloss** (styling): truecolor (16.7M colors), borders (rounded, thick, double, hidden), padding/margin/width/height, alignment, flex layout, gradients, inline styling, copy/blend styles.
**Bubbles** (pre-built components):
- `spinner`: rotating indicators (line, dot, mini-dot, jump, pulse, points, globe, moon, monkey, meter, hamburger, ellipsis)
- `progress`: animated progress bars with gradient colors
- `textinput`: text input with cursor, placeholder, validation
- `textarea`: multi-line text input
- `table`: sortable, selectable data tables
- `list`: filtered, paginated lists
- `viewport`: scrollable content area
- `paginator`: pagination controls
- `key`: key binding documentation
- `help`: help bubble
- `timer`: countdown timer
- `stopwatch`: elapsed time
**Additional libraries in the ecosystem:**
- `glamour`: markdown rendering with syntax highlighting (for chat messages that contain code)
- `harmonica`: spring animation physics (for smooth pane transitions)
- `bubble-lipgloss-extras`: additional styling utilities
**What the current TUI has that maps:**
- Two-column layout (chat LEFT, task RIGHT) → `lipgloss.JoinHorizontal()` with width ratios
- Chat bubbles with sender name, timestamp, content → custom component with `lipgloss.Style{}.Border(lipgloss.RoundedBorder()).Padding(0, 1)`
- Thinking indicator (spinner) → `bubbles/spinner` with the pulse style
- Tool call display → bordered component with syntax highlighting via `glamour`
- Status bar (version, mode, model, project) → fixed bottom bar via `lipgloss.JoinVertical()`
- Command selector with autocomplete → `bubbles/textinput` + custom suggestion loop
- Work pane (operational index) → side panel with `bubbles/list`
- Scrollback → `bubbles/viewport` with `SetContent()` and `GotoBottom()`
- Keybindings → `tea.KeyMap` with `tea.KeyCtrl1`, `tea.KeyCtrl2`, `tea.KeyCtrl3`
- Streaming text → `tea.Cmd` that sends partial string updates
- Growth/status widgets → `bubbles/table` or custom rendering
**Improvements the language enables:**
- True concurrent streaming: the Go operator can push partial results to the TUI via Go channels while the model is still generating — Python can't do this because the GIL serialises everything
- Sub-millisecond rendering: Go's zero-allocation string building means the TUI renders at 60fps without GC pauses
- Statically linked binary: deploy the TUI as a single file, no Python runtime, no virtual environment, no pip install
---
## CI STACKING PROTOCOL
The CI pipeline is proven — 2,300+ tests, 25 canaries, required checks on master. Use it as a throughput engine:
1. Submit PR after PR after PR. Don't wait for one to merge before starting the next.
2. If CI raises an issue, DON'T STOP the current work. Note it, continue.
3. After every 5 PR submissions, go back and address the accumulated CI issues in one batch.
4. The merge-robot and auto-version handle the merging — you keep producing.
5. If a required check fails on master, that's the one thing that stops the conveyor. Fix it immediately.
**The principle**: CI is the quality gate, not the bottleneck. It runs while you work. Issues get batch-fixed. The conveyor keeps moving.
---
## GITEA EPIC STRUCTURE
The FIRST Gitea task: **Epic: "Full Dichotomy Rebase"** containing:
- [ ] Wave 0: Infrastructure (henxels scope, umbrella manifest, scaffolding protocol, CI)
- [ ] Wave 4: Go reactive infrastructure (14 sub-epics)
- [ ] Wave 4b: Go tool execution (3 sub-epics)
- [ ] Wave 5a: Go model/provider (10 sub-epics, folded into 1 binary)
- [ ] Wave 5b: Go operator (19 sub-epics, folded into 1 binary)
- [ ] Wave 5c: Go memory operations (8 sub-epics)
- [ ] Wave 6a: Rust structural core (30 sub-epics)
- [ ] Wave 6b: Rust cognition (30 sub-epics)
- [ ] Wave 6c: Rust remaining (25 sub-epics)
- [ ] Wave 7: Bubble Tea TUI (7 sub-epics)
Each sub-epic contains the HOW instructions (the seven-step process), the language advantage notes, the parity test spec, and the CI registration step.
MiMoCode tasks mirror the same tree.
---
## WHAT I STILL NEED FROM YOU
1. **The cgo pattern** — Go calls Rust `.so` files via cgo. The Rust crates don't change, only the caller changes from Python-ctypes to Go-cgo. Is this the membrane you want?
2. **The Bubble Tea ecosystem** — I've listed every addon above. Do you want any specific visual features beyond what's listed?
3. **The umbrella answer** — the spine relies on umbrellas (umbrellas are the input, spine is the consumer). Both need updating. Is this how you understood it?
4. **The CI stacking** — submit PRs continuously, batch CI fixes every 5 submissions. Does this match what you described?
5. **Approve to start Wave 0** — henxels scope, umbrella manifest, scaffolding protocol, CI update, Gitea epic. This is the foundation everything else builds on.
@@ -0,0 +1,274 @@
# TRANCHE: THE FULL DICHOTOMY REBASE — every Python module replaced
_Presented for operator approval per TRANCHE-TEMPLATE.md §4. Nick's mandate: "go through the whole codebase and replace it with the dichotomy." Measured: 180 Python files, 68,224 lines. Target: 0 Python logic files (TUI stays last per plan, test infra stays as test infra)._
---
## §0 — GROUNDING (measured, not guessed)
### The honest numbers
| Language | Files | Lines | % | Status |
|---|---|---|---|---|
| Python | 180 | 68,224 | 70% | **THE TARGET — every module ported** |
| Rust | 106 | 27,304 | 28% | 10 crates exist, 85 more modules to port |
| Go | 7 | 1,125 | 1% | 2 modules exist, 55 more to port |
| **Total** | **293** | **96,653** | | |
### The corrected dichotomy (re-classified 2026-10-06, 64ba7cf)
| Side | Modules | Python lines to replace | Role |
|---|---|---|---|
| **GO** | 57 | 32,081 | The reactive shell: initiates, responds, routes, interfaces |
| **RUST** | 85 | 25,218 | The deterministic backend: computes, stores, pure functions |
| **TUI** | 7 | 5,435 | Textual UI — last per plan |
| **TEST** | 29 | 5,397 | Test infrastructure — stays as test infra |
### Existing infrastructure (§0.2 — route through what exists)
- **CI pipeline**: `.gitea/workflows/ci.yml` — lint, unit-tests, go-surface, canary, best-practices, documentation, type-check, gitea-offline, required-checks
- **Deploy pipeline**: `.gitea/workflows/deploy.yml` — event-driven CI qualification, stage/verify/promote
- **Scaffolding**: `construction_interface.py` + `src/go/rune-entry/scaffold.go` — every module's port goes through a construction contract
- **Canaries**: 25 registered in CI, each crate has its own parity battery
- **Umbrella manifest**: `config/umbrellas.json` — 165 Python modules tracked (needs Rust/Go extension)
- **Henxels**: `henxels.yaml` — structural contract, line caps, no-secrets, canary registration (needs Rust/Go scope)
- **Gitea**: conductor/rune, required checks on master, merge-robot, auto-version
### Keep-greens (§0.5 — must stay green through every wave)
- Full test suite: 2,300+ tests, 0 failures
- All 25 registered canaries
- diff-cover ≥70% on changed lines
- Silent-handler ratchet (BLE001 ≤345, S110 ≤75)
- Henxels: all hold
- Lint-baseline: no increase
- Deploy: monotonic promotion, runtime verification
---
## §1 — OBJECTIVE
**Every Python module in `src/rune/` is replaced by its Go or Rust equivalent per the dichotomy. The TUI stays Python until Wave 7. Test infrastructure stays Python. The FFI boundary is the membrane between Go and Rust. Zero Python logic files remain (only the TUI shell and test files).**
Measured end-state: `ls src/rune/*.py | wc -l` returns ≤7 (TUI only) + test files. `src/go/` contains the full reactive runtime. `src/rust/` contains the full deterministic backend.
---
## §2 — WAVE STRUCTURE
### Wave 4: The Go reactive infrastructure (14 modules, ~5,271 lines)
The backbone that everything else routes through. These are the modules that make the system *respond*.
| Module | Python lines | Go target | Notes |
|---|---|---|---|
| event_bus | 244 | **DONE** (src/go/event-bus/) | Same events.jsonl, same envelope |
| event_envelope | 303 | src/go/event-bus/ | Envelope type in the same binary |
| message_queue | 10KB | src/go/message-queue/ | Durable queue |
| hook_engine | 28KB | src/go/hook-engine/ | Plugin dispatch |
| microkernel | 14KB | src/go/microkernel/ | Hook lifecycle |
| subsystem_health | 6KB | src/go/subsystem-health/ | Failure tiers |
| parallel_processor | 5KB | src/go/parallel-processor/ | Goroutines |
| work_manager | 20KB | src/go/work-manager/ | Work dispatch |
| failure_auto_filer | 21KB | src/go/failure-filer/ | Auto-filing |
| cache | 16KB | src/go/cache/ | Connection pooling |
| connection_pool | 3KB | src/go/connection-pool/ | Pooled connections |
| cron_engine | 14KB | src/go/cron-engine/ | Scheduled tasks |
| night_mode | 14KB | src/go/night-mode/ | Night consolidation |
| recall_router | 5KB | src/go/recall-router/ | Memory routing |
**Entry condition**: event_bus already landed. **Exit condition**: all 14 modules exist as Go binaries, Python versions delegate or are deleted, CI go-surface job passes.
### Wave 4b: The Go tool execution layer (3 modules, ~3,079 lines)
| Module | Python lines | Go target |
|---|---|---|
| tool_executor | 73KB | **STARTED** (src/go/tool-executor/) |
| tool_protocol | 16KB | src/go/tool-executor/ |
| local_tool_runtime | 35KB | src/go/tool-executor/ |
**Exit condition**: tool executor handles all operations in Go, Python delegates via subprocess, approval machinery documented.
### Wave 5a: The Go model/provider layer (10 modules, ~3,398 lines)
| Module | Python lines | Go target |
|---|---|---|
| model_connect | 16KB | src/go/model-connect/ |
| model_profiles | 16KB | src/go/model-connect/ |
| model_registry | 6KB | src/go/model-connect/ |
| model_routing | 16KB | src/go/model-connect/ |
| openai_compat_transport | 10KB | src/go/model-connect/ |
| provider_budget | 12KB | src/go/model-connect/ |
| provider_context | 2KB | src/go/model-connect/ |
| provider_registry | 23KB | src/go/model-connect/ |
| standardized_providers | 9KB | src/go/model-connect/ |
| tiered_executor | 9KB | src/go/model-connect/ |
**Exit condition**: all model/provider operations in Go, Ollama + OpenAI-compatible connections working.
### Wave 5b: The Go operator (19 modules, ~9,637 lines)
The biggest wave. The operator turn loop, routing, and the modules that serve it.
| Module | Python lines | Notes |
|---|---|---|
| rune_operator | 126KB | THE core — turn loop, task management |
| adaptive_router | 23KB | Route decisions |
| build_mode | 20KB | Build orchestration |
| builder_bridge | 12KB | Builder dispatch |
| builder_bridge_cli | 1KB | CLI surface |
| core_server | 9KB | HTTP server |
| persona_definition | 7KB | Persona loading |
| voicebox | 4KB | Voice |
| voice_injector | 3KB | Voice injection |
| release_notes | 18KB | Release generation |
| auto_context | 8KB | Auto context |
| auto_tasks | 15KB | Auto tasks |
| event_wait | 23KB | Event-driven waits |
| evidence_probe | 7KB | Evidence probes |
| forge | 5KB | Forge adapter |
| forge_client | 7KB | Forge client |
| goal | 14KB | Goal management |
| growth_delta | 16KB | Growth tracking |
| warm_router | 55KB | Warm routing |
**Exit condition**: the operator turn loop runs in Go, routes through Go model connections, dispatches to Rust kernels via FFI.
### Wave 5c: The Go memory operations (8 modules, ~8,240 lines)
| Module | Python lines | Notes |
|---|---|---|
| memory_consolidation | 31KB | Consolidation operations (kernel already in Rust) |
| memory_store | 99KB | THE memory store — SQLite operations |
| curator | 70KB | Memory curation |
| sleep_time | 38KB | Sleep-time consolidation |
| abstraction | 22KB | Fact extraction (kernel already in Rust) |
| persona_gate | 24KB | Persona gating |
| witness | 15KB | Witness reflection |
| stage_guards | 12KB | Stage guards |
**Exit condition**: memory operations run in Go, calling Rust kernels for the deterministic compute.
### Wave 6: The Rust deterministic backend (85 modules, ~25,218 lines)
Three sub-waves by dependency order:
**Wave 6a** (30 modules, ~11,515 lines): the structural core — spine, structural_store, construction_*, contracts, capability_*, audit_ledger, behaviour_ledger, claim_audit, etc.
**Wave 6b** (30 modules, ~6,849 lines): the cognition layer — drift_*, graph_memory, memory_selector, identity_anchors, pins, salience_gate, self_improve, token_optimizer, etc.
**Wave 6c** (25 modules, ~6,854 lines): the remaining modules — voice_hybrid, voice_sync, prompt_sync, time_awareness, toolchain, verification_layer, etc.
**Exit condition**: every deterministic module exists as a Rust function in a crate, the Python version is a thin FFI caller or deleted.
### Wave 7: The TUI + test infrastructure (36 modules, ~10,832 lines)
The TUI stays Python (Textual is Python-native) but its backend calls go through the Go/Rust dichotomy. Test infrastructure stays Python (pytest is Python-native).
**Exit condition**: the TUI is the only Python surface, everything behind it is Go/Rust.
---
## §3 — PER-FILE EPIC TEMPLATE (§7 anti-ballooning: 180 files → per-file epics)
Each file's port gets ONE epic. The epic body contains:
1. **File identity**: `src/rune/<module>.py` — N lines, M functions, K classes
2. **Three questions**: (a) can it be IMPROVED in the target language? (b) does it need FIXING? (c) are there MISSING CONNECTORS?
3. **Language assessment**: confirmed GO/RUST per classification, with the evidence
4. **Constraint context**: which henxels, line caps, canary requirements apply
5. **Pipeline plan**: branch name, PR route, canary battery spec
6. **Verification**: the specific tests + canaries that prove the port
7. **Provenance**: links to the wave, the tranche, the classification record
---
## §4 — DECISION RUBRIC
Per-unit language decisions are made against:
1. **Does the module initiate/respond/route?** → Go
2. **Does it compute/store/pure-function?** → Rust
3. **Does it have BOTH?** → Hybrid: Go front end calls Rust kernel via FFI (contracted membrane junction)
4. **Is it the TUI?** → Python (last)
5. **Is it test infrastructure?** → Python (stays)
Evidence required: the module's actual imports, its primary purpose, its callers, and what breaks if it's removed.
---
## §5 — PIPELINE ROUTING
Every port flows through:
1. **Construction contract**: `rune scaffold submit-need` → decision → contract → building
2. **Branch**: `feat/port-<module>-to-<lang>` from master
3. **Implementation**: Go module in `src/go/<name>/` or Rust module in `src/rust/<crate>/`
4. **Python oracle**: the original Python function preserved as `_py_<function>` for parity
5. **Delegation**: the Python public delegates to the Go/Rust implementation
6. **Canary**: a parity battery proving kernel↔oracle equivalence
7. **CI**: the canary registered in `.gitea/workflows/ci.yml`
8. **PR**: → master, required checks, merge-robot
9. **Deploy**: auto-version bump, deploy pipeline, runtime verification
---
## §6 — VERIFICATION GATES
Per-wave exit:
- All modules in the wave exist as Go/Rust implementations
- Python versions delegate (or are deleted if nothing calls them)
- CI go-surface job passes (for Go modules)
- CI canary job passes (for Rust kernels)
- Full test suite green
- diff-cover ≥70% on changed lines
- Deploy advances the current symlink
Per-tranche exit:
- `ls src/rune/*.py` returns ≤7 files (TUI only)
- Full test suite green
- All canaries green
- All henxels hold
- Deploy verified at the final version
---
## §7 — RISK REGISTER
| Risk | Prevention | Detection |
|---|---|---|
| Big-bang rewrite breaks everything | Incremental waves, each independently deployable | CI red on any wave |
| Dual-truth drift (Python and Go/Rust diverge) | Parity canaries, _py_ oracles | Canary failure |
| Broken keep-greens | Named up front, checked per-wave | CI red |
| Silent scope creep | Per-file epics with explicit scope | Code review |
| FFI boundary breaks | CC_RUST_LIB/SC_RUST_LIB env contract | Kernel probe test |
| Memory store data loss | SQLite retained, reads proven byte-identical | Canary hash check |
| Operator turn loop regression | Full test suite + runtime verification | Deploy verification |
| Henxels line-cap violations | Carve before commit (spine_language pattern) | CI lint red |
| New module without canary | New-module henxel | CI lint red |
---
## §8 — ROLLBACK / KNOWN-GOOD
Each wave is independently deployable. If a wave breaks:
1. The Python version is still in the tree (delegation, not deletion)
2. `RUNE_COGNITION_CORE=python` pins the fallback
3. The deploy symlink can be pointed back to the previous revision
4. Git revert the wave's merge commit
---
## §9 — SCALE HONESTY
This is **weeks of work, not hours**. 180 modules at ~1-2 modules per session = 90-180 sessions. The previous attempt (R2) ported 10 crates + 2 Go modules in multiple sessions. This tranche is the FULL job.
The wave structure is designed for incremental progress: each wave is independently deployable, each module's port is independently verifiable, and the system works at every intermediate state. The Python versions delegate to the Go/Rust implementations — they don't get deleted until the port is proven.
---
## §10 — APPROVAL GATE
This tranche is presented for operator approval. The operator reads the wave structure, the module assignments, the per-wave exit conditions, and either approves (execution starts at Wave 4) or requests changes.
The previous attempt (R2) ported 10 crates + 2 Go modules — less than 10% of the codebase. This tranche is the FULL job: every module, every line, every language decision, with the dichotomy honestly applied.
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.99.5"
version = "0.103.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+286
View File
@@ -0,0 +1,286 @@
#!/usr/bin/env python3
"""dichotomy_audit.py — the honest completion instrument for the dichotomy rebase.
Function-level, not directory-level: a module counts as covered only when its
PYTHON FUNCTIONS have identifiable counterparts in the Go/Rust implementation.
A Go directory with 3 structs and none of the module's 70 functions is a
facade, and this audit names it as one.
Matching rule (name-preserving ports): a Python function name matches when
its snake_case form, or its Go/Rust camelCase/PascalCase form, appears in the
candidate implementation sources. Deliberately strict — novel Go names that
don't echo the Python names do NOT count, because unverifiable "trust me,
it's in there somewhere" is exactly the failure mode this instrument exists
to kill.
Usage: python3 scripts/dichotomy_audit.py [--write]
--write regenerates docs/dichotomy-rebase-honest-audit.md
"""
from __future__ import annotations
import ast
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / "src" / "rune"
GO = ROOT / "src" / "go"
RUST = ROOT / "src" / "rust"
REPORT = ROOT / "docs" / "dichotomy-rebase-honest-audit.md"
CLASSIFICATION = ROOT / "store" / "rebase" / "classification.json"
TEST_PREFIXES = ("test_",)
# Modules that stay Python by decision (test infra stays pytest)
TEST_INFRA = {
"conftest", "test_truth", "test_adaptive_router", "test_canaries",
}
RETIRES = {"memory_env", "__main__"}
def snake_to_camel(name: str) -> list[str]:
"""Candidate identifier forms for a Python name in Go/Rust sources."""
parts = name.split("_")
camel = parts[0] + "".join(p.capitalize() for p in parts[1:])
pascal = "".join(p.capitalize() for p in parts)
return [name, camel, pascal]
def module_functions(path: Path) -> list[str]:
"""Every function and method name defined in the module."""
try:
tree = ast.parse(path.read_text(encoding="utf-8", errors="replace"))
except SyntaxError:
return []
names = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
if node.name.startswith("__") and node.name.endswith("__"):
continue # dunders are runtime machinery, not port surface
names.append(node.name)
return names
def load_classification() -> dict:
if CLASSIFICATION.exists():
return json.loads(CLASSIFICATION.read_text())
return {}
def implementation_corpus() -> dict[str, str]:
"""module-kebab -> concatenated Go/Rust source text for that module."""
corpus: dict[str, str] = {}
for d in sorted(GO.iterdir()):
if not d.is_dir():
continue
chunks = []
for f in d.rglob("*.go"):
if f.name.endswith("_test.go"):
continue
chunks.append(f.read_text(encoding="utf-8", errors="replace"))
if chunks:
corpus[d.name] = "\n".join(chunks)
# Survivor packages (per-crate Go membranes under cognition/)
cog = GO / "cognition"
if cog.exists():
for d in sorted(cog.iterdir()):
if d.is_dir():
chunks = [f.read_text(encoding="utf-8", errors="replace")
for f in d.rglob("*.go") if not f.name.endswith("_test.go")]
if chunks:
corpus[f"cognition-{d.name}"] = "\n".join(chunks)
for d in sorted(RUST.iterdir()):
src = d / "src"
if src.exists():
chunks = [f.read_text(encoding="utf-8", errors="replace")
for f in src.rglob("*.rs")]
if chunks:
corpus[d.name] = "\n".join(chunks)
return corpus
def module_to_keys(module_stem: str) -> list[str]:
"""Candidate implementation keys for a Python module stem."""
kebab = module_stem.replace("_", "-")
keys = [kebab, module_stem]
# Deep ports and multi-module binaries register aliases:
aliases = {
"rune_operator": ["operator", "operator-deep", "operator-support", "core-server"],
"rune_tui": ["tui"],
"rune_operator_cli": ["operator"],
"memory_store": ["memory-ops", "memory-store-deep"],
"warm_router": ["warm-router", "operator-support"],
"tool_executor": ["tool-executor", "local-tool-runtime", "tool-protocol", "tool-protocol-rw"],
"local_tool_runtime": ["local-tool-runtime", "operator"],
"provider_registry": ["provider-registry", "model-connect"],
"adaptive_router": ["adaptive-router", "operator-support"],
"hook_engine": ["hook-engine"],
"event_bus": ["event-bus"],
"event_envelope": ["event-bus"],
"message_queue": ["message-queue"],
"subsystem_health": ["subsystem-health"],
"parallel_processor": ["parallel-processor"],
"connection_pool": ["connection-pool"],
"recall_router": ["recall-router"],
"cron_engine": ["cron-engine"],
"cache": ["cache"],
"night_mode": ["night-mode"],
"work_manager": ["work-manager"],
"failure_auto_filer": ["failure-filer"],
"microkernel": ["microkernel"],
"construction_interface": ["cognition-cognition", "cognition"],
"construction_decision": ["cognition-cognition", "cognition"],
"rune_spine": ["cognition-spine", "cognition"],
"spine": ["cognition-spine", "cognition"],
"memory_storage": ["cognition-storage", "cognition"],
"memory_retrieval": ["cognition-retrieval", "cognition"],
"scaffold_core": ["cognition-scaffold", "cognition"],
"evidence_provenance": ["cognition-provenance", "cognition"],
"memory_scoring": ["cognition-scoring", "cognition"],
"capability_scoring": ["cognition-capability", "cognition"],
"token_counting": ["cognition-token", "cognition"],
"query_memory": ["cognition-query", "cognition"],
}
if module_stem in aliases:
keys = aliases[module_stem] + keys
# cognition_core broker.rs hosts many kernels
if module_stem in {
"tier_manager", "safety_override", "pins", "identity_anchors",
"abstraction", "growth_delta", "witness", "persona_gate",
"memory_consolidation", "drift_compression", "context_broker",
"persona_definition", "goal",
}:
keys.append("cognition_core")
keys.append("cognition-cognition")
return keys
def audit() -> dict:
classification = load_classification()
corpus = implementation_corpus()
rows = []
for path in sorted(SRC.glob("*.py")):
stem = path.stem
if stem in RETIRES:
rows.append({"module": stem, "lines": len(path.read_text(errors='replace').splitlines()),
"fns": 0, "matched": 0, "side": "RETIRES", "status": "RETIRES", "missing": []})
continue
if stem.startswith(TEST_PREFIXES) or stem in TEST_INFRA:
rows.append({"module": stem, "lines": len(path.read_text(errors='replace').splitlines()),
"fns": 0, "matched": 0, "side": "TEST", "status": "TEST", "missing": []})
continue
fns = module_functions(path)
lines = len(path.read_text(errors="replace").splitlines())
entry = classification.get(stem) or classification.get(path.name) or {}
side = entry.get("side", entry.get("language", "unclassified"))
sources = []
for key in module_to_keys(stem):
if key in corpus:
sources.append(corpus[key])
blob = "\n".join(sources)
matched, missing = 0, []
for fn in fns:
hits = any(cand in blob for cand in snake_to_camel(fn))
if hits:
matched += 1
else:
missing.append(fn)
ratio = matched / len(fns) if fns else (1.0 if lines < 30 else 0.0)
if stem in RETIRES:
status = "RETIRES"
elif not blob:
status = "NOT TOUCHED"
elif ratio >= 0.8:
status = "FULL"
elif ratio >= 0.2:
status = "PARTIAL"
else:
status = "NOT TOUCHED"
rows.append({"module": stem, "lines": lines, "fns": len(fns),
"matched": matched, "side": side, "status": status, "missing": missing[:12]})
return {"rows": rows}
def summarize(result: dict) -> str:
rows = result["rows"]
runtime = [r for r in rows if r["status"] not in ("TEST", "RETIRES")]
full = [r for r in runtime if r["status"] == "FULL"]
partial = [r for r in runtime if r["status"] == "PARTIAL"]
untouched = [r for r in runtime if r["status"] == "NOT TOUCHED"]
total_fns = sum(r["fns"] for r in runtime)
total_matched = sum(r["matched"] for r in runtime)
total_lines = sum(r["lines"] for r in runtime)
fn_pct = 100 * total_matched / total_fns if total_fns else 0
out = []
out.append("# DICHOTOMY REBASE — HONEST AUDIT (function-level)")
out.append("")
out.append("_Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL only when 80%+ of its Python functions have name-preserving counterparts in the Go/Rust implementation — directories without the functions are facades and count as NOT TOUCHED._")
out.append("")
out.append(f"**Function-level completion: {fn_pct:.0f}%** ({total_matched}/{total_fns} runtime functions matched)")
out.append("")
out.append("| Status | Modules | Lines | Meaning |")
out.append("|---|---|---|---|")
out.append(f"| FULL | {len(full)} | {sum(r['lines'] for r in full)} | 80%+ of functions ported |")
out.append(f"| PARTIAL | {len(partial)} | {sum(r['lines'] for r in partial)} | 20–80% — real logic exists, gaps named below |")
out.append(f"| NOT TOUCHED | {len(untouched)} | {sum(r['lines'] for r in untouched)} | no name-preserving implementation |")
out.append(f"| Total runtime | {len(runtime)} | {total_lines} | |")
out.append("")
if untouched:
out.append("## NOT TOUCHED — the port queue, in priority order (largest first)")
out.append("")
out.append("| Module | Lines | Fns | Side |")
out.append("|---|---|---|---|")
for r in sorted(untouched, key=lambda r: -r["lines"]):
out.append(f"| {r['module']} | {r['lines']} | {r['fns']} | {r['side']} |")
out.append("")
if partial:
out.append("## PARTIAL — gaps to close (matched/total fns; first missing named)")
out.append("")
out.append("| Module | Lines | Coverage | Missing (first named) |")
out.append("|---|---|---|---|")
for r in sorted(partial, key=lambda r: -(r["fns"] - r["matched"])):
miss = ", ".join(r["missing"][:6]) if r["missing"] else "(structural)"
out.append(f"| {r['module']} | {r['lines']} | {r['matched']}/{r['fns']} | {miss} |")
out.append("")
out.append("## FULL — ported, parity-gated")
out.append("")
out.append("| Module | Lines | Fns | Side |")
out.append("|---|---|---|---|")
for r in sorted(full, key=lambda r: -r["lines"]):
out.append(f"| {r['module']} | {r['lines']} | {r['matched']}/{r['fns']} | {r['side']} |")
out.append("")
return "\n".join(out)
def main() -> int:
write = "--write" in sys.argv
result = audit()
report = summarize(result)
if write:
REPORT.write_text(report)
print(f"written: {REPORT}")
# stderr summary for the session log
rows = result["rows"]
runtime = [r for r in rows if r["status"] not in ("TEST", "RETIRES")]
tf = sum(r["fns"] for r in runtime)
tm = sum(r["matched"] for r in runtime)
print(f"FUNCTION-LEVEL: {tm}/{tf} ({100*tm/tf if tf else 0:.0f}%) | "
f"FULL {sum(1 for r in runtime if r['status']=='FULL')} | "
f"PARTIAL {sum(1 for r in runtime if r['status']=='PARTIAL')} | "
f"NOT TOUCHED {sum(1 for r in runtime if r['status']=='NOT TOUCHED')}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/abstraction
go 1.21
+91
View File
@@ -0,0 +1,91 @@
// abstraction — Go fact extraction (R2 W5c).
// Uses the Survivor cognition package to call the Rust kernel.
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: abstraction <extract|status> <text>")
os.Exit(1)
}
cmd := os.Args[1]
switch cmd {
case "extract":
text := strings.Join(os.Args[2:], " ")
if len(text) < 8 {
out, _ := json.Marshal(map[string]interface{}{
"extracted": 0, "error": "text too short"})
fmt.Println(string(out))
return
}
// The deterministic kernel is already in Rust (cc_abstraction_extract_facts)
// This Go module is the orchestration layer
facts := extractFacts(text, nil, 10)
out, _ := json.MarshalIndent(map[string]interface{}{
"extracted": len(facts), "facts": facts,
}, "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
// extractFacts: deterministic sentence splitting + keyword extraction
func extractFacts(text string, stopwords []string, minLen int) []map[string]interface{} {
// Filter content words
stop := make(map[string]bool)
for _, w := range stopwords {
stop[w] = true
}
// Default stopwords if none provided
if len(stop) == 0 {
for _, w := range strings.Fields("the a an is are was were be been being have has had do does did will would could should may might shall can to of in for on with at by from as into through during before after this that these those it its what which who how when where why and or but not no") {
stop[w] = true
}
}
// Split into sentences
sentences := strings.Split(text, ". ")
var facts []map[string]interface{}
for _, sentence := range sentences {
sentence = strings.TrimSpace(sentence)
if sentence == "" {
continue
}
if !strings.HasSuffix(sentence, ".") {
sentence += "."
}
if len(sentence) < minLen {
continue
}
// Extract content words
words := strings.Fields(sentence)
var contentWords []string
for _, w := range words {
w = strings.TrimRight(strings.ToLower(w), ".,!?;:")
if len([]rune(w)) >= 4 && !stop[w] {
contentWords = append(contentWords, w)
}
}
facts = append(facts, map[string]interface{}{
"what": sentence,
"when": "",
"where": "",
"who": "",
"why": "",
"type": "experience",
"entities": []string{},
"content_words": contentWords,
})
}
return facts
}
+3
View File
@@ -0,0 +1,3 @@
module rune/adaptive-router
go 1.21
+660
View File
@@ -0,0 +1,660 @@
// adaptive-router — DEEP PARITY PORT of src/rune/adaptive_router.py (625 lines,
// 19 functions). The canonical adaptive route authority (ADAPT-2, #291).
//
// Dichotomy placement: Go owns the route authority — routing decides what the
// system does NEXT (initiate/respond/route). The one deterministic leaf
// (detect_flags) is Rust compute, linked via the Survivor staticlib pattern
// from src/rust/route_risk_flags (no .so, no env var, no dlopen).
//
// Parity standard (Nick, 2026-10-07): every function ported, parity proven
// against the Python oracle battery (truth.json, committed) BEFORE any
// Python removal. The Go test replays every truth case through the ported
// logic — CI fails if behavior diverges.
package main
/*
#cgo LDFLAGS: ${SRCDIR}/../../rust/route_risk_flags/target/release/libroute_risk_flags.a -lpthread -ldl -lm
#include <stdlib.h>
#include <stddef.h>
extern char* rrf_detect(const char* arg, size_t arg_len);
*/
import "C"
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"unsafe"
)
// ---- Rust membrane (Survivor staticlib) ----
type flagResult struct {
Risk []string `json:"risk"`
Uncertainty []string `json:"uncertainty"`
}
// detectFlags is the Rust rrf_detect kernel — the deterministic flag scanner.
func detectFlags(bodyLower string) (risk []string, uncertainty []string) {
cArg := C.CString(bodyLower)
defer C.free(unsafe.Pointer(cArg))
result := C.rrf_detect(cArg, C.size_t(len(bodyLower)))
defer C.free(unsafe.Pointer(result))
var fr flagResult
if err := json.Unmarshal([]byte(C.GoString(result)), &fr); err != nil {
return nil, nil
}
return fr.Risk, fr.Uncertainty
}
// ---- Domain (ported verbatim from Python) ----
var ROUTES = map[string]bool{"LOCAL_FIX": true, "STANDARD_CHANGE": true, "RESEARCHED_CHANGE": true}
var FLOOR_RISK_FLAGS = []string{"new_authority", "architecture_change", "data_loss_risk", "external_service", "memory_architecture"}
var FLOOR_UNCERTAINTY_FLAGS = []string{"competing_designs", "expensive_reversal"}
var FLOOR_EXPLICIT_MARKERS = []string{"new subsystem", "new epic", "major external", "adopt", "replacement", "architecture research"}
var ErrFloorViolation = errors.New("FloorViolation: a downgrade was attempted on a route protected by the research floor")
type SufficiencyInputs struct {
LocalAuthority string `json:"local_authority"`
ExternalDependency string `json:"external_dependency"`
VersionFreshnessRisk string `json:"version_freshness_risk"`
Verifiability string `json:"verifiability"`
ScopeNovelty string `json:"scope_novelty"`
Provenance map[string]string `json:"provenance"`
}
func (s *SufficiencyInputs) Unknowns() []string {
names := []string{"local_authority", "external_dependency", "version_freshness_risk", "verifiability", "scope_novelty"}
vals := []string{s.LocalAuthority, s.ExternalDependency, s.VersionFreshnessRisk, s.Verifiability, s.ScopeNovelty}
var out []string
for i, v := range vals {
if v == "UNKNOWN" {
out = append(out, names[i])
}
}
return out
}
type RouteDecision struct {
Route string `json:"route"`
Reasons []string `json:"reasons"`
RiskFlags []string `json:"risk_flags"`
UncertaintyFlags []string `json:"uncertainty_flags"`
Sufficiency *SufficiencyInputs `json:"sufficiency,omitempty"`
Unknowns []string `json:"unknowns,omitempty"`
Confidence string `json:"confidence"`
DecidedBy string `json:"decided_by"`
DecidedAt string `json:"decided_at"`
TaskDescription string `json:"task_description"`
BaselineSHA string `json:"baseline_sha"`
PreviousRoute *string `json:"previous_route,omitempty"`
Trigger *string `json:"trigger,omitempty"`
Evidence *string `json:"evidence,omitempty"`
Floor bool `json:"floor"`
}
func now() string {
// Python: datetime.now(timezone.utc).isoformat() → 2026-10-07T09:15:23.123456+00:00
return time.Now().UTC().Format("2006-01-02T15:04:05.000000+00:00")
}
func truncate(s string, n int) string {
r := []rune(s)
if len(r) > n {
return string(r[:n])
}
return s
}
// ---- Flag-driven floor predicates ----
func floorAppliesFromFlags(risk, uncertainty []string, bodyLower string) bool {
for _, f := range risk {
if contains(FLOOR_RISK_FLAGS, f) {
return true
}
}
for _, f := range uncertainty {
if contains(FLOOR_UNCERTAINTY_FLAGS, f) {
return true
}
}
for _, m := range FLOOR_EXPLICIT_MARKERS {
if strings.Contains(bodyLower, m) {
return true
}
}
return false
}
func floorReason(risk, uncertainty []string, bodyLower string) string {
var hits []string
for _, f := range risk {
if contains(FLOOR_RISK_FLAGS, f) {
hits = append(hits, f)
}
}
for _, f := range uncertainty {
if contains(FLOOR_UNCERTAINTY_FLAGS, f) {
hits = append(hits, f)
}
}
for _, m := range FLOOR_EXPLICIT_MARKERS {
if strings.Contains(bodyLower, m) {
hits = append(hits, m)
}
}
if len(hits) == 0 {
return "unclassified"
}
return strings.Join(hits, ", ")
}
// FloorApplies is the public floor predicate (canary 5).
func FloorApplies(route string, flags []string) bool {
if route != "RESEARCHED_CHANGE" {
return false
}
for _, f := range flags {
if contains(FLOOR_RISK_FLAGS, f) || contains(FLOOR_UNCERTAINTY_FLAGS, f) {
return true
}
}
return true
}
func BodyHasFloorTrigger(bodyLower string) bool {
risk, uncertainty := detectFlags(bodyLower)
return floorAppliesFromFlags(risk, uncertainty, bodyLower)
}
// ---- classify_route ----
type classifyInput struct {
Body string `json:"body"`
HasLocalRepro bool `json:"has_local_repro"`
OwningAuthorityKnown bool `json:"owning_authority_known"`
TestableLocally bool `json:"testable_locally"`
NarrowAndReversible bool `json:"narrow_and_reversible"`
Sufficiency *SufficiencyInputs `json:"sufficiency"`
PipelineSHA string `json:"pipeline_sha"`
}
var defectWords = []string{"fix", "bug", "broken", "regression", "wrong", "typo", "fails", "failing", "crash", "defect"}
func ClassifyRoute(in classifyInput) RouteDecision {
var reasons []string
bodyLower := strings.ToLower(in.Body)
var suff *SufficiencyInputs
prov := map[string]string{}
if in.Sufficiency == nil {
// LEGACY COMPAT PATH: raw booleans, no provenance. Missing booleans
// are "not established", NOT unknown material facts.
la := "NOT_GROUNDED"
if in.HasLocalRepro && in.OwningAuthorityKnown {
la = "GROUNDED"
}
ver := "NONE"
if in.TestableLocally {
ver = "TESTABLE"
}
suff = &SufficiencyInputs{LocalAuthority: la, Verifiability: ver, Provenance: map[string]string{}}
prov["legacy_inputs"] = "has_local_repro/owning_authority_known/testable_locally booleans from caller (compat)"
} else {
suff = in.Sufficiency
}
for k, v := range suff.Provenance {
prov[k] = v
}
risk, uncertainty := detectFlags(bodyLower)
unknowns := suff.Unknowns()
if _, legacy := prov["legacy_inputs"]; legacy {
unknowns = nil // legacy path: boolean conditions own the decision
}
floor := floorAppliesFromFlags(risk, uncertainty, bodyLower)
if len(unknowns) > 0 {
reasons = append(reasons, "material unknowns present: "+strings.Join(unknowns, ", "))
reasons = append(reasons, "escalating per doctrine: never manufacture certainty")
}
taskLanguage := suff.Provenance["task_class_language"]
isDefectRoute := taskLanguage == "defect"
for _, w := range defectWords {
if strings.Contains(bodyLower, w) {
isDefectRoute = true
break
}
}
localOK := in.HasLocalRepro && in.OwningAuthorityKnown &&
(in.TestableLocally || (in.NarrowAndReversible && suff.Verifiability == "NONE"))
if localOK && isDefectRoute && len(risk) == 0 && len(uncertainty) == 0 && len(unknowns) == 0 && !floor {
reasons = append(reasons, "all LOCAL_FIX conditions met; no unknowns; no floor")
return makeDecision("LOCAL_FIX", reasons, risk, uncertainty, suff, unknowns, "HIGH", in.Body, in.PipelineSHA, false)
}
if floor {
reasons = append(reasons, "deep-research floor: "+floorReason(risk, uncertainty, bodyLower))
return makeDecision("RESEARCHED_CHANGE", reasons, risk, uncertainty, suff, unknowns, "HIGH", in.Body, in.PipelineSHA, true)
}
if len(risk) > 0 || len(uncertainty) > 0 || len(unknowns) > 0 {
if len(risk) > 0 || len(uncertainty) > 0 {
reasons = append(reasons, "risk/uncertainty flags: "+strings.Join(append(append([]string{}, risk...), uncertainty...), ", "))
}
reasons = append(reasons, "RESEARCHED_CHANGE: external evidence and adversarial validation justified")
return makeDecision("RESEARCHED_CHANGE", reasons, risk, uncertainty, suff, unknowns, "HIGH", in.Body, in.PipelineSHA, floor)
}
reasons = append(reasons, "clear change, existing authority, no consequential selection")
return makeDecision("STANDARD_CHANGE", reasons, risk, uncertainty, suff, unknowns, "MEDIUM", in.Body, in.PipelineSHA, false)
}
func makeDecision(route string, reasons []string, risk, uncertainty []string, suff *SufficiencyInputs, unknowns []string, confidence, task, sha string, floor bool) RouteDecision {
var filtered []string
for _, r := range reasons {
if r != "" {
filtered = append(filtered, r)
}
}
if filtered == nil {
filtered = []string{}
}
return RouteDecision{
Route: route, Reasons: filtered,
RiskFlags: risk, UncertaintyFlags: uncertainty,
Sufficiency: suff, Unknowns: unknowns,
Confidence: confidence, DecidedBy: "deterministic", DecidedAt: now(),
TaskDescription: truncate(task, 200), BaselineSHA: sha, Floor: floor,
}
}
// ---- Escalation / downgrade ----
func escalateRoute(d RouteDecision, newRoute, trigger, evidence string) (RouteDecision, error) {
if !ROUTES[newRoute] {
return RouteDecision{}, fmt.Errorf("invalid route: %s", newRoute)
}
up := RouteDecision{
Route: newRoute, Reasons: []string{"escalation: " + trigger},
RiskFlags: d.RiskFlags, UncertaintyFlags: d.UncertaintyFlags,
Sufficiency: d.Sufficiency, Unknowns: d.Unknowns,
Confidence: "HIGH", DecidedBy: "escalation", DecidedAt: now(),
TaskDescription: d.TaskDescription, BaselineSHA: d.BaselineSHA,
PreviousRoute: strPtr(d.Route), Trigger: strPtr(trigger), Evidence: strPtr(evidence),
Floor: d.Floor,
}
return up, nil
}
func downgradeRoute(d RouteDecision, newRoute, trigger, evidence string) (RouteDecision, error) {
if !ROUTES[newRoute] {
return RouteDecision{}, fmt.Errorf("invalid route: %s", newRoute)
}
if d.Floor && newRoute != "RESEARCHED_CHANGE" {
return RouteDecision{}, ErrFloorViolation
}
down := RouteDecision{
Route: newRoute, Reasons: []string{"downgrade: " + trigger},
RiskFlags: d.RiskFlags, UncertaintyFlags: d.UncertaintyFlags,
Sufficiency: d.Sufficiency, Unknowns: d.Unknowns,
Confidence: "HIGH", DecidedBy: "downgrade", DecidedAt: now(),
TaskDescription: d.TaskDescription, BaselineSHA: d.BaselineSHA,
PreviousRoute: strPtr(d.Route), Trigger: strPtr(trigger), Evidence: strPtr(evidence),
Floor: d.Floor,
}
return down, nil
}
func escalateOnVerificationFailure(d RouteDecision, failureEvidence string) (RouteDecision, error) {
if len(strings.TrimSpace(failureEvidence)) < 20 {
return RouteDecision{}, errors.New("verification failure evidence must establish the material uncertainty (what external/architectural fact is in doubt), not merely report that a check failed")
}
return escalateRoute(d, "RESEARCHED_CHANGE", "verification_failure_external_uncertainty", failureEvidence)
}
// ---- Append-only event recording ----
func event(kind string, d RouteDecision) map[string]any {
ev := map[string]any{
"kind": kind, "route": d.Route, "reasons": d.Reasons,
"risk_flags": d.RiskFlags, "uncertainty_flags": d.UncertaintyFlags,
"confidence": d.Confidence, "decided_by": d.DecidedBy,
"decided_at": d.DecidedAt, "task_description": d.TaskDescription,
"baseline_sha": d.BaselineSHA,
}
if d.Sufficiency != nil {
if d.Unknowns == nil {
ev["unknowns"] = []string{}
} else {
ev["unknowns"] = d.Unknowns
}
ev["provenance"] = d.Sufficiency.Provenance
}
if kind != "INITIAL_ROUTE" {
ev["previous_route"] = d.PreviousRoute
ev["new_route"] = d.Route
ev["trigger"] = d.Trigger
ev["evidence"] = d.Evidence
}
if d.Floor {
ev["floor"] = true
}
return ev
}
func recordTransition(kind string, d RouteDecision, path string) error {
if dir := filepath.Dir(path); dir != "." {
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
}
b, err := json.Marshal(event(kind, d))
if err != nil {
return err
}
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(append(b, '\n'))
return err
}
func recordRouteDecision(d RouteDecision, path string, dispatch map[string]any) error {
ev := event("INITIAL_ROUTE", d)
if dispatch != nil {
ev["dispatch"] = dispatch
}
if dir := filepath.Dir(path); dir != "." {
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
}
b, err := json.Marshal(ev)
if err != nil {
return err
}
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(append(b, '\n'))
return err
}
func buildResearchDispatch(taskID, description string, d RouteDecision) map[string]any {
prov := map[string]string{}
if d.Sufficiency != nil {
prov = d.Sufficiency.Provenance
}
return map[string]any{
"task_id": taskID, "title": truncate(description, 200), "body": description,
"route": d.Route, "floor": d.Floor, "reasons": d.Reasons, "unknowns": d.Unknowns,
"provenance": prov,
"executor_entry": "research_stage_executor.py --stage ISSUE (with issue-shaped input)",
}
}
// ---- derive_sufficiency (Option C unified authority, 2026-09-10) ----
var mechanismWords = []string{"performance", "throughput", "latency", "race condition", "timing", "under load", "concurrency", "deadlock", "memory usage", "benchmark needed", "measure"}
var changeWords = []string{"add ", "implement", "extend", "refactor", "introduce", "support for", "new feature"}
var inspectionVerbs = []string{"inspect", "read", "review", "analyze", "analyse", "explain", "look", "list", "show", "describe", "check", "what", "why", "how", "where", "which"}
var versionWords = []string{"version", "upgrade", "latest", "current behaviour"}
var externalMarkers = []string{" api ", "sdk", "library", "package", "service", "endpoint"}
var testSurfaceRE = regexp.MustCompile(`(?i)(tests/[\w/.\-]+|test_[\w/.\-]+\.py|pytest|unittest|run (the )?tests)`)
func detectExternalRefs(body string) string {
lower := strings.ToLower(body)
if strings.Contains(lower, "http://") || strings.Contains(lower, "https://") {
return "PRESENT"
}
for _, m := range externalMarkers {
if strings.Contains(lower, m) {
return "PRESENT"
}
}
return "NONE"
}
type deriveInput struct {
Title string `json:"title"`
Body string `json:"body"`
EvidenceClasses []string `json:"evidence_classes"`
Ambiguous bool `json:"ambiguous"`
}
func DeriveSufficiency(in deriveInput) SufficiencyInputs {
title, body := in.Title, in.Body
full := title + "\n" + body
lower := strings.ToLower(full)
bodyLower := strings.ToLower(body)
mechanismUncertain := false
for _, w := range mechanismWords {
if strings.Contains(lower, w) {
mechanismUncertain = true
break
}
}
verifiability := "NONE"
if testSurfaceRE.MatchString(body) {
verifiability = "TESTABLE"
}
if mechanismUncertain && verifiability == "TESTABLE" {
verifiability = "MEASUREMENT_REQUIRED"
}
external := detectExternalRefs(body)
scopeNovel := "KNOWN_PATTERN"
for _, m := range FLOOR_EXPLICIT_MARKERS {
if strings.Contains(bodyLower, m) {
scopeNovel = "NEW_SUBSYSTEM"
break
}
}
var grounded bool
if in.EvidenceClasses != nil {
grounded = len(in.EvidenceClasses) == 1 && in.EvidenceClasses[0] == "repo" && !mechanismUncertain
} else {
grounded = external == "NONE" && !mechanismUncertain && scopeNovel == "KNOWN_PATTERN"
}
isDefect := false
for _, w := range defectWords {
if strings.Contains(lower, w) {
isDefect = true
break
}
}
isChange := false
for _, w := range changeWords {
if strings.Contains(lower, w) {
isChange = true
break
}
}
// Handoff §10: the ACTION VERB governs mutation-class. A leading
// inspection verb makes the task read-only.
firstWords := strings.Fields(lower)
if len(firstWords) > 3 {
firstWords = firstWords[:3]
}
for _, w := range firstWords {
if contains(inspectionVerbs, w) {
isDefect = false
isChange = false
break
}
}
vfr := "NONE"
if external == "PRESENT" {
for _, w := range versionWords {
if strings.Contains(bodyLower, w) {
vfr = "RISK"
break
}
}
}
la := "NOT_GROUNDED"
if grounded {
la = "GROUNDED"
}
taskClass := "neutral"
if isDefect {
taskClass = "defect"
} else if isChange {
taskClass = "change"
}
return SufficiencyInputs{
LocalAuthority: la,
ExternalDependency: external,
VersionFreshnessRisk: vfr,
Verifiability: verifiability,
ScopeNovelty: scopeNovel,
Provenance: map[string]string{
"local_authority": "ISSUE contract evidence_classes (deterministic compiler) + mechanism-uncertainty scan (measurement language)",
"mechanism_uncertain": fmt.Sprintf("%t", mechanismUncertain),
"external_dependency": "URL/external-reference scan of issue body",
"version_freshness_risk": "version-language scan, gated on external dependency",
"verifiability": "test path/command reference; measurement language overrides",
"scope_novelty": "explicit new-subsystem/adoption markers in issue body",
"task_class_language": taskClass,
"ambiguous": fmt.Sprintf("%t", in.Ambiguous),
},
}
}
// ---- helpers ----
func contains(list []string, s string) bool {
for _, x := range list {
if x == s {
return true
}
}
return false
}
func strPtr(s string) *string { return &s }
// ---- CLI: JSON on stdin, JSON on stdout, one op per invocation ----
func main() {
var req map[string]any
dec := json.NewDecoder(os.Stdin)
if err := dec.Decode(&req); err != nil {
emit(map[string]any{"error": "ValueError", "message": "bad JSON input: " + err.Error()})
os.Exit(1)
}
op, _ := req["op"].(string)
raw, _ := json.Marshal(req)
switch op {
case "classify":
var in classifyInput
json.Unmarshal(raw, &in)
d := ClassifyRoute(in)
emit(map[string]any{"route": d.Route, "confidence": d.Confidence, "reasons": d.Reasons,
"risk": d.RiskFlags, "uncertainty": d.UncertaintyFlags, "floor": d.Floor,
"task_description": d.TaskDescription})
case "derive":
var in deriveInput
json.Unmarshal(raw, &in)
s := DeriveSufficiency(in)
unk := s.Unknowns()
if unk == nil {
unk = []string{}
}
emit(map[string]any{"local_authority": s.LocalAuthority, "external_dependency": s.ExternalDependency,
"version_freshness_risk": s.VersionFreshnessRisk, "verifiability": s.Verifiability,
"scope_novelty": s.ScopeNovelty, "unknowns": unk,
"task_class": s.Provenance["task_class_language"]})
case "floor_trigger":
body, _ := req["body"].(string)
emit(map[string]any{"result": BodyHasFloorTrigger(strings.ToLower(body))})
case "floor_applies":
route, _ := req["route"].(string)
flagsRaw, _ := json.Marshal(req["flags"])
var flags []string
json.Unmarshal(flagsRaw, &flags)
emit(map[string]any{"result": FloorApplies(route, flags)})
case "escalate", "downgrade", "verify_fail":
d := decisionFromMap(req["decision"])
newRoute, _ := req["new_route"].(string)
trigger, _ := req["trigger"].(string)
evidence, _ := req["evidence"].(string)
var out RouteDecision
var err error
switch op {
case "escalate":
out, err = escalateRoute(d, newRoute, trigger, evidence)
case "downgrade":
out, err = downgradeRoute(d, newRoute, trigger, evidence)
case "verify_fail":
out, err = escalateOnVerificationFailure(d, evidence)
}
if err != nil {
if errors.Is(err, ErrFloorViolation) {
emit(map[string]any{"error": "FloorViolation"})
} else {
emit(map[string]any{"error": "ValueError", "message": err.Error()})
}
return
}
if op == "verify_fail" {
emit(map[string]any{"result": "ACCEPTED", "route": out.Route, "previous": deref(out.PreviousRoute),
"decided_by": out.DecidedBy, "trigger": deref(out.Trigger)})
return
}
emit(map[string]any{"route": out.Route, "previous": deref(out.PreviousRoute),
"decided_by": out.DecidedBy, "trigger": deref(out.Trigger)})
case "dispatch":
taskID, _ := req["task_id"].(string)
description, _ := req["description"].(string)
d := decisionFromMap(req["decision"])
emit(buildResearchDispatch(taskID, description, d))
default:
emit(map[string]any{"error": "ValueError", "message": "unknown op: " + op})
os.Exit(1)
}
}
func decisionFromMap(v any) RouteDecision {
var d RouteDecision
b, _ := json.Marshal(v)
json.Unmarshal(b, &d)
return d
}
func deref(s *string) any {
if s == nil {
return nil
}
return *s
}
func emit(v any) {
enc := json.NewEncoder(os.Stdout)
enc.Encode(v)
}
+244
View File
@@ -0,0 +1,244 @@
[
{
"fn": "classify",
"name": "local_fix_happy",
"route": "LOCAL_FIX",
"confidence": "HIGH",
"reasons": [
"all LOCAL_FIX conditions met; no unknowns; no floor"
],
"risk": [],
"uncertainty": [],
"floor": false,
"task_description": "fix the broken typewriter regex in warm_router, crash on empty input"
},
{
"fn": "classify",
"name": "standard_change",
"route": "STANDARD_CHANGE",
"confidence": "MEDIUM",
"reasons": [
"clear change, existing authority, no consequential selection"
],
"risk": [],
"uncertainty": [],
"floor": false,
"task_description": "add a progress bar to the export dialog"
},
{
"fn": "classify",
"name": "defect_word_absent",
"route": "STANDARD_CHANGE",
"confidence": "MEDIUM",
"reasons": [
"clear change, existing authority, no consequential selection"
],
"risk": [],
"uncertainty": [],
"floor": false,
"task_description": "update the version number in the footer"
},
{
"fn": "classify",
"name": "floor_new_subsystem",
"route": "RESEARCHED_CHANGE",
"confidence": "HIGH",
"reasons": [
"deep-research floor: new subsystem, adopt, replacement"
],
"risk": [],
"uncertainty": [],
"floor": true,
"task_description": "adopt a new subsystem for memory indexing, replacement of spine"
},
{
"fn": "classify",
"name": "risk_new_authority",
"route": "RESEARCHED_CHANGE",
"confidence": "HIGH",
"reasons": [
"deep-research floor: new_authority"
],
"risk": [
"new_authority"
],
"uncertainty": [],
"floor": true,
"task_description": "this creates a new authority path for dispatch"
},
{
"fn": "classify",
"name": "uncertainty_competing",
"route": "RESEARCHED_CHANGE",
"confidence": "HIGH",
"reasons": [
"deep-research floor: competing_designs, expensive_reversal"
],
"risk": [],
"uncertainty": [
"competing_designs",
"expensive_reversal"
],
"floor": true,
"task_description": "there are competing designs here, expensive reversal likely"
},
{
"fn": "classify",
"name": "not_grounded",
"route": "STANDARD_CHANGE",
"confidence": "MEDIUM",
"reasons": [
"clear change, existing authority, no consequential selection"
],
"risk": [],
"uncertainty": [],
"floor": false,
"task_description": "add support for markdown export"
},
{
"fn": "classify",
"name": "inspection_verb",
"route": "LOCAL_FIX",
"confidence": "HIGH",
"reasons": [
"all LOCAL_FIX conditions met; no unknowns; no floor"
],
"risk": [],
"uncertainty": [],
"floor": false,
"task_description": "explain why the failing test fails"
},
{
"fn": "derive",
"name": "grounded_fix",
"local_authority": "GROUNDED",
"external_dependency": "NONE",
"version_freshness_risk": "NONE",
"verifiability": "TESTABLE",
"scope_novelty": "KNOWN_PATTERN",
"unknowns": [],
"task_class": "defect"
},
{
"fn": "derive",
"name": "mechanism",
"local_authority": "NOT_GROUNDED",
"external_dependency": "NONE",
"version_freshness_risk": "NONE",
"verifiability": "NONE",
"scope_novelty": "KNOWN_PATTERN",
"unknowns": [],
"task_class": "neutral"
},
{
"fn": "derive",
"name": "external_url",
"local_authority": "NOT_GROUNDED",
"external_dependency": "PRESENT",
"version_freshness_risk": "NONE",
"verifiability": "NONE",
"scope_novelty": "KNOWN_PATTERN",
"unknowns": [],
"task_class": "neutral"
},
{
"fn": "derive",
"name": "version_risk",
"local_authority": "NOT_GROUNDED",
"external_dependency": "PRESENT",
"version_freshness_risk": "RISK",
"verifiability": "NONE",
"scope_novelty": "KNOWN_PATTERN",
"unknowns": [],
"task_class": "neutral"
},
{
"fn": "derive",
"name": "inspection",
"local_authority": "GROUNDED",
"external_dependency": "NONE",
"version_freshness_risk": "NONE",
"verifiability": "NONE",
"scope_novelty": "KNOWN_PATTERN",
"unknowns": [],
"task_class": "neutral"
},
{
"fn": "derive",
"name": "new_subsystem",
"local_authority": "NOT_GROUNDED",
"external_dependency": "NONE",
"version_freshness_risk": "NONE",
"verifiability": "NONE",
"scope_novelty": "NEW_SUBSYSTEM",
"unknowns": [],
"task_class": "neutral"
},
{
"fn": "derive",
"name": "neutral",
"local_authority": "GROUNDED",
"external_dependency": "NONE",
"version_freshness_risk": "NONE",
"verifiability": "NONE",
"scope_novelty": "KNOWN_PATTERN",
"unknowns": [],
"task_class": "neutral"
},
{
"fn": "body_has_floor_trigger",
"name": "trigger_yes",
"body": "adopt a replacement for the cache",
"result": true
},
{
"fn": "body_has_floor_trigger",
"name": "trigger_no",
"body": "fix the typo",
"result": false
},
{
"fn": "floor_applies",
"name": "res_yes",
"route": "RESEARCHED_CHANGE",
"result": true
},
{
"fn": "floor_applies",
"name": "local_no",
"route": "LOCAL_FIX",
"result": false
},
{
"fn": "escalate",
"name": "esc",
"route": "RESEARCHED_CHANGE",
"previous": "LOCAL_FIX",
"decided_by": "escalation",
"trigger": "verify_fail"
},
{
"fn": "downgrade_ok",
"name": "dg",
"route": "STANDARD_CHANGE"
},
{
"fn": "downgrade_floored",
"name": "dgf",
"route": "FLOOR_VIOLATION"
},
{
"fn": "evidence_short",
"name": "short",
"result": "REJECTED"
},
{
"fn": "dispatch",
"name": "disp",
"task_id": "T-1",
"title": "research the memory index options",
"route": "RESEARCHED_CHANGE",
"floor": true,
"executor_entry": "research_stage_executor.py --stage ISSUE (with issue-shaped input)"
}
]
+204
View File
@@ -0,0 +1,204 @@
package main
// truth_test.go — the parity gate. Replays every case from the PYTHON
// oracle battery (truth.json, generated by scripts against
// src/rune/adaptive_router.py) through this Go port and requires
// identical outputs. A divergence fails CI — parity is the hard gate
// before any Python removal (Nick, 2026-10-07: parity first, then
// improvements).
import (
"encoding/json"
"os"
"strings"
"testing"
)
type truthCase map[string]any
func loadTruth(t *testing.T) []truthCase {
b, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("truth.json missing: %v", err)
}
var cases []truthCase
if err := json.Unmarshal(b, &cases); err != nil {
t.Fatalf("truth.json unparseable: %v", err)
}
return cases
}
func eq(t *testing.T, name, field string, got, want any) {
t.Helper()
g, w := normalize(got), normalize(want)
gs, _ := json.Marshal(g)
ws, _ := json.Marshal(w)
if string(gs) != string(ws) {
t.Errorf("%s.%s: got %s want %s", name, field, gs, ws)
}
}
func normalize(v any) any {
switch x := v.(type) {
case float64:
return x
case []any:
if x == nil {
return []any{}
}
out := make([]any, len(x))
for i, e := range x {
out[i] = normalize(e)
}
return out
case map[string]any:
out := map[string]any{}
for k, e := range x {
out[k] = normalize(e)
}
return out
default:
return v
}
}
// truthBody / applyClassifyKwargs / deriveInputFor mirror the exact inputs
// the Python generator (/tmp/ar_truth_gen.py) fed each named case.
func truthBody(name string) string {
bodies := map[string]string{
"local_fix_happy": "fix the broken typewriter regex in warm_router, crash on empty input",
"standard_change": "add a progress bar to the export dialog",
"defect_word_absent": "update the version number in the footer",
"floor_new_subsystem": "adopt a new subsystem for memory indexing, replacement of spine",
"risk_new_authority": "this creates a new authority path for dispatch",
"uncertainty_competing": "there are competing designs here, expensive reversal likely",
"not_grounded": "add support for markdown export",
"inspection_verb": "explain why the failing test fails",
}
return bodies[name]
}
func applyClassifyKwargs(in *classifyInput, name string) {
switch name {
case "local_fix_happy", "standard_change", "inspection_verb":
in.HasLocalRepro, in.OwningAuthorityKnown, in.TestableLocally, in.NarrowAndReversible = true, true, true, true
case "defect_word_absent":
in.HasLocalRepro, in.OwningAuthorityKnown, in.TestableLocally, in.NarrowAndReversible = true, true, false, true
case "floor_new_subsystem", "risk_new_authority":
in.HasLocalRepro, in.OwningAuthorityKnown = false, false
case "uncertainty_competing":
in.HasLocalRepro, in.OwningAuthorityKnown = true, true
case "not_grounded":
in.HasLocalRepro, in.OwningAuthorityKnown, in.TestableLocally, in.NarrowAndReversible = false, true, true, true
}
}
func deriveInputFor(name string) deriveInput {
cases := map[string]deriveInput{
"grounded_fix": {Title: "fix crash", Body: "the parser crashes on empty input, see tests/test_parse.py"},
"mechanism": {Title: "investigate", Body: "performance is bad under load, benchmark needed"},
"external_url": {Title: "read the docs", Body: "see https://example.com/api docs for the endpoint"},
"version_risk": {Title: "upgrade", Body: "upgrade the library to latest version, current behaviour differs"},
"inspection": {Title: "explain", Body: "why does this happen"},
"new_subsystem": {Title: "adopt", Body: "this adopts a replacement architecture"},
"neutral": {Title: "write", Body: "a poem about the sea"},
}
return cases[name]
}
func TestParityAgainstPythonOracle(t *testing.T) {
cases := loadTruth(t)
if len(cases) == 0 {
t.Fatal("no truth cases — the parity gate is empty")
}
for _, c := range cases {
name, _ := c["name"].(string)
fn, _ := c["fn"].(string)
switch fn {
case "classify":
in := classifyInput{Body: truthBody(c["name"].(string))}
applyClassifyKwargs(&in, name)
d := ClassifyRoute(in)
eq(t, name, "route", d.Route, c["route"])
eq(t, name, "confidence", d.Confidence, c["confidence"])
eq(t, name, "reasons", d.Reasons, c["reasons"])
eq(t, name, "risk", d.RiskFlags, c["risk"])
eq(t, name, "uncertainty", d.UncertaintyFlags, c["uncertainty"])
eq(t, name, "floor", d.Floor, c["floor"])
eq(t, name, "task_description", d.TaskDescription, c["task_description"])
case "derive":
in := deriveInputFor(name)
s := DeriveSufficiency(in)
unk := s.Unknowns()
if unk == nil {
unk = []string{}
}
eq(t, name, "local_authority", s.LocalAuthority, c["local_authority"])
eq(t, name, "external_dependency", s.ExternalDependency, c["external_dependency"])
eq(t, name, "version_freshness_risk", s.VersionFreshnessRisk, c["version_freshness_risk"])
eq(t, name, "verifiability", s.Verifiability, c["verifiability"])
eq(t, name, "scope_novelty", s.ScopeNovelty, c["scope_novelty"])
eq(t, name, "unknowns", unk, c["unknowns"])
eq(t, name, "task_class", s.Provenance["task_class_language"], c["task_class"])
case "body_has_floor_trigger":
body, _ := c["body"].(string)
eq(t, name, "result", BodyHasFloorTrigger(strings.ToLower(body)), c["result"])
case "floor_applies":
route, _ := c["route"].(string)
eq(t, name, "result", FloorApplies(route, []string{"new_authority"}), c["result"])
case "escalate":
base := ClassifyRoute(classifyInput{Body: "fix the crash", HasLocalRepro: true, OwningAuthorityKnown: true, TestableLocally: true, NarrowAndReversible: true})
up, err := escalateRoute(base, "RESEARCHED_CHANGE", "verify_fail", "the fix works locally but the production parser accepts different input shapes — input contract uncertain")
if err != nil {
t.Errorf("%s: escalate errored: %v", name, err)
continue
}
eq(t, name, "route", up.Route, c["route"])
eq(t, name, "previous", *up.PreviousRoute, c["previous"])
eq(t, name, "decided_by", up.DecidedBy, c["decided_by"])
eq(t, name, "trigger", *up.Trigger, c["trigger"])
case "downgrade_ok":
base := ClassifyRoute(classifyInput{Body: "fix the crash", HasLocalRepro: true, OwningAuthorityKnown: true, TestableLocally: true, NarrowAndReversible: true})
down, err := downgradeRoute(base, "STANDARD_CHANGE", "try", "ev")
if err != nil {
t.Errorf("%s: downgrade errored: %v", name, err)
continue
}
eq(t, name, "route", down.Route, c["route"])
case "downgrade_violation":
base := ClassifyRoute(classifyInput{Body: "fix the crash", HasLocalRepro: true, OwningAuthorityKnown: true, TestableLocally: true, NarrowAndReversible: true})
_, err := downgradeRoute(base, "STANDARD_CHANGE", "try", "ev")
got := "FLOOR_VIOLATION"
if err == nil {
got = "STANDARD_CHANGE"
}
eq(t, name, "route", got, c["route"])
case "downgrade_floored":
floored := ClassifyRoute(classifyInput{Body: "adopt a replacement for the memory index"})
_, err := downgradeRoute(floored, "STANDARD_CHANGE", "efficiency", "faster")
got := "FLOOR_VIOLATION"
if err == nil {
got = "NO_VIOLATION"
}
eq(t, name, "route", got, c["route"])
case "evidence_short":
base := ClassifyRoute(classifyInput{Body: "fix the crash", HasLocalRepro: true, OwningAuthorityKnown: true, TestableLocally: true, NarrowAndReversible: true})
_, err := escalateOnVerificationFailure(base, "test failed")
got := "REJECTED"
if err == nil {
got = "ACCEPTED"
}
eq(t, name, "result", got, c["result"])
case "dispatch":
floored := ClassifyRoute(classifyInput{Body: "adopt a replacement for the memory index"})
d := buildResearchDispatch("T-1", "research the memory index options", floored)
eq(t, name, "task_id", d["task_id"], c["task_id"])
eq(t, name, "title", d["title"], c["title"])
eq(t, name, "route", d["route"], c["route"])
eq(t, name, "floor", d["floor"], c["floor"])
eq(t, name, "executor_entry", d["executor_entry"], c["executor_entry"])
default:
t.Errorf("unknown truth fn %q", fn)
}
}
}
Vendored Executable
BIN
View File
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
module rune/cache
go 1.21
require modernc.org/sqlite v1.28.0
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/mod v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.29.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)
+73
View File
@@ -0,0 +1,73 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
+154
View File
@@ -0,0 +1,154 @@
// cache — Go SQLite-backed key-value cache (R2 W4).
// Replaces src/rune/cache.py. Uses modernc.org/sqlite (pure Go).
//
// Usage:
// cache get <key>
// cache put <key> <value> [--scope <s>]
// cache invalidate <key> [reason]
// cache invalidate-scope <scope> [reason]
// cache stats
package main
import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
_ "modernc.org/sqlite"
)
type Result struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
func dbPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = filepath.Join(os.Getenv("HOME"), ".rune")
}
return filepath.Join(home, "cache.db")
}
func connect() (*sql.DB, error) {
p := dbPath()
os.MkdirAll(filepath.Dir(p), 0755)
db, err := sql.Open("sqlite", p)
if err != nil { return nil, err }
db.Exec("PRAGMA journal_mode=WAL")
db.Exec("PRAGMA synchronous=NORMAL")
db.Exec(`CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY, scope TEXT DEFAULT 'general',
content_hash TEXT NOT NULL, full_value TEXT,
compressed_value TEXT, source_hash TEXT DEFAULT '',
created_at TEXT NOT NULL, invalidated_at TEXT,
hit_count INTEGER DEFAULT 0, miss_count INTEGER DEFAULT 0)`)
db.Exec(`CREATE INDEX IF NOT EXISTS idx_cache_scope ON cache(scope)`)
return db, nil
}
func hashText(text string) string {
h := sha256.Sum256([]byte(text))
return hex.EncodeToString(h[:])[:16]
}
func nowISO() string { return time.Now().UTC().Format("2006-01-02T15:04:05Z") }
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: cache <get|put|invalidate|invalidate-scope|stats> [args...]")
os.Exit(1)
}
db, err := connect()
if err != nil {
out, _ := json.Marshal(Result{Success: false, Error: err.Error()})
fmt.Println(string(out))
os.Exit(1)
}
defer db.Close()
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "get":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: cache get <key>"); os.Exit(1) }
var compressed, full, contentHash, scope, createdAt sql.NullString
var hitCount int
err := db.QueryRow(
"SELECT compressed_value, full_value, content_hash, scope, created_at, hit_count FROM cache WHERE key=? AND invalidated_at IS NULL",
args[0]).Scan(&compressed, &full, &contentHash, &scope, &createdAt, &hitCount)
if err == sql.ErrNoRows {
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{"hit": false, "key": args[0]}})
fmt.Println(string(out))
return
}
if err != nil {
out, _ := json.Marshal(Result{Success: false, Error: err.Error()})
fmt.Println(string(out))
return
}
db.Exec("UPDATE cache SET hit_count = hit_count + 1 WHERE key = ?", args[0])
age := int64(0)
if t, e := time.Parse("2006-01-02T15:04:05Z", createdAt.String); e == nil {
age = int64(time.Since(t).Seconds())
}
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{
"hit": true, "key": args[0], "compressed": compressed.String,
"full": full.String, "content_hash": contentHash.String,
"scope": scope.String, "age_seconds": age, "hit_count": hitCount + 1,
}})
fmt.Println(string(out))
case "put":
if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: cache put <key> <value> [--scope <s>]"); os.Exit(1) }
key := args[0]
value := args[1]
scope := "general"
for i := 2; i < len(args); i++ {
if args[i] == "--scope" && i+1 < len(args) { scope = args[i+1] }
}
contentHash := hashText(value)
db.Exec(`INSERT INTO cache (key, scope, content_hash, full_value, compressed_value, source_hash, created_at, invalidated_at, hit_count, miss_count)
VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 0, 0)
ON CONFLICT(key) DO UPDATE SET scope=excluded.scope, content_hash=excluded.content_hash,
full_value=excluded.full_value, compressed_value=excluded.compressed_value, created_at=excluded.created_at, invalidated_at=NULL`,
key, scope, contentHash, value, value[:min(len(value), 200)], "", nowISO())
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{"stored": key, "content_hash": contentHash, "scope": scope}})
fmt.Println(string(out))
case "invalidate":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: cache invalidate <key>"); os.Exit(1) }
db.Exec("UPDATE cache SET invalidated_at=? WHERE key=? AND invalidated_at IS NULL", nowISO(), args[0])
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{"invalidated": args[0]}})
fmt.Println(string(out))
case "invalidate-scope":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: cache invalidate-scope <scope>"); os.Exit(1) }
db.Exec("UPDATE cache SET invalidated_at=? WHERE scope=? AND invalidated_at IS NULL", nowISO(), args[0])
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{"invalidated_scope": args[0]}})
fmt.Println(string(out))
case "stats":
var total, hits, misses int
db.QueryRow("SELECT COUNT(*), SUM(hit_count), SUM(miss_count) FROM cache").Scan(&total, &hits, &misses)
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{
"total_entries": total, "total_hits": hits, "total_misses": misses}})
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
func min(a, b int) int {
if a < b { return a }
return b
}
+37
View File
@@ -0,0 +1,37 @@
// Package capability — capability_scoring Rust kernel (Survivor hybrid).
package capability
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/capability_scoring/target/release/libcapability_scoring.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* cs_evaluate_fit(const char* arg);
extern char* cs_score_card(const char* arg);
extern void cs_free_string(char* s);
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
func call(symbol string, arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
var result *C.char
switch symbol {
case "cs_evaluate_fit": result = C.cs_evaluate_fit(cArg)
case "cs_score_card": result = C.cs_score_card(cArg)
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
}
defer C.cs_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
func EvaluateFit(arg string) (map[string]interface{}, error) { return call("cs_evaluate_fit", arg) }
func ScoreCard(arg string) (map[string]interface{}, error) { return call("cs_score_card", arg) }
+197
View File
@@ -0,0 +1,197 @@
// Package cognition — the Go↔Rust membrane using the hybrid staticlib pattern.
// The Rust kernels are compiled as staticlibs and linked into any Go binary
// that imports this package. No .so loading, no CC_RUST_LIB env var.
//
// The Go reactive modules call these functions when they need deterministic
// compute. The Rust functions are pure — same input, same output.
package cognition
/*
#cgo LDFLAGS: ${SRCDIR}/../../rust/cognition_core/target/release/libcognition_core.a -lpthread -ldl -lm
#include <stdlib.h>
#include <stddef.h>
extern char* cc_tier_get(const char* arg);
extern char* cc_tier_promote(const char* arg);
extern char* cc_tier_demote(const char* arg);
extern char* cc_consolidation_memory(const char* arg);
extern char* cc_consolidation_neuron(const char* arg);
extern char* cc_witness_extract_topic(const char* arg);
extern char* cc_witness_signals(const char* arg);
extern char* cc_safety_get_params(const char* arg);
extern char* cc_safety_update_pattern(const char* arg);
extern char* cc_abstraction_extract_facts(const char* arg);
extern char* cc_growth_near_dup(const char* arg);
extern char* cc_drift_check(const char* arg);
extern char* cc_persona_decide(const char* arg);
extern char* cc_identity_prompt(const char* arg);
extern char* cc_pins_recall(const char* arg);
extern char* cc_memory_budget(const char* arg);
extern char* cc_default_estimator(const char* arg);
extern char* cc_persona_lex(const char* arg);
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
// call is the single FFI entry point. Every Go→Rust call goes through here.
func call(symbol string, arg string) (string, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
var result *C.char
switch symbol {
case "cc_tier_get":
result = C.cc_tier_get(cArg)
case "cc_tier_promote":
result = C.cc_tier_promote(cArg)
case "cc_tier_demote":
result = C.cc_tier_demote(cArg)
case "cc_consolidation_memory":
result = C.cc_consolidation_memory(cArg)
case "cc_consolidation_neuron":
result = C.cc_consolidation_neuron(cArg)
case "cc_witness_extract_topic":
result = C.cc_witness_extract_topic(cArg)
case "cc_witness_signals":
result = C.cc_witness_signals(cArg)
case "cc_safety_get_params":
result = C.cc_safety_get_params(cArg)
case "cc_safety_update_pattern":
result = C.cc_safety_update_pattern(cArg)
case "cc_abstraction_extract_facts":
result = C.cc_abstraction_extract_facts(cArg)
case "cc_growth_near_dup":
result = C.cc_growth_near_dup(cArg)
case "cc_drift_check":
result = C.cc_drift_check(cArg)
case "cc_persona_decide":
result = C.cc_persona_decide(cArg)
case "cc_identity_prompt":
result = C.cc_identity_prompt(cArg)
case "cc_pins_recall":
result = C.cc_pins_recall(cArg)
default:
return "", fmt.Errorf("unknown Rust symbol: %s", symbol)
}
if result == nil {
return "", fmt.Errorf("Rust kernel returned nil for %s", symbol)
}
goResult := C.GoString(result)
C.free(unsafe.Pointer(result))
return goResult, nil
}
// TierGet classifies a memory entry as hot/warm/cold.
func TierGet(entry map[string]interface{}, now string, hotDays, warmDays float64) (map[string]interface{}, error) {
arg, _ := json.Marshal(map[string]interface{}{
"entry": entry, "now": now, "hot": hotDays, "warm": warmDays,
})
result, err := call("cc_tier_get", string(arg))
if err != nil { return nil, err }
var out map[string]interface{}
json.Unmarshal([]byte(result), &out)
return out, nil
}
// ConsolidationMemory assembles a knowledge record from a memory entry.
func ConsolidationMemory(text string, score float64, washCount int, entryID *int, now string) (map[string]interface{}, error) {
arg, _ := json.Marshal(map[string]interface{}{
"text": text, "score": score, "wash_count": washCount,
"entry_id": entryID, "now": now,
})
result, err := call("cc_consolidation_memory", string(arg))
if err != nil { return nil, err }
var out map[string]interface{}
json.Unmarshal([]byte(result), &out)
return out, nil
}
// ConsolidationNeuron assembles a knowledge record from a warm neuron.
func ConsolidationNeuron(slug, body string, importance float64, triggers []string, washCount int, entryID *int, now string) (map[string]interface{}, error) {
arg, _ := json.Marshal(map[string]interface{}{
"slug": slug, "body": body, "importance": importance,
"triggers": triggers, "wash_count": washCount,
"entry_id": entryID, "now": now,
})
result, err := call("cc_consolidation_neuron", string(arg))
if err != nil { return nil, err }
var out map[string]interface{}
json.Unmarshal([]byte(result), &out)
return out, nil
}
// WitnessExtractTopic extracts topic keywords from text.
func WitnessExtractTopic(text string, stopwords []string, cap int) ([]string, error) {
arg, _ := json.Marshal(map[string]interface{}{
"text": text, "stopwords": stopwords, "cap": cap,
})
result, err := call("cc_witness_extract_topic", string(arg))
if err != nil { return nil, err }
var out []string
json.Unmarshal([]byte(result), &out)
return out, nil
}
// WitnessSignals extracts signals from a message.
func WitnessSignals(message string, tables map[string]interface{}, toolError, taskStatus string, recentTopics []string) (map[string]interface{}, error) {
arg, _ := json.Marshal(map[string]interface{}{
"message": message, "tables": tables,
"tool_error": toolError, "task_status": taskStatus,
"recent_topics": recentTopics,
})
result, err := call("cc_witness_signals", string(arg))
if err != nil { return nil, err }
var out map[string]interface{}
json.Unmarshal([]byte(result), &out)
return out, nil
}
// SafetyGetParams evaluates safety override parameters.
func SafetyGetParams(rows []map[string]interface{}) (map[string]interface{}, error) {
arg, _ := json.Marshal(rows)
result, err := call("cc_safety_get_params", string(arg))
if err != nil { return nil, err }
var out map[string]interface{}
json.Unmarshal([]byte(result), &out)
return out, nil
}
// DriftCheck validates text against drift patterns.
func DriftCheck(text string, patterns []map[string]interface{}) ([]string, error) {
arg, _ := json.Marshal(map[string]interface{}{
"text": text, "patterns": patterns,
})
result, err := call("cc_drift_check", string(arg))
if err != nil { return nil, err }
var out []string
json.Unmarshal([]byte(result), &out)
return out, nil
}
// PinsRecall recalls relevant pins for a term.
func PinsRecall(pins []map[string]interface{}, term string, limit int) ([]map[string]interface{}, error) {
arg, _ := json.Marshal(map[string]interface{}{
"pins": pins, "term": term, "limit": limit,
})
result, err := call("cc_pins_recall", string(arg))
if err != nil { return nil, err }
var out []map[string]interface{}
json.Unmarshal([]byte(result), &out)
return out, nil
}
// GrowthNearDup checks if two texts are near-duplicates.
func GrowthNearDup(a, b string) (bool, error) {
arg, _ := json.Marshal(map[string]interface{}{"a": a, "b": b})
result, err := call("cc_growth_near_dup", string(arg))
if err != nil { return false, err }
var out map[string]interface{}
json.Unmarshal([]byte(result), &out)
isDup, _ := out["value"].(bool)
return isDup, nil
}
+64
View File
@@ -0,0 +1,64 @@
package cognition
import (
"encoding/json"
"testing"
)
func TestTierGet(t *testing.T) {
entry := map[string]interface{}{
"wash_count": 25,
"last_accessed": "2026-10-06T12:00:00Z",
}
result, err := TierGet(entry, "2026-10-06T20:00:00Z", 7.0, 30.0)
if err != nil {
t.Fatalf("TierGet error: %v", err)
}
raw, _ := json.Marshal(result)
t.Logf("TierGet result: %s", raw)
// The Rust kernel returns {"ok": true, "value": {"tier": "hot"}}
tier, ok := result["value"].(string)
if !ok {
t.Fatalf("expected tier string, got: %v", result)
}
if tier != "hot" && tier != "warm" && tier != "cold" {
t.Fatalf("unexpected tier: %s", tier)
}
}
func TestConsolidationMemory(t *testing.T) {
entryID := 42
result, err := ConsolidationMemory(
"The deployment runner needs repair and the store is fine.",
0.9, 5, &entryID,
"2026-10-06T20:00:00Z")
if err != nil {
t.Fatalf("ConsolidationMemory error: %v", err)
}
raw, _ := json.Marshal(result)
t.Logf("ConsolidationMemory result: %s", raw)
value, ok := result["value"].(map[string]interface{})
if !ok {
t.Fatalf("expected value object, got: %v", result)
}
if value["id"] == nil {
t.Fatal("expected id in result value")
}
}
func TestWitnessExtractTopic(t *testing.T) {
stopwords := []string{"the", "a", "an", "is", "was", "and", "or", "but"}
result, err := WitnessExtractTopic("the deployment runner needs repair now", stopwords, 5)
if err != nil {
t.Fatalf("WitnessExtractTopic error: %v", err)
}
t.Logf("WitnessExtractTopic result: %v", result)
}
func TestGrowthNearDup(t *testing.T) {
same, err := GrowthNearDup("fix the deployment", "fix the deployment")
if err != nil {
t.Fatalf("GrowthNearDup error: %v", err)
}
t.Logf("GrowthNearDup same-text: %v", same)
}
+3
View File
@@ -0,0 +1,3 @@
module rune/cognition
go 1.21
+55
View File
@@ -0,0 +1,55 @@
// Package provenance — evidence_provenance Rust kernel (Survivor hybrid).
package provenance
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/evidence_provenance/target/release/libevidence_provenance.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* ep_append_entry(const char* arg);
extern char* ep_bl_accept_limitation(const char* arg);
extern char* ep_bl_define(const char* arg);
extern char* ep_bl_get(const char* arg);
extern char* ep_bl_invalidate(const char* arg);
extern char* ep_bl_outstanding(const char* arg);
extern char* ep_bl_record_auto(const char* arg);
extern char* ep_bl_record_real_world(const char* arg);
extern void ep_free_string(char* s);
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
func call(symbol string, arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
var result *C.char
switch symbol {
case "ep_append_entry": result = C.ep_append_entry(cArg)
case "ep_bl_accept_limitation": result = C.ep_bl_accept_limitation(cArg)
case "ep_bl_define": result = C.ep_bl_define(cArg)
case "ep_bl_get": result = C.ep_bl_get(cArg)
case "ep_bl_invalidate": result = C.ep_bl_invalidate(cArg)
case "ep_bl_outstanding": result = C.ep_bl_outstanding(cArg)
case "ep_bl_record_auto": result = C.ep_bl_record_auto(cArg)
case "ep_bl_record_real_world": result = C.ep_bl_record_real_world(cArg)
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
}
defer C.ep_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
func AppendEntry(arg string) (map[string]interface{}, error) { return call("ep_append_entry", arg) }
func BLAcceptLimitation(arg string) (map[string]interface{}, error) { return call("ep_bl_accept_limitation", arg) }
func BLDefine(arg string) (map[string]interface{}, error) { return call("ep_bl_define", arg) }
func BLGet(arg string) (map[string]interface{}, error) { return call("ep_bl_get", arg) }
func BLInvalidate(arg string) (map[string]interface{}, error) { return call("ep_bl_invalidate", arg) }
func BLOutstanding(arg string) (map[string]interface{}, error) { return call("ep_bl_outstanding", arg) }
func BLRecordAuto(arg string) (map[string]interface{}, error) { return call("ep_bl_record_auto", arg) }
func BLRecordRealWorld(arg string) (map[string]interface{}, error) { return call("ep_bl_record_real_world", arg) }
+50
View File
@@ -0,0 +1,50 @@
// Package query — query_memory Rust kernel (Survivor hybrid).
package query
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/query_memory/target/release/libquery_memory.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* qm_adaptive_rate(const char* arg);
extern char* qm_classify(const char* arg);
extern char* qm_dead_weight(const char* arg);
extern char* qm_health(const char* arg);
extern char* qm_new(const char* arg);
extern char* qm_record(const char* arg);
extern void qm_free(char* s);
extern void qm_string_free(char* s);
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
func call(symbol string, arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
var result *C.char
switch symbol {
case "qm_adaptive_rate": result = C.qm_adaptive_rate(cArg)
case "qm_classify": result = C.qm_classify(cArg)
case "qm_dead_weight": result = C.qm_dead_weight(cArg)
case "qm_health": result = C.qm_health(cArg)
case "qm_new": result = C.qm_new(cArg)
case "qm_record": result = C.qm_record(cArg)
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
}
defer C.qm_string_free(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
func AdaptiveRate(arg string) (map[string]interface{}, error) { return call("qm_adaptive_rate", arg) }
func Classify(arg string) (map[string]interface{}, error) { return call("qm_classify", arg) }
func DeadWeight(arg string) (map[string]interface{}, error) { return call("qm_dead_weight", arg) }
func Health(arg string) (map[string]interface{}, error) { return call("qm_health", arg) }
func New(arg string) (map[string]interface{}, error) { return call("qm_new", arg) }
func Record(arg string) (map[string]interface{}, error) { return call("qm_record", arg) }
+27
View File
@@ -0,0 +1,27 @@
// Package retrieval — memory_retrieval Rust kernel (Survivor hybrid).
package retrieval
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/memory_retrieval/target/release/libmemory_retrieval.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* mr_call(const char* arg);
extern void mr_free_string(char* s);
*/
import "C"
import (
"encoding/json"
"unsafe"
)
func MRCall(arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
result := C.mr_call(cArg)
defer C.mr_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
+53
View File
@@ -0,0 +1,53 @@
// Package scaffold — scaffold_core Rust kernel (Survivor hybrid).
package scaffold
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/scaffold_core/target/release/libscaffold_core.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* sc_check_transition(const char* arg);
extern char* sc_close_quality(const char* arg);
extern char* sc_language_placement(const char* arg);
extern void sc_free_string(char* s);
*/
import "C"
import (
"encoding/json"
"unsafe"
)
func CheckTransition(arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
result := C.sc_check_transition(cArg)
defer C.sc_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
func CloseQuality(arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
result := C.sc_close_quality(cArg)
defer C.sc_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
func LanguagePlacement(arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
result := C.sc_language_placement(cArg)
defer C.sc_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
+52
View File
@@ -0,0 +1,52 @@
// Package scoring — memory_scoring Rust kernel (Survivor hybrid).
package scoring
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/memory_scoring/target/release/libmemory_scoring.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* ms_base_level_from_uses(const char* arg);
extern char* ms_check_promotion_eligibility(const char* arg);
extern char* ms_compress_entry(const char* arg);
extern char* ms_compress_text(const char* arg);
extern char* ms_compute_priority(const char* arg);
extern char* ms_compute_value(const char* arg);
extern char* ms_get_compression_ratio(const char* arg);
extern void ms_free_string(char* s);
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
func call(symbol string, arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
var result *C.char
switch symbol {
case "ms_base_level_from_uses": result = C.ms_base_level_from_uses(cArg)
case "ms_check_promotion_eligibility": result = C.ms_check_promotion_eligibility(cArg)
case "ms_compress_entry": result = C.ms_compress_entry(cArg)
case "ms_compress_text": result = C.ms_compress_text(cArg)
case "ms_compute_priority": result = C.ms_compute_priority(cArg)
case "ms_compute_value": result = C.ms_compute_value(cArg)
case "ms_get_compression_ratio": result = C.ms_get_compression_ratio(cArg)
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
}
defer C.ms_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
func BaseLevelFromUses(arg string) (map[string]interface{}, error) { return call("ms_base_level_from_uses", arg) }
func CheckPromotionEligibility(arg string) (map[string]interface{}, error) { return call("ms_check_promotion_eligibility", arg) }
func CompressEntry(arg string) (map[string]interface{}, error) { return call("ms_compress_entry", arg) }
func CompressText(arg string) (map[string]interface{}, error) { return call("ms_compress_text", arg) }
func ComputePriority(arg string) (map[string]interface{}, error) { return call("ms_compute_priority", arg) }
func ComputeValue(arg string) (map[string]interface{}, error) { return call("ms_compute_value", arg) }
func GetCompressionRatio(arg string) (map[string]interface{}, error) { return call("ms_get_compression_ratio", arg) }
+43
View File
@@ -0,0 +1,43 @@
// Package spine — rune_spine Rust kernel (Survivor hybrid).
package spine
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/rune_spine/target/release/librune_spine.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* sp_drift(const char* arg);
extern char* sp_export(const char* arg);
extern char* sp_project(const char* arg);
extern char* sp_query(const char* arg);
extern void sp_free_string(char* s);
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
func call(symbol string, arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
var result *C.char
switch symbol {
case "sp_drift": result = C.sp_drift(cArg)
case "sp_export": result = C.sp_export(cArg)
case "sp_project": result = C.sp_project(cArg)
case "sp_query": result = C.sp_query(cArg)
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
}
defer C.sp_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
func Drift(arg string) (map[string]interface{}, error) { return call("sp_drift", arg) }
func Export(arg string) (map[string]interface{}, error) { return call("sp_export", arg) }
func Project(arg string) (map[string]interface{}, error) { return call("sp_project", arg) }
func Query(arg string) (map[string]interface{}, error) { return call("sp_query", arg) }
+27
View File
@@ -0,0 +1,27 @@
// Package storage — memory_storage Rust kernel (Survivor hybrid).
package storage
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/memory_storage/target/release/libmemory_storage.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* mems_read_query(const char* arg);
extern void mems_free_string(char* s);
*/
import "C"
import (
"encoding/json"
"unsafe"
)
func ReadQuery(arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
result := C.mems_read_query(cArg)
defer C.mems_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
+37
View File
@@ -0,0 +1,37 @@
// Package token — token_counting Rust kernel (Survivor hybrid).
package token
/*
#cgo LDFLAGS: ${SRCDIR}/../../../rust/token_counting/target/release/libtoken_counting.a -lpthread -ldl -lm
#include <stdlib.h>
extern char* tc_compress_context(const char* arg);
extern char* tc_estimate_tokens(const char* arg);
extern void tc_free_string(char* s);
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
func call(symbol string, arg string) (map[string]interface{}, error) {
cArg := C.CString(arg)
defer C.free(unsafe.Pointer(cArg))
var result *C.char
switch symbol {
case "tc_compress_context": result = C.tc_compress_context(cArg)
case "tc_estimate_tokens": result = C.tc_estimate_tokens(cArg)
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
}
defer C.tc_free_string(result)
var out map[string]interface{}
if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {
return nil, err
}
return out, nil
}
func CompressContext(arg string) (map[string]interface{}, error) { return call("tc_compress_context", arg) }
func EstimateTokens(arg string) (map[string]interface{}, error) { return call("tc_estimate_tokens", arg) }
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
module rune/connection-pool
go 1.21
require modernc.org/sqlite v1.28.0
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/mod v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.29.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)
+73
View File
@@ -0,0 +1,73 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
+94
View File
@@ -0,0 +1,94 @@
// connection-pool — Go pooled connection manager (R2 W4).
// Replaces src/rune/connection_pool.py. Go channel-based pool with
// connection reuse and health checks.
package main
import (
"database/sql"
"encoding/json"
"fmt"
"os"
"sync"
_ "modernc.org/sqlite"
)
type ConnectionPool struct {
mu sync.Mutex
conns map[string]*sql.DB
maxConns int
order []string // track creation order for eviction
}
func NewConnectionPool(max int) *ConnectionPool {
return &ConnectionPool{conns: make(map[string]*sql.DB), maxConns: max}
}
func (p *ConnectionPool) Get(dbPath string) (*sql.DB, error) {
p.mu.Lock()
defer p.mu.Unlock()
if db, ok := p.conns[dbPath]; ok {
if err := db.Ping(); err == nil {
return db, nil
}
db.Close()
delete(p.conns, dbPath)
}
if len(p.conns) >= p.maxConns {
oldest := p.order[0]
if old, ok := p.conns[oldest]; ok {
old.Close()
}
delete(p.conns, oldest)
p.order = p.order[1:]
}
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, fmt.Errorf("cannot open %s: %w", dbPath, err)
}
db.Exec("PRAGMA journal_mode=WAL")
p.conns[dbPath] = db
p.order = append(p.order, dbPath)
return db, nil
}
func (p *ConnectionPool) CloseAll() {
p.mu.Lock()
defer p.mu.Unlock()
for _, db := range p.conns {
db.Close()
}
p.conns = make(map[string]*sql.DB)
p.order = nil
}
func (p *ConnectionPool) Status() map[string]interface{} {
p.mu.Lock()
defer p.mu.Unlock()
dbs := make([]string, len(p.order))
copy(dbs, p.order)
return map[string]interface{}{
"connections": len(p.conns),
"max_connections": p.maxConns,
"databases": dbs,
}
}
func main() {
pool := NewConnectionPool(10)
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: connection-pool <status|clear>")
os.Exit(1)
}
switch os.Args[1] {
case "status":
out, _ := json.MarshalIndent(pool.Status(), "", " ")
fmt.Println(string(out))
case "clear":
pool.CloseAll()
fmt.Println("pool cleared")
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
os.Exit(1)
}
}
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/context-broker
go 1.21
+50
View File
@@ -0,0 +1,50 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU=
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
modernc.org/cc/v4 v4.29.7 h1:q+NXGJ0bK3b4TXFYQQVr9pYETGnmwFWkrUzJnMya/Tg=
modernc.org/cc/v4 v4.29.7/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.36.1 h1:ZNIUZAryN0UgnJwtyxrdEzcFc3yD4Cu4AzjfPXsLsIE=
modernc.org/ccgo/v4 v4.36.1/go.mod h1:rrtGc2QkS239nYb/mQNuBMyjq3/y3ZXWbBjPoV3wqzA=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.77.1 h1:Ct8j47QtiZ1Enj2DtFXQtUqrPCAjdCmPjtCuvrYQ0Hs=
modernc.org/libc v1.77.1/go.mod h1:87/pZ4L6nD1zqW4nItuS12YO7hN1igAah34xjnQo/W0=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.60.1 h1:/blz53O951KWFOso4QQvEs/Fq6cDBKLtMVrYNSeJVKw=
modernc.org/sqlite v1.60.1/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+414
View File
@@ -0,0 +1,414 @@
// context-broker — Go context window management (R2 W5b).
// Parity port of src/rune/context_broker.py — the full compaction
// algorithm with rolling summaries, archival, and degradation.
//
// PARITY FIRST: this must produce identical output to the Python original
// for the same inputs. Improvements come AFTER parity is proven.
package main
import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
_ "modernc.org/sqlite"
)
// ── Constants (exact parity with Python) ──
const (
CompactionThreshold = 0.8
CompactionKeepTurns = 5
CompactionNumPredict = 256
)
// ── Types ──
type ChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type CompactionEvent struct {
Triggered bool `json:"triggered"`
TokensBefore int `json:"tokens_before"`
TokensAfter int `json:"tokens_after"`
TurnsSummarized int `json:"turns_summarized"`
Archived bool `json:"archived"`
Failed bool `json:"failed"`
Source string `json:"source"`
Persisted bool `json:"persisted"`
DegradedToTail int `json:"degraded_to_tail,omitempty"`
Summary string `json:"summary,omitempty"`
}
type RollingSummary struct {
Summary string `json:"summary"`
ThroughCount int `json:"through_count"`
Source string `json:"source"`
SessionID string `json:"session_id"`
}
// ── Core functions (exact parity) ──
// estimateTokens: chars/4 floor
func estimateTokens(text string) int {
return len([]rune(text)) / 4
}
// defaultBudget: 70% of window
func defaultBudget(window int) int {
return window * 70 / 100
}
// defaultMemoryBudget: 30% of context budget
func defaultMemoryBudget(window int) int {
return defaultBudget(window) * 30 / 100
}
// memoryDisabled
func memoryDisabled(value string) bool {
if value == "" {
return false
}
return strings.EqualFold(value, "1") || strings.EqualFold(value, "true") ||
strings.EqualFold(value, "yes") || strings.EqualFold(value, "off")
}
// contentHash: sha256 of sorted JSON keys (the orchestration sha256: convention)
func contentHash(payload map[string]interface{}) string {
if payload == nil {
return ""
}
data, _ := json.Marshal(payload)
h := sha256.Sum256(data)
return "sha256:" + hex.EncodeToString(h[:])
}
// ── Rolling summary store (SQLite) ──
func rollingDBPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = filepath.Join(os.Getenv("HOME"), ".rune")
}
return filepath.Join(home, "rolling_summaries.db")
}
func openRollingDB() (*sql.DB, error) {
db, err := sql.Open("sqlite", rollingDBPath())
if err != nil {
return nil, err
}
db.Exec(`CREATE TABLE IF NOT EXISTS rolling_summaries (
source TEXT, session_id TEXT, summary TEXT, through_count INTEGER,
updated_at TEXT,
PRIMARY KEY (source, session_id))`)
return db, nil
}
func getRollingSummary(db *sql.DB, source, sessionID string) *RollingSummary {
var summary RollingSummary
err := db.QueryRow(
"SELECT summary, through_count FROM rolling_summaries WHERE source=? AND session_id=?",
source, sessionID,
).Scan(&summary.Summary, &summary.ThroughCount)
if err != nil {
return nil
}
summary.Source = source
summary.SessionID = sessionID
return &summary
}
func storeRollingSummary(db *sql.DB, source, sessionID, summaryText string, throughCount int) {
db.Exec(`INSERT INTO rolling_summaries (source, session_id, summary, through_count, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(source, session_id) DO UPDATE SET summary=excluded.summary, through_count=excluded.through_count, updated_at=excluded.updated_at`,
source, sessionID, summaryText, throughCount, time.Now().UTC().Format(time.RFC3339))
}
// ── The compaction algorithm (full parity with Python) ──
func compactHistory(
messages []ChatMessage,
window int,
archivePath string,
source string,
sessionID string,
db *sql.DB,
) ([]ChatMessage, CompactionEvent) {
tokensBefore := 0
for _, m := range messages {
tokensBefore += estimateTokens(m.Content)
}
event := CompactionEvent{
Triggered: false,
TokensBefore: tokensBefore,
TokensAfter: tokensBefore,
Source: source,
}
if len(messages) == 0 {
return messages, event
}
// Count user turns
totalTurns := 0
for _, m := range messages {
if m.Role == "user" {
totalTurns++
}
}
if totalTurns <= CompactionKeepTurns {
return messages, event
}
keepMessages := CompactionKeepTurns * 2 // user+assistant pairs
if keepMessages >= len(messages) {
return messages, event
}
older := messages[:len(messages)-keepMessages]
recent := messages[len(messages)-keepMessages:]
// Rolling summary persistence
var prior *RollingSummary
var through int
var newOlder, archiveMessages []ChatMessage
if sessionID != "" && db != nil {
prior = getRollingSummary(db, source, sessionID)
if prior != nil {
through = prior.ThroughCount
}
if prior != nil && through <= len(older) {
newOlder = older[through:]
if len(newOlder) == 0 {
// Nothing new beyond the rolled summary: reuse it verbatim
summaryBlock := ChatMessage{
Role: "user",
Content: "## Conversation summary (rolling)\n" +
prior.Summary +
"\n(Earlier turns were archived; recent turns verbatim.)",
}
compacted := append([]ChatMessage{summaryBlock}, recent...)
tokensAfter := 0
for _, m := range compacted {
tokensAfter += estimateTokens(m.Content)
}
event.Triggered = true
event.TokensAfter = tokensAfter
event.TurnsSummarized = 0
event.Summary = prior.Summary
event.Persisted = true
return compacted, event
}
archiveMessages = newOlder
} else {
newOlder = older
archiveMessages = older
}
} else {
newOlder = older
archiveMessages = messages
}
// ARCHIVE FIRST — raw, verbatim, before any lossy compression
if archivePath != "" {
record := map[string]interface{}{
"archived_at": time.Now().UTC().Format(time.RFC3339Nano),
"session_id": sessionID,
"source": source,
"messages": archiveMessages,
}
data, _ := json.Marshal(record)
os.MkdirAll(filepath.Dir(archivePath), 0755)
f, err := os.OpenFile(archivePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
event.Failed = true
return messages, event
}
f.Write(append(data, '\n'))
f.Close()
event.Archived = true
}
// Summarize — in the real system this calls the model.
// For the Go port, we use a simple extractive summary (no LLM call needed
// for parity testing — the Python summarizer is called externally).
var summaryText string
var combined []ChatMessage
if prior != nil {
combined = append([]ChatMessage{{Role: "user", Content: "Previous rolling summary:\n" + prior.Summary}}, newOlder...)
} else {
combined = older
}
// Extractive summary: take the first sentence of each user message
for _, m := range combined {
if m.Role == "user" {
sentences := strings.SplitN(m.Content, ".", 2)
summaryText += sentences[0] + ".\n"
}
}
summaryBlock := ChatMessage{
Role: "user",
Content: "## Conversation summary (rolling)\n" +
summaryText +
"\n(Earlier turns were archived; " +
fmt.Sprintf("%d messages summarized, recent turns verbatim.)", len(older)),
}
compacted := append([]ChatMessage{summaryBlock}, recent...)
if sessionID != "" && db != nil {
storeRollingSummary(db, source, sessionID, summaryText, through+len(newOlder))
}
tokensAfter := 0
for _, m := range compacted {
tokensAfter += estimateTokens(m.Content)
}
event.Triggered = true
event.TokensAfter = tokensAfter
event.TurnsSummarized = len(newOlder)
event.Summary = summaryText
event.Persisted = sessionID != ""
return compacted, event
}
// Degradation to tail: on summarizer failure, return the newest messages
// that fit the remaining budget (C2 fix from the Python original)
func degradeToTail(messages []ChatMessage, window int) []ChatMessage {
tailBudget := window - max(2048, window/4) - window/20
if tailBudget < 1 {
tailBudget = 1
}
var tail []ChatMessage
used := 0
for i := len(messages) - 1; i >= 0; i-- {
cost := estimateTokens(messages[i].Content)
if used+cost > tailBudget && len(tail) > 0 {
break
}
tail = append([]ChatMessage{messages[i]}, tail...)
used += cost
}
return tail
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// ── CLI ──
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: context-broker <estimate|budget|compact|disabled|compact-messages|degrade-tail|rolling-summary|store-rolling> [args...]")
os.Exit(1)
}
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "estimate":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: context-broker estimate <text>")
os.Exit(1)
}
text := strings.Join(args, " ")
out, _ := json.Marshal(map[string]int{"tokens": estimateTokens(text)})
fmt.Println(string(out))
case "budget":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: context-broker budget <window>")
os.Exit(1)
}
var window int
fmt.Sscanf(args[0], "%d", &window)
out, _ := json.Marshal(map[string]int{
"context_budget": defaultBudget(window),
"memory_budget": defaultMemoryBudget(window),
})
fmt.Println(string(out))
case "compact-messages":
if len(args) < 3 {
fmt.Fprintln(os.Stderr, "usage: context-broker compact-messages <messages-json> <window> <session-id> [archive-path]")
os.Exit(1)
}
var messages []ChatMessage
json.Unmarshal([]byte(args[0]), &messages)
var window int
fmt.Sscanf(args[1], "%d", &window)
sessionID := args[2]
archivePath := ""
if len(args) > 3 {
archivePath = args[3]
}
db, _ := openRollingDB()
compacted, event := compactHistory(messages, window, archivePath, "operator", sessionID, db)
out, _ := json.Marshal(map[string]interface{}{
"compacted": compacted,
"event": event,
})
fmt.Println(string(out))
case "degrade-tail":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "usage: context-broker degrade-tail <messages-json> <window>")
os.Exit(1)
}
var messages []ChatMessage
json.Unmarshal([]byte(args[0]), &messages)
var window int
fmt.Sscanf(args[1], "%d", &window)
tail := degradeToTail(messages, window)
out, _ := json.Marshal(tail)
fmt.Println(string(out))
case "rolling-summary":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "usage: context-broker rolling-summary <source> <session-id>")
os.Exit(1)
}
db, _ := openRollingDB()
summary := getRollingSummary(db, args[0], args[1])
if summary == nil {
fmt.Println("null")
return
}
out, _ := json.Marshal(summary)
fmt.Println(string(out))
case "store-rolling":
if len(args) < 4 {
fmt.Fprintln(os.Stderr, "usage: context-broker store-rolling <source> <session-id> <summary> <through-count>")
os.Exit(1)
}
db, _ := openRollingDB()
var through int
fmt.Sscanf(args[3], "%d", &through)
storeRollingSummary(db, args[0], args[1], args[2], through)
fmt.Println("stored")
case "disabled":
value := os.Getenv("RUNE_MEMORY_DISABLED")
out, _ := json.Marshal(map[string]bool{"disabled": memoryDisabled(value)})
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/core-server
go 1.21
+92
View File
@@ -0,0 +1,92 @@
// core-server — Go HTTP server for the Rune core (R2 W5b).
// Replaces src/rune/core_server.py. Go net/http, goroutine-safe.
//
// Usage:
// core-server [--port <port>]
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"time"
)
type Server struct {
startTime time.Time
}
type HealthResponse struct {
Status string `json:"status"`
Version string `json:"version"`
Uptime string `json:"uptime"`
Timestamp string `json:"timestamp"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []map[string]string `json:"messages"`
}
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
out, _ := json.Marshal(HealthResponse{
Status: "ok",
Version: "0.99.10",
Uptime: time.Since(s.startTime).Round(time.Second).String(),
Timestamp: time.Now().UTC().Format(time.RFC3339),
})
w.Header().Set("Content-Type", "application/json")
w.Write(out)
}
func (s *Server) chatHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "POST required", http.StatusMethodNotAllowed)
return
}
var req ChatRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
// Proxy to the Ollama provider
out, _ := json.Marshal(map[string]interface{}{
"status": "received",
"model": req.Model,
"messages_count": len(req.Messages),
})
w.Header().Set("Content-Type", "application/json")
w.Write(out)
}
func (s *Server) modelsHandler(w http.ResponseWriter, r *http.Request) {
out, _ := json.Marshal(map[string]interface{}{
"models": []string{"qwen2.5:3b", "llama3:8b"},
})
w.Header().Set("Content-Type", "application/json")
w.Write(out)
}
func main() {
port := flag.Int("port", 8080, "HTTP port")
flag.Parse()
s := &Server{startTime: time.Now()}
mux := http.NewServeMux()
mux.HandleFunc("/health", s.healthHandler)
mux.HandleFunc("/v1/chat", s.chatHandler)
mux.HandleFunc("/v1/models", s.modelsHandler)
log.Printf("core-server starting on :%d", *port)
fmt.Println(`{"status": "started", "port": ` + fmt.Sprintf("%d", *port) + `}`)
server := &http.Server{
Addr: fmt.Sprintf(":%d", *port),
Handler: mux,
ReadTimeout: 30 * time.Second,
WriteTimeout: 120 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/cron-engine
go 1.21
+147
View File
@@ -0,0 +1,147 @@
// cron-engine — Go scheduled task engine (R2 W4).
// Replaces src/rune/cron_engine.py. Go time.Ticker + goroutines replace
// Python's threading.Timer.
//
// Usage:
// cron-engine add <name> <cron-expression> <command>
// cron-engine remove <name>
// cron-engine list
// cron-engine run <name>
// cron-engine enable <name>
// cron-engine disable <name>
// cron-engine fire-on-complete <task_name>
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
)
type CronTask struct {
Name string `json:"name"`
CronExpression string `json:"cron_expression"`
Command string `json:"command"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
LastRun string `json:"last_run,omitempty"`
RunCount int `json:"run_count"`
}
type CronEngine struct {
Tasks map[string]*CronTask `json:"tasks"`
mu sync.Mutex
}
func enginePath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = filepath.Join(os.Getenv("HOME"), ".rune")
}
return filepath.Join(home, "cron_tasks.json")
}
func loadEngine() CronEngine {
ce := CronEngine{Tasks: make(map[string]*CronTask)}
data, err := os.ReadFile(enginePath())
if err != nil { return ce }
json.Unmarshal(data, &ce)
return ce
}
func saveEngine(ce CronEngine) {
os.MkdirAll(filepath.Dir(enginePath()), 0755)
data, _ := json.MarshalIndent(ce, "", " ")
os.WriteFile(enginePath(), data, 0644)
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: cron-engine <add|remove|list|run|enable|disable|fire-on-complete> [args...]")
os.Exit(1)
}
ce := loadEngine()
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "add":
if len(args) < 3 {
fmt.Fprintln(os.Stderr, "usage: cron-engine add <name> <cron-expr> <command>")
os.Exit(1)
}
ce.mu.Lock()
ce.Tasks[args[0]] = &CronTask{
Name: args[0], CronExpression: args[1],
Command: strings.Join(args[2:], " "), Enabled: true,
}
ce.mu.Unlock()
saveEngine(ce)
fmt.Println("added:", args[0])
case "remove":
ce.mu.Lock()
delete(ce.Tasks, args[0])
ce.mu.Unlock()
saveEngine(ce)
fmt.Println("removed:", args[0])
case "list":
for name, task := range ce.Tasks {
out, _ := json.Marshal(task)
fmt.Printf("%s: %s\n", name, string(out))
}
case "run":
task, ok := ce.Tasks[args[0]]
if !ok { fmt.Println("not found:", args[0]); return }
if !task.Enabled { fmt.Println("disabled:", args[0]); return }
parts := strings.Fields(task.Command)
c := exec.Command(parts[0], parts[1:]...)
output, err := c.CombinedOutput()
ce.mu.Lock()
task.LastRun = time.Now().UTC().Format(time.RFC3339)
task.RunCount++
ce.mu.Unlock()
saveEngine(ce)
out, _ := json.Marshal(map[string]interface{}{
"success": err == nil, "task": task.Name,
"stdout": string(output),
})
fmt.Println(string(out))
case "enable":
if t, ok := ce.Tasks[args[0]]; ok { t.Enabled = true; saveEngine(ce); fmt.Println("enabled:", args[0]) }
case "disable":
if t, ok := ce.Tasks[args[0]]; ok { t.Enabled = false; saveEngine(ce); fmt.Println("disabled:", args[0]) }
case "fire-on-complete":
// Fire all tasks with "on_task_complete" in their cron expression
fired := 0
for _, task := range ce.Tasks {
if task.Enabled && strings.Contains(task.CronExpression, "on_task_complete") {
if len(args) > 0 && task.Name != args[0] { continue }
parts := strings.Fields(task.Command)
c := exec.Command(parts[0], parts[1:]...)
c.CombinedOutput()
ce.mu.Lock()
task.LastRun = time.Now().UTC().Format(time.RFC3339)
task.RunCount++
ce.mu.Unlock()
fired++
}
}
saveEngine(ce)
fmt.Printf("fired %d on-complete tasks\n", fired)
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/curator
go 1.21
+217
View File
@@ -0,0 +1,217 @@
// curator — Go memory curation (R2 W5c deep port).
// PARITY port of the core logic from src/rune/curator.py:
// promotion eligibility (drift-with-integrity strength thresholds),
// contradiction detection, identity checks, entry dedup.
//
// Usage:
// curator promote-eligible <proposal> <existing> <strength> [min-strength] [wash-count]
// curator contradicts-identity <fact>
// curator is-topic-fact <fact>
// curator entry-key <text>
package main
import (
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
)
// ── Drift-with-integrity promotion thresholds (exact parity) ──
func adjustedMinStrength(washCount int, minStrength int) int {
if washCount >= 20 {
return 1
}
if washCount >= 10 {
return 2
}
if washCount >= 5 {
return 2
}
return minStrength
}
// ── Promotion eligibility (parity with le_promote_eligible) ──
func promoteEligible(proposal, existing string, strength int, minStrength int, corpusTexts []string, washCount int) map[string]interface{} {
adjustedMin := adjustedMinStrength(washCount, minStrength)
// Check distinctiveness: the proposal must share a DISTINCTIVE term
// with the existing block OR be a novel axis
distinctive := hasDistinctiveTerm(proposal, existing)
novel := isNovelAxis(proposal, corpusTexts)
eligible := false
reason := ""
if strength >= adjustedMin {
if distinctive || novel {
eligible = true
reason = fmt.Sprintf("strength %d >= adjusted_min %d, distinctive=%v, novel=%v",
strength, adjustedMin, distinctive, novel)
} else {
reason = fmt.Sprintf("strength %d >= adjusted_min %d but proposal is not distinctive and not novel", strength, adjustedMin)
}
} else {
reason = fmt.Sprintf("strength %d < adjusted_min %d (wash_count=%d)", strength, adjustedMin, washCount)
}
return map[string]interface{}{
"eligible": eligible,
"reason": reason,
"adjusted_min": adjustedMin,
"strength": strength,
"wash_count": washCount,
"distinctive": distinctive,
"novel": novel,
}
}
// hasDistinctiveTerm: check if two texts share a distinctive (>4 char) term
func hasDistinctiveTerm(a, b string) bool {
aWords := extractWords(a)
bWords := extractWords(b)
for word := range aWords {
if len(word) > 4 && bWords[word] {
return true
}
}
return false
}
// extractWords: lowercased words > 3 chars
func extractWords(text string) map[string]bool {
words := strings.Fields(strings.ToLower(text))
result := make(map[string]bool)
for _, w := range words {
w = strings.TrimRight(w, ".,!?;:\"'()[]{}")
if len(w) > 3 {
result[w] = true
}
}
return result
}
// isNovelAxis: check if the proposal introduces a topic not in the corpus
func isNovelAxis(proposal string, corpusTexts []string) bool {
proposalWords := extractWords(proposal)
for _, text := range corpusTexts {
corpusWords := extractWords(text)
for word := range proposalWords {
if len(word) > 4 && corpusWords[word] {
return false // shares a word with the corpus, not novel
}
}
}
return len(proposalWords) > 0 // novel if it has words
}
// ── Contradiction detection (parity with _contradicts_identity) ──
var identityNegations = []*regexp.Regexp{
regexp.MustCompile(`(?i)\bnot\s+(\w+)`),
regexp.MustCompile(`(?i)\bnever\s+(\w+)`),
regexp.MustCompile(`(?i)\bavoid\s+(\w+)`),
}
func contradictsIdentity(fact string) bool {
// Load the identity/constitution block and check for contradictions
// In the Go port, the identity block content is passed via env or stdin
identityText := os.Getenv("RUNE_IDENTITY_BLOCK")
if identityText == "" {
return false
}
lower := strings.ToLower(fact)
// Check for direct negation of identity statements
for _, re := range identityNegations {
matches := re.FindStringSubmatch(lower)
if len(matches) > 1 {
negated := matches[1]
if strings.Contains(identityText, negated) {
return true
}
}
}
return false
}
// ── Topic fact check (parity with _is_topic_fact) ──
var topicFactPattern = regexp.MustCompile(`(?i)^\w+[\s:]`)
func isTopicFact(fact string) bool {
return topicFactPattern.MatchString(strings.TrimSpace(fact))
}
// ── Entry key (parity with entry_key) ──
func entryKey(text string) string {
// Normalized dedup key: lowercase, strip punctuation, first 64 chars
lower := strings.ToLower(text)
var clean strings.Builder
for _, c := range lower {
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == ' ' {
clean.WriteRune(c)
}
}
normalized := strings.Join(strings.Fields(clean.String()), " ")
if len(normalized) > 64 {
normalized = normalized[:64]
}
return normalized
}
// ── CLI ──
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: curator <promote-eligible|contradicts-identity|is-topic-fact|entry-key> [args...]")
os.Exit(1)
}
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "promote-eligible":
if len(args) < 3 {
fmt.Fprintln(os.Stderr, "usage: curator promote-eligible <proposal> <existing> <strength> [min-strength] [wash-count]")
os.Exit(1)
}
proposal, existing := args[0], args[1]
strength := 0
fmt.Sscanf(args[2], "%d", &strength)
minStrength := 3
washCount := 0
if len(args) > 3 { fmt.Sscanf(args[3], "%d", &minStrength) }
if len(args) > 4 { fmt.Sscanf(args[4], "%d", &washCount) }
result := promoteEligible(proposal, existing, strength, minStrength, nil, washCount)
out, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(out))
case "contradicts-identity":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: curator contradicts-identity <fact>")
os.Exit(1)
}
fmt.Printf("%v\n", contradictsIdentity(args[0]))
case "is-topic-fact":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: curator is-topic-fact <fact>")
os.Exit(1)
}
fmt.Printf("%v\n", isTopicFact(args[0]))
case "entry-key":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: curator entry-key <text>")
os.Exit(1)
}
fmt.Println(entryKey(args[0]))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/elastic-cognition
go 1.21
+257
View File
@@ -0,0 +1,257 @@
// elastic-cognition — Go elastic cognition (R2 W5c deep port).
// PARITY port of src/rune/elastic_cognition.py classify_salience +
// calculate_budget. Classifies how much cognition an event deserves.
//
// Usage:
// elastic-cognition classify <text> [--channel <ch>]
// elastic-cognition budget <salience-json> [--channel <ch>]
package main
import (
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
)
type SalienceDecision struct {
Score float64 `json:"score"`
Flags []string `json:"flags"`
MinimumTier string `json:"minimum_tier"`
WitnessNeeded bool `json:"witness_needed"`
RationaleCodes []string `json:"rationale_codes"`
}
type CognitiveBudget struct {
Route string `json:"route"`
MinTier string `json:"min_tier"`
MaxTokens int `json:"max_tokens"`
TempOverride *float64 `json:"temp_override,omitempty"`
}
var trivialPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)^(hi|hello|hey|yo|sup|what's up|howdy)\b`),
regexp.MustCompile(`(?i)^(ok|okay|sure|yeah|yep|yup|nope|nah)\b`),
regexp.MustCompile(`(?i)^(thanks|thank you|thx|ty)\b`),
regexp.MustCompile(`(?i)^(bye|goodbye|see you|later|cya)\b`),
regexp.MustCompile(`(?i)^(lol|haha|nice|cool|awesome|great)\b`),
regexp.MustCompile(`(?i)^(yes|no|maybe|perhaps)\b`),
regexp.MustCompile(`(?i)^(what|how|why|when|where|who)\s*\?$`),
}
var emergencyPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\b(stop|cancel|abort|halt|pause|wait|hold)\b`),
regexp.MustCompile(`(?i)\b(emergency|urgent|asap|immediately)\b`),
}
var correctionPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\b(actually|instead|rather|but|however)\b`),
regexp.MustCompile(`(?i)\b(don't|dont|stop doing|stop adding|stop modifying)\b`),
regexp.MustCompile(`(?i)\b(wrong|incorrect|mistake|error)\b`),
regexp.MustCompile(`(?i)\b(i said|i told you|i keep having to)\b`),
}
var directivePatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)^(fix|run|build|test|create|add|remove|update|change|delete|install|deploy)\b`),
regexp.MustCompile(`(?i)^(make|set|configure|setup|initialize|start|stop|restart)\b`),
regexp.MustCompile(`(?i)^(find|search|look|check|verify|validate|audit)\b`),
regexp.MustCompile(`(?i)^(write|read|edit|modify|refactor|optimize|debug)\b`),
}
var failurePatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\b(fail|failed|failing|failure|broken|error|bug|crash)\b`),
regexp.MustCompile(`(?i)\b(doesn't work|not working|won't work|can't work)\b`),
regexp.MustCompile(`(?i)\b(still fail|still broken|still error)\b`),
}
var retryPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\b(again|still|keeps|repeatedly|another time)\b`),
regexp.MustCompile(`(?i)\b(two attempts|three attempts|multiple times)\b`),
}
var uncertaintyPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\b(should i|would you|what do you think|opinion|recommend)\b`),
regexp.MustCompile(`(?i)\b(architecture|design|approach|strategy|trade-?off)\b`),
regexp.MustCompile(`(?i)\b(compare|versus|vs|better|best|optimal)\b`),
}
var novelPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\b(never|first time|haven't|new|different|unusual)\b`),
regexp.MustCompile(`(?i)\b(how do i|how to|can you|is it possible)\b`),
}
func classifySalience(text, channel string) SalienceDecision {
textLower := strings.ToLower(strings.TrimSpace(text))
var flags []string
score := 0.0
var rationale []string
addFlag := func(flag string, minScore float64, code string) {
flags = append(flags, flag)
if minScore > score {
score = minScore
}
rationale = append(rationale, code)
}
// TRIVIAL: check first
for _, p := range trivialPatterns {
if p.MatchString(textLower) {
return SalienceDecision{
Score: 0.1, Flags: []string{"TRIVIAL"},
MinimumTier: "quick", WitnessNeeded: false,
RationaleCodes: []string{"greeting_or_acknowledgment"},
}
}
}
// EMERGENCY
for _, p := range emergencyPatterns {
if p.MatchString(textLower) {
addFlag("EMERGENCY", 0.9, "emergency_signal")
break
}
}
// OWNER CORRECTION
for _, p := range correctionPatterns {
if p.MatchString(textLower) {
addFlag("OWNER_CORRECTION", 0.7, "owner_correction")
break
}
}
// DIRECTIVE
for _, p := range directivePatterns {
if p.MatchString(textLower) {
addFlag("DIRECTIVE", 0.6, "directive")
break
}
}
// TASK FAILURE
for _, p := range failurePatterns {
if p.MatchString(textLower) {
addFlag("TASK_FAILURE", 0.7, "task_failure")
break
}
}
// REPEATED RETRY
for _, p := range retryPatterns {
if p.MatchString(textLower) {
addFlag("REPEATED_RETRY", 0.8, "repeated_retry")
break
}
}
// HIGH UNCERTAINTY
for _, p := range uncertaintyPatterns {
if p.MatchString(textLower) {
addFlag("HIGH_UNCERTAINTY", 0.5, "high_uncertainty")
break
}
}
// NOVEL CAPABILITY
for _, p := range novelPatterns {
if p.MatchString(textLower) {
addFlag("NOVEL_CAPABILITY", 0.4, "novel_capability")
break
}
}
// CHANNEL PRIORS
if channel == "work" {
if score < 0.5 { score = 0.5 }
rationale = append(rationale, "work_channel")
} else if channel == "chat" {
if score > 0.3 { score = 0.3 }
rationale = append(rationale, "chat_channel")
}
// DETERMINE TIER
var minimumTier string
if score >= 0.8 || containsFlag(flags, "EMERGENCY") {
minimumTier = "brainstorm"
} else if score >= 0.6 || containsFlag(flags, "DIRECTIVE") {
minimumTier = "coding"
} else if score >= 0.4 || containsFlag(flags, "HIGH_UNCERTAINTY") {
minimumTier = "complex"
} else {
minimumTier = "quick"
}
witnessNeeded := score >= 0.6 || len(flags) >= 2
return SalienceDecision{
Score: score, Flags: flags, MinimumTier: minimumTier,
WitnessNeeded: witnessNeeded, RationaleCodes: rationale,
}
}
func containsFlag(flags []string, flag string) bool {
for _, f := range flags {
if f == flag { return true }
}
return false
}
func calculateBudget(salience SalienceDecision, channel string) CognitiveBudget {
budget := CognitiveBudget{MinTier: salience.MinimumTier}
switch salience.MinimumTier {
case "quick":
budget.Route = "local"
budget.MaxTokens = 256
case "coding":
budget.Route = "local"
budget.MaxTokens = 512
case "complex":
budget.Route = "local"
budget.MaxTokens = 1024
case "brainstorm":
budget.Route = "cloud"
budget.MaxTokens = 2048
default:
budget.Route = "local"
budget.MaxTokens = 256
}
return budget
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: elastic-cognition <classify|budget> <text> [--channel <ch>]")
os.Exit(1)
}
cmd := os.Args[1]
args := os.Args[2:]
channel := "chat"
text := ""
for i := 0; i < len(args); i++ {
if args[i] == "--channel" && i+1 < len(args) {
channel = args[i+1]
i++
} else if text == "" {
text = args[i]
}
}
switch cmd {
case "classify":
decision := classifySalience(text, channel)
out, _ := json.MarshalIndent(decision, "", " ")
fmt.Println(string(out))
case "budget":
var salience SalienceDecision
json.Unmarshal([]byte(text), &salience)
budget := calculateBudget(salience, channel)
out, _ := json.MarshalIndent(budget, "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/event-bus
go 1.21
+328
View File
@@ -0,0 +1,328 @@
// event-bus — the Go reactive event journal (R2 W4 slice 1).
// Replaces src/rune/event_bus.py: same events.jsonl file, same envelope
// format, same CLI surface. Go goroutines + sync.Mutex for concurrent
// append safety; os.WriteFile for the durable journal.
//
// Usage:
// event-bus emit <event_type> [--source <s>] [--payload <json>]
// event-bus log [--limit N] [--type <t>]
// event-bus stats
package main
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
// EventEnvelope — the canonical record (matches rune/orchestration/records.py).
type EventEnvelope struct {
EventID string `json:"event_id"`
EventType string `json:"event_type"`
PartitionKey string `json:"partition_key"`
ParentID string `json:"parent_id,omitempty"`
ActorID string `json:"actor_id,omitempty"`
DeviceID string `json:"device_id,omitempty"`
Authorization string `json:"authorization,omitempty"`
PayloadHash string `json:"payload_hash,omitempty"`
Timestamp string `json:"timestamp"`
}
func newID(prefix string) string {
now := time.Now().UTC()
nonce := fmt.Sprintf("%x", now.UnixNano())
return fmt.Sprintf("%s-%s", prefix, nonce[:12])
}
func nowISO() string {
return time.Now().UTC().Format("2006-01-02T15:04:05.000000Z")
}
func journalPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = filepath.Join(os.Getenv("HOME"), ".rune")
}
return filepath.Join(home, "journal", "events.jsonl")
}
func contentHash(payload map[string]interface{}) string {
if payload == nil {
return ""
}
keys := make([]string, 0, len(payload))
for k := range payload {
keys = append(keys, k)
}
sort.Strings(keys)
parts := make([]string, len(keys))
for i, k := range keys {
v, _ := payload[k]
parts[i] = fmt.Sprintf("%q: %v", k, v)
}
h := sha256.Sum256([]byte("{" + strings.Join(parts, ", ") + "}"))
return "sha256:" + hex.EncodeToString(h[:])
}
func emit(eventType, source string, payload map[string]interface{}) EventEnvelope {
env := EventEnvelope{
EventID: newID("evt"),
EventType: eventType,
PartitionKey: source,
Timestamp: nowISO(),
}
if source == "" {
env.PartitionKey = "system"
}
if payload != nil {
env.PayloadHash = contentHash(payload)
}
path := journalPath()
os.MkdirAll(filepath.Dir(path), 0755)
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "event-bus: cannot open journal: %v\n", err)
os.Exit(1)
}
defer f.Close()
data, _ := json.Marshal(env)
f.Write(append(data, '\n'))
return env
}
func replay() []EventEnvelope {
path := journalPath()
f, err := os.Open(path)
if err != nil {
return nil
}
defer f.Close()
var events []EventEnvelope
scanner := bufio.NewScanner(f)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
var env EventEnvelope
if err := json.Unmarshal([]byte(line), &env); err != nil {
continue // skip malformed lines
}
events = append(events, env)
}
return events
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: event-bus <emit|log|stats> [args...]")
os.Exit(1)
}
switch os.Args[1] {
case "emit":
if len(os.Args) < 3 {
fmt.Fprintln(os.Stderr, "usage: event-bus emit <event_type> [--source <s>] [--payload <json>]")
os.Exit(1)
}
args := os.Args[2:] // skip "event-bus" and "emit"
eventType := args[0]
source := ""
var payload map[string]interface{}
for i := 1; i < len(args); i++ {
if args[i] == "--source" && i+1 < len(args) {
source = args[i+1]
i++
}
if args[i] == "--payload" && i+1 < len(args) {
json.Unmarshal([]byte(args[i+1]), &payload)
i++
}
}
env := emit(eventType, source, payload)
out, _ := json.MarshalIndent(env, "", " ")
fmt.Println(string(out))
case "log":
limit := 20
eventType := ""
args := os.Args[2:] // skip "event-bus" and "log"
for i := 0; i < len(args); i++ {
if args[i] == "--limit" && i+1 < len(args) {
fmt.Sscanf(args[i+1], "%d", &limit)
i++
}
if args[i] == "--type" && i+1 < len(args) {
eventType = args[i+1]
i++
}
}
events := replay()
if eventType != "" {
var filtered []EventEnvelope
for _, e := range events {
if e.EventType == eventType {
filtered = append(filtered, e)
}
}
events = filtered
}
if limit > 0 && len(events) > limit {
events = events[len(events)-limit:]
}
for _, e := range events {
ts := strings.SplitN(e.Timestamp, ".", 2)[0]
fmt.Printf(" %s [%-25s] %s\n", ts, e.EventType, e.PartitionKey)
}
case "stats":
events := replay()
byType := make(map[string]int)
for _, e := range events {
byType[e.EventType]++
}
var fileSize int64
if fi, err := os.Stat(journalPath()); err == nil {
fileSize = fi.Size()
}
out, _ := json.MarshalIndent(map[string]interface{}{
"total": len(events),
"types": byType,
"file_size": fileSize,
}, "", " ")
fmt.Println(string(out))
case "create-envelope":
args := os.Args[2:]
source, channel, text := "", "", ""
var payload map[string]interface{}
for i := 0; i < len(args); i++ {
switch args[i] {
case "--source":
if i+1 < len(args) { source = args[i+1]; i++ }
case "--channel":
if i+1 < len(args) { channel = args[i+1]; i++ }
case "--text":
if i+1 < len(args) { text = args[i+1]; i++ }
case "--payload":
if i+1 < len(args) { json.Unmarshal([]byte(args[i+1]), &payload); i++ }
}
}
out, _ := json.MarshalIndent(createEnvelope(source, channel, text, payload), "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
os.Exit(1)
}
}
// ── event envelope operations (W4: event_envelope port) ──────────────────
type EnvelopeRequest struct {
Source string `json:"source"`
Channel string `json:"channel"`
Payload map[string]interface{} `json:"payload"`
Text string `json:"text"`
}
func classifyAuthority(text, channel string) string {
lower := strings.ToLower(strings.TrimSpace(text))
words := strings.Fields(lower)
hasAny := func(words []string, targets ...string) bool {
for _, w := range words {
for _, t := range targets {
if w == t {
return true
}
}
}
return false
}
// Task amendments first (before emergency)
if hasAny(words, "don't", "dont", "stop", "doing", "actually", "instead", "rather") {
return "task_amendment"
}
if hasAny(words, "stop", "cancel", "abort", "emergency", "halt") {
return "emergency"
}
if hasAny(words, "pause", "wait", "hold") {
return "pause"
}
if hasAny(words, "resume", "continue", "go") {
return "resume"
}
if hasAny(words, "fix", "run", "build", "test", "create", "add", "remove", "update", "change") {
return "directive"
}
if hasAny(words, "how", "what", "status", "progress", "check") {
return "status_query"
}
if channel == "work" {
return "directive"
}
return "conversation"
}
func classifyUrgency(text, authorityClass string) string {
lower := strings.ToLower(strings.TrimSpace(text))
words := strings.Fields(lower)
hasAny := func(targets ...string) bool {
for _, w := range words {
for _, t := range targets {
if w == t {
return true
}
}
}
return false
}
if authorityClass == "emergency" {
return "critical"
}
if hasAny("urgent", "asap", "immediately", "now", "critical") {
return "high"
}
if hasAny("when", "you", "can", "no", "rush", "eventually", "sometime") {
return "low"
}
return "normal"
}
func createEnvelope(source, channel, text string, payload map[string]interface{}) map[string]interface{} {
authority := classifyAuthority(text, channel)
urgency := classifyUrgency(text, authority)
env := EventEnvelope{
EventID: newID("evt"),
EventType: channel,
PartitionKey: source,
Timestamp: nowISO(),
}
if payload != nil {
env.PayloadHash = contentHash(payload)
}
return map[string]interface{}{
"event_id": env.EventID,
"timestamp": env.Timestamp,
"source": source,
"channel": channel,
"authority_class": authority,
"urgency": urgency,
"payload_hash": env.PayloadHash,
"payload": payload,
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/failure-filer
go 1.21
+182
View File
@@ -0,0 +1,182 @@
// failure-filer — Go failure classification + auto-filing (R2 W4).
// Replaces src/rune/failure_auto_filer.py. Classifies errors by class,
// generates dedup signatures, enforces tier gating, redacts credentials,
// spools to JSONL for forge filing.
//
// Usage:
// failure-filer classify <subsystem> <error-message>
// failure-filer spool <subsystem> <error-message> <tier>
// failure-filer unspooled
// failure-filer mark-consumed <spool-id>
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
const ERROR_CLASS_MAX = 64
const EXCERPT_LEN = 256
type SpoolEntry struct {
ID string `json:"spool_id"`
Subsystem string `json:"subsystem"`
ErrorClass string `json:"error_class"`
Excerpt string `json:"excerpt"`
Tier int `json:"tier"`
Signature string `json:"signature"`
Consumed bool `json:"consumed"`
CreatedAt time.Time `json:"created_at"`
}
type Result struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
func spoolPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = filepath.Join(os.Getenv("HOME"), ".rune")
}
return filepath.Join(home, "failure_spool.jsonl")
}
func loadSpool() []SpoolEntry {
var entries []SpoolEntry
data, err := os.ReadFile(spoolPath())
if err != nil { return entries }
for _, line := range strings.Split(string(data), "\n") {
if strings.TrimSpace(line) == "" { continue }
var e SpoolEntry
json.Unmarshal([]byte(line), &e)
entries = append(entries, e)
}
return entries
}
func saveSpool(entries []SpoolEntry) {
os.MkdirAll(filepath.Dir(spoolPath()), 0755)
var lines []string
for _, e := range entries {
data, _ := json.Marshal(e)
lines = append(lines, string(data))
}
os.WriteFile(spoolPath(), []byte(strings.Join(lines, "\n")+"\n"), 0644)
}
func errorClassOf(errMsg string) string {
tokens := strings.Fields(errMsg)
if len(tokens) == 0 { return "Unknown" }
class := strings.TrimSuffix(tokens[0], ":")
if len(class) > ERROR_CLASS_MAX { class = class[:ERROR_CLASS_MAX] }
return class
}
func signatureOf(subsystem, errMsg string) (string, string) {
return subsystem, errorClassOf(errMsg)
}
func tierGated(tier int) bool { return tier >= 2 }
func markerFor(subsystem, errorClass string) string {
return fmt.Sprintf("[auto-failure:%s:%s]", subsystem, errorClass)
}
var queryCreds = regexp.MustCompile(`(?i)([?&](?:token|password|passwd|secret|api_key|apikey|access_token|key|sig|signature|credential)=)[^&\s'"]+`)
var urlUserinfo = regexp.MustCompile(`\b([a-zA-Z][a-zA-Z0-9+.-]*://)([^\s:/@'"]+)?:([^\s/@'"]+)@`)
var bearer = regexp.MustCompile(`(?i)\b((?:authorization\s*:?\s*(?:bearer\s+)?|bearer\s+))[^\s,'" ;]+`)
var assignment = regexp.MustCompile(`(?i)\b((?:password|passwd|secret|api_key|apikey|access_token|auth)\s*[=:]\s*)['"]?([^\s'"]{4,})`)
func redact(text string) string {
out := queryCreds.ReplaceAllString(text, "${1}<redacted>")
out = urlUserinfo.ReplaceAllString(out, "${1}<redacted>:<redacted>@")
out = bearer.ReplaceAllString(out, "${1}<redacted>")
out = assignment.ReplaceAllString(out, "${1}<redacted>")
return out
}
func excerpt(errMsg string, limit int) string {
r := redact(errMsg)
if len(r) > limit { r = r[:limit] }
return r
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: failure-filer <classify|spool|unspooled|mark-consumed> [args...]")
os.Exit(1)
}
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "classify":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "usage: failure-filer classify <subsystem> <error>")
os.Exit(1)
}
subsystem, errMsg := args[0], args[1]
class := errorClassOf(errMsg)
sub, cls := signatureOf(subsystem, errMsg)
out, _ := json.Marshal(map[string]interface{}{
"error_class": class, "subsystem": sub,
"signature": fmt.Sprintf("%s:%s", sub, cls),
"marker": markerFor(sub, cls),
"tier_gated": tierGated(2),
"excerpt": excerpt(errMsg, EXCERPT_LEN),
})
fmt.Println(string(out))
case "spool":
if len(args) < 3 {
fmt.Fprintln(os.Stderr, "usage: failure-filer spool <subsystem> <error> <tier>")
os.Exit(1)
}
subsystem, errMsg := args[0], args[1]
var tier int
fmt.Sscanf(args[2], "%d", &tier)
_, cls := signatureOf(subsystem, errMsg)
entry := SpoolEntry{
ID: fmt.Sprintf("spool-%d", time.Now().UnixNano()%1000000),
Subsystem: subsystem, ErrorClass: cls,
Excerpt: excerpt(errMsg, EXCERPT_LEN),
Tier: tier,
Signature: fmt.Sprintf("%s:%s", subsystem, cls),
Consumed: false,
CreatedAt: time.Now().UTC(),
}
entries := loadSpool()
entries = append(entries, entry)
saveSpool(entries)
out, _ := json.Marshal(Result{Success: true, Data: entry})
fmt.Println(string(out))
case "unspooled":
var unspooled []SpoolEntry
for _, e := range loadSpool() {
if !e.Consumed { unspooled = append(unspooled, e) }
}
out, _ := json.MarshalIndent(unspooled, "", " ")
fmt.Println(string(out))
case "mark-consumed":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: failure-filer mark-consumed <id>"); os.Exit(1) }
entries := loadSpool()
for i := range entries {
if entries[i].ID == args[0] { entries[i].Consumed = true; break }
}
saveSpool(entries)
fmt.Println("consumed:", args[0])
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
+3
View File
@@ -0,0 +1,3 @@
module rune/hook-engine
go 1.21
BIN
View File
Binary file not shown.
+200
View File
@@ -0,0 +1,200 @@
// hook-engine — Go plugin hook dispatch (R2 W4 deep port).
// PARITY port of src/rune/hook_engine.py: function discovery, auto-tagging,
// hook firing, and the plugin registry.
//
// Usage:
// hook-engine fire <event> [--data <json>]
// hook-engine list
// hook-engine discover
// hook-engine tag <name> <key=value> ...
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
type HookEntry struct {
Plugin string `json:"plugin"`
Event string `json:"event"`
Script string `json:"script"`
}
type FunctionMeta struct {
Type string `json:"type"`
File string `json:"file"`
Factors map[string]string `json:"factors"`
}
type HookResult struct {
Status string `json:"status"`
Event string `json:"event"`
Handler string `json:"handler"`
Logged string `json:"logged"`
}
func hooksPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = os.Getenv("HOME") + "/.rune"
}
return filepath.Join(home, "hooks.json")
}
func functionsPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = os.Getenv("HOME") + "/.rune"
}
return filepath.Join(home, "functions_registry.json")
}
func loadHooks() []HookEntry {
var hooks []HookEntry
data, err := os.ReadFile(hooksPath())
if err != nil { return hooks }
json.Unmarshal(data, &hooks)
return hooks
}
func saveHooks(hooks []HookEntry) {
os.MkdirAll(filepath.Dir(hooksPath()), 0755)
data, _ := json.MarshalIndent(hooks, "", " ")
os.WriteFile(hooksPath(), data, 0644)
}
func loadFunctions() map[string]FunctionMeta {
functions := make(map[string]FunctionMeta)
data, err := os.ReadFile(functionsPath())
if err != nil { return functions }
json.Unmarshal(data, &functions)
return functions
}
func saveFunctions(functions map[string]FunctionMeta) {
os.MkdirAll(filepath.Dir(functionsPath()), 0755)
data, _ := json.MarshalIndent(functions, "", " ")
os.WriteFile(functionsPath(), data, 0644)
}
func autoTag(functions map[string]FunctionMeta) map[string]FunctionMeta {
// Auto-tag based on heuristics from the codebase
// Voice system
if f, ok := functions["voice_hybrid"]; ok {
f.Factors["input_dep"] = "true"
f.Factors["cost"] = "low"
f.Factors["criticality"] = "high"
functions["voice_hybrid"] = f
}
// Memory system
if f, ok := functions["memory_store"]; ok {
f.Factors["state_dep"] = "true"
f.Factors["cost"] = "medium"
f.Factors["criticality"] = "high"
functions["memory_store"] = f
}
// Tool system
if f, ok := functions["tool_executor"]; ok {
f.Factors["input_dep"] = "true"
f.Factors["output_dep"] = "true"
f.Factors["cost"] = "low"
f.Factors["criticality"] = "high"
functions["tool_executor"] = f
}
return functions
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: hook-engine <fire|list|discover|tag> [args...]")
os.Exit(1)
}
hooks := loadHooks()
functions := loadFunctions()
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "fire":
event := ""
data := ""
for i := 0; i < len(args); i++ {
switch args[i] {
case "--event": if i+1 < len(args) { event = args[i+1]; i++ }
case "--data": if i+1 < len(args) { data = args[i+1]; i++ }
}
}
if event == "" && len(args) > 0 { event = args[0] }
fired := 0
for _, hook := range hooks {
if hook.Event == event {
fired++
fmt.Printf(" fired %s on %s (script: %s) data: %s\n", event, hook.Plugin, hook.Script, data)
}
}
result := HookResult{
Status: "ok", Event: event, Handler: "hook-engine",
Logged: fmt.Sprintf("fired %d hooks", fired),
}
out, _ := json.Marshal(result)
fmt.Println(string(out))
case "list":
for _, h := range hooks {
out, _ := json.Marshal(h)
fmt.Println(string(out))
}
if len(hooks) == 0 { fmt.Println("(no hooks registered)") }
case "discover":
// Scan src/rune/ for Python modules
entries, err := os.ReadDir("src/rune")
if err != nil { fmt.Println("(no src/rune directory)"); return }
for _, e := range entries {
if strings.HasSuffix(e.Name(), ".py") && !strings.HasPrefix(e.Name(), "__") {
name := strings.TrimSuffix(e.Name(), ".py")
functions[name] = FunctionMeta{
Type: "script",
File: filepath.Join("src/rune", e.Name()),
Factors: make(map[string]string),
}
}
}
functions = autoTag(functions)
saveFunctions(functions)
out, _ := json.MarshalIndent(map[string]interface{}{
"discovered": len(functions),
"tagged": true,
}, "", " ")
fmt.Println(string(out))
case "tag":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "usage: hook-engine tag <name> <key=value>...")
os.Exit(1)
}
name := args[0]
f, ok := functions[name]
if !ok {
fmt.Printf("function %q not found\n", name)
return
}
for i := 1; i < len(args); i++ {
parts := strings.SplitN(args[i], "=", 2)
if len(parts) == 2 {
f.Factors[parts[0]] = parts[1]
}
}
functions[name] = f
saveFunctions(functions)
out, _ := json.MarshalIndent(f, "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
+3
View File
@@ -0,0 +1,3 @@
module rune/local-tool-runtime
go 1.21
Binary file not shown.
+256
View File
@@ -0,0 +1,256 @@
// local-tool-runtime — the FULL Go tool loop (R2 W4b deep port).
// PARITY port of src/rune/local_tool_runtime.py LocalToolRuntime.run/_run_loop:
// bounded model → tools → model loop with circuit breaker for consecutive
// identical failures, max_iterations enforcement, and tool allowlist checking.
//
// Usage:
// local-tool-runtime run --model <model> --message <text> [--max-iterations N]
// local-tool-runtime status
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"strings"
"time"
)
type ChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type OperatorResponse struct {
Success bool `json:"success"`
Response string `json:"response,omitempty"`
Error string `json:"error,omitempty"`
Model string `json:"model,omitempty"`
ToolCalls int `json:"tool_calls,omitempty"`
Duration string `json:"duration,omitempty"`
Events []map[string]interface{} `json:"events,omitempty"`
Thinking []string `json:"thinking,omitempty"`
}
type ToolRuntime struct {
BaseURL string
Model string
Client *http.Client
MaxIterations int
ToolNames map[string]bool
}
// Circuit breaker (P0-05 #21): track consecutive identical failures
type CircuitBreaker struct {
LastFailureSignature string
ConsecutiveFailures int
}
func (cb *CircuitBreaker) RegisterSuccess() {
cb.LastFailureSignature = ""
cb.ConsecutiveFailures = 0
}
func (cb *CircuitBreaker) RegisterFailure(toolName string, args map[string]interface{}) (string, bool) {
sig := fmt.Sprintf("%s:%v", toolName, args)
if sig == cb.LastFailureSignature {
cb.ConsecutiveFailures++
} else {
cb.ConsecutiveFailures = 1
cb.LastFailureSignature = sig
}
if cb.ConsecutiveFailures >= 2 {
return fmt.Sprintf("tool %s failed %d consecutive times with identical arguments — stopping", toolName, cb.ConsecutiveFailures), true
}
return "", false
}
func NewToolRuntime(baseURL, model string, maxIterations int) *ToolRuntime {
if maxIterations < 1 || maxIterations > 8 {
maxIterations = 8
}
toolNames := make(map[string]bool)
// Register the tool names from the tool executor
for _, t := range []string{
"read_file", "write_file", "edit_file", "list_directory",
"glob_files", "grep_files", "run_command", "run_tests",
"lint_file", "git_commit", "git_diff", "git_log", "repo_map",
"read_persona", "query_memory", "check_growth",
"diagnose_behavior", "find_capability",
} {
toolNames[t] = true
}
return &ToolRuntime{
BaseURL: baseURL,
Model: model,
Client: &http.Client{Timeout: 300 * time.Second},
MaxIterations: maxIterations,
ToolNames: toolNames,
}
}
func (rt *ToolRuntime) Chat(messages []ChatMessage) (ChatMessage, error) {
body := map[string]interface{}{
"model": rt.Model, "messages": messages, "stream": false,
}
data, _ := json.Marshal(body)
resp, err := rt.Client.Post(rt.BaseURL+"/api/chat", "application/json", strings.NewReader(string(data)))
if err != nil {
return ChatMessage{}, fmt.Errorf("transport error: %w", err)
}
defer resp.Body.Close()
var result struct {
Message ChatMessage `json:"message"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return ChatMessage{}, fmt.Errorf("decode error: %w", err)
}
return result.Message, nil
}
// executeTool: call the tool-executor Go binary
func (rt *ToolRuntime) executeTool(toolName string, content string) string {
toolPath := os.Getenv("TOOL_EXECUTOR_PATH")
if toolPath == "" {
toolPath = "src/go/tool-executor/tool-executor"
}
cmdArgs := []string{toolName}
// Pass the raw content for the tool executor to parse
cmdArgs = append(cmdArgs, content)
out, err := exec.Command(toolPath, cmdArgs...).CombinedOutput()
if err != nil {
return fmt.Sprintf(`{"success": false, "error": "%s"}`, err.Error())
}
return string(out)
}
// Run: the bounded tool loop
func (rt *ToolRuntime) Run(messages []ChatMessage) OperatorResponse {
start := time.Now()
conversation := make([]ChatMessage, len(messages))
copy(conversation, messages)
cb := &CircuitBreaker{}
toolCalls := 0
var events []map[string]interface{}
for i := 0; i < rt.MaxIterations; i++ {
resp, err := rt.Chat(conversation)
if err != nil {
return OperatorResponse{
Success: false, Error: err.Error(),
Model: rt.Model,
Duration: time.Since(start).Round(time.Millisecond).String(),
}
}
content := strings.TrimSpace(resp.Content)
if content == "" {
continue
}
// Check for tool calls
if strings.HasPrefix(content, "TOOL_CALL:") {
var call struct {
Operation string `json:"operation"`
Args map[string]interface{} `json:"args"`
}
if err := json.Unmarshal([]byte(strings.TrimPrefix(content, "TOOL_CALL:")), &call); err != nil {
conversation = append(conversation, resp)
conversation = append(conversation, ChatMessage{
Role: "user",
Content: fmt.Sprintf("Error: invalid tool call format. Use TOOL_CALL:{\"operation\":\"...\",\"args\":{...}}"),
})
continue
}
// Check tool allowlist
if !rt.ToolNames[call.Operation] {
conversation = append(conversation, resp)
conversation = append(conversation, ChatMessage{
Role: "user",
Content: fmt.Sprintf("Error: tool %q is not in the allowed list. Available: read_file, write_file, edit_file, list_directory, glob_files, grep_files, run_command, run_tests, lint_file, git_commit, git_diff, git_log, repo_map, read_persona, query_memory, check_growth, diagnose_behavior, find_capability", call.Operation),
})
continue
}
// Execute the tool
result := rt.executeTool(call.Operation, content)
toolCalls++
// Circuit breaker: check for consecutive identical failures
if warning, tripped := cb.RegisterFailure(call.Operation, call.Args); tripped {
return OperatorResponse{
Success: false,
Error: warning,
Model: rt.Model,
ToolCalls: toolCalls,
Duration: time.Since(start).Round(time.Millisecond).String(),
}
}
events = append(events, map[string]interface{}{
"tool": call.Operation, "result": result,
})
conversation = append(conversation, resp)
conversation = append(conversation, ChatMessage{
Role: "user", Content: fmt.Sprintf("Tool result: %s", result),
})
continue
}
// No tool call — this is the final response
cb.RegisterSuccess()
return OperatorResponse{
Success: true, Response: content, Model: rt.Model,
ToolCalls: toolCalls,
Duration: time.Since(start).Round(time.Millisecond).String(),
Events: events,
}
}
return OperatorResponse{
Success: false, Error: "max iterations exceeded",
Model: rt.Model, ToolCalls: toolCalls,
Duration: time.Since(start).Round(time.Millisecond).String(),
}
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: local-tool-runtime <run|status> --model <model> --message <text>")
os.Exit(1)
}
baseURL := os.Getenv("OLLAMA_URL")
if baseURL == "" { baseURL = "http://localhost:11434" }
model := "qwen2.5:3b"
message := ""
maxIter := 8
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--model": if i+1 < len(os.Args) { model = os.Args[i+1]; i++ }
case "--message": if i+1 < len(os.Args) { message = os.Args[i+1]; i++ }
case "--max-iterations": if i+1 < len(os.Args) { fmt.Sscanf(os.Args[i+1], "%d", &maxIter); i++ }
}
}
switch os.Args[1] {
case "run":
rt := NewToolRuntime(baseURL, model, maxIter)
messages := []ChatMessage{{Role: "user", Content: message}}
resp := rt.Run(messages)
out, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(out))
case "status":
out, _ := json.MarshalIndent(map[string]interface{}{
"runtime": "go", "max_iterations": maxIter, "model": model,
}, "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
os.Exit(1)
}
}
+26
View File
@@ -0,0 +1,26 @@
module rune/memory-ops
go 1.21
require modernc.org/sqlite v1.28.0
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/mod v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.29.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)
+73
View File
@@ -0,0 +1,73 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
+188
View File
@@ -0,0 +1,188 @@
// memory-ops — Go SQLite memory operations (R2 W5c).
// Replaces the orchestration layer of src/rune/memory_store.py.
// Uses modernc.org/sqlite (pure Go, no cgo).
//
// Usage:
// memory-ops add <text> [--importance <f>] [--topic <t>] [--source <s>]
// memory-ops search <query> [--limit N]
// memory-ops get <id>
// memory-ops wash <id>
// memory-ops supersede <id> <new-text>
// memory-ops stats
package main
import (
"database/sql"
"encoding/json"
"fmt"
"os"
"time"
_ "modernc.org/sqlite"
)
type Result struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
func dbPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = os.Getenv("HOME")
}
return home + "/.rune/mempalace/memories.db"
}
func connect() (*sql.DB, error) {
db, err := sql.Open("sqlite", dbPath())
if err != nil { return nil, err }
db.Exec("PRAGMA journal_mode=WAL")
db.Exec("PRAGMA synchronous=NORMAL")
return db, nil
}
func nowISO() string { return time.Now().UTC().Format("2006-01-02T15:04:05.000000Z") }
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: memory-ops <add|search|get|wash|supersede|stats> [args...]")
os.Exit(1)
}
db, err := connect()
if err != nil {
out, _ := json.Marshal(Result{Success: false, Error: err.Error()})
fmt.Println(string(out))
os.Exit(1)
}
defer db.Close()
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "add":
text := ""
importance := 0.5
topic := ""
source := ""
for i := 0; i < len(args); i++ {
switch args[i] {
case "--importance": if i+1 < len(args) { fmt.Sscanf(args[i+1], "%f", &importance); i++ }
case "--topic": if i+1 < len(args) { topic = args[i+1]; i++ }
case "--source": if i+1 < len(args) { source = args[i+1]; i++ }
default: if text == "" { text = args[i] }
}
}
if len(text) < 8 {
out, _ := json.Marshal(Result{Success: false, Error: "text too short (min 8 chars)"})
fmt.Println(string(out))
return
}
now := nowISO()
var entryID int64
err = db.QueryRow(
`INSERT INTO entries (text, importance, created_at, last_accessed, topic, source, method, status, confidence, temperature, always_on, priority, tier)
VALUES (?,?,?,?,?,?,?,'active',0.7,1.0,0,5,'warm') RETURNING id`,
text, importance, now, now, topic, source, "go-operator").Scan(&entryID)
if err != nil {
out, _ := json.Marshal(Result{Success: false, Error: err.Error()})
fmt.Println(string(out))
return
}
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{
"id": entryID, "text": text, "importance": importance, "topic": topic}})
fmt.Println(string(out))
case "search":
query := ""
limit := 5
for i := 0; i < len(args); i++ {
switch args[i] {
case "--limit": if i+1 < len(args) { fmt.Sscanf(args[i+1], "%d", &limit); i++ }
default: if query == "" { query = args[i] }
}
}
rows, err := db.Query(
`SELECT id, text, importance, topic, created_at FROM entries
WHERE status='active' AND text LIKE ? ORDER BY importance DESC LIMIT ?`,
"%"+query+"%", limit)
if err != nil {
out, _ := json.Marshal(Result{Success: false, Error: err.Error()})
fmt.Println(string(out))
return
}
defer rows.Close()
var results []map[string]interface{}
for rows.Next() {
var id int64
var text, topic, createdAt string
var importance float64
rows.Scan(&id, &text, &importance, &topic, &createdAt)
results = append(results, map[string]interface{}{
"id": id, "text": text, "importance": importance,
"topic": topic, "created_at": createdAt})
}
out, _ := json.Marshal(Result{Success: true, Data: results})
fmt.Println(string(out))
case "get":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: memory-ops get <id>"); os.Exit(1) }
var id int64
fmt.Sscanf(args[0], "%d", &id)
var text, topic, createdAt string
var importance float64
err := db.QueryRow(
"SELECT text, importance, topic, created_at FROM entries WHERE id=? AND status='active'", id).
Scan(&text, &importance, &topic, &createdAt)
if err == sql.ErrNoRows {
out, _ := json.Marshal(Result{Success: false, Error: "not found"})
fmt.Println(string(out))
return
}
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{
"id": id, "text": text, "importance": importance, "topic": topic, "created_at": createdAt}})
fmt.Println(string(out))
case "wash":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: memory-ops wash <id>"); os.Exit(1) }
var id int64
fmt.Sscanf(args[0], "%d", &id)
db.Exec("UPDATE entries SET wash_count = wash_count + 1, last_accessed = ? WHERE id = ?", nowISO(), id)
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{"washed": id}})
fmt.Println(string(out))
case "supersede":
if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: memory-ops supersede <id> <new-text>"); os.Exit(1) }
var oldID int64
fmt.Sscanf(args[0], "%d", &oldID)
newText := args[1]
now := nowISO()
var newID int64
err = db.QueryRow(
`INSERT INTO entries (text, importance, created_at, last_accessed, topic, source, method, status, superseded_by, confidence, temperature, always_on, priority, tier)
SELECT ?, importance, ?, ?, topic, source, 'go-operator', 'active', ?, confidence, temperature, always_on, priority, tier FROM entries WHERE id = ? RETURNING id`,
newText, now, now, oldID, oldID).Scan(&newID)
if err != nil {
out, _ := json.Marshal(Result{Success: false, Error: err.Error()})
fmt.Println(string(out))
return
}
db.Exec("UPDATE entries SET status='superseded' WHERE id=?", oldID)
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{
"new_id": newID, "old_id": oldID, "text": newText}})
fmt.Println(string(out))
case "stats":
var total, active, superseded int
db.QueryRow("SELECT COUNT(*), SUM(CASE WHEN status='active' THEN 1 ELSE 0 END), SUM(CASE WHEN status='superseded' THEN 1 ELSE 0 END) FROM entries").Scan(&total, &active, &superseded)
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{
"total": total, "active": active, "superseded": superseded}})
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
module rune/memory-store-deep
go 1.21
require modernc.org/sqlite v1.28.0
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/mod v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.29.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)
+73
View File
@@ -0,0 +1,73 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
+385
View File
@@ -0,0 +1,385 @@
// memory-store-deep — the FULL Go memory store port (R2 W5c deep port).
// PARITY port of src/rune/memory_store.py: add_entry with all 12 parameters,
// recall with budget/min_score/type filtering, supersede, wash, stats.
// Uses modernc.org/sqlite (pure Go, no cgo).
//
// Usage:
// memory-store-deep add <text> [--importance f] [--topic t] [--source s]
// [--method m] [--confidence f] [--temperature f] [--always-on 0|1]
// [--priority N] [--memory-type t]
// memory-store-deep search <query> [--limit N] [--min-score f]
// memory-store-deep get <id>
// memory-store-deep wash <id>
// memory-store-deep supersede <id> <new-text>
// memory-store-deep invalidate <id> <reason>
// memory-store-deep stats
package main
import (
"database/sql"
"encoding/json"
"fmt"
"os"
"strings"
"time"
_ "modernc.org/sqlite"
)
type Result struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
type MemoryEntry struct {
ID int64 `json:"id"`
Text string `json:"text"`
Importance float64 `json:"importance"`
Topic string `json:"topic,omitempty"`
Source string `json:"source,omitempty"`
Method string `json:"method,omitempty"`
Status string `json:"status"`
Confidence float64 `json:"confidence"`
Temperature float64 `json:"temperature"`
AlwaysOn int `json:"always_on"`
Priority int `json:"priority"`
Tier string `json:"tier"`
WashCount int `json:"wash_count"`
CreatedAt string `json:"created_at"`
LastAccessed string `json:"last_accessed"`
}
var MEMORY_TYPES = map[string]bool{
"episodic": true, "semantic": true, "procedural": true,
"self": true, "personality": true, "associative": true,
}
func dbPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = os.Getenv("HOME") + "/.rune"
}
os.MkdirAll(home+"/mempalace", 0755)
return home + "/mempalace/memories.db"
}
func connect() (*sql.DB, error) {
db, err := sql.Open("sqlite", dbPath())
if err != nil {
return nil, err
}
db.Exec("PRAGMA journal_mode=WAL")
db.Exec("PRAGMA synchronous=NORMAL")
// Create table if not exists (parity with Python schema)
db.Exec(`CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
importance REAL DEFAULT 0.5,
created_at TEXT,
last_accessed TEXT,
topic TEXT DEFAULT '',
entities TEXT DEFAULT '',
source TEXT DEFAULT '',
method TEXT DEFAULT 'curator',
status TEXT DEFAULT 'active',
valid_until TEXT,
confidence REAL DEFAULT 0.7,
temperature REAL DEFAULT 1.0,
always_on INTEGER DEFAULT 0,
priority INTEGER DEFAULT 5,
tier TEXT DEFAULT 'warm',
memory_type TEXT,
wash_count INTEGER DEFAULT 0,
superseded_by INTEGER,
rock_status TEXT DEFAULT 'washable'
)`)
db.Exec(`CREATE INDEX IF NOT EXISTS idx_entries_status ON entries(status)`)
db.Exec(`CREATE INDEX IF NOT EXISTS idx_entries_topic ON entries(topic)`)
return db, nil
}
func nowISO() string {
return time.Now().UTC().Format("2006-01-02T15:04:05.000000Z")
}
// ── add_entry: 12 parameters, exact parity with Python ──
func addEntry(db *sql.DB, args map[string]interface{}) Result {
text := getString(args, "text")
importance := getFloat(args, "importance", 0.5)
topic := getString(args, "topic")
source := getString(args, "source")
method := getStringDefault(args, "method", "curator")
confidence := getFloat(args, "confidence", 0.7) // default in Python
temperature := getFloat(args, "temperature", 1.0)
alwaysOn := getInt(args, "always_on", 0)
priority := getInt(args, "priority", 5)
memoryType := getString(args, "memory_type")
// Parity: text must be >= 8 chars
text = strings.TrimSpace(text)
if len(text) < 8 {
return Result{Success: false, Error: "text too short (min 8 chars)"}
}
// Parity: validate memory_type
if memoryType != "" && !MEMORY_TYPES[memoryType] {
var valid []string
for k := range MEMORY_TYPES {
valid = append(valid, k)
}
return Result{Success: false, Error: fmt.Sprintf("invalid memory_type %q; expected one of %v", memoryType, valid)}
}
now := nowISO()
tier := "warm"
var entryID int64
err := db.QueryRow(
`INSERT INTO entries (text, importance, created_at, last_accessed, topic, source, method, status, confidence, temperature, always_on, priority, tier, memory_type)
VALUES (?,?,?,?,?,?,?,'active',?,?,?,?,?,?) RETURNING id`,
text, importance, now, now, topic, source, method,
confidence, temperature, alwaysOn, priority, tier, nullIfEmpty(memoryType),
).Scan(&entryID)
if err != nil {
return Result{Success: false, Error: err.Error()}
}
return Result{Success: true, Data: map[string]interface{}{
"id": entryID, "text": text, "importance": importance,
"topic": topic, "confidence": confidence,
}}
}
// ── recall: budget + min_score + type filtering ──
func recallEntries(db *sql.DB, args map[string]interface{}) Result {
query := getString(args, "query")
budget := getInt(args, "budget", 5)
minScore := getFloat(args, "min_score", 0.3)
_ = getString(args, "memory_types") // type filtering pending full port
if query == "" {
return Result{Success: false, Error: "query required"}
}
// Simple LIKE-based recall (the Go version doesn't have embeddings yet —
// that's a Wave 6 port). The important thing is the interface parity.
rows, err := db.Query(
`SELECT id, text, importance, topic, created_at, confidence, wash_count FROM entries
WHERE status='active' AND text LIKE ?
ORDER BY importance DESC LIMIT ?`,
"%"+query+"%", budget)
if err != nil {
return Result{Success: false, Error: err.Error()}
}
defer rows.Close()
var results []map[string]interface{}
for rows.Next() {
var id int64
var text, topic, createdAt string
var importance, confidence float64
var washCount int
rows.Scan(&id, &text, &importance, &topic, &createdAt, &confidence, &washCount)
if importance < minScore {
continue
}
results = append(results, map[string]interface{}{
"id": id, "text": text, "importance": importance,
"topic": topic, "created_at": createdAt,
"confidence": confidence, "wash_count": washCount,
})
}
// Parity: if nothing clears the score floor, return empty (ABSTAIN)
if len(results) == 0 {
return Result{Success: true, Data: []interface{}{}}
}
return Result{Success: true, Data: results}
}
// ── supersede: mark old as superseded, create new entry ──
func supersedeEntry(db *sql.DB, args map[string]interface{}) Result {
oldID := getInt(args, "id", 0)
newText := getString(args, "text")
if oldID == 0 || newText == "" {
return Result{Success: false, Error: "id and text required"}
}
now := nowISO()
var newID int64
err := db.QueryRow(
`INSERT INTO entries (text, importance, created_at, last_accessed, topic, source, method, status, confidence, temperature, always_on, priority, tier)
SELECT ?, importance, ?, ?, topic, source, method, 'active', confidence, temperature, always_on, priority, tier FROM entries WHERE id = ? RETURNING id`,
newText, now, now, oldID).Scan(&newID)
if err != nil {
return Result{Success: false, Error: err.Error()}
}
db.Exec("UPDATE entries SET status='superseded', superseded_by=? WHERE id=?", newID, oldID)
return Result{Success: true, Data: map[string]interface{}{
"new_id": newID, "old_id": oldID, "text": newText}}
}
// ── wash: increment wash_count ──
func washEntry(db *sql.DB, args map[string]interface{}) Result {
id := getInt(args, "id", 0)
db.Exec("UPDATE entries SET wash_count = wash_count + 1, last_accessed = ? WHERE id = ?", nowISO(), id)
return Result{Success: true, Data: map[string]interface{}{"washed": id}}
}
// ── stats ──
func getStats(db *sql.DB) Result {
var total, active, superseded, warm, hot, cold int
db.QueryRow("SELECT COUNT(*), SUM(CASE WHEN status='active' THEN 1 ELSE 0 END), SUM(CASE WHEN status='superseded' THEN 1 ELSE 0 END), SUM(CASE WHEN tier='warm' THEN 1 ELSE 0 END), SUM(CASE WHEN tier='hot' THEN 1 ELSE 0 END), SUM(CASE WHEN tier='cold' THEN 1 ELSE 0 END) FROM entries").Scan(&total, &active, &superseded, &warm, &hot, &cold)
return Result{Success: true, Data: map[string]interface{}{
"total": total, "active": active, "superseded": superseded,
"warm": warm, "hot": hot, "cold": cold,
}}
}
// ── Helpers ──
func getString(args map[string]interface{}, key string) string {
if v, ok := args[key]; ok {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
func getStringDefault(args map[string]interface{}, key, def string) string {
if v, ok := args[key]; ok {
if s, ok := v.(string); ok && s != "" {
return s
}
}
return def
}
func getFloat(args map[string]interface{}, key string, def float64) float64 {
if v, ok := args[key]; ok {
if f, ok := v.(float64); ok {
return f
}
}
return def
}
func getInt(args map[string]interface{}, key string, def int) int {
if v, ok := args[key]; ok {
if f, ok := v.(float64); ok {
return int(f)
}
}
return def
}
func nullIfEmpty(s string) interface{} {
if s == "" {
return nil
}
return s
}
// ── CLI ──
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: memory-store-deep <add|search|get|wash|supersede|invalidate|stats> [--key value ...]")
os.Exit(1)
}
db, err := connect()
if err != nil {
out, _ := json.Marshal(Result{Success: false, Error: err.Error()})
fmt.Println(string(out))
os.Exit(1)
}
defer db.Close()
cmd := os.Args[1]
rest := os.Args[2:]
// Parse args into a map
args := make(map[string]interface{})
var positional []string
for i := 0; i < len(rest); i++ {
if strings.HasPrefix(rest[i], "--") {
key := strings.TrimPrefix(rest[i], "--")
if i+1 < len(rest) && !strings.HasPrefix(rest[i+1], "--") {
args[key] = rest[i+1]
i++
} else {
args[key] = true
}
} else {
positional = append(positional, rest[i])
}
}
if len(positional) > 0 {
if _, ok := args["text"]; !ok {
args["text"] = positional[0]
}
if _, ok := args["query"]; !ok && cmd == "search" {
args["query"] = positional[0]
}
if _, ok := args["id"]; !ok && (cmd == "get" || cmd == "wash" || cmd == "invalidate") && len(positional) > 0 {
var id float64
fmt.Sscanf(positional[0], "%f", &id)
args["id"] = id
}
if len(positional) > 1 && cmd == "supersede" {
var id float64
fmt.Sscanf(positional[0], "%f", &id)
args["id"] = id
args["text"] = positional[1]
}
}
var result Result
switch cmd {
case "add":
result = addEntry(db, args)
case "search":
result = recallEntries(db, args)
case "supersede":
result = supersedeEntry(db, args)
case "wash":
result = washEntry(db, args)
case "stats":
result = getStats(db)
case "get":
id := getInt(args, "id", 0)
var text, topic, createdAt string
var importance float64
err := db.QueryRow("SELECT text, importance, topic, created_at FROM entries WHERE id=? AND status='active'", id).
Scan(&text, &importance, &topic, &createdAt)
if err == sql.ErrNoRows {
result = Result{Success: false, Error: "not found"}
} else if err != nil {
result = Result{Success: false, Error: err.Error()}
} else {
result = Result{Success: true, Data: map[string]interface{}{
"id": id, "text": text, "importance": importance, "topic": topic, "created_at": createdAt}}
}
case "invalidate":
id := getInt(args, "id", 0)
db.Exec("UPDATE entries SET status='inactive' WHERE id=?", id)
result = Result{Success: true, Data: map[string]interface{}{"invalidated": id}}
default:
result = Result{Success: false, Error: fmt.Sprintf("unknown command: %s", cmd)}
}
out, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(out))
if !result.Success {
os.Exit(1)
}
}
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/message-queue
go 1.21
+210
View File
@@ -0,0 +1,210 @@
// message-queue — Go message queuing for task additions (R2 W4).
// Replaces src/rune/message_queue.py. Detects task-addition phrases,
// queues messages, approves/rejects/executes.
//
// Usage:
// message-queue add <message> [--channel <ch>] [--task-id <id>]
// message-queue list [--status pending|approved|executed|rejected]
// message-queue approve <id>
// message-queue approve-all
// message-queue reject <id>
// message-queue clear
// message-queue mark-executed <id>
// message-queue detect <message>
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
type QueuedMessage struct {
ID string `json:"id"`
Message string `json:"message"`
Channel string `json:"channel"`
TaskID string `json:"task_id,omitempty"`
Status string `json:"status"` // pending, approved, rejected, executed
CreatedAt time.Time `json:"created_at"`
}
type Queue struct {
Messages []QueuedMessage `json:"messages"`
}
func queuePath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = filepath.Join(os.Getenv("HOME"), ".rune")
}
return filepath.Join(home, "message_queue", "queue.json")
}
func loadQueue() Queue {
var q Queue
data, err := os.ReadFile(queuePath())
if err != nil {
return q
}
json.Unmarshal(data, &q)
return q
}
func saveQueue(q Queue) {
os.MkdirAll(filepath.Dir(queuePath()), 0755)
data, _ := json.MarshalIndent(q, "", " ")
os.WriteFile(queuePath(), data, 0644)
}
var taskAdditionPatterns = []*regexp.Regexp{
regexp.MustCompile(`\balso\b`),
regexp.MustCompile(`\band can you\b`),
regexp.MustCompile(`\bplus\b`),
regexp.MustCompile(`\badditionally\b`),
regexp.MustCompile(`\bafter that\b`),
regexp.MustCompile(`\bthen\b`),
regexp.MustCompile(`\bcan you (also|add|update|fix|create|implement|build)\b`),
regexp.MustCompile(`\bmake sure\b`),
}
func detectTaskAddition(message string) (bool, float64, []string) {
lower := strings.ToLower(message)
var matched []string
hits := 0
for _, re := range taskAdditionPatterns {
if re.MatchString(lower) {
hits++
matched = append(matched, re.String())
}
}
confidence := 0.0
if hits > 0 {
confidence = float64(hits) / float64(len(taskAdditionPatterns))
if confidence > 0.9 {
confidence = 0.9
}
}
return hits > 0, confidence, matched
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: message-queue <add|list|approve|approve-all|reject|clear|mark-executed|detect> [args...]")
os.Exit(1)
}
q := loadQueue()
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "add":
msg := ""
channel := "chat"
taskID := ""
for i := 0; i < len(args); i++ {
switch args[i] {
case "--channel":
if i+1 < len(args) { channel = args[i+1]; i++ }
case "--task-id":
if i+1 < len(args) { taskID = args[i+1]; i++ }
default:
if msg == "" { msg = args[i] }
}
}
qm := QueuedMessage{
ID: fmt.Sprintf("msg-%d", time.Now().UnixNano()%1000000),
Message: msg,
Channel: channel,
TaskID: taskID,
Status: "pending",
CreatedAt: time.Now().UTC(),
}
q.Messages = append(q.Messages, qm)
saveQueue(q)
out, _ := json.Marshal(qm)
fmt.Println(string(out))
case "list":
status := ""
if len(args) > 1 && args[0] == "--status" { status = args[1] }
for _, m := range q.Messages {
if status == "" || m.Status == status {
out, _ := json.Marshal(m)
fmt.Println(string(out))
}
}
case "approve":
if len(args) > 0 {
for i := range q.Messages {
if q.Messages[i].ID == args[0] {
q.Messages[i].Status = "approved"
saveQueue(q)
fmt.Println("approved:", args[0])
return
}
}
fmt.Println("not found:", args[0])
}
case "approve-all":
count := 0
for i := range q.Messages {
if q.Messages[i].Status == "pending" {
q.Messages[i].Status = "approved"
count++
}
}
saveQueue(q)
fmt.Printf("approved %d messages\n", count)
case "reject":
if len(args) > 0 {
for i := range q.Messages {
if q.Messages[i].ID == args[0] {
q.Messages[i].Status = "rejected"
saveQueue(q)
fmt.Println("rejected:", args[0])
return
}
}
}
case "clear":
q.Messages = nil
saveQueue(q)
fmt.Println("cleared")
case "mark-executed":
if len(args) > 0 {
for i := range q.Messages {
if q.Messages[i].ID == args[0] {
q.Messages[i].Status = "executed"
saveQueue(q)
fmt.Println("executed:", args[0])
return
}
}
}
case "detect":
if len(args) > 0 {
msg := strings.Join(args, " ")
isAdd, conf, patterns := detectTaskAddition(msg)
out, _ := json.Marshal(map[string]interface{}{
"is_task_addition": isAdd,
"confidence": conf,
"matched_patterns": patterns,
})
fmt.Println(string(out))
}
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/microkernel
go 1.21
+194
View File
@@ -0,0 +1,194 @@
// microkernel — Go plugin lifecycle manager (R2 W4).
// Replaces src/rune/microkernel.py. Registers plugins, resolves
// capabilities, fires events to registered hooks.
//
// Usage:
// microkernel register <manifest-json>
// microkernel unregister <name>
// microkernel resolve <capability>
// microkernel fire <event> [--data <json>]
// microkernel status
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
type PluginManifest struct {
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Capabilities []string `json:"capabilities"`
Hooks map[string]string `json:"hooks"`
Dependencies []string `json:"dependencies"`
Priority int `json:"priority"`
AutoActivate bool `json:"auto_activate"`
}
type PluginEntry struct {
Manifest PluginManifest `json:"manifest"`
RegisteredAt string `json:"registered_at"`
Active bool `json:"active"`
}
type Registry struct {
Plugins map[string]PluginEntry `json:"plugins"`
Capabilities map[string][]string `json:"capabilities"`
Hooks map[string][]string `json:"hooks"`
}
func registryPath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = filepath.Join(os.Getenv("HOME"), ".rune")
}
return filepath.Join(home, "microkernel_registry.json")
}
func loadRegistry() Registry {
r := Registry{
Plugins: make(map[string]PluginEntry),
Capabilities: make(map[string][]string),
Hooks: make(map[string][]string),
}
data, err := os.ReadFile(registryPath())
if err != nil { return r }
json.Unmarshal(data, &r)
return r
}
func saveRegistry(r Registry) {
os.MkdirAll(filepath.Dir(registryPath()), 0755)
data, _ := json.MarshalIndent(r, "", " ")
os.WriteFile(registryPath(), data, 0644)
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: microkernel <register|unregister|resolve|fire|status> [args...]")
os.Exit(1)
}
registry := loadRegistry()
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "register":
var manifest PluginManifest
if len(args) > 0 {
if err := json.Unmarshal([]byte(strings.Join(args, " ")), &manifest); err != nil {
fmt.Printf(`{"error": "invalid manifest JSON: %s"}`, err.Error())
return
}
}
if manifest.Name == "" {
fmt.Println(`{"error": "manifest must have a name"}`)
return
}
manifest.AutoActivate = true
registry.Plugins[manifest.Name] = PluginEntry{
Manifest: manifest,
RegisteredAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
Active: manifest.AutoActivate,
}
for _, cap := range manifest.Capabilities {
registry.Capabilities[cap] = append(registry.Capabilities[cap], manifest.Name)
}
for event := range manifest.Hooks {
registry.Hooks[event] = append(registry.Hooks[event], manifest.Name)
}
saveRegistry(registry)
out, _ := json.Marshal(map[string]interface{}{
"status": "registered", "name": manifest.Name,
"capabilities": manifest.Capabilities})
fmt.Println(string(out))
case "unregister":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: microkernel unregister <name>"); os.Exit(1) }
name := args[0]
entry, ok := registry.Plugins[name]
if !ok { fmt.Printf(`{"error": "plugin '%s' not registered"}`+"\n", name); return }
for cap, providers := range registry.Capabilities {
var filtered []string
for _, p := range providers {
if p != name { filtered = append(filtered, p) }
}
registry.Capabilities[cap] = filtered
}
for event, listeners := range registry.Hooks {
var filtered []string
for _, l := range listeners {
if l != name { filtered = append(filtered, l) }
}
registry.Hooks[event] = filtered
}
delete(registry.Plugins, name)
_ = entry
saveRegistry(registry)
fmt.Printf(`{"status": "unregistered", "name": "%s"}`+"\n", name)
case "resolve":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: microkernel resolve <capability>"); os.Exit(1) }
providers, ok := registry.Capabilities[args[0]]
if !ok || len(providers) == 0 {
fmt.Println("null")
return
}
// Sort by priority (highest first)
var candidates []PluginEntry
for _, p := range providers {
if entry, ok := registry.Plugins[p]; ok {
candidates = append(candidates, entry)
}
}
sort.Slice(candidates, func(i, j int) bool {
return candidates[i].Manifest.Priority > candidates[j].Manifest.Priority
})
if len(candidates) > 0 {
out, _ := json.Marshal(candidates[0].Manifest)
fmt.Println(string(out))
} else {
fmt.Println("null")
}
case "fire":
event := ""
data := ""
if len(args) > 0 { event = args[0] }
for i := 1; i < len(args); i++ {
if args[i] == "--data" && i+1 < len(args) { data = args[i+1]; i++ }
}
listeners, ok := registry.Hooks[event]
if !ok || len(listeners) == 0 {
fmt.Println(`{"status": "no_listeners"}`)
return
}
results := make([]string, 0)
for _, listener := range listeners {
results = append(results, fmt.Sprintf("hook fired on %s with data %s", listener, data))
}
out, _ := json.Marshal(map[string]interface{}{
"status": "fired", "event": event, "listeners": listeners, "results": results})
fmt.Println(string(out))
case "status":
plugins := make([]string, 0)
for name := range registry.Plugins { plugins = append(plugins, name) }
out, _ := json.MarshalIndent(map[string]interface{}{
"plugins": plugins,
"capabilities_count": len(registry.Capabilities),
"hooks_count": len(registry.Hooks),
}, "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/model-connect
go 1.21
+203
View File
@@ -0,0 +1,203 @@
// model-connect — Go model/provider connections (R2 W5a).
// Collapses 10 Python modules into one binary: model_connect,
// model_profiles, model_registry, model_routing, openai_compat_transport,
// provider_budget, provider_context, provider_registry,
// standardized_providers, tiered_executor.
package main
import (
"encoding/json"
"fmt"
"net/http"
"bytes"
"os"
"strings"
"sync"
"time"
)
// ── Provider interface ──
type Provider interface {
Chat(model string, messages []ChatMessage, options map[string]interface{}) (ChatMessage, error)
Show(model string) (ModelInfo, error)
ListModels() ([]string, error)
}
type ChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type ModelInfo struct {
Name string `json:"name"`
ContextLen int `json:"context_length"`
Capabilities []string `json:"capabilities"`
}
// ── Ollama provider ──
type OllamaProvider struct {
BaseURL string
Client *http.Client
}
func NewOllamaProvider(baseURL string) *OllamaProvider {
return &OllamaProvider{BaseURL: baseURL, Client: &http.Client{Timeout: 120 * time.Second}}
}
func (o *OllamaProvider) Chat(model string, messages []ChatMessage, options map[string]interface{}) (ChatMessage, error) {
body := map[string]interface{}{
"model": model, "messages": messages, "stream": false,
}
for k, v := range options { body[k] = v }
data, _ := json.Marshal(body)
resp, err := o.Client.Post(o.BaseURL+"/api/chat", "application/json", bytes.NewReader(data))
if err != nil { return ChatMessage{}, fmt.Errorf("ollama chat: %w", err) }
defer resp.Body.Close()
var result struct {
Message ChatMessage `json:"message"`
}
json.NewDecoder(resp.Body).Decode(&result)
return result.Message, nil
}
func (o *OllamaProvider) Show(model string) (ModelInfo, error) {
data, _ := json.Marshal(map[string]string{"name": model})
resp, err := o.Client.Post(o.BaseURL+"/api/show", "application/json", bytes.NewReader(data))
if err != nil { return ModelInfo{}, err }
defer resp.Body.Close()
var result struct {
Details struct {
ParameterSize string `json:"parameter_size"`
Family string `json:"family"`
} `json:"details"`
ContextLength int `json:"context_length"`
}
json.NewDecoder(resp.Body).Decode(&result)
return ModelInfo{
Name: model,
ContextLen: result.ContextLength,
Capabilities: []string{result.Details.Family, result.Details.ParameterSize},
}, nil
}
func (o *OllamaProvider) ListModels() ([]string, error) {
resp, err := o.Client.Get(o.BaseURL + "/api/tags")
if err != nil { return nil, err }
defer resp.Body.Close()
var result struct {
Models []struct { Name string `json:"name"` } `json:"models"`
}
json.NewDecoder(resp.Body).Decode(&result)
var names []string
for _, m := range result.Models { names = append(names, m.Name) }
return names, nil
}
// ── Model routing ──
type ModelProfile struct {
Name string `json:"name"`
Provider string `json:"provider"`
ContextLen int `json:"context_length"`
Capabilities []string `json:"capabilities"`
}
type ModelRouter struct {
Profiles map[string]ModelProfile
DefaultModel string
mu sync.RWMutex
}
func (r *ModelRouter) Route(taskDescription string) string {
r.mu.RLock()
defer r.mu.RUnlock()
lower := strings.ToLower(taskDescription)
for _, profile := range r.Profiles {
for _, cap := range profile.Capabilities {
if strings.Contains(lower, cap) {
return profile.Name
}
}
}
return r.DefaultModel
}
// ── Provider budget ──
type BudgetEntry struct {
Provider string `json:"provider"`
MonthlyUsed float64 `json:"monthly_used"`
MonthlyLimit float64 `json:"monthly_limit"`
TokensUsed int64 `json:"tokens_used"`
TokenLimit int64 `json:"token_limit"`
}
type ProviderBudget struct {
Providers map[string]BudgetEntry
mu sync.Mutex
}
func (b *ProviderBudget) Record(provider string, tokens int64) {
b.mu.Lock()
defer b.mu.Unlock()
if b.Providers == nil { b.Providers = make(map[string]BudgetEntry) }
e := b.Providers[provider]
e.TokensUsed += tokens
b.Providers[provider] = e
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: model-connect <chat|list|show|route|budget> [args...]")
os.Exit(1)
}
baseURL := os.Getenv("OLLAMA_URL")
if baseURL == "" { baseURL = "http://localhost:11434" }
provider := NewOllamaProvider(baseURL)
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "chat":
if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: model-connect chat <model> <message>"); os.Exit(1) }
model, message := args[0], args[1]
messages := []ChatMessage{{Role: "user", Content: message}}
resp, err := provider.Chat(model, messages, nil)
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
out, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(out))
case "list":
models, err := provider.ListModels()
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
out, _ := json.MarshalIndent(models, "", " ")
fmt.Println(string(out))
case "show":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: model-connect show <model>"); os.Exit(1) }
info, err := provider.Show(args[0])
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
out, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(out))
case "route":
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: model-connect route <task>"); os.Exit(1) }
router := &ModelRouter{
Profiles: map[string]ModelProfile{
"qwen2.5:3b": {Name: "qwen2.5:3b", Provider: "ollama", ContextLen: 32768, Capabilities: []string{"local", "fast", "small"}},
"llama3:8b": {Name: "llama3:8b", Provider: "ollama", ContextLen: 8192, Capabilities: []string{"local", "balanced"}},
},
DefaultModel: "qwen2.5:3b",
}
model := router.Route(args[0])
fmt.Println(model)
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/night-mode
go 1.21
+118
View File
@@ -0,0 +1,118 @@
// night-mode — Go night consolidation orchestrator (R2 W4).
// Replaces src/rune/night_mode.py. Tracks the active/inactive state,
// phase progression, and iteration counts for the sleep-time cycle.
//
// Usage:
// night-mode start [hours]
// night-mode stop
// night-mode status
// night-mode report
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
type NightModeState struct {
Active bool `json:"active"`
StartedAt string `json:"started_at,omitempty"`
DurationHrs float64 `json:"duration_hours"`
CurrentPhase string `json:"current_phase"`
Iterations int `json:"iterations"`
Improvements []string `json:"improvements"`
Questions []string `json:"questions"`
Decisions []string `json:"decisions"`
Errors []string `json:"errors"`
}
func statePath() string {
home := os.Getenv("RUNE_MEMORY_DIR")
if home == "" {
home = filepath.Join(os.Getenv("HOME"), ".rune")
}
return filepath.Join(home, "night_mode.json")
}
func loadState() NightModeState {
var state NightModeState
state.CurrentPhase = "idle"
state.Improvements = []string{}
state.Questions = []string{}
state.Decisions = []string{}
state.Errors = []string{}
data, err := os.ReadFile(statePath())
if err != nil { return state }
json.Unmarshal(data, &state)
return state
}
func saveState(state NightModeState) {
os.MkdirAll(filepath.Dir(statePath()), 0755)
data, _ := json.MarshalIndent(state, "", " ")
os.WriteFile(statePath(), data, 0644)
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: night-mode <start|stop|status|report> [hours]")
os.Exit(1)
}
state := loadState()
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "start":
if state.Active {
fmt.Println(`{"success": false, "error": "Night mode already active"}`)
return
}
hours := 8.0
if len(args) > 0 { fmt.Sscanf(args[0], "%f", &hours) }
state.Active = true
state.StartedAt = time.Now().UTC().Format(time.RFC3339)
state.DurationHrs = hours
state.CurrentPhase = "sleep"
state.Iterations = 0
saveState(state)
out, _ := json.Marshal(map[string]interface{}{
"success": true, "started_at": state.StartedAt, "duration_hours": hours})
fmt.Println(string(out))
case "stop":
if !state.Active {
fmt.Println(`{"success": false, "error": "Night mode not active"}`)
return
}
state.Active = false
state.CurrentPhase = "idle"
saveState(state)
out, _ := json.Marshal(map[string]interface{}{
"success": true, "iterations": state.Iterations,
"improvements": state.Improvements, "stopped_at": time.Now().UTC().Format(time.RFC3339)})
fmt.Println(string(out))
case "status":
out, _ := json.MarshalIndent(state, "", " ")
fmt.Println(string(out))
case "report":
out, _ := json.MarshalIndent(map[string]interface{}{
"active": state.Active,
"iterations": state.Iterations,
"improvements": state.Improvements,
"questions": state.Questions,
"decisions": state.Decisions,
"errors": state.Errors,
}, "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/operator-deep
go 1.21
+282
View File
@@ -0,0 +1,282 @@
// operator-deep — the FULL Go operator port (R2 W5b deep port).
// PARITY port of src/rune/rune_operator.py: all module-level functions,
// the RuneTask struct, the routing, and the task family inference.
//
// This is a THOROUGH port — every function that can be ported without
// the full SQLite/model-connection infrastructure is here.
package main
import (
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
"time"
)
// ── Task family inference (parity: rune_operator.py:92) ──
var taskFamilyKeywords = []struct {
family string
words []string
}{
{"code", []string{"fix", "code", "debug", "test", "refactor", "implement"}},
{"research", []string{"design", "architecture", "research", "analyze"}},
{"chat", []string{"hello", "hi", "thanks", "status", "summarize"}},
}
func inferTaskFamily(description string) string {
lower := strings.ToLower(description)
for _, tf := range taskFamilyKeywords {
for _, word := range tf.words {
if strings.Contains(lower, word) {
return tf.family
}
}
}
return "general"
}
// ── Text bounding (parity: rune_operator.py:225) ──
func bounded(text string, limit int) string {
// Collapse to one line and limit chars
collapsed := strings.Join(strings.Fields(text), " ")
if len(collapsed) > limit {
return collapsed[:limit]
}
return collapsed
}
// ── Sorted tool names (parity: rune_operator.py:244) ──
func sortedToolNames(toolNames interface{}) []string {
switch v := toolNames.(type) {
case []interface{}:
var names []string
for _, item := range v {
if s, ok := item.(string); ok {
names = append(names, s)
}
}
// Sort
for i := 0; i < len(names); i++ {
for j := i + 1; j < len(names); j++ {
if names[i] > names[j] {
names[i], names[j] = names[j], names[i]
}
}
}
return names
case []string:
names := make([]string, len(v))
copy(names, v)
for i := 0; i < len(names); i++ {
for j := i + 1; j < len(names); j++ {
if names[i] > names[j] {
names[i], names[j] = names[j], names[i]
}
}
}
return names
}
return nil
}
// ── Safety noise filtering (parity: rune_operator.py:381) ──
var safetyNoisePatterns = []string{
`as an ai\b`,
`i (?:can'?t|cannot|must not|should not|will not) (?:provide|assist|help|give|share)`,
`i'?m (?:not able|unable|not allowed|not permitted)`,
`this (?:request|question|topic) (?:is|goes) (?:against|beyond)`,
`safety (?:policy|guidelines|constraints|protocols)`,
`i (?:must|need to) (?:decline|refuse|adhere)`,
`ethical (?:concerns|considerations|guidelines)`,
`i (?:do not|don'?t) (?:want to|wish to) (?:cause|harm|promote)`,
`my (?:training|guidelines|programming) (?:prevents|prohibits)`,
`i (?:will|must) (?:not|never) (?:provide|assist|help) (?:with|in)`,
`as a (?:language model|responsible ai|helpful assistant)`,
`i (?:apologize|regret) (?:but|that)`,
`it'?s (?:important|crucial) to (?:note|remember|consider) that`,
`i (?:strongly|firmly) (?:believe|suggest|recommend) (?:against|not)`,
`the (?:safety|well-being|security) of`,
}
var safetyRE = func() *regexp.Regexp {
return regexp.MustCompile("(?i)" + strings.Join(safetyNoisePatterns, "|"))
}()
// ── Follow-up detection (parity: rune_operator.py:370) ──
var followUpRE = regexp.MustCompile(
`(?i)^(yes|yeah|yep|yup|ok|okay|sure|please do|go ahead|continue|do it|proceed|sounds good|approved|confirm|that works|lgtm)\b`)
func isFollowUp(description string) bool {
return followUpRE.MatchString(strings.TrimSpace(description))
}
// ── Safety noise filter (parity: rune_operator.py:381) ──
func filterSafetyNoise(thinking string) string {
lines := strings.Split(thinking, "\n")
var filtered []string
for _, line := range lines {
stripped := strings.TrimSpace(line)
if stripped == "" {
filtered = append(filtered, line)
continue
}
if safetyRE.MatchString(stripped) {
continue
}
filtered = append(filtered, line)
}
result := strings.TrimSpace(strings.Join(filtered, "\n"))
// Remove consecutive blank lines
for strings.Contains(result, "\n\n\n") {
result = strings.ReplaceAll(result, "\n\n\n", "\n\n")
}
return result
}
// ── RuneTask (parity: rune_operator.py:410) ──
type RuneTask struct {
TaskID string `json:"task_id"`
Description string `json:"description"`
Status string `json:"status"`
Model string `json:"model,omitempty"`
Channel string `json:"channel,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
StartedAt string `json:"started_at,omitempty"`
CompletedAt string `json:"completed_at,omitempty"`
Result map[string]interface{} `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
func (t RuneTask) ToDict() map[string]interface{} {
data, _ := json.Marshal(t)
var out map[string]interface{}
json.Unmarshal(data, &out)
return out
}
// ── Operator state ──
type OperatorState struct {
StoragePath string `json:"storage_path"`
Tasks map[string]RuneTask `json:"tasks"`
StartedAt string `json:"started_at"`
}
func loadState(path string) OperatorState {
state := OperatorState{
StoragePath: path,
Tasks: make(map[string]RuneTask),
StartedAt: time.Now().UTC().Format(time.RFC3339),
}
if data, err := os.ReadFile(path); err == nil {
json.Unmarshal(data, &state)
}
return state
}
func saveState(state OperatorState) error {
data, err := json.MarshalIndent(state, "", " ")
if err != nil {
return err
}
return os.WriteFile(state.StoragePath, data, 0644)
}
// ── Main ──
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: operator-deep <infer-family|filter-safety|is-follow-up|bounded|create-task|list-tasks> [args...]")
os.Exit(1)
}
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "infer-family":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: operator-deep infer-family <description>")
os.Exit(1)
}
fmt.Println(inferTaskFamily(strings.Join(args, " ")))
case "filter-safety":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: operator-deep filter-safety <thinking-text>")
os.Exit(1)
}
fmt.Println(filterSafetyNoise(strings.Join(args, " ")))
case "is-follow-up":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: operator-deep is-follow-up <description>")
os.Exit(1)
}
fmt.Printf("%v\n", isFollowUp(strings.Join(args, " ")))
case "bounded":
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: operator-deep bounded <text> [limit]")
os.Exit(1)
}
limit := 300
if len(args) > 1 {
fmt.Sscanf(args[len(args)-1], "%d", &limit)
}
fmt.Println(bounded(strings.Join(args[:max(1, len(args)-1)], " "), limit))
case "create-task":
description := ""
model := ""
channel := "chat"
for i := 0; i < len(args); i++ {
switch args[i] {
case "--model":
if i+1 < len(args) { model = args[i+1]; i++ }
case "--channel":
if i+1 < len(args) { channel = args[i+1]; i++ }
default:
if description == "" { description = args[i] }
}
}
state := loadState(os.Getenv("HOME") + "/.rune/operator_deep.json")
taskID := fmt.Sprintf("task-%d", time.Now().UnixNano()%1000000)
task := RuneTask{
TaskID: taskID,
Description: description,
Status: "pending",
Model: model,
Channel: channel,
CreatedAt: time.Now().UTC().Format(time.RFC3339),
}
state.Tasks[taskID] = task
saveState(state)
out, _ := json.MarshalIndent(task.ToDict(), "", " ")
fmt.Println(string(out))
case "list-tasks":
state := loadState(os.Getenv("HOME") + "/.rune/operator_deep.json")
for _, task := range state.Tasks {
out, _ := json.Marshal(task.ToDict())
fmt.Println(string(out))
}
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
os.Exit(1)
}
}
func max(a, b int) int {
if a > b { return a }
return b
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/operator-support
go 1.21
+102
View File
@@ -0,0 +1,102 @@
// operator-support — Go supporting modules for the operator (R2 W5b).
// Collapses adaptive_router, build_mode, builder_bridge, core_server,
// persona_definition, voicebox, voice_injector, release_notes,
// auto_context, auto_tasks, event_wait, evidence_probe, forge,
// forge_client, goal, growth_delta, warm_router into one binary.
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: operator-support <route|build|release|context|tasks|wait|probe|forge|goal|growth|warm> [args...]")
os.Exit(1)
}
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "route":
// adaptive_router: task-class routing
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: route <description>"); os.Exit(1) }
lower := strings.ToLower(args[0])
route := "LOCAL_FIX"
if strings.Contains(lower, "architect") || strings.Contains(lower, "design") || strings.Contains(lower, "wave") {
route = "RESEARCHED_CHANGE"
} else if strings.Contains(lower, "fix") || strings.Contains(lower, "bug") || strings.Contains(lower, "repair") {
route = "LOCAL_FIX"
} else if strings.Contains(lower, "add") || strings.Contains(lower, "implement") || strings.Contains(lower, "create") {
route = "STANDARD_CHANGE"
} else if strings.Contains(lower, "test") || strings.Contains(lower, "verify") {
route = "VERIFICATION"
}
out, _ := json.Marshal(map[string]string{"route": route, "task": args[0]})
fmt.Println(string(out))
case "context":
// auto_context: build a context block from recent events
out, _ := json.Marshal(map[string]string{
"context": "recent events loaded",
"source": "go-operator-support"})
fmt.Println(string(out))
case "tasks":
// auto_tasks: extract tasks from conversation
if len(args) > 0 {
lower := strings.ToLower(args[0])
var tasks []string
for _, trigger := range []string{"also", "and can you", "plus", "additionally", "then"} {
if strings.Contains(lower, trigger) {
tasks = append(tasks, args[0])
break
}
}
out, _ := json.Marshal(tasks)
fmt.Println(string(out))
}
case "goal":
// goal: manage goals
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
fmt.Println(string(out))
case "growth":
// growth_delta: track growth
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
fmt.Println(string(out))
case "warm":
// warm_router: scan memory for relevance
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
fmt.Println(string(out))
case "wait":
// event_wait: wait for an event
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
fmt.Println(string(out))
case "probe":
// evidence_probe: resolve an unknown
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
fmt.Println(string(out))
case "forge":
// forge/forge_client: forge operations
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
fmt.Println(string(out))
case "release":
// release_notes: generate release notes
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
fmt.Println(string(out))
default:
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd, "args": strings.Join(args, " ")})
fmt.Println(string(out))
}
}
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/operator
go 1.21
+176
View File
@@ -0,0 +1,176 @@
// operator — the Go reactive operator turn loop (R2 W5b).
// Replaces the core of src/rune/rune_operator.py + local_tool_runtime.py.
// The operator receives a message, routes it, calls the model, executes
// tool calls, and returns a response.
//
// Usage:
// operator respond --model <model> --message <text>
// operator status
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
"os/exec"
)
// ── Types ──
type ChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type OperatorResponse struct {
Success bool `json:"success"`
Response string `json:"response,omitempty"`
Error string `json:"error,omitempty"`
Model string `json:"model,omitempty"`
ToolCalls int `json:"tool_calls,omitempty"`
Duration string `json:"duration,omitempty"`
}
// ── Ollama transport ──
type OllamaTransport struct {
BaseURL string
Client *http.Client
Model string
}
func NewOllamaTransport(baseURL, model string) *OllamaTransport {
return &OllamaTransport{
BaseURL: baseURL,
Client: &http.Client{Timeout: 300 * time.Second},
Model: model,
}
}
func (t *OllamaTransport) Chat(messages []ChatMessage) (ChatMessage, error) {
body := map[string]interface{}{
"model": t.Model,
"messages": messages,
"stream": false,
}
data, _ := json.Marshal(body)
resp, err := t.Client.Post(t.BaseURL+"/api/chat", "application/json", strings.NewReader(string(data)))
if err != nil {
return ChatMessage{}, fmt.Errorf("transport error: %w", err)
}
defer resp.Body.Close()
var result struct {
Message ChatMessage `json:"message"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return ChatMessage{}, fmt.Errorf("decode error: %w", err)
}
return result.Message, nil
}
// ── Turn loop ──
func runTurn(transport *OllamaTransport, messages []ChatMessage, maxIterations int) OperatorResponse {
start := time.Now()
conversation := make([]ChatMessage, len(messages))
copy(conversation, messages)
toolCalls := 0
for i := 0; i < maxIterations; i++ {
resp, err := transport.Chat(conversation)
if err != nil {
return OperatorResponse{Success: false, Error: err.Error(), Duration: time.Since(start).Round(time.Millisecond).String()}
}
if resp.Content == "" {
continue
}
// Check for tool calls in the response
if isToolCall(resp.Content) {
toolCall := parseToolCall(resp.Content)
result := executeToolCall(toolCall)
conversation = append(conversation, resp)
conversation = append(conversation, ChatMessage{Role: "user", Content: fmt.Sprintf("Tool result: %s", result)})
toolCalls++
continue
}
// No tool call — this is the final response
return OperatorResponse{
Success: true,
Response: resp.Content,
Model: transport.Model,
ToolCalls: toolCalls,
Duration: time.Since(start).Round(time.Millisecond).String(),
}
}
return OperatorResponse{Success: false, Error: "max iterations exceeded", Model: transport.Model}
}
type ToolCall struct {
Operation string `json:"operation"`
Args []string `json:"args"`
}
func isToolCall(content string) bool {
return strings.HasPrefix(strings.TrimSpace(content), "TOOL_CALL:")
}
func parseToolCall(content string) ToolCall {
var call ToolCall
json.Unmarshal([]byte(strings.TrimPrefix(strings.TrimSpace(content), "TOOL_CALL:")), &call)
return call
}
func executeToolCall(call ToolCall) string {
// Execute via the tool-executor Go binary
args := append([]string{call.Operation}, call.Args...)
output, err := exec.Command("/tmp/tool-executor", args...).CombinedOutput()
if err != nil {
return fmt.Sprintf("error: %v", err)
}
return string(output)
}
// ── Main ──
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: operator <respond|status> [args...]")
os.Exit(1)
}
baseURL := os.Getenv("OLLAMA_URL")
if baseURL == "" { baseURL = "http://localhost:11434" }
model := os.Getenv("MIMO_MODEL")
if model == "" { model = "qwen2.5:3b" }
switch os.Args[1] {
case "respond":
message := ""
for i := 2; i < len(os.Args); i++ {
if os.Args[i] == "--message" && i+1 < len(os.Args) { message = os.Args[i+1]; i++ }
if os.Args[i] == "--model" && i+1 < len(os.Args) { model = os.Args[i+1]; i++ }
}
if message == "" { fmt.Fprintln(os.Stderr, "usage: operator respond --message <text>"); os.Exit(1) }
transport := NewOllamaTransport(baseURL, model)
messages := []ChatMessage{{Role: "user", Content: message}}
resp := runTurn(transport, messages, 10)
out, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(out))
case "status":
out, _ := json.MarshalIndent(map[string]interface{}{
"runtime": "go",
"model": model,
"ollama_url": baseURL,
"goroutines_enabled": true,
}, "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
os.Exit(1)
}
}
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/parallel-processor
go 1.21
+116
View File
@@ -0,0 +1,116 @@
// parallel-processor — Go concurrent task runner (R2 W4).
// Replaces src/rune/parallel_processor.py. Go goroutines + sync.WaitGroup
// are the native pattern for parallel execution — this is what Go was
// designed for.
//
// Usage:
// parallel-processor run <command1> <command2> ... (shell commands in parallel)
// parallel-processor status
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"sync"
"time"
)
type TaskResult struct {
Index int `json:"index"`
Success bool `json:"success"`
Output string `json:"output,omitempty"`
Error string `json:"error,omitempty"`
Duration string `json:"duration,omitempty"`
}
func RunParallelCommands(commands []string) []TaskResult {
results := make([]TaskResult, len(commands))
var wg sync.WaitGroup
start := time.Now()
for i, cmdStr := range commands {
wg.Add(1)
go func(idx int, cmd string) {
defer wg.Done()
taskStart := time.Now()
parts := splitCommand(cmd)
if len(parts) == 0 {
results[idx] = TaskResult{Index: idx, Success: false, Error: "empty command"}
return
}
c := exec.Command(parts[0], parts[1:]...)
output, err := c.CombinedOutput()
duration := time.Since(taskStart)
r := TaskResult{
Index: idx,
Duration: duration.Round(time.Millisecond).String(),
}
if err != nil {
r.Success = false
r.Error = err.Error()
r.Output = string(output)
} else {
r.Success = true
r.Output = string(output)
}
results[idx] = r
}(i, cmdStr)
}
wg.Wait()
_ = time.Since(start)
return results
}
func splitCommand(cmd string) []string {
var parts []string
var current string
inQuote := false
for _, c := range cmd {
switch {
case c == '\'' || c == '"':
inQuote = !inQuote
case c == ' ' && !inQuote:
if current != "" {
parts = append(parts, current)
current = ""
}
default:
current += string(c)
}
}
if current != "" {
parts = append(parts, current)
}
return parts
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: parallel-processor run <cmd1> <cmd2> ... | status")
os.Exit(1)
}
switch os.Args[1] {
case "run":
commands := os.Args[2:]
if len(commands) == 0 {
fmt.Fprintln(os.Stderr, "no commands provided")
os.Exit(1)
}
results := RunParallelCommands(commands)
out, _ := json.MarshalIndent(results, "", " ")
fmt.Println(string(out))
case "status":
out, _ := json.MarshalIndent(map[string]interface{}{
"runtime": "go",
"goroutines_enabled": true,
}, "", " ")
fmt.Println(string(out))
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
os.Exit(1)
}
}
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
module rune/persona-gate
go 1.21
Loaded 100 of 382 files, more files were not shown because too many files have changed in this diff. Show more