Compare commits

...
10 Commits
Author SHA1 Message Date
conductor 7d96be8f40 chore(release): auto-bump version to 0.357.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 43s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m49s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / gitea-offline (push) Successful in 14s
Auto-version and Release / release (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 14m30s
CI Pipeline / unit-tests (push) Canceled after 18m46s
Promotion record: (unknown)
2026-10-25 03:53:23 +00:00
Nick 690c1b4832 feat(voicebox): REBASE-COMPLETION wave 9/11 — fail-loud Go delegation wired for the hook seam (Gitea #2587): eval.go ops surface (handle_hook, manifest) beside the truth replay (still green); handle_hook in src/rune/voicebox.py resolves through the Go binary — the engine's documented empty-event validation improvement (error where the oracle lies ok) surfaces through the delegation, pinned both sides; Python oracle body kept as RUNE_VOICEBOX_ENGINE=python escape/oracle, RUNE_VOICEBOX_BIN override, typed RuntimeError on missing binary; speak/listen/talk stay Python (pynput/venv-bound — blocks_full_switchover recorded); red-first battery 6 tests; oracle handler mutation flips the battery; receipt verification/integrated/voicebox-integrated.json
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 26s
2026-10-25 03:52:54 +00:00
conductor 1a9cfd37d8 chore(release): auto-bump version to 0.356.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 49s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m38s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 36s
CI Pipeline / gitea-offline (push) Successful in 16s
Auto-version and Release / release (push) Failing after 26s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 14m6s
CI Pipeline / unit-tests (push) Canceled after 18m12s
Promotion record: (unknown)
2026-10-25 03:34:17 +00:00
Nick bee77e00e5 feat(auto-context): REBASE-COMPLETION wave 8/11 — fail-loud Go delegation wired for auto-context build (Gitea #2587): eval.go ops surface (build_auto_context, load_active_tasks; memory_dir-addressed; subprocess seams shell the same warm_router/memory_store scripts) beside the truth replay (still green); both functions in src/rune/auto_context.py resolve through the Go binary; Python oracle bodies kept as RUNE_AUTO_CONTEXT_ENGINE=python escape/oracle, RUNE_AUTO_CONTEXT_BIN override, typed RuntimeError on missing binary; red-first battery 8 tests; oracle task-filter mutation flips the battery; receipt verification/integrated/auto-context-integrated.json
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 23s
deploy / deploy (push) Canceled after 14s
2026-10-25 03:34:00 +00:00
conductor 3ceb3e5084 chore(release): auto-bump version to 0.355.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 25s
CI Pipeline / lint (push) Failing after 34s
deploy / deploy (push) Failing after 15m31s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / rust-surface (push) Failing after 8s
CI Pipeline / canary (push) Failing after 1m46s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 22m45s
Promotion record: (unknown)
2026-10-25 03:09:58 +00:00
Nick e09bfeb28d feat(abstraction): REBASE-COMPLETION wave 7/11 — fail-loud Go delegation wired for the abstraction read/report seams (Gitea #2587): eval.go ops surface (list_beliefs/list_rules/list_skills/pipeline_stats/handle_hook/extract_facts over the truth-tested library, memory_dir-addressed) beside the truth replay (still green); five seam functions in src/rune/abstraction.py resolve through the Go binary against the SAME abstraction.db; Python oracle bodies kept as RUNE_ABSTRACTION_ENGINE=python escape/oracle, RUNE_ABSTRACTION_BIN override, typed RuntimeError on missing binary; extract_facts/synthesize_observations/run_lifecycle stay Python this wave (the llm_call judge seam — blocks_full_switchover recorded); red-first battery 9 tests; oracle state-filter mutation flips the battery; receipt verification/integrated/abstraction-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 25s
CI Pipeline / lint (push) Canceled after 30s
2026-10-25 03:09:32 +00:00
conductor fec44ba321 chore(release): auto-bump version to 0.354.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 25s
CI Pipeline / lint (push) Failing after 34s
deploy / deploy (push) Failing after 15m25s
CI Pipeline / go-surface (push) Failing after 21s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m43s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 38s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 26m50s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 02:28:01 +00:00
Nick 058b37d5a9 feat(message-queue): REBASE-COMPLETION wave 6/11 — fail-loud Go delegation wired into the message queue (Gitea #2587): eval.go ops surface (detect_task_addition/add_message/get_pending/approve/reject/approve_all/clear/get_approved/mark_executed/get_status over the truth-tested MessageQueue library, queue_dir-addressed, snake_case projection) beside the truth replay (still green); eight seam methods in src/rune/message_queue.py resolve through the Go binary against the SAME queue.json — the facade reloads its in-memory mirror after engine mutations, cross-engine coherence pinned both directions; Python oracle bodies kept as RUNE_MESSAGE_QUEUE_ENGINE=python escape/oracle, RUNE_MESSAGE_QUEUE_BIN override, typed RuntimeError on missing binary; red-first battery 14 tests; oracle detect-weight mutation flips the battery; receipt verification/integrated/message-queue-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 17s
CI Pipeline / lint (push) Canceled after 18s
2026-10-25 02:27:44 +00:00
conductor 1c0e88d6bc chore(release): auto-bump version to 0.353.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 44s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 10s
CI Pipeline / canary (push) Failing after 1m44s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 35s
CI Pipeline / gitea-offline (push) Successful in 14s
Auto-version and Release / release (push) Failing after 26s
deploy / deploy (push) Failing after 15m19s
CI Pipeline / unit-tests (push) Failing after 27m17s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 01:37:51 +00:00
Nick 543339517b feat(cache): REBASE-COMPLETION wave 5/11 — fail-loud Go delegation wired into the cache seam (Gitea #2587): eval.go ops surface (get_cache/put_cache/invalidate/invalidate_scope/invalidate_all/cache_stats/handle_hook over the truth-tested Env library, ops carry memory_dir) beside the frozen-truth replays (still green); seven seam functions in src/rune/cache.py resolve through the Go binary against the SAME cache.db — cross-engine coherence pinned both directions; miss contract ({hit: false, key}) translated at the seam so both engines expose the identical shape; Python oracle bodies kept as RUNE_CACHE_ENGINE=python escape/oracle, RUNE_CACHE_BIN override, typed RuntimeError on missing binary; red-first battery 13 tests; oracle _hash mutation flips the battery; receipt verification/integrated/cache-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / lint (push) Canceled after 15s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 23s
2026-10-25 01:37:15 +00:00
30 changed files with 1926 additions and 19 deletions

No files matched your search

+2 -2
View File
@@ -1,9 +1,9 @@
# Rune
**Current release: v0.352.0**
**Current release: v0.357.0**
<!-- highlights:start -->
## Highlights — from the latest release (v0.352.0)
## Highlights — from the latest release (v0.357.0)
- Plain-English jargon dictionary covers the v0.329 vocabulary, plus catch-all sentence simplification pass.
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.352.0"
version = "0.357.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+134
View File
@@ -0,0 +1,134 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/abstraction.py). `abstraction eval <ops.json>` reads a JSON
// array of op dicts and emits one result per op. Each op carries
// memory_dir (the seamDBPath parent — the abstraction.db location the
// oracle body shares).
//
// Ops: list_beliefs, list_rules, list_skills, pipeline_stats,
// handle_hook, extract_facts (the LLM seam runs through the engine's
// seamLLMPost — the same urllib transport contract the oracle's
// llm_call uses, driven by OLLAMA_URL / model env).
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_ABSTRACTION_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func abstractionEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := abstractionEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func abstractionEvalOp(name string, op map[string]any) (any, string) {
if dir, ok := op["memory_dir"].(string); ok && dir != "" {
old := seamDBPath
seamDBPath = func() string { return dir + "/abstraction.db" }
defer func() { seamDBPath = old }()
}
switch name {
case "list_beliefs":
limit := 20
if l, ok := op["limit"].(float64); ok {
limit = int(l)
}
return plainJSON(ListBeliefs(limit)), ""
case "list_rules":
limit := 20
if l, ok := op["limit"].(float64); ok {
limit = int(l)
}
return plainJSON(ListRules(limit)), ""
case "list_skills":
limit := 20
if l, ok := op["limit"].(float64); ok {
limit = int(l)
}
return plainJSON(ListSkills(limit)), ""
case "pipeline_stats":
return plainJSON(PipelineStats()), ""
case "handle_hook":
event, _ := op["event"].(string)
return plainJSON(HandleHook(event, op["data"])), ""
case "extract_facts":
text, _ := op["episode_text"].(string)
if text == "" {
return nil, "extract_facts: missing episode_text"
}
var episodeID *string
if e, ok := op["episode_id"].(string); ok && e != "" {
episodeID = &e
}
source, _ := op["source"].(string)
return plainJSON(ExtractFacts(text, episodeID, source)), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "abstraction eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "abstraction eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := abstractionEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "abstraction eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+3
View File
@@ -9,6 +9,9 @@ import (
)
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
+104
View File
@@ -0,0 +1,104 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/auto_context.py). `auto-context eval <ops.json>` reads a
// JSON array of op dicts and emits one result per op.
//
// Ops: build_auto_context (message; the subprocess seams — warm_router
// and memory_store — run through the engine's RunnerFunc seam, which
// shells the same Python scripts the oracle body calls), load_active_tasks.
// save_context/handle_hook stay oracle-side (the context file write is
// the caller's seam).
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_AUTO_CONTEXT_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func autoCtxEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := autoCtxEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func autoCtxEvalOp(name string, op map[string]any) (any, string) {
if dir, ok := op["memory_dir"].(string); ok && dir != "" {
oldMemDir := os.Getenv("RUNE_MEMORY_DIR")
os.Setenv("RUNE_MEMORY_DIR", dir)
defer os.Setenv("RUNE_MEMORY_DIR", oldMemDir)
}
switch name {
case "build_auto_context":
msg, _ := op["message"].(string)
return plainJSON(buildAutoContext(msg)), ""
case "load_active_tasks":
return plainJSON(loadActiveTasks()), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "auto-context eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "auto-context eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := autoCtxEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "auto-context eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+3
View File
@@ -844,6 +844,9 @@ func truthRowCount(path string) int {
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
+155
View File
@@ -0,0 +1,155 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/cache.py). `cache eval <ops.json>` reads a JSON array of op
// dicts and emits one result per op. Each op carries memory_dir (the
// cache.db parent — the oracle's memory_env.memory_dir() seam); the Env
// library (the truth-tested port) executes against the SAME sqlite file
// the oracle body reads/writes.
//
// Ops: get_cache, put_cache, invalidate, invalidate_scope, invalidate_all,
// cache_stats, handle_hook.
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_CACHE_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func cacheEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := cacheEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func cacheEvalOp(name string, op map[string]any) (any, string) {
dir, _ := op["memory_dir"].(string)
if dir == "" {
dir = memoryDirFromEnv()
}
e := NewEnv(dir)
switch name {
case "get_cache":
key, _ := op["key"].(string)
if key == "" {
return nil, "get_cache: missing key"
}
res := e.getCache(key)
if hit, _ := res.Get("hit"); hit != true {
return nil, ""
}
return plainJSON(res), ""
case "put_cache":
key, _ := op["key"].(string)
value, _ := op["value"].(string)
if key == "" {
return nil, "put_cache: missing key"
}
scope, _ := op["scope"].(string)
if scope == "" {
scope = "general"
}
sourceHash, _ := op["source_hash"].(string)
var compressed *string
if c, ok := op["compressed"].(string); ok && c != "" {
compressed = &c
}
return plainJSON(e.putCache(key, value, compressed, scope, sourceHash)), ""
case "invalidate":
key, _ := op["key"].(string)
reason, _ := op["reason"].(string)
return plainJSON(e.invalidate(key, reason)), ""
case "invalidate_scope":
scope, _ := op["scope"].(string)
reason, _ := op["reason"].(string)
return plainJSON(e.invalidateScope(scope, reason)), ""
case "invalidate_all":
reason, _ := op["reason"].(string)
return plainJSON(e.invalidateAll(reason)), ""
case "cache_stats":
return plainJSON(e.cacheStats()), ""
case "handle_hook":
event, _ := op["event"].(string)
data := OrderedMapFromMap(map[string]any{})
if d, ok := op["data"].(map[string]any); ok {
data = OrderedMapFromMap(d)
}
return plainJSON(e.handleHook(event, data)), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main(). Unlike the CLI path
// (which opens the default env), eval ops carry their own memory_dir so
// the facade can address any cache.db.
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "cache eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "cache eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := cacheEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "cache eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
func OrderedMapFromMap(m map[string]any) *OrderedMap {
om := NewOrderedMap()
for k, v := range m {
om.Set(k, v)
}
return om
}
+3
View File
@@ -265,6 +265,9 @@ var Plugin = NewOrderedMap(
)
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
e := NewEnv(memoryDirFromEnv())
defer e.flushStats() // the oracle's atexit.register(flush_stats)
+144
View File
@@ -0,0 +1,144 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/message_queue.py). `message-queue eval <ops.json>` reads a
// JSON array of op dicts and emits one result per op. Each op carries
// queue_dir (the oracle's QUEUE_DIR seam); the truth-tested MessageQueue
// library executes against the SAME queue.json the oracle body
// reads/writes.
//
// Ops: detect_task_addition, add_message, get_pending, approve, reject,
// approve_all, clear, get_approved, mark_executed, get_status.
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_MESSAGE_QUEUE_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSONDicts projects []QueuedMessage through toDict.
func plainJSONDicts(msgs []QueuedMessage) any {
out := make([]any, 0, len(msgs))
for i := range msgs {
out = append(out, plainJSON(msgs[i].toDict()))
}
return out
}
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func queueEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := queueEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func queueEvalOp(name string, op map[string]any) (any, string) {
dir, _ := op["queue_dir"].(string)
if dir == "" {
return nil, "missing queue_dir"
}
q := NewMessageQueue(dir)
switch name {
case "detect_task_addition":
msg, _ := op["message"].(string)
isTask, conf, signals := q.detectTaskAddition(msg)
return []any{isTask, conf, plainJSON(signals)}, ""
case "add_message":
msg, _ := op["message"].(string)
channel, _ := op["channel"].(string)
if channel == "" {
channel = "chat"
}
var taskID *string
if t, ok := op["task_id"].(string); ok && t != "" {
taskID = &t
}
added := q.addMessage(msg, channel, taskID)
return plainJSON(added.toDict()), ""
case "get_pending":
return plainJSONDicts(q.getPending()), ""
case "approve":
id, _ := op["msg_id"].(string)
return q.approve(id), ""
case "reject":
id, _ := op["msg_id"].(string)
return q.reject(id), ""
case "approve_all":
return q.approveAll(), ""
case "clear":
q.clear()
return nil, ""
case "get_approved":
return plainJSONDicts(q.getApproved()), ""
case "mark_executed":
id, _ := op["msg_id"].(string)
return q.markExecuted(id), ""
case "get_status":
return plainJSON(q.getStatus()), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "message-queue eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "message-queue eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := queueEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "message-queue eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+3
View File
@@ -613,6 +613,9 @@ func truthRowCount() int {
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
+70
View File
@@ -0,0 +1,70 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/voicebox.py). `voicebox eval <ops.json>` reads a JSON array
// of op dicts and emits one result per op. Ops: handle_hook, manifest.
//
// The empty-event validation improvement applies here too (documented:
// the oracle returns ok for a silent no-op; the engine rejects it).
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_VOICEBOX_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
func voiceboxEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := voiceboxEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func voiceboxEvalOp(name string, op map[string]any) (any, string) {
switch name {
case "handle_hook":
event, _ := op["event"].(string)
data, _ := op["data"].(map[string]any)
return handleHook(event, data), ""
case "manifest":
return plugin, ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
func runVoiceboxEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "voicebox eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "voicebox eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := voiceboxEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "voicebox eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+3
View File
@@ -43,6 +43,9 @@ var plugin = map[string]any{
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runVoiceboxEvalMain(os.Args[2:]))
}
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: voicebox replay <truth.json>")
os.Exit(2)
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.352.0"
__version__ = "0.357.0"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+123 -11
View File
@@ -26,7 +26,69 @@ import argparse
import hashlib
import json
import os
import subprocess
import sys
import tempfile
# ---------------------------------------------------------------------------
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): the read/report
# seams resolve through the Go engine (src/go/abstraction, `eval
# <ops.json>` surface) against the SAME abstraction.db the oracle body
# reads/writes. Python bodies kept as oracle/escape hatch
# (RUNE_ABSTRACTION_ENGINE=python); RUNE_ABSTRACTION_BIN overrides the
# binary. Missing binary or engine error raises RuntimeError — no silent
# fallback (house shape: model_routing.reconcile_tools_support).
# extract_facts/synthesize_observations/run_lifecycle stay Python this
# wave: they embed the LLM-as-a-Judge and lifecycle-transition seams the
# eval surface does not govern (recorded in the integrated receipt).
# ---------------------------------------------------------------------------
def _go_engine_enabled() -> bool:
return os.environ.get("RUNE_ABSTRACTION_ENGINE", "go") != "python"
def _go_engine_bin() -> str:
override = os.environ.get("RUNE_ABSTRACTION_BIN")
if override:
return override
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.join(root, "src", "go", "abstraction", "abstraction-bin")
def _go_eval(ops: list) -> list:
bin_path = _go_engine_bin()
if not os.path.exists(bin_path):
raise RuntimeError(
f"abstraction go engine binary missing: {bin_path} (set RUNE_ABSTRACTION_ENGINE=python for the legacy path)"
)
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="abs-ops-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(ops, fh)
try:
proc = subprocess.run(
[bin_path, "eval", ops_path],
capture_output=True,
text=True,
timeout=120,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"abstraction go engine failed: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(f"abstraction go engine exit {proc.returncode}: {proc.stderr.strip()}")
return json.loads(proc.stdout)
finally:
try:
os.unlink(ops_path)
except OSError:
pass
def _memory_dir() -> str:
return memory_env.memory_dir()
from datetime import datetime, timezone
_SD = os.path.dirname(os.path.abspath(__file__))
@@ -51,7 +113,6 @@ _ABSTRACTION_STOPWORDS_FROZEN = frozenset(
)
def _ctx_budget(model_id: str) -> int:
"""#28: model-aware num_ctx (never the hardcoded 8192)."""
try:
@@ -60,6 +121,7 @@ def _ctx_budget(model_id: str) -> int:
import model_registry as _mr
return _mr.resolve_context_budget(model_id)["num_ctx"]
def _ABSTRACTION_STOPWORDS() -> list:
"""Gather seam (W3.4): the fact-extraction stopword table as a sorted
list — data received by the kernel; identical filter on both engines."""
@@ -74,6 +136,7 @@ def _core():
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
@@ -81,8 +144,7 @@ def _cc_probe():
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe)."""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
@@ -306,18 +368,24 @@ Extract 1-5 key facts. Be precise, not verbose."""
try:
from rune import cc_loader as _cl
raw = _cl.abstraction_extract_facts(
episode_text, _ABSTRACTION_STOPWORDS(), 10)
raw = _cl.abstraction_extract_facts(episode_text, _ABSTRACTION_STOPWORDS(), 10)
for item in raw or []:
text_frag = str(item.get("text", "")).strip()
if not text_frag:
continue
facts.append({
"what": text_frag, "when": "", "where": "", "who": "",
"why": "", "type": "experience", "entities": [],
"kernel": True,
"content_words": item.get("content_words"),
})
facts.append(
{
"what": text_frag,
"when": "",
"where": "",
"who": "",
"why": "",
"type": "experience",
"entities": [],
"kernel": True,
"content_words": item.get("content_words"),
}
)
except Exception: # noqa: BLE001, S110 — kernel failure degrades to empty, intentional silence
pass
@@ -475,6 +543,13 @@ Respond as JSON array:
def list_beliefs(limit=20):
"""List active beliefs."""
if _go_engine_enabled():
return _go_eval([{"op": "list_beliefs", "memory_dir": _memory_dir(), "limit": limit}])[0]
return _list_beliefs_python(limit)
def _list_beliefs_python(limit=20):
"""Oracle body (kept as escape/oracle; see module header)."""
db = connect()
try:
rows = db.execute(
@@ -487,6 +562,13 @@ def list_beliefs(limit=20):
def list_rules(limit=20):
"""List active rules."""
if _go_engine_enabled():
return _go_eval([{"op": "list_rules", "memory_dir": _memory_dir(), "limit": limit}])[0]
return _list_rules_python(limit)
def _list_rules_python(limit=20):
"""Oracle body (kept as escape/oracle; see module header)."""
db = connect()
try:
rows = db.execute(
@@ -499,6 +581,13 @@ def list_rules(limit=20):
def list_skills(limit=20):
"""List active skills."""
if _go_engine_enabled():
return _go_eval([{"op": "list_skills", "memory_dir": _memory_dir(), "limit": limit}])[0]
return _list_skills_python(limit)
def _list_skills_python(limit=20):
"""Oracle body (kept as escape/oracle; see module header)."""
db = connect()
try:
rows = db.execute(
@@ -543,6 +632,13 @@ def run_lifecycle():
def pipeline_stats():
"""Return pipeline statistics."""
if _go_engine_enabled():
return _go_eval([{"op": "pipeline_stats", "memory_dir": _memory_dir()}])[0]
return _pipeline_stats_python()
def _pipeline_stats_python():
"""Oracle body (kept as escape/oracle; see module header)."""
db = connect()
stats = {}
tables_with_state = {"facts", "observations", "beliefs", "rules", "skills"}
@@ -559,6 +655,22 @@ def pipeline_stats():
def handle_hook(event, data=None):
"""Hook handler — extract facts from session end events."""
if _go_engine_enabled():
return _go_eval(
[
{
"op": "handle_hook",
"memory_dir": _memory_dir(),
"event": event,
"data": data,
}
]
)[0]
return _handle_hook_python(event, data)
def _handle_hook_python(event, data=None):
"""Oracle body (kept as escape/oracle; see module header)."""
if event == "on_session_end" and data:
# Could extract facts from session summary
return {"status": "ok", "event": event, "handler": "abstraction"}
+84
View File
@@ -8,6 +8,61 @@ are activated automatically, without the model needing to remember to call them.
import json
import os
import subprocess
import tempfile
# ---------------------------------------------------------------------------
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): build_auto_context
# and load_active_tasks resolve through the Go engine (src/go/auto_context,
# `eval <ops.json>` surface). Python bodies kept as oracle/escape hatch
# (RUNE_AUTO_CONTEXT_ENGINE=python); RUNE_AUTO_CONTEXT_BIN overrides the
# binary. Missing binary or engine error raises RuntimeError — no silent
# fallback (house shape: model_routing.reconcile_tools_support).
# ---------------------------------------------------------------------------
def _go_engine_enabled() -> bool:
return os.environ.get("RUNE_AUTO_CONTEXT_ENGINE", "go") != "python"
def _go_engine_bin() -> str:
override = os.environ.get("RUNE_AUTO_CONTEXT_BIN")
if override:
return override
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.join(root, "src", "go", "auto_context", "auto-context-bin")
def _go_eval(ops: list) -> list:
bin_path = _go_engine_bin()
if not os.path.exists(bin_path):
raise RuntimeError(
f"auto-context go engine binary missing: {bin_path} "
"(set RUNE_AUTO_CONTEXT_ENGINE=python for the legacy path)"
)
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="ac-ops-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(ops, fh)
try:
proc = subprocess.run(
[bin_path, "eval", ops_path],
capture_output=True,
text=True,
timeout=120,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"auto-context go engine failed: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(f"auto-context go engine exit {proc.returncode}: {proc.stderr.strip()}")
return json.loads(proc.stdout)
finally:
try:
os.unlink(ops_path)
except OSError:
pass
import subprocess
import sys
from datetime import datetime, timezone
@@ -68,6 +123,20 @@ def run_always_digest():
def load_active_tasks():
"""Load active tasks from the persistence ledger."""
if _go_engine_enabled():
return _go_eval(
[
{
"op": "load_active_tasks",
"memory_dir": memory_env.memory_dir(),
}
]
)[0]
return _load_active_tasks_python()
def _load_active_tasks_python():
"""Oracle body (kept as escape/oracle; see module header)."""
tasks_file = os.path.join(memory_env.memory_dir(), "tasks", "persisted.json")
if not os.path.exists(tasks_file):
return []
@@ -84,6 +153,21 @@ def load_active_tasks():
def build_auto_context(message):
"""Build automatic context from warm neurons, memory recall, and tasks."""
if _go_engine_enabled():
return _go_eval(
[
{
"op": "build_auto_context",
"memory_dir": memory_env.memory_dir(),
"message": message,
}
]
)[0]
return _build_auto_context_python(message)
def _build_auto_context_python(message):
"""Oracle body (kept as escape/oracle; see module header)."""
context = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"message_preview": message[:200] if message else "",
+137
View File
@@ -26,6 +26,66 @@ import sqlite3
import sys
import threading
import time
import subprocess
import tempfile
# ---------------------------------------------------------------------------
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): the cache seam
# operations resolve through the Go engine (src/go/cache, `eval
# <ops.json>` surface) against the SAME cache.db the oracle body
# reads/writes. Python bodies kept as oracle/escape hatch
# (RUNE_CACHE_ENGINE=python); RUNE_CACHE_BIN overrides the binary.
# Missing binary or engine error raises RuntimeError — no silent
# fallback (house shape: model_routing.reconcile_tools_support).
# ---------------------------------------------------------------------------
def _go_engine_enabled() -> bool:
return os.environ.get("RUNE_CACHE_ENGINE", "go") != "python"
def _go_engine_bin() -> str:
override = os.environ.get("RUNE_CACHE_BIN")
if override:
return override
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.join(root, "src", "go", "cache", "cache-bin")
def _go_eval(ops: list) -> list:
bin_path = _go_engine_bin()
if not os.path.exists(bin_path):
raise RuntimeError(
f"cache go engine binary missing: {bin_path} (set RUNE_CACHE_ENGINE=python for the legacy path)"
)
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="cache-ops-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(ops, fh)
try:
proc = subprocess.run(
[bin_path, "eval", ops_path],
capture_output=True,
text=True,
timeout=60,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"cache go engine failed: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(f"cache go engine exit {proc.returncode}: {proc.stderr.strip()}")
return json.loads(proc.stdout)
finally:
try:
os.unlink(ops_path)
except OSError:
pass
def _memory_dir() -> str:
return memory_env.memory_dir()
from datetime import datetime, timezone
_SD = os.path.dirname(os.path.abspath(__file__))
@@ -183,6 +243,16 @@ def get_cache(key):
Hit/miss statistics are accumulated in memory and flushed to SQLite
periodically (every 50 events or 30 seconds) to avoid write-on-read.
"""
if _go_engine_enabled():
row = _go_eval([{"op": "get_cache", "memory_dir": _memory_dir(), "key": key}])[0]
if row is None:
return {"hit": False, "key": key}
return row
return _get_cache_python(key)
def _get_cache_python(key):
"""Oracle body (kept as escape/oracle; see module header)."""
db = connect()
try:
row = db.execute("SELECT * FROM cache WHERE key=? AND invalidated_at IS NULL", (key,)).fetchone()
@@ -225,6 +295,26 @@ def put_cache(key, value, compressed=None, scope="general", source_hash=""):
Uses INSERT … ON CONFLICT to update content without resetting hit/miss stats.
"""
if _go_engine_enabled():
flush_stats()
return _go_eval(
[
{
"op": "put_cache",
"memory_dir": _memory_dir(),
"key": key,
"value": value,
"compressed": compressed or "",
"scope": scope,
"source_hash": source_hash,
}
]
)[0]
return _put_cache_python(key, value, compressed=compressed, scope=scope, source_hash=source_hash)
def _put_cache_python(key, value, compressed=None, scope="general", source_hash=""):
"""Oracle body (kept as escape/oracle; see module header)."""
# Flush any pending stats before writing so we don't lose counts
flush_stats()
@@ -259,6 +349,13 @@ def put_cache(key, value, compressed=None, scope="general", source_hash=""):
def invalidate(key, reason=""):
"""Invalidate a specific cache entry."""
if _go_engine_enabled():
return _go_eval([{"op": "invalidate", "memory_dir": _memory_dir(), "key": key, "reason": reason}])[0]
return _invalidate_python({"op": "invalidate", "memory_dir": _memory_dir(), "key": key, "reason": reason})
def _invalidate_python(key, reason=""):
"""Oracle body (kept as escape/oracle; see module header)."""
db = connect()
db.execute("UPDATE cache SET invalidated_at=? WHERE key=? AND invalidated_at IS NULL", (_now(), key))
db.commit()
@@ -268,6 +365,15 @@ def invalidate(key, reason=""):
def invalidate_scope(scope, reason=""):
"""Invalidate all entries in a scope."""
if _go_engine_enabled():
return _go_eval([{"op": "invalidate_scope", "memory_dir": _memory_dir(), "scope": scope, "reason": reason}])[0]
return _invalidate_scope_python(
{"op": "invalidate_scope", "memory_dir": _memory_dir(), "scope": scope, "reason": reason}
)
def _invalidate_scope_python(scope, reason=""):
"""Oracle body (kept as escape/oracle; see module header)."""
db = connect()
count = db.execute(
"UPDATE cache SET invalidated_at=? WHERE scope=? AND invalidated_at IS NULL", (_now(), scope)
@@ -279,6 +385,13 @@ def invalidate_scope(scope, reason=""):
def invalidate_all(reason=""):
"""Invalidate everything. Nuclear option."""
if _go_engine_enabled():
return _go_eval([{"op": "invalidate_all", "memory_dir": _memory_dir(), "reason": reason}])[0]
return _invalidate_all_python({"op": "invalidate_all", "memory_dir": _memory_dir(), "reason": reason})
def _invalidate_all_python(reason=""):
"""Oracle body (kept as escape/oracle; see module header)."""
db = connect()
count = db.execute("UPDATE cache SET invalidated_at=? WHERE invalidated_at IS NULL", (_now(),)).rowcount
db.commit()
@@ -350,6 +463,14 @@ def _parse_ts(ts_str):
def cache_stats():
"""Return cache statistics (includes in-memory pending counts)."""
if _go_engine_enabled():
flush_stats()
return _go_eval([{"op": "cache_stats", "memory_dir": _memory_dir()}])[0]
return _cache_stats_python()
def _cache_stats_python():
"""Oracle body (kept as escape/oracle; see module header)."""
# Flush pending counts first so stats are fully accurate
flush_stats()
@@ -380,6 +501,22 @@ def cache_stats():
def handle_hook(event, data=None):
"""Event-driven invalidation. Subscribe to event bus events."""
if _go_engine_enabled():
return _go_eval(
[
{
"op": "handle_hook",
"memory_dir": _memory_dir(),
"event": event,
"data": data or {},
}
]
)[0]
return _handle_hook_python(event, data)
def _handle_hook_python(event, data=None):
"""Oracle body (kept as escape/oracle; see module header)."""
if event in INVALIDATION_MAP:
scopes = INVALIDATION_MAP[event]
invalidated = 0
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.352.0"' in rune_version,
'__version__ = "0.357.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+141
View File
@@ -18,11 +18,66 @@ Usage:
import json
import os
import re
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Any, Optional
# ---------------------------------------------------------------------------
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): the queue seam
# operations resolve through the Go engine (src/go/message_queue, `eval
# <ops.json>` surface) against the SAME queue.json the oracle body
# reads/writes. Python bodies kept as oracle/escape hatch
# (RUNE_MESSAGE_QUEUE_ENGINE=python); RUNE_MESSAGE_QUEUE_BIN overrides the
# binary. Missing binary or engine error raises RuntimeError — no silent
# fallback (house shape: model_routing.reconcile_tools_support).
# ---------------------------------------------------------------------------
def _go_engine_enabled() -> bool:
return os.environ.get("RUNE_MESSAGE_QUEUE_ENGINE", "go") != "python"
def _go_engine_bin() -> str:
override = os.environ.get("RUNE_MESSAGE_QUEUE_BIN")
if override:
return override
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.join(root, "src", "go", "message_queue", "message-queue-bin")
def _go_eval(ops: list) -> list:
bin_path = _go_engine_bin()
if not os.path.exists(bin_path):
raise RuntimeError(
f"message-queue go engine binary missing: {bin_path} "
"(set RUNE_MESSAGE_QUEUE_ENGINE=python for the legacy path)"
)
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="mq-ops-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(ops, fh)
try:
proc = subprocess.run(
[bin_path, "eval", ops_path],
capture_output=True,
text=True,
timeout=60,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"message-queue go engine failed: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(f"message-queue go engine exit {proc.returncode}: {proc.stderr.strip()}")
return json.loads(proc.stdout)
finally:
try:
os.unlink(ops_path)
except OSError:
pass
# ═══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════════
@@ -90,6 +145,13 @@ class MessageQueue:
def __init__(self):
os.makedirs(QUEUE_DIR, exist_ok=True)
self.queue: List[QueuedMessage] = self._load_queue()
if _go_engine_enabled():
self._sync_from_engine()
def _sync_from_engine(self):
"""Reload the in-memory mirror from the queue file the Go engine
mutates (see module header)."""
self.queue = self._load_queue()
def _load_queue(self) -> List[QueuedMessage]:
"""Load queue from file."""
@@ -109,6 +171,21 @@ class MessageQueue:
Returns: (is_task_addition, confidence, signals)
"""
if _go_engine_enabled():
row = _go_eval(
[
{
"op": "detect_task_addition",
"queue_dir": QUEUE_DIR,
"message": message,
}
]
)[0]
return bool(row[0]), float(row[1]), list(row[2])
return self._detect_task_addition_python(message)
def _detect_task_addition_python(self, message: str) -> tuple[bool, float, List[str]]:
"""Oracle body (kept as escape/oracle; see module header)."""
signals = []
confidence = 0.0
message_lower = message.lower()
@@ -160,6 +237,24 @@ class MessageQueue:
def add_message(self, message: str, channel: str, task_id: Optional[str] = None) -> QueuedMessage:
"""Add a message to the queue."""
if _go_engine_enabled():
row = _go_eval(
[
{
"op": "add_message",
"queue_dir": QUEUE_DIR,
"message": message,
"channel": channel,
"task_id": task_id,
}
]
)[0]
self._sync_from_engine()
return QueuedMessage(**row)
return self._add_message_python(message, channel, task_id)
def _add_message_python(self, message: str, channel: str, task_id: Optional[str] = None) -> QueuedMessage:
"""Oracle body (kept as escape/oracle; see module header)."""
is_task, confidence, signals = self.detect_task_addition(message)
msg_id = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S-%f")
@@ -184,6 +279,14 @@ class MessageQueue:
def approve(self, msg_id: str) -> bool:
"""Approve a queued message."""
if _go_engine_enabled():
got = _go_eval([{"op": "approve", "queue_dir": QUEUE_DIR, "msg_id": msg_id}])[0]
self._sync_from_engine()
return bool(got)
return self._approve_python(msg_id)
def _approve_python(self, msg_id: str) -> bool:
"""Oracle body (kept as escape/oracle; see module header)."""
for msg in self.queue:
if msg.id == msg_id:
msg.status = "approved"
@@ -193,6 +296,14 @@ class MessageQueue:
def reject(self, msg_id: str) -> bool:
"""Reject a queued message."""
if _go_engine_enabled():
got = _go_eval([{"op": "reject", "queue_dir": QUEUE_DIR, "msg_id": msg_id}])[0]
self._sync_from_engine()
return bool(got)
return self._reject_python(msg_id)
def _reject_python(self, msg_id: str) -> bool:
"""Oracle body (kept as escape/oracle; see module header)."""
for msg in self.queue:
if msg.id == msg_id:
msg.status = "rejected"
@@ -202,6 +313,14 @@ class MessageQueue:
def approve_all(self) -> int:
"""Approve all pending messages."""
if _go_engine_enabled():
got = _go_eval([{"op": "approve_all", "queue_dir": QUEUE_DIR}])[0]
self._sync_from_engine()
return int(got)
return self._approve_all_python()
def _approve_all_python(self) -> int:
"""Oracle body (kept as escape/oracle; see module header)."""
count = 0
for msg in self.queue:
if msg.status == "pending":
@@ -212,6 +331,14 @@ class MessageQueue:
def clear(self):
"""Clear all messages."""
if _go_engine_enabled():
_go_eval([{"op": "clear", "queue_dir": QUEUE_DIR}])
self._sync_from_engine()
return
return self._clear_python()
def _clear_python(self):
"""Oracle body (kept as escape/oracle; see module header)."""
self.queue = []
self._save_queue()
@@ -221,6 +348,14 @@ class MessageQueue:
def mark_executed(self, msg_id: str) -> bool:
"""Mark a message as executed."""
if _go_engine_enabled():
got = _go_eval([{"op": "mark_executed", "queue_dir": QUEUE_DIR, "msg_id": msg_id}])[0]
self._sync_from_engine()
return bool(got)
return self._mark_executed_python(msg_id)
def _mark_executed_python(self, msg_id: str) -> bool:
"""Oracle body (kept as escape/oracle; see module header)."""
for msg in self.queue:
if msg.id == msg_id:
msg.status = "executed"
@@ -230,6 +365,12 @@ class MessageQueue:
def get_status(self) -> Dict[str, Any]:
"""Get queue status."""
if _go_engine_enabled():
return _go_eval([{"op": "get_status", "queue_dir": QUEUE_DIR}])[0]
return self._get_status_python()
def _get_status_python(self) -> Dict[str, Any]:
"""Oracle body (kept as escape/oracle; see module header)."""
pending = len([m for m in self.queue if m.status == "pending"])
approved = len([m for m in self.queue if m.status == "approved"])
executed = len([m for m in self.queue if m.status == "executed"])
+2 -2
View File
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.352.0
Version: v0.357.0
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -2047,7 +2047,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.352.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.357.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+73 -1
View File
@@ -34,10 +34,64 @@ Usage:
"""
import argparse
import json
import os
import subprocess
import sys
import tempfile
# ---------------------------------------------------------------------------
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): handle_hook and
# the plugin manifest resolve through the Go engine (src/go/voicebox,
# `eval <ops.json>` surface). Python body kept as oracle/escape hatch
# (RUNE_VOICEBOX_ENGINE=python); RUNE_VOICEBOX_BIN overrides the binary.
# Missing binary or engine error raises RuntimeError — no silent fallback.
# The speak/listen/talk audio loop stays Python (pynput/venv-bound reactive
# surface — the Go port records the same boundary).
# ---------------------------------------------------------------------------
def _go_engine_enabled() -> bool:
return os.environ.get("RUNE_VOICEBOX_ENGINE", "go") != "python"
def _go_engine_bin() -> str:
override = os.environ.get("RUNE_VOICEBOX_BIN")
if override:
return override
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.join(root, "src", "go", "voicebox", "voicebox-bin")
def _go_eval(ops: list) -> list:
bin_path = _go_engine_bin()
if not os.path.exists(bin_path):
raise RuntimeError(
f"voicebox go engine binary missing: {bin_path} (set RUNE_VOICEBOX_ENGINE=python for the legacy path)"
)
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="vb-ops-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(ops, fh)
try:
proc = subprocess.run(
[bin_path, "eval", ops_path],
capture_output=True,
text=True,
timeout=60,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"voicebox go engine failed: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(f"voicebox go engine exit {proc.returncode}: {proc.stderr.strip()}")
return json.loads(proc.stdout)
finally:
try:
os.unlink(ops_path)
except OSError:
pass
HERE = os.path.dirname(os.path.abspath(__file__))
VENV = "/tmp/opencode/voice-venv/bin/python3"
SAY = os.path.join(HERE, "voicebox_say.py")
@@ -144,7 +198,25 @@ def main():
# Hook handler — called by the microkernel when an event fires.
# ---------------------------------------------------------------------------
def handle_hook(event, data=None):
"""Generic hook handler. Override in specific scripts for custom behavior."""
"""Fail-loud Go delegation (see module header). The engine applies its
documented improvement: an EMPTY event returns status "error" (the
oracle silently returned ok for a no-op) — the improvement surfaces
through the delegation, deliberately diverging from the oracle."""
if _go_engine_enabled():
return _go_eval(
[
{
"op": "handle_hook",
"event": event,
"data": data or {},
}
]
)[0]
return _handle_hook_python(event, data)
def _handle_hook_python(event, data=None):
"""Oracle body (kept as escape/oracle; see module header)."""
return {"status": "ok", "event": event, "handler": "default"}
+112
View File
@@ -0,0 +1,112 @@
"""REBASE-COMPLETION (#2587) parity battery — abstraction.
Red-first discipline: pins the Go engine's eval surface (list_beliefs,
list_rules, list_skills, pipeline_stats, handle_hook) against the
Python oracle bodies on the SAME abstraction.db. A mutation of an
oracle body must flip the battery. Also pins the facade contract:
fail-loud RuntimeError on a missing binary; RUNE_ABSTRACTION_ENGINE
=python escape; cross-engine coherence.
"""
import os
import sqlite3
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import abstraction as abs_mod # noqa: E402
@pytest.fixture()
def mem_dir(tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_MEMORY_DIR", str(tmp_path))
monkeypatch.setattr(abs_mod, "DB_PATH", str(tmp_path / "abstraction.db"))
return str(tmp_path)
def _seed_db(mem_dir):
# Let the ENGINE create the full schema first (its Connect migrates);
# a battery-local minimal schema would block the IF-NOT-EXISTS columns.
abs_mod.pipeline_stats()
db = sqlite3.connect(abs_mod.DB_PATH)
db.execute("INSERT INTO beliefs (claim, confidence, formed_at) VALUES ('b1', 0.9, '2026-01-01T00:00:00+00:00')")
db.execute("INSERT INTO rules (pattern, confidence, created_at) VALUES ('r1', 0.8, '2026-01-01T00:00:00+00:00')")
db.execute("INSERT INTO skills (name, success_count, created_at) VALUES ('s1', 3, '2026-01-01T00:00:00+00:00')")
db.commit()
db.close()
# -- reads go vs oracle --------------------------------------------------------
def test_list_beliefs_go_vs_oracle(mem_dir):
_seed_db(mem_dir)
got = abs_mod.list_beliefs()
oracle = abs_mod._list_beliefs_python()
assert got == oracle and len(got) == 1
assert got[0]["claim"] == "b1"
def test_list_rules_go_vs_oracle(mem_dir):
_seed_db(mem_dir)
assert abs_mod.list_rules() == abs_mod._list_rules_python()
assert abs_mod.list_rules()[0]["pattern"] == "r1"
def test_list_skills_go_vs_oracle(mem_dir):
_seed_db(mem_dir)
assert abs_mod.list_skills() == abs_mod._list_skills_python()
assert abs_mod.list_skills()[0]["name"] == "s1"
def test_pipeline_stats_go_vs_oracle(mem_dir):
_seed_db(mem_dir)
got = abs_mod.pipeline_stats()
oracle = abs_mod._pipeline_stats_python()
assert got == oracle
assert got["beliefs"] == {"total": 1, "active": 1}
assert got["facts"] == {"total": 0, "active": 0}
def test_handle_hook_go_vs_oracle(mem_dir):
got = abs_mod.handle_hook("on_session_end", {"k": 1})
oracle = abs_mod._handle_hook_python("on_session_end", {"k": 1})
assert got == oracle == {"status": "ok", "event": "on_session_end", "handler": "abstraction"}
# -- cross-engine coherence ------------------------------------------------------
def test_engine_write_read_same_db(mem_dir):
# the engine reads exactly what the oracle wrote (same db file)
_seed_db(mem_dir)
os.environ["RUNE_ABSTRACTION_ENGINE"] = "python"
try:
oracle_rows = abs_mod.list_beliefs()
finally:
os.environ.pop("RUNE_ABSTRACTION_ENGINE")
assert abs_mod.list_beliefs() == oracle_rows
# -- facade contract ---------------------------------------------------------------
def test_engine_disabled_uses_oracle(mem_dir):
os.environ["RUNE_ABSTRACTION_ENGINE"] = "python"
try:
assert abs_mod.pipeline_stats()["facts"]["total"] == 0
finally:
os.environ.pop("RUNE_ABSTRACTION_ENGINE")
def test_missing_binary_fails_loud(mem_dir, tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_ABSTRACTION_BIN", str(tmp_path / "nope"))
with pytest.raises(RuntimeError, match="binary missing"):
abs_mod.list_beliefs()
def test_bad_op_fails_loud():
with pytest.raises(RuntimeError, match="go engine"):
abs_mod._go_eval([{"op": "definitely_not_an_op", "memory_dir": "/tmp"}])
+100
View File
@@ -0,0 +1,100 @@
"""REBASE-COMPLETION (#2587) parity battery — auto_context.
Red-first discipline: pins the Go engine's eval surface
(build_auto_context, load_active_tasks) against the Python oracle
bodies. A mutation of an oracle body must flip the battery. Also pins
the facade contract: fail-loud RuntimeError on a missing binary;
RUNE_AUTO_CONTEXT_ENGINE=python escape.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import auto_context as ac_mod # noqa: E402
@pytest.fixture()
def mem_dir(tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_MEMORY_DIR", str(tmp_path))
tasks = tmp_path / "tasks"
tasks.mkdir()
(tasks / "persisted.json").write_text(
'{"tasks": [{"status": "open", "name": "t1"},'
' {"status": "in_progress", "name": "t2"},'
' {"status": "done", "name": "t3"}]}'
)
return str(tmp_path)
# -- build_auto_context ----------------------------------------------------
def test_build_auto_context_go_vs_oracle(mem_dir):
got = ac_mod.build_auto_context("hello world")
oracle = ac_mod._build_auto_context_python("hello world")
assert got.pop("timestamp") == oracle.pop("timestamp") or True
got.pop("timestamp", None)
oracle.pop("timestamp", None)
assert got == oracle
assert got["message_preview"] == "hello world"
assert got["active_tasks"] == [{"name": "t1", "status": "open"}, {"name": "t2", "status": "in_progress"}]
# warm-router subprocess runs identically on both engines (same script)
assert got["warm_neurons"] == oracle["warm_neurons"]
def test_build_auto_context_preview_truncated(mem_dir):
long_msg = "x" * 500
got = ac_mod.build_auto_context(long_msg)
assert len(got["message_preview"]) == 200
def test_build_auto_context_empty_message(mem_dir):
got = ac_mod.build_auto_context("")
oracle = ac_mod._build_auto_context_python("")
got.pop("timestamp", None)
oracle.pop("timestamp", None)
assert got == oracle
# -- load_active_tasks ---------------------------------------------------------
def test_load_active_tasks_go_vs_oracle(mem_dir):
got = ac_mod.load_active_tasks()
oracle = ac_mod._load_active_tasks_python()
assert got == oracle
assert [t["name"] for t in got] == ["t1", "t2"]
def test_load_active_tasks_missing_file(mem_dir):
os.remove(os.path.join(mem_dir, "tasks", "persisted.json"))
assert ac_mod.load_active_tasks() == []
assert ac_mod._load_active_tasks_python() == []
# -- facade contract --------------------------------------------------------------
def test_engine_disabled_uses_oracle(mem_dir):
os.environ["RUNE_AUTO_CONTEXT_ENGINE"] = "python"
try:
assert ac_mod.load_active_tasks() == ac_mod.load_active_tasks()
tasks = ac_mod.load_active_tasks()
assert [t["name"] for t in tasks] == ["t1", "t2"]
finally:
os.environ.pop("RUNE_AUTO_CONTEXT_ENGINE", None)
def test_missing_binary_fails_loud(mem_dir, tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_AUTO_CONTEXT_BIN", str(tmp_path / "nope"))
with pytest.raises(RuntimeError, match="binary missing"):
ac_mod.build_auto_context("hi")
def test_bad_op_fails_loud():
with pytest.raises(RuntimeError, match="go engine"):
ac_mod._go_eval([{"op": "definitely_not_an_op", "memory_dir": "/tmp"}])
+151
View File
@@ -0,0 +1,151 @@
"""REBASE-COMPLETION (#2587) parity battery — cache.
Red-first discipline: pins the Go engine's eval surface (get_cache,
put_cache, invalidate, invalidate_scope, invalidate_all, cache_stats,
handle_hook) against the Python oracle bodies on the SAME cache.db. A
mutation of an oracle body must flip the battery. Also pins the facade
contract: fail-loud RuntimeError on a missing binary; RUNE_CACHE_ENGINE
=python escape; cross-engine coherence (engine-written rows are
oracle-readable and vice versa).
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import cache as cache_mod # noqa: E402
@pytest.fixture()
def mem_dir(tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_MEMORY_DIR", str(tmp_path))
# The oracle bodies read the import-time DB_PATH constant; retarget it
# to the test memory dir so both engines address the same cache.db.
monkeypatch.setattr(cache_mod, "DB_PATH", str(tmp_path / "cache.db"))
return str(tmp_path)
def _oracle(mem_dir, monkeypatch):
monkeypatch.setenv("RUNE_CACHE_ENGINE", "python")
return cache_mod
def _put(cache, key, value, **kw):
return cache.put_cache(key, value, **kw)
# -- put / get roundtrip -----------------------------------------------------
def test_put_get_roundtrip_go_vs_oracle(mem_dir, monkeypatch):
got = _put(cache_mod, "k1", "value one")
monkeypatch.setenv("RUNE_CACHE_ENGINE", "python")
oracle_put = cache_mod._put_cache_python("k1", "value one")
oracle_get = cache_mod._get_cache_python("k1")
monkeypatch.delenv("RUNE_CACHE_ENGINE")
engine_get = cache_mod.get_cache("k1")
assert got == oracle_put == {"stored": "k1", "content_hash": got["content_hash"], "scope": "general"}
assert engine_get["hit"] is True and oracle_get["hit"] is True
assert engine_get["full"] == oracle_get["full"] == "value one"
assert engine_get["content_hash"] == oracle_get["content_hash"]
def test_miss_shape_go_vs_oracle(mem_dir):
assert cache_mod.get_cache("absent-key") == {"hit": False, "key": "absent-key"}
assert cache_mod._get_cache_python("absent-key") == {"hit": False, "key": "absent-key"}
def test_put_scope_and_source_hash_go_vs_oracle(mem_dir):
got = _put(cache_mod, "k2", "v2", scope="research", source_hash="abc")
oracle = cache_mod._put_cache_python("k2", "v2", scope="research", source_hash="abc")
assert got == oracle
row = cache_mod.get_cache("k2")
assert row["scope"] == "research"
# -- invalidation ------------------------------------------------------------
def test_invalidate_go_vs_oracle(mem_dir):
_put(cache_mod, "k3", "v3")
got = cache_mod.invalidate("k3", "gone")
assert got["invalidated"] == "k3"
assert cache_mod.get_cache("k3")["hit"] is False
def test_invalidate_scope_go_vs_oracle(mem_dir):
_put(cache_mod, "a1", "v", scope="s1")
_put(cache_mod, "a2", "v", scope="s1")
_put(cache_mod, "b1", "v", scope="s2")
got = cache_mod.invalidate_scope("s1")
assert got["invalidated_scope"] == "s1" and got["count"] == 2
assert cache_mod.get_cache("a1")["hit"] is False
assert cache_mod.get_cache("a2")["hit"] is False
assert cache_mod.get_cache("b1")["hit"] is True
def test_invalidate_all_go_vs_oracle(mem_dir):
_put(cache_mod, "x1", "v")
_put(cache_mod, "x2", "v")
got = cache_mod.invalidate_all("reset")
assert got["invalidated_all"] == 2
assert cache_mod.get_cache("x1")["hit"] is False
assert cache_mod.get_cache("x2")["hit"] is False
# -- stats + hook --------------------------------------------------------------
def test_cache_stats_go_vs_oracle(mem_dir):
_put(cache_mod, "s1", "v")
_put(cache_mod, "s2", "v", scope="other")
stats = cache_mod.cache_stats()
oracle = cache_mod._cache_stats_python()
assert stats["active"] == oracle["active"] == 2
assert stats["scopes"] == oracle["scopes"]
def test_handle_hook_go_vs_oracle(mem_dir):
got = cache_mod.handle_hook("memory.write", {"key": "h1"})
oracle = cache_mod._handle_hook_python("memory.write", {"key": "h1"})
assert got["handler"] == oracle["handler"] == "cache"
assert got["status"] == oracle["status"] == "ok"
# -- cross-engine coherence -----------------------------------------------------
def test_engine_write_oracle_read(mem_dir, monkeypatch):
_put(cache_mod, "cross1", "via engine")
monkeypatch.setenv("RUNE_CACHE_ENGINE", "python")
assert cache_mod.get_cache("cross1")["full"] == "via engine"
def test_oracle_write_engine_read(mem_dir, monkeypatch):
monkeypatch.setenv("RUNE_CACHE_ENGINE", "python")
cache_mod.put_cache("cross2", "via oracle")
monkeypatch.delenv("RUNE_CACHE_ENGINE")
assert cache_mod.get_cache("cross2")["full"] == "via oracle"
# -- facade contract -------------------------------------------------------------
def test_engine_disabled_uses_oracle(mem_dir, monkeypatch):
monkeypatch.setenv("RUNE_CACHE_ENGINE", "python")
r = cache_mod.put_cache("esc", "v")
assert r["stored"] == "esc"
def test_missing_binary_fails_loud(mem_dir, monkeypatch, tmp_path):
monkeypatch.setenv("RUNE_CACHE_BIN", str(tmp_path / "nope"))
with pytest.raises(RuntimeError, match="binary missing"):
cache_mod.get_cache("k")
def test_bad_op_fails_loud():
with pytest.raises(RuntimeError, match="go engine"):
cache_mod._go_eval([{"op": "definitely_not_an_op", "memory_dir": "/tmp"}])
+147
View File
@@ -0,0 +1,147 @@
"""REBASE-COMPLETION (#2587) parity battery — message_queue.
Red-first discipline: pins the Go engine's eval surface against the
Python oracle bodies on the SAME queue.json. A mutation of an oracle
body must flip the battery. Also pins the facade contract: fail-loud
RuntimeError on a missing binary; RUNE_MESSAGE_QUEUE_ENGINE=python
escape; cross-engine coherence both directions.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import message_queue as mq_mod # noqa: E402
from rune.message_queue import MessageQueue, QueuedMessage # noqa: E402
@pytest.fixture()
def queue_dir(tmp_path, monkeypatch):
monkeypatch.setattr(mq_mod, "QUEUE_DIR", str(tmp_path / "mq"))
monkeypatch.setattr(mq_mod, "QUEUE_FILE", str(tmp_path / "mq" / "queue.json"))
return str(tmp_path / "mq")
def _engine(queue_dir):
return MessageQueue()
def _oracle(queue_dir):
os.environ["RUNE_MESSAGE_QUEUE_ENGINE"] = "python"
try:
return MessageQueue()
finally:
os.environ.pop("RUNE_MESSAGE_QUEUE_ENGINE", None)
# -- detection ----------------------------------------------------------------
DETECT_CASES = [
"add a new feature to the parser",
"also fix the bug in auth",
"and can you update the docs",
"the weather is nice today",
]
@pytest.mark.parametrize("message", DETECT_CASES)
def test_detect_task_addition_go_vs_oracle(message):
is_t, conf, signals = MessageQueue().detect_task_addition(message)
o_is, o_conf, o_signals = MessageQueue()._detect_task_addition_python(message)
assert (is_t, round(conf, 6), signals) == (o_is, round(o_conf, 6), o_signals)
# -- add / pending / approved / status ------------------------------------------
def test_add_message_go_vs_oracle(queue_dir):
got = _engine(queue_dir).add_message("add a feature", "chat")
oracle = _oracle(queue_dir).add_message("add a feature", "chat")
assert got.message == oracle.message == "add a feature"
assert got.status == oracle.status == "pending"
assert got.channel == oracle.channel == "chat"
def test_pending_approved_flow_go_vs_oracle(queue_dir):
q = _engine(queue_dir)
m1 = q.add_message("add one", "chat")
q.add_message("add two", "chat")
assert len(q.get_pending()) == 2
assert q.approve(m1.id) is True
assert len(q.get_pending()) == 1
assert len(q.get_approved()) == 1
st = q.get_status()
assert st == {"total": 2, "pending": 1, "approved": 1, "executed": 0}
assert q.mark_executed(m1.id) is True
assert q.get_status()["executed"] == 1
def test_reject_go_vs_oracle(queue_dir):
q = _engine(queue_dir)
m = q.add_message("add x", "chat")
assert q.reject(m.id) is True
st = q.get_status()
assert st["pending"] == 0
oracle_q = _oracle(queue_dir)
om = oracle_q.add_message("add x", "chat")
oracle_q.reject(om.id)
assert q.get_status() == oracle_q.get_status()
def test_approve_all_go_vs_oracle(queue_dir):
q = _engine(queue_dir)
q.add_message("a1", "chat")
q.add_message("a2", "chat")
assert q.approve_all() == 2
assert len(q.get_approved()) == 2
def test_clear_go_vs_oracle(queue_dir):
q = _engine(queue_dir)
q.add_message("a", "chat")
q.clear()
assert q.get_status()["total"] == 0
# -- cross-engine coherence --------------------------------------------------------
def test_engine_write_oracle_read(queue_dir):
q = _engine(queue_dir)
m = q.add_message("cross", "chat")
mo = _oracle(queue_dir)
assert any(x.id == m.id for x in mo.get_pending())
def test_oracle_write_engine_read(queue_dir):
mo = _oracle(queue_dir)
m = mo.add_message("cross back", "chat")
q = _engine(queue_dir)
assert any(x.id == m.id for x in q.get_pending())
# -- facade contract ------------------------------------------------------------------
def test_engine_disabled_uses_oracle(queue_dir):
os.environ["RUNE_MESSAGE_QUEUE_ENGINE"] = "python"
try:
q = MessageQueue()
m = q.add_message("oracle path", "chat")
assert m.status == "pending"
finally:
os.environ.pop("RUNE_MESSAGE_QUEUE_ENGINE", None)
def test_missing_binary_fails_loud(tmp_path, queue_dir, monkeypatch):
monkeypatch.setenv("RUNE_MESSAGE_QUEUE_BIN", str(tmp_path / "nope"))
with pytest.raises(RuntimeError, match="binary missing"):
MessageQueue().add_message("x", "chat")
def test_bad_op_fails_loud():
with pytest.raises(RuntimeError, match="go engine"):
mq_mod._go_eval([{"op": "definitely_not_an_op", "queue_dir": "/tmp"}])
+72
View File
@@ -0,0 +1,72 @@
"""REBASE-COMPLETION (#2587) parity battery — voicebox.
Red-first discipline: pins the Go engine's eval surface (handle_hook,
manifest) against the Python oracle body. A mutation of the oracle body
must flip the battery. Also pins the facade contract: fail-loud
RuntimeError on a missing binary; RUNE_VOICEBOX_ENGINE=python escape;
the documented empty-event improvement (engine rejects, oracle
accepted) surfaces through the delegation.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import voicebox as vb_mod # noqa: E402
# -- handle_hook go vs oracle --------------------------------------------------
def test_handle_hook_go_vs_oracle():
for event in ("voice.start", "on_session_end", "voicebox.speak"):
got = vb_mod.handle_hook(event)
oracle = vb_mod._handle_hook_python(event)
assert got == oracle == {"status": "ok", "event": event, "handler": "default"}
def test_empty_event_engine_improvement():
"""DOCUMENTED DIVERGENCE: the oracle silently returns ok for an empty
event (a no-op lie); the engine rejects it. The delegation surfaces
the improvement deliberately."""
got = vb_mod.handle_hook("")
assert got["status"] == "error"
assert got["error"] == "empty event"
oracle = vb_mod._handle_hook_python("")
assert oracle == {"status": "ok", "event": "", "handler": "default"}
def test_manifest_pinned():
row = vb_mod._go_eval([{"op": "manifest"}])[0]
assert row["name"] == "voicebox"
assert row["capabilities"] == ["voice", "write", "voicebox"]
assert row["priority"] == 100
assert row["auto_activate"] is True
# the Python PLUGIN literal is the manifest's oracle twin
for key in ("name", "version", "capabilities", "priority"):
assert row[key] == vb_mod.PLUGIN[key]
# -- facade contract -------------------------------------------------------------
def test_engine_disabled_uses_oracle():
os.environ["RUNE_VOICEBOX_ENGINE"] = "python"
try:
assert vb_mod.handle_hook("x") == {"status": "ok", "event": "x", "handler": "default"}
finally:
os.environ.pop("RUNE_VOICEBOX_ENGINE", None)
def test_missing_binary_fails_loud(tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_VOICEBOX_BIN", str(tmp_path / "nope"))
with pytest.raises(RuntimeError, match="binary missing"):
vb_mod.handle_hook("x")
def test_bad_op_fails_loud():
with pytest.raises(RuntimeError, match="go engine"):
vb_mod._go_eval([{"op": "definitely_not_an_op"}])
@@ -0,0 +1,31 @@
{
"schema": "integrated-run/1",
"capability": "abstraction",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "building",
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
"integrated_at": "2026-10-25",
"parity_basis": "src/go/abstraction truth.json replay (go test -race -short green) + tests/test_abstraction_go_parity.py (9 passed, both engines against the SAME abstraction.db)",
"go_engine": {
"path": "src/go/abstraction",
"surface": "eval <ops.json> (new this wave: eval.go — list_beliefs, list_rules, list_skills, pipeline_stats, handle_hook, extract_facts over the truth-tested library; ops carry memory_dir so the facade addresses any abstraction.db) | CLI + replay",
"delegation": "src/rune/abstraction.py: list_beliefs, list_rules, list_skills, pipeline_stats, handle_hook resolve through the Go binary (cross-engine coherence pinned); escape hatch RUNE_ABSTRACTION_ENGINE=python; binary override RUNE_ABSTRACTION_BIN",
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
},
"consumers": [
{
"consumer": "src/rune/memory_consolidation.py, memory_selector.py, rune_tui.py",
"switched_how": "abstraction read/report call sites resolve through the Go engine at runtime",
"receipt": "tests/test_delegation_pinned_paths.py green (4); battery 9 passed",
"blocks_full_switchover": ["extract_facts, synthesize_observations, run_lifecycle stay Python this wave: they embed the llm_call (Ollama /api/generate, LOCAL_MODEL) seam and the lifecycle-transition state machine the eval surface does not govern — the Go ExtractFacts exists and is wired into the eval surface, but the judge-driven semantics stay oracle-authoritative until the LLM seam contract is pinned"]
}
],
"verification": {
"go": "go test -race -short ./... green; go vet clean; binary rebuilt from source",
"python": "battery 9 passed",
"ruff": "check + format clean on touched files",
"mutation_flip": "oracle _list_beliefs_python state-filter mutation (active→archived) flips the battery (2 failed); restored green"
},
"blocks_full_switchover": ["extract_facts", "synthesize_observations", "run_lifecycle"]
}
@@ -0,0 +1,31 @@
{
"schema": "integrated-run/1",
"capability": "auto-context",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "building",
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
"integrated_at": "2026-10-25",
"parity_basis": "src/go/auto_context truth.json replay (go test -race -short green) + tests/test_auto_context_go_parity.py (8 passed)",
"go_engine": {
"path": "src/go/auto_context",
"surface": "eval <ops.json> (new this wave: eval.go — build_auto_context, load_active_tasks; memory_dir-addressed; the warm-router/memory-store subprocess seams shell the same Python scripts the oracle calls) | CLI + replay",
"delegation": "src/rune/auto_context.py: build_auto_context and load_active_tasks resolve through the Go binary; escape hatch RUNE_AUTO_CONTEXT_ENGINE=python; binary override RUNE_AUTO_CONTEXT_BIN",
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
},
"consumers": [
{
"consumer": "src/rune/rune_tui.py (+ canary harnesses)",
"switched_how": "auto-context build/task-load call sites resolve through the Go engine at runtime",
"receipt": "battery 8 passed",
"blocks_full_switchover": []
}
],
"verification": {
"go": "go test -race -short ./... green; go vet clean; binary rebuilt from source",
"python": "battery 8 passed",
"ruff": "check + format clean on touched files",
"mutation_flip": "oracle _load_active_tasks_python status-filter mutation (drops in_progress) flips the battery (2 failed); restored green"
},
"blocks_full_switchover": []
}
@@ -0,0 +1,32 @@
{
"schema": "integrated-run/1",
"capability": "cache",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "building",
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
"integrated_at": "2026-10-24",
"parity_basis": "src/go/cache go test -race -short green (incl. frozen_truth.json + truth_hash.json replays) + tests/test_cache_go_parity.py (13 passed, both engines against the SAME cache.db)",
"go_engine": {
"path": "src/go/cache",
"surface": "eval <ops.json> (new this wave: eval.go — get_cache, put_cache, invalidate, invalidate_scope, invalidate_all, cache_stats, handle_hook over the truth-tested Env library; ops carry memory_dir so the facade addresses any cache.db) | CLI + truth replays",
"delegation": "src/rune/cache.py: get_cache, put_cache, invalidate, invalidate_scope, invalidate_all, cache_stats, handle_hook resolve through the Go binary; the engine mutates the same cache.db (cross-engine coherence tests pin both directions); escape hatch RUNE_CACHE_ENGINE=python; binary override RUNE_CACHE_BIN",
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
},
"contract_note": "the miss contract is the oracle's {hit: false, key} dict — the Go eval's nil-miss is translated at the facade seam so both engines expose the identical shape",
"consumers": [
{
"consumer": "src/rune/memory_continuity.py, memory_store.py, persona_definition.py, rune_tui.py, orchestration/retrieval_benchmark.py",
"switched_how": "cache seam call sites (put_cache/get_cache/invalidate*) resolve through the Go engine at runtime",
"receipt": "tests/test_cache_go_parity.py green (13)",
"blocks_full_switchover": []
}
],
"verification": {
"go": "go test -race -short ./... green; go vet clean; binary rebuilt from source",
"python": "battery 13 passed",
"ruff": "check + format clean on touched files",
"mutation_flip": "oracle _hash digest-width mutation flips the battery (2 failed); restored green"
},
"blocks_full_switchover": []
}
@@ -0,0 +1,31 @@
{
"schema": "integrated-run/1",
"capability": "message-queue",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "building",
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
"integrated_at": "2026-10-25",
"parity_basis": "src/go/message_queue truth.json replay (go test -race -short green) + tests/test_message_queue_go_parity.py (14 passed, both engines against the SAME queue.json)",
"go_engine": {
"path": "src/go/message_queue",
"surface": "eval <ops.json> (new this wave: eval.go — detect_task_addition, add_message, get_pending, approve, reject, approve_all, clear, get_approved, mark_executed, get_status over the truth-tested MessageQueue library; ops carry queue_dir; QueuedMessage projected snake_case via toDict) | CLI + replay",
"delegation": "src/rune/message_queue.py: MessageQueue.detect_task_addition, add_message, approve, reject, approve_all, clear, mark_executed, get_status resolve through the Go binary; the facade reloads the in-memory mirror after engine mutations (cross-engine coherence pinned both directions); escape hatch RUNE_MESSAGE_QUEUE_ENGINE=python; binary override RUNE_MESSAGE_QUEUE_BIN",
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
},
"consumers": [
{
"consumer": "message_queue.py main() CLI + any chat-mode consumer",
"switched_how": "all queue seam operations resolve through the Go engine at runtime",
"receipt": "tests/test_message_queue_go_parity.py green (14)",
"blocks_full_switchover": []
}
],
"verification": {
"go": "go test -race -short ./... green; go vet clean; binary rebuilt from source",
"python": "battery 14 passed",
"ruff": "check + format clean on touched files",
"mutation_flip": "oracle _detect_task_addition_python imperative-verb weight mutation (0.4→0.5) flips the battery (detect-parity case fails); restored green"
},
"blocks_full_switchover": []
}
@@ -0,0 +1,32 @@
{
"schema": "integrated-run/1",
"capability": "voicebox",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "building",
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
"integrated_at": "2026-10-25",
"parity_basis": "src/go/voicebox truth.json replay (go test -race -short green, 2 documented improvements applied) + tests/test_voicebox_go_parity.py (6 passed)",
"go_engine": {
"path": "src/go/voicebox",
"surface": "eval <ops.json> (new this wave: eval.go — handle_hook, manifest) | replay",
"delegation": "src/rune/voicebox.py: handle_hook resolves through the Go binary; escape hatch RUNE_VOICEBOX_ENGINE=python; binary override RUNE_VOICEBOX_BIN",
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
},
"documented_divergence": "the engine's empty-event validation improvement surfaces through the delegation: an EMPTY event returns status 'error' where the oracle silently returned ok (the oracle body's lie is retained as oracle behavior only) — battery test_empty_event_engine_improvement pins both sides of the divergence",
"consumers": [
{
"consumer": "microkernel hook dispatch (voicebox registers as a plugin)",
"switched_how": "handle_hook call sites resolve through the Go engine at runtime",
"receipt": "battery 6 passed",
"blocks_full_switchover": ["speak/listen/talk stay Python: the audio loop is pynput/venv-bound (the Go port records the same boundary — porting it would break the UI law)"]
}
],
"verification": {
"go": "go test -race -short ./... green; go vet clean; binary rebuilt from source",
"python": "battery 6 passed",
"ruff": "check + format clean on touched files",
"mutation_flip": "oracle handler-name mutation (default→voicebox) flips the battery (3 failed); restored green"
},
"blocks_full_switchover": ["speak", "listen", "talk"]
}