Compare commits

..
Author SHA1 Message Date
Nick 3497d2b7a9 feat(operator): forge projection wired into the failure path (T5.15 slice 5)
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 38s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / gitea-offline (push) 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
The final orphan: testing_mode.project_failure_to_forge was TEST_PROVEN with zero runtime callers. _project_failure_to_forge() invokes it from the operator failure path using the already-wired forge client (create_forge_client → NullAdapter when unconfigured = dormant by construction); a thin shim adapts create_issue to the projection's create_or_patch_release contract; ForgeUnavailable degrades to a marker, never breaking the failure path (rule 19P). Three contracts: projection fires, dormant-not-error, forge-down-never-breaks. Completion wave wire-up complete: all 5 orphans now have live callers.
2026-10-06 04:09:22 +01:00
Nick 60a21dc7da feat(scaffold): iteration impact-delta recorded at close (T5.15 slice 4)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 43s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 4m53s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
Slice 4 of the completion wave: a material closure (scope_files present) records a predicted-vs-observed impact delta into impact_deltas.jsonl (rule 19H — deltas compound into better future prediction). Governance-only closes record nothing (the delta ledger only carries material changes); delta failure degrades to a session record, never fails a closure. Two contracts, both red-first. Also resolves the stash collision: slice-3 and slice-4 test blocks both retained.
2026-10-06 03:54:01 +01:00
436 changed files with 655 additions and 29632 deletions

No files matched your search

-20
View File
@@ -1,20 +0,0 @@
---
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.
+47 -50
View File
@@ -7,20 +7,47 @@ on:
types: [opened, edited]
jobs:
# 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).
# 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.`,
});
auto-label:
runs-on: rune-ci-desktop
steps:
- name: Infer, apply, then gate-check (one pass)
- name: Analyze and label
uses: actions/github-script@v7
with:
script: |
@@ -38,7 +65,6 @@ 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();
@@ -117,7 +143,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
// gate check at the bottom of THIS job).
// priority-gate job above).
const pMatch = title.match(/\bP([0-3])\b/);
const priorityLabelNames = ['P0 — critical', 'P1 — high', 'P2 — medium', 'P3 — low'];
if (pMatch) {
@@ -180,16 +206,9 @@ jobs:
labels: missingDesired,
});
}
// ---- 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;
}
// 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.
const reconciled = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber,
@@ -197,33 +216,11 @@ 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) {
// 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({
if (activePriority.length === 1 && flag) {
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber, labels: ['unprioritized'],
issue_number: issueNumber, id: flag.id,
});
}
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}.`);
console.log(`Removed managed labels: ${staleManaged.join(', ') || 'none'}`);
console.log(`Added managed labels: ${missingDesired.join(', ') || 'none'}`);
+4 -49
View File
@@ -63,13 +63,6 @@ jobs:
- name: Structural contract (henxels)
run: |
.ci-venv/bin/henxels check --all --plain
# ABSOLUTE SCAFFOLDING LAW (2026-10-07): every Go/Rust implementation
# unit must map to a construction contract in store/scaffold/. New
# units without a contract fail the build — open the contract BEFORE
# the commit lands, never after.
- name: Scaffolding coverage (absolute law)
run: |
python3 scripts/scaffolding_coverage.py
unit-tests:
runs-on: rune-ci-desktop
@@ -88,7 +81,6 @@ 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
@@ -143,7 +135,6 @@ 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,
@@ -170,44 +161,12 @@ 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).
# Every Survivor-linked crate must have its .a in-tree before the
# go loop: adaptive-router → route_risk_flags; cognition →
# cognition_core. Add crates here as Survivor packages grow.
cargo build --release --manifest-path src/rust/route_risk_flags/Cargo.toml
cargo build --release --manifest-path src/rust/cognition_core/Cargo.toml
cargo build --release --manifest-path src/rust/construction_interface/Cargo.toml
- name: prune stale /tmp cargo target dirs (tmpfs guard)
run: |
# /tmp is a 32G tmpfs; every CI run leaves a ~218MB
# rune-ci-target-<run_id> dir. Without pruning, 300+ runs filled
# the tmpfs (2026-10-07: venv creation failed disk-full across
# every job). Keep the 3 newest; drop the rest.
cd /tmp && ls -dt rune-ci-target-* 2>/dev/null | tail -n +4 | xargs -r rm -rf
- name: go vet + go test + go build
run: |
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
OUT_DIR="${GITHUB_WORKSPACE:-$PWD}/bins"
mkdir -p "$OUT_DIR"
go build -o "$OUT_DIR/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
cd src/go/rune-entry
go vet ./...
go test ./...
go build -o /tmp/rune-entry-ci .
canary:
runs-on: rune-ci-desktop
@@ -265,7 +224,6 @@ 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
@@ -304,9 +262,6 @@ 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
-3
View File
@@ -26,9 +26,6 @@ Only use `git commit --no-verify` in a genuine emergency: it bypasses the hooks
- Every workflow is registered in the pipeline registry — new pipeline machinery ships with documentation (in ./.gitea/workflows/*.yml)
- Every TUI surface is registered — no silent UI growth (in ./src/rune/*tui*.py)
- New rune modules ship with tests or a canary — no ungoverned code surfaces (in ./src/rune/*.py)
- Rust crate files stay under a line budget (in ./src/rust/*/src/*.rs)
- Go modules stay under a line budget (in ./src/go/**/*.go)
- No secrets in Rust or Go source files (in ./src/rust/*/src/*.rs, ./src/go/**/*.go)
- PRDs are living documents — their generated state sections regenerate identically from governed sources
- PRD staleness is a CI failure — docs rot is silent failure (in ./scripts/gen_prd_state.py)
- The adversarial review procedure is binding — dispatch templates must reference it (in ./docs/adversarial-review.md)
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.113.0**
**Current release: v0.99.5**
**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.**
+4 -55
View File
@@ -1,5 +1,5 @@
{
"_comment": "GENERATED by rune.spine — compatibility view for legacy umbrella consumers. DO NOT EDIT: structural truth lives in store/spine/current.json; edits here are flagged by spine drift.",
"_comment": "GENERATED by rune.spine \u2014 compatibility view for legacy umbrella consumers. DO NOT EDIT: structural truth lives in store/spine/current.json; edits here are flagged by spine drift.",
"_generated_by_spine": true,
"generated_at": "2026-09-16T18:23:38Z",
"umbrellas": {
@@ -17,12 +17,7 @@
],
"description": "Tool selection and execution authority",
"go_surfaces": [
"rune-entry",
"model-connect",
"provider-registry",
"provider-budget",
"standardized-providers",
"tiered-executor"
"rune-entry"
],
"mirrors": [],
"modules": [
@@ -78,10 +73,8 @@
"drift_ledger",
"drift_observability",
"failure_auto_filer",
"grading",
"programme_gates",
"sc_loader",
"worthiness"
"sc_loader"
],
"rust_crates": [
"evidence_provenance",
@@ -117,12 +110,7 @@
"depends_on": [
"governance"
],
"description": "Who the agent is — persona, voice, anchors",
"go_surfaces": [
"persona-definition",
"voicebox",
"voice-injector"
],
"description": "Who the agent is \u2014 persona, voice, anchors",
"mirrors": [],
"modules": [
"behavior",
@@ -174,15 +162,6 @@
"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",
@@ -232,36 +211,6 @@
"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",
-205
View File
@@ -1,205 +0,0 @@
# 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
@@ -1,134 +0,0 @@
# 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)
-60
View File
@@ -1,60 +0,0 @@
# SUPERSESSION REPORT — programmatic evidence
_Created 2026-10-07. Question: is curator superseded by memory_consolidation? Can supersession be determined programmatically?_
## Answer in one paragraph
No — they are NOT supersessions of each other. They are two different machines in one pipeline: **memory_consolidation compresses what already exists** (warm neurons, graph entities, abstraction facts → consolidated knowledge records), while **curator gates what gets written** (evidence → worthiness filter → graded routing → tiers). Both are wired into sleep_time's nightly run: consolidation runs in-process, curator runs as a subprocess. The real finding is darker: **curator has NEVER successfully run in production** — three of its four dependencies were never ported from the Odysseus branch, so every invocation died at import. Both diagnoses were reached programmatically.
## The programmatic supersession test (reusable)
Six signals, each binary and evidence-based (`/tmp/ss_engine.py`, promote to scripts/):
| Signal | Test | Weight |
|---|---|---|
| R1 Import graph | production importers vs canary-only importers | high |
| R2 Subprocess summon | module executed as a script by another module | high |
| R3 Output artifacts | documented output files exist with recent mtimes | high |
| R4 Function overlap | name overlap with the suspected successor | low (weak signal) |
| R5 Importability | `import X` succeeds (phantom dep = dead) | gate — fails = dead regardless |
| R6 End-to-end probe | documented entry path executes with real input | decisive |
## Applied to curator vs memory_consolidation
| Signal | curator | memory_consolidation |
|---|---|---|
| R1 | 0 production importers | 2 (memory_selector, sleep_time) |
| R2 | summoned as script by sleep_time + dependency_tracker | summoned by dependency_tracker |
| R3 | **index/blocks_meta.json DOES NOT EXIST — no journal files — never wrote output** | graph.sqlite exists but **stale (Aug 22)**; consolidation/*.json never created |
| R4 | 2% overlap — different function sets (confirms: different jobs) | 2% |
| R5 | importable NOW (after repair); dead since #314 | importable |
| R6 | dry-run probe NOW green (post-repair); was JSONDecodeError-crash | consolidate() wired, but its outputs absent too |
## Verdict
1. **NOT a supersession.** 2% function overlap + different roles: curator = write-path gate (worthiness → grade → tier), memory_consolidation = read-path compressor (existing memories → knowledge records). One writes, one compresses. Both belong.
2. **Both are broken in production.** Curator never ran (phantom deps). memory_consolidation's outputs are stale/absent — its last graph write was Aug 22. The whole sleep-time memory pipeline has been quietly degraded.
3. **Disposition: REPAIR, not retire.** grading.py + worthiness.py are now recovered from the Odysseus source branch (provenance, not reconstruction) with frozen tests; curator's dry-run executes end-to-end. Remaining phantom for curator: lexicon_evolution (guarded — degrades), aaak (guarded — optional compression). Both degradable, not blocking.
## The phantom-import census (the general finding)
16 phantom imports found across src/rune. The important ones:
| Module | Importers | Guarded? | Disposition |
|---|---|---|---|
| worthiness | curator | NO (hard crash) | ✅ RECOVERED from Odysseus + frozen tests |
| grading | curator | NO | ✅ RECOVERED from Odysseus + frozen tests |
| lexicon_evolution | curator | yes (degrades) | recover-or-rename decision needed |
| aaak | curator, memory_store, warm_router | yes | optional compression; recover or accept degrade |
| build_pipeline_policy | prompt_sync | NO | phantom — prompt_sync is dead on arrival |
| gitea_auth | forge | NO | forge.py dead; is forge itself superseded? |
| faster_whisper, pynput | voicebox | yes | optional deps — correct as-is |
| research_lens | claim_audit | yes | lives in ~/.config/opencode/scripts — cross-repo dep |
| ep_loader | 5 modules | false positive | resolves via sys.path hack to src/rust/ |
| autonomy_loop, cognitive_loop, dream_cycle | rune_tui | yes | TUI plugins — recover or retire |
| commitments, dedupe_policy, local_memory | warm_router, memory_store, sleep_time | yes | recover-or-accept-degrade |
**Hard-crash phantoms mean the owning module is dead code:** prompt_sync, forge, and (until today) curator.
## What the dictonomy rebase inherits
The Go/Rust port queue should SKIP dead code until its disposition is decided. A module that never ran has no behavioral parity to prove — porting it would faithfully reproduce a ghost. The audit instrument now needs an R5 importability column so dead modules are marked before they're counted.
-13
View File
@@ -94,19 +94,6 @@ 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:
@@ -1,354 +0,0 @@
# 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.
@@ -1,274 +0,0 @@
# 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.113.0"
version = "0.99.5"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+1 -1
View File
@@ -39,7 +39,7 @@ idna==3.20
# anyio
# httpx
# yarl
iniconfig==2.3.1
iniconfig==2.3.0
# via pytest
jinja2==3.1.6
# via diff-cover
-286
View File
@@ -1,286 +0,0 @@
#!/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())
-111
View File
@@ -1,111 +0,0 @@
import json, sys
sys.path.insert(0, 'src')
from rune.adaptive_router import (
classify_route, derive_sufficiency, body_has_floor_trigger, floor_applies,
escalate_route, downgrade_route, escalate_on_verification_failure,
build_research_dispatch, FloorViolation,
)
cases = []
ESC_EVIDENCE = "the fix works locally but the production parser accepts different input shapes — input contract uncertain"
cr_cases = [
("local_fix_happy", "fix the broken typewriter regex in warm_router, crash on empty input",
dict(has_local_repro=True, owning_authority_known=True, testable_locally=True, narrow_and_reversible=True)),
("standard_change", "add a progress bar to the export dialog",
dict(has_local_repro=True, owning_authority_known=True, testable_locally=True, narrow_and_reversible=True)),
("defect_word_absent", "update the version number in the footer",
dict(has_local_repro=True, owning_authority_known=True, testable_locally=False, narrow_and_reversible=True)),
("floor_new_subsystem", "adopt a new subsystem for memory indexing, replacement of spine",
dict(has_local_repro=False, owning_authority_known=False)),
("risk_new_authority", "this creates a new authority path for dispatch",
dict(has_local_repro=False, owning_authority_known=False)),
("uncertainty_competing", "there are competing designs here, expensive reversal likely",
dict(has_local_repro=True, owning_authority_known=True)),
("not_grounded", "add support for markdown export",
dict(has_local_repro=False, owning_authority_known=True, testable_locally=True, narrow_and_reversible=True)),
("inspection_verb", "explain why the failing test fails",
dict(has_local_repro=True, owning_authority_known=True, testable_locally=True, narrow_and_reversible=True)),
]
for name, body, kw in cr_cases:
d = classify_route(body, **kw)
cases.append({"fn": "classify", "name": name,
"input": {"body": body, **kw, "pipeline_sha": "UNKNOWN"},
"expected": {"route": d.route, "confidence": d.confidence, "reasons": d.reasons,
"risk": d.risk_flags, "uncertainty": d.uncertainty_flags,
"floor": d.floor, "task_description": d.task_description}})
ds_cases = [
("grounded_fix", "fix crash", "the parser crashes on empty input, see tests/test_parse.py"),
("mechanism", "investigate", "performance is bad under load, benchmark needed"),
("external_url", "read the docs", "see https://example.com/api docs for the endpoint"),
("version_risk", "upgrade", "upgrade the library to latest version, current behaviour differs"),
("inspection", "explain", "why does this happen"),
("new_subsystem", "adopt", "this adopts a replacement architecture"),
("neutral", "write", "a poem about the sea"),
]
for name, title, body in ds_cases:
s = derive_sufficiency(title, body)
cases.append({"fn": "derive", "name": name,
"input": {"title": title, "body": body, "ambiguous": False},
"expected": {"local_authority": s.local_authority,
"external_dependency": s.external_dependency,
"version_freshness_risk": s.version_freshness_risk,
"verifiability": s.verifiability, "scope_novelty": s.scope_novelty,
"unknowns": s.unknowns(),
"task_class": s.provenance.get("task_class_language")}})
cases.append({"fn": "floor_trigger", "name": "trigger_yes",
"input": {"body": "adopt a replacement for the cache"},
"expected": {"result": body_has_floor_trigger("adopt a replacement for the cache")}})
cases.append({"fn": "floor_trigger", "name": "trigger_no",
"input": {"body": "fix the typo"},
"expected": {"result": body_has_floor_trigger("fix the typo")}})
cases.append({"fn": "floor_applies", "name": "res_yes", "input": {"route": "RESEARCHED_CHANGE", "flags": ["new_authority"]},
"expected": {"result": floor_applies("RESEARCHED_CHANGE", ["new_authority"])}})
cases.append({"fn": "floor_applies", "name": "local_no", "input": {"route": "LOCAL_FIX", "flags": ["new_authority"]},
"expected": {"result": floor_applies("LOCAL_FIX", ["new_authority"])}})
base = classify_route("fix the crash", has_local_repro=True, owning_authority_known=True,
testable_locally=True, narrow_and_reversible=True)
esc = escalate_route(base, "RESEARCHED_CHANGE", "verify_fail", ESC_EVIDENCE)
cases.append({"fn": "escalate", "name": "esc",
"input": {"decision_route": base.route, "new_route": "RESEARCHED_CHANGE",
"trigger": "verify_fail", "evidence": ESC_EVIDENCE},
"expected": {"route": esc.route, "previous": esc.previous_route,
"decided_by": esc.decided_by, "trigger": esc.trigger}})
try:
downgrade_route(base, "STANDARD_CHANGE", "try", "ev")
cases.append({"fn": "downgrade", "name": "dg_ok", "input": {"base_route": base.route, "base_floor": False,
"new_route": "STANDARD_CHANGE"}, "expected": {"route": "STANDARD_CHANGE", "error": None}})
except FloorViolation:
cases.append({"fn": "downgrade", "name": "dg_ok", "input": {}, "expected": {"route": None, "error": "FloorViolation"}})
floored = classify_route("adopt a replacement for the memory index")
try:
downgrade_route(floored, "STANDARD_CHANGE", "efficiency", "faster")
cases.append({"fn": "downgrade", "name": "dg_floored", "input": {"base_floor": True, "new_route": "STANDARD_CHANGE"},
"expected": {"route": "STANDARD_CHANGE", "error": None}})
except FloorViolation:
cases.append({"fn": "downgrade", "name": "dg_floored", "input": {"base_floor": True, "new_route": "STANDARD_CHANGE"},
"expected": {"route": None, "error": "FloorViolation"}})
try:
escalate_on_verification_failure(base, "test failed", "/tmp/ar_probe.jsonl")
cases.append({"fn": "evidence_short", "name": "short", "input": {"evidence": "test failed"},
"expected": {"result": "ACCEPTED"}})
except ValueError:
cases.append({"fn": "evidence_short", "name": "short", "input": {"evidence": "test failed"},
"expected": {"result": "REJECTED"}})
disp = build_research_dispatch("T-1", "research the memory index options", floored)
cases.append({"fn": "dispatch", "name": "disp",
"input": {"task_id": "T-1", "description": "research the memory index options"},
"expected": {"task_id": disp["task_id"], "title": disp["title"], "route": disp["route"],
"floor": disp["floor"], "executor_entry": disp["executor_entry"]}})
json.dump(cases, open('src/go/adaptive-router/truth.json', 'w'), indent=1)
print(f"{len(cases)} truth cases with inputs")
for c in cases:
print(f" {c['fn']:14} {c['name']}")
-137
View File
@@ -1,137 +0,0 @@
#!/usr/bin/env python3
"""scaffolding_coverage.py — CI gate: every Go/Rust implementation module
must map to a construction contract in store/scaffold/.
The absolute-scaffolding law (Nick, 2026-10-07): scaffolding is not optional
process, it is the record. A Go/Rust unit without a contract whose
affected_surface covers it is ungoverned code — this gate fails the build.
Rules:
* Every directory under src/go/ (and every crate under src/rust/) must
appear in at least one contract's scope.
* Contracts are read from store/scaffold/ (the durable trace).
* EXEMPT: crates that pre-date the pipeline (the 10 original kernels) are
mapped via the legacy bootstrap contract; test fixtures and _test.go are
not units.
Exit 0 = all covered. Exit 1 = ungoverned units (listed).
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SCAFFOLD = ROOT / "store" / "scaffold"
GO = ROOT / "src" / "go"
RUST = ROOT / "src" / "rust"
# Units predating the absolute law, retro-adopted 2026-10-07 (contracts
# BUILDER-2026-10-07T11:32-33Z-*). The map is the adoption record; new units
# MUST get their contract BEFORE their first commit lands.
RETRO_ADOPTED = {
"adaptive-router", "memory-store-deep", "operator-deep", "curator-core",
# Original kernels + infra from the pre-pipeline era (bootstrap contract):
"event-bus", "message-queue", "subsystem-health", "parallel-processor",
"connection-pool", "recall-router", "cron-engine", "cache", "night-mode",
"work-manager", "failure-filer", "microkernel", "tool-executor",
"rune-entry", "hook-engine", "model-connect", "operator", "operator-support",
"memory-ops", "sleep-time", "core-server", "context-broker", "stage-guards",
"tool-protocol", "tool-protocol-rw", "witness", "curator", "elastic-cognition",
"persona-gate", "scaffold", "abstraction", "tui", "warm-router",
"provider-registry", "local-tool-runtime", "memory-store-deep",
"cognition",
# Rust kernels (the 10 originals + W6 wave — bootstrap-era + parity ports)
"route_risk_flags", "impact_preview", "programme_gates", "structural_migration",
"rune_spinner", "warm_neuron_store", "graph_projection", "failure_recovery",
"construction_depth", "intent_continuity", "wait_guard", "task_persist",
"response_adapter", "salience_gate", "time_awareness", "recall_uplift",
"verification_layer", "project_registry", "behaviour_ledger", "audit_ledger",
"cognitive_budgeter", "progressive_escalation", "shard_manager", "task_gate",
"drift_core", "self_improve", "token_optimizer", "dependency_tracker",
"context_pressure", "inference_trace", "wait_guard_corpus", "rune_profiler",
"construction_projection", "outcome_ledger", "construction_mode",
"drift_resume", "toolchain", "construction_commands", "construction_activation",
"subconscious_pass", "attention_capsule", "contracts", "capability_fit",
"capability_ledger", "memory_selector", "graph_memory", "claim_audit",
"build_basis", "prompt_sync", "voice_sync", "layout_contract",
"system_registry", "voice_hybrid", "capability_broker", "structural_store",
"spine", "behavior", "construction_intent", "construction_decision",
"construction_pattern", "structural_model", "programme_continuation",
"cognition_core", "memory_retrieval", "memory_storage", "scaffold_core",
"evidence_provenance", "rune_spine", "memory_scoring", "capability_scoring",
"token_counting", "query_memory",
"drift_observability", "iteration", "spine_loader", "testing_mode",
"construction_interface", "construction_patterns_builtin", "drift_cache",
"drift_ledger", "scaffold-membrane",
}
def contract_scopes() -> set[str]:
"""Every surface named in any persisted contract under store/scaffold/."""
scopes: set[str] = set()
if not SCAFFOLD.exists():
return scopes
for f in SCAFFOLD.rglob("*.json"):
try:
data = json.loads(f.read_text())
except (json.JSONDecodeError, OSError):
continue
blobs = _iter_dicts(data)
for d in blobs:
for key in ("scope_files", "affected_surface", "surfaces", "scope"):
v = d.get(key)
if isinstance(v, list):
for item in v:
if isinstance(item, str):
scopes.add(item)
scopes.add(item.rstrip("/").split("/")[-1])
elif isinstance(v, str):
scopes.add(v)
scopes.add(v.rstrip("/").split("/")[-1])
return scopes
def _iter_dicts(obj):
if isinstance(obj, dict):
yield obj
for v in obj.values():
yield from _iter_dicts(v)
elif isinstance(obj, list):
for v in obj:
yield from _iter_dicts(v)
def main() -> int:
scopes = contract_scopes()
ungoverned = []
if GO.exists():
for d in sorted(GO.iterdir()):
if d.is_dir() and (d / "go.mod").exists():
if d.name not in scopes and d.name not in RETRO_ADOPTED:
ungoverned.append(f"src/go/{d.name}/")
if RUST.exists():
for d in sorted(RUST.iterdir()):
if (d / "Cargo.toml").exists():
if d.name not in scopes and d.name not in RETRO_ADOPTED:
ungoverned.append(f"src/rust/{d.name}/")
if ungoverned:
print("UNGOVERNED UNITS — no construction contract covers them")
print("(absolute-scaffolding law 2026-10-07: open a contract via")
print(" construction_interface.submit_need BEFORE the next commit):")
for u in ungoverned:
print(f" {u}")
return 1
print(f"scaffolding coverage: all Go/Rust units governed "
f"({len(RETRO_ADOPTED)} retro-adopted/bootstrap, "
f"{len(scopes)} contract scopes)")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
module rune/abstraction
go 1.21
-91
View File
@@ -1,91 +0,0 @@
// 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
}
-127
View File
@@ -1,127 +0,0 @@
// cli.go — JSON CLI: one op per invocation (matches the Go binary family
// contract). Op dispatch is a thin shell over the ported functions so the
// truth test exercises the SAME code paths operators call.
package main
import (
"encoding/json"
"fmt"
"os"
)
func main() {
var req map[string]interface{}
if err := json.NewDecoder(os.Stdin).Decode(&req); err != nil {
emit(map[string]interface{}{"error": "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)
risk, unc := d.RiskFlags, d.UncertaintyFlags
if risk == nil {
risk = []string{}
}
if unc == nil {
unc = []string{}
}
emit(map[string]interface{}{"route": d.Route, "confidence": d.Confidence, "reasons": d.Reasons,
"risk": risk, "uncertainty": unc, "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]interface{}{"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]interface{}{"result": BodyHasFloorTrigger(body)})
case "floor_applies":
route, _ := req["route"].(string)
var flags []string
flagsRaw, _ := json.Marshal(req["flags"])
json.Unmarshal(flagsRaw, &flags)
emit(map[string]interface{}{"result": FloorApplies(route, flags)})
case "escalate":
newRoute, _ := req["new_route"].(string)
trigger, _ := req["trigger"].(string)
evidence, _ := req["evidence"].(string)
base := baseDecisionFrom(req)
out, err := escalateRoute(base, newRoute, trigger, evidence)
if err != nil {
emit(map[string]interface{}{"error": "ValueError"})
os.Exit(1)
}
emit(map[string]interface{}{"route": out.Route, "previous": out.PreviousRoute,
"decided_by": out.DecidedBy, "trigger": out.Trigger})
case "downgrade":
newRoute, _ := req["new_route"].(string)
trigger, _ := req["trigger"].(string)
evidence, _ := req["evidence"].(string)
base := baseDecisionFrom(req)
out, err := downgradeRoute(base, newRoute, trigger, evidence)
if err != nil {
emit(map[string]interface{}{"error": "FloorViolation"})
os.Exit(1)
}
emit(map[string]interface{}{"route": out.Route})
case "verify_fail":
evidence, _ := req["evidence"].(string)
base := baseDecisionFrom(req)
if _, err := EscalateOnVerificationFailure(base, evidence); err != nil {
emit(map[string]interface{}{"result": "REJECTED"})
return
}
emit(map[string]interface{}{"result": "ACCEPTED"})
case "dispatch":
taskID, _ := req["task_id"].(string)
description, _ := req["description"].(string)
base := baseDecisionFrom(req)
emit(BuildResearchDispatch(taskID, description, base))
default:
emit(map[string]interface{}{"error": "unknown op: " + op})
os.Exit(1)
}
}
// baseDecisionFrom builds the minimal base decision the transition ops need
// (the truth battery passes base_route/base_floor; full decisions come
// through the "decision" key when the operator delegates).
func baseDecisionFrom(req map[string]interface{}) RouteDecision {
d := RouteDecision{Reasons: []string{}}
if dec, ok := req["decision"].(map[string]interface{}); ok {
b, _ := json.Marshal(dec)
json.Unmarshal(b, &d)
return d
}
if r, ok := req["decision_route"].(string); ok {
d.Route = r
}
if f, ok := req["base_floor"].(bool); ok {
d.Floor = f
}
if f, ok := req["floor"].(bool); ok {
d.Floor = f
}
return d
}
func emit(v interface{}) {
b, err := json.Marshal(v)
if err != nil {
fmt.Fprintln(os.Stderr, "marshal:", err)
os.Exit(1)
}
fmt.Println(string(b))
}
-20
View File
@@ -1,20 +0,0 @@
package main
import (
"strings"
"testing"
)
func TestFFIMembraneLive(t *testing.T) {
risk, unc := detectFlags("this creates a new authority path and carries data loss risk, expensive reversal likely")
if !contains(risk, "new_authority") || !contains(risk, "data_loss_risk") {
t.Errorf("Rust membrane silent: risk = %v (want new_authority + data_loss_risk)", risk)
}
if !contains(unc, "expensive_reversal") {
t.Errorf("Rust membrane silent: uncertainty = %v", unc)
}
empty_r, empty_u := detectFlags(strings.ToLower("fix the typo"))
if len(empty_r) != 0 || len(empty_u) != 0 {
t.Errorf("unexpected flags on clean body: %v %v", empty_r, empty_u)
}
}
-3
View File
@@ -1,3 +0,0 @@
module rune/adaptive-router
go 1.21
-551
View File
@@ -1,551 +0,0 @@
// adaptive-router — DEEP PARITY PORT of src/rune/adaptive_router.py
// (625 lines, 19 functions). This is the canonical adaptive route authority:
// LOCAL_FIX / STANDARD_CHANGE / RESEARCHED_CHANGE with traceable provenance,
// the deep-research floor, escalation/downgrade transitions, and evidence-
// sufficiency derivation (Option C).
//
// Survivor hybrid: the one deterministic leaf (detect_flags) is Rust compute,
// linked from the route_risk_flags staticlib — no .so loading, no env var.
// Everything reactive (routing decisions, transitions, dispatch) is Go.
//
// Parity: truth.json carries 24 Python-oracle cases WITH inputs; the test
// replays every case and requires identical outputs. A divergence fails the
// port, not the oracle.
package main
/*
#cgo LDFLAGS: ${SRCDIR}/../../rust/route_risk_flags/target/release/libroute_risk_flags.a -ldl -lm
#include <stdlib.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 — deterministic flag detection.
func detectFlags(bodyLower string) ([]string, []string) {
cArg := C.CString(bodyLower)
defer C.free(unsafe.Pointer(cArg))
res := C.rrf_detect(cArg, C.size_t(len(bodyLower)))
defer C.free(unsafe.Pointer(res))
var out flagResult
if err := json.Unmarshal([]byte(C.GoString(res)), &out); err != nil {
return nil, nil
}
return out.Risk, out.Uncertainty
}
// ── Domain (parity with adaptive_router.py) ────────────────────────────────
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"`
}
// Unknowns — exact field order from Python unknowns().
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()
return time.Now().UTC().Format("2006-01-02T15:04:05.000000+00:00")
}
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n]
}
// ── 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 — 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
}
// BodyHasFloorTrigger — public floor predicate over raw task text.
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"}
// ClassifyRoute — deterministic-first route classification with provenance.
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.
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 {
both := append(append([]string{}, risk...), uncertainty...)
reasons = append(reasons, "risk/uncertainty flags: "+strings.Join(both, ", "))
}
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)
}
return 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: d.Route, Trigger: trigger, Evidence: evidence, Floor: d.Floor,
}, 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
}
return 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: d.Route, Trigger: trigger, Evidence: evidence, Floor: d.Floor,
}, nil
}
// EscalateOnVerificationFailure — production hook for the local lane
// (canary 4). Evidence must name the material uncertainty.
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]interface{} {
ev := map[string]interface{}{
"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
}
if d.Sufficiency.Provenance == nil {
ev["provenance"] = map[string]string{}
} else {
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 appendEvent(ev map[string]interface{}, path string) error {
if dir := filepath.Dir(path); dir != "." && 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 RecordTransition(kind string, d RouteDecision, path string) error {
return appendEvent(event(kind, d), path)
}
func RecordFinalRoute(d RouteDecision, path string) error {
return RecordTransition("FINAL_ROUTE", d, path)
}
// RecordRouteDecision — persists INITIAL_ROUTE; RESEARCHED_CHANGE callers
// attach the executor-consumable dispatch payload.
func RecordRouteDecision(d RouteDecision, path string, dispatch map[string]interface{}) error {
ev := event("INITIAL_ROUTE", d)
if dispatch != nil {
ev["dispatch"] = dispatch
}
return appendEvent(ev, path)
}
func BuildResearchDispatch(taskID, description string, d RouteDecision) map[string]interface{} {
prov := map[string]string{}
if d.Sufficiency != nil && d.Sufficiency.Provenance != nil {
prov = d.Sufficiency.Provenance
}
unknowns := d.Unknowns
if unknowns == nil {
unknowns = []string{}
}
return map[string]interface{}{
"task_id": taskID, "title": truncate(description, 200), "body": description,
"route": d.Route, "floor": d.Floor, "reasons": d.Reasons,
"unknowns": unknowns, "provenance": prov,
"executor_entry": "research_stage_executor.py --stage ISSUE (with issue-shaped input)",
}
}
// ── derive_sufficiency (Option C unified authority) ────────────────────────
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"`
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
}
}
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
}
-423
View File
@@ -1,423 +0,0 @@
[
{
"fn": "classify",
"name": "local_fix_happy",
"input": {
"body": "fix the broken typewriter regex in warm_router, crash on empty input",
"has_local_repro": true,
"owning_authority_known": true,
"testable_locally": true,
"narrow_and_reversible": true,
"pipeline_sha": "UNKNOWN"
},
"expected": {
"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",
"input": {
"body": "add a progress bar to the export dialog",
"has_local_repro": true,
"owning_authority_known": true,
"testable_locally": true,
"narrow_and_reversible": true,
"pipeline_sha": "UNKNOWN"
},
"expected": {
"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",
"input": {
"body": "update the version number in the footer",
"has_local_repro": true,
"owning_authority_known": true,
"testable_locally": false,
"narrow_and_reversible": true,
"pipeline_sha": "UNKNOWN"
},
"expected": {
"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",
"input": {
"body": "adopt a new subsystem for memory indexing, replacement of spine",
"has_local_repro": false,
"owning_authority_known": false,
"pipeline_sha": "UNKNOWN"
},
"expected": {
"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",
"input": {
"body": "this creates a new authority path for dispatch",
"has_local_repro": false,
"owning_authority_known": false,
"pipeline_sha": "UNKNOWN"
},
"expected": {
"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",
"input": {
"body": "there are competing designs here, expensive reversal likely",
"has_local_repro": true,
"owning_authority_known": true,
"pipeline_sha": "UNKNOWN"
},
"expected": {
"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",
"input": {
"body": "add support for markdown export",
"has_local_repro": false,
"owning_authority_known": true,
"testable_locally": true,
"narrow_and_reversible": true,
"pipeline_sha": "UNKNOWN"
},
"expected": {
"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",
"input": {
"body": "explain why the failing test fails",
"has_local_repro": true,
"owning_authority_known": true,
"testable_locally": true,
"narrow_and_reversible": true,
"pipeline_sha": "UNKNOWN"
},
"expected": {
"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",
"input": {
"title": "fix crash",
"body": "the parser crashes on empty input, see tests/test_parse.py",
"ambiguous": false
},
"expected": {
"local_authority": "GROUNDED",
"external_dependency": "NONE",
"version_freshness_risk": "NONE",
"verifiability": "TESTABLE",
"scope_novelty": "KNOWN_PATTERN",
"unknowns": [],
"task_class": "defect"
}
},
{
"fn": "derive",
"name": "mechanism",
"input": {
"title": "investigate",
"body": "performance is bad under load, benchmark needed",
"ambiguous": false
},
"expected": {
"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",
"input": {
"title": "read the docs",
"body": "see https://example.com/api docs for the endpoint",
"ambiguous": false
},
"expected": {
"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",
"input": {
"title": "upgrade",
"body": "upgrade the library to latest version, current behaviour differs",
"ambiguous": false
},
"expected": {
"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",
"input": {
"title": "explain",
"body": "why does this happen",
"ambiguous": false
},
"expected": {
"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",
"input": {
"title": "adopt",
"body": "this adopts a replacement architecture",
"ambiguous": false
},
"expected": {
"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",
"input": {
"title": "write",
"body": "a poem about the sea",
"ambiguous": false
},
"expected": {
"local_authority": "GROUNDED",
"external_dependency": "NONE",
"version_freshness_risk": "NONE",
"verifiability": "NONE",
"scope_novelty": "KNOWN_PATTERN",
"unknowns": [],
"task_class": "neutral"
}
},
{
"fn": "floor_trigger",
"name": "trigger_yes",
"input": {
"body": "adopt a replacement for the cache"
},
"expected": {
"result": true
}
},
{
"fn": "floor_trigger",
"name": "trigger_no",
"input": {
"body": "fix the typo"
},
"expected": {
"result": false
}
},
{
"fn": "floor_applies",
"name": "res_yes",
"input": {
"route": "RESEARCHED_CHANGE",
"flags": [
"new_authority"
]
},
"expected": {
"result": true
}
},
{
"fn": "floor_applies",
"name": "local_no",
"input": {
"route": "LOCAL_FIX",
"flags": [
"new_authority"
]
},
"expected": {
"result": false
}
},
{
"fn": "escalate",
"name": "esc",
"input": {
"decision_route": "LOCAL_FIX",
"new_route": "RESEARCHED_CHANGE",
"trigger": "verify_fail",
"evidence": "the fix works locally but the production parser accepts different input shapes \u2014 input contract uncertain"
},
"expected": {
"route": "RESEARCHED_CHANGE",
"previous": "LOCAL_FIX",
"decided_by": "escalation",
"trigger": "verify_fail"
}
},
{
"fn": "downgrade",
"name": "dg_ok",
"input": {
"base_route": "LOCAL_FIX",
"base_floor": false,
"new_route": "STANDARD_CHANGE"
},
"expected": {
"route": "STANDARD_CHANGE",
"error": null
}
},
{
"fn": "downgrade",
"name": "dg_floored",
"input": {
"base_floor": true,
"new_route": "STANDARD_CHANGE"
},
"expected": {
"route": null,
"error": "FloorViolation"
}
},
{
"fn": "evidence_short",
"name": "short",
"input": {
"evidence": "test failed"
},
"expected": {
"result": "REJECTED"
}
},
{
"fn": "dispatch",
"name": "disp",
"input": {
"task_id": "T-1",
"description": "research the memory index options"
},
"expected": {
"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)"
}
}
]
-153
View File
@@ -1,153 +0,0 @@
package main
// truth_test.go — the parity gate. Replays every case from truth.json
// (generated against src/rune/adaptive_router.py, inputs AND expected
// outputs committed) through the ported Go functions. A divergence fails
// the port, not the oracle.
import (
"encoding/json"
"os"
"testing"
)
type truthCase struct {
Fn string `json:"fn"`
Name string `json:"name"`
Input map[string]interface{} `json:"input"`
Expected map[string]interface{} `json:"expected"`
}
func loadTruth(t *testing.T) []truthCase {
t.Helper()
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 jsonEq(t *testing.T, name, field string, got, want interface{}) {
t.Helper()
gs, _ := json.Marshal(got)
ws, _ := json.Marshal(want)
if string(gs) != string(ws) {
t.Errorf("%s.%s: got %s want %s", name, field, gs, ws)
}
}
func str(m map[string]interface{}, k string) string {
s, _ := m[k].(string)
return s
}
func boolField(m map[string]interface{}, k string) bool {
b, _ := m[k].(bool)
return b
}
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 {
switch c.Fn {
case "classify":
body := str(c.Input, "body")
in := ClassifyInput{
Body: body,
HasLocalRepro: boolField(c.Input, "has_local_repro"),
OwningAuthorityKnown: boolField(c.Input, "owning_authority_known"),
TestableLocally: boolField(c.Input, "testable_locally"),
NarrowAndReversible: boolField(c.Input, "narrow_and_reversible"),
PipelineSHA: str(c.Input, "pipeline_sha"),
}
d := ClassifyRoute(in)
e := c.Expected
jsonEq(t, c.Name, "route", d.Route, e["route"])
jsonEq(t, c.Name, "confidence", d.Confidence, e["confidence"])
jsonEq(t, c.Name, "reasons", d.Reasons, e["reasons"])
jsonEq(t, c.Name, "risk", d.RiskFlags, e["risk"])
jsonEq(t, c.Name, "uncertainty", d.UncertaintyFlags, e["uncertainty"])
jsonEq(t, c.Name, "floor", d.Floor, e["floor"])
jsonEq(t, c.Name, "task_description", d.TaskDescription, e["task_description"])
case "derive":
in := DeriveInput{Title: str(c.Input, "title"), Body: str(c.Input, "body"), Ambiguous: boolField(c.Input, "ambiguous")}
s := DeriveSufficiency(in)
e := c.Expected
jsonEq(t, c.Name, "local_authority", s.LocalAuthority, e["local_authority"])
jsonEq(t, c.Name, "external_dependency", s.ExternalDependency, e["external_dependency"])
jsonEq(t, c.Name, "version_freshness_risk", s.VersionFreshnessRisk, e["version_freshness_risk"])
jsonEq(t, c.Name, "verifiability", s.Verifiability, e["verifiability"])
jsonEq(t, c.Name, "scope_novelty", s.ScopeNovelty, e["scope_novelty"])
unk := s.Unknowns()
if unk == nil {
unk = []string{}
}
jsonEq(t, c.Name, "unknowns", unk, e["unknowns"])
jsonEq(t, c.Name, "task_class", s.Provenance["task_class_language"], e["task_class"])
case "floor_trigger":
got := BodyHasFloorTrigger(str(c.Input, "body"))
jsonEq(t, c.Name, "result", got, c.Expected["result"])
case "floor_applies":
var flags []string
b, _ := json.Marshal(c.Input["flags"])
json.Unmarshal(b, &flags)
got := FloorApplies(str(c.Input, "route"), flags)
jsonEq(t, c.Name, "result", got, c.Expected["result"])
case "escalate":
base := RouteDecision{Route: str(c.Input, "decision_route"), Reasons: []string{}}
out, err := escalateRoute(base, str(c.Input, "new_route"), str(c.Input, "trigger"), str(c.Input, "evidence"))
if err != nil {
t.Errorf("%s: escalate errored: %v", c.Name, err)
continue
}
e := c.Expected
jsonEq(t, c.Name, "route", out.Route, e["route"])
jsonEq(t, c.Name, "previous", out.PreviousRoute, e["previous"])
jsonEq(t, c.Name, "decided_by", out.DecidedBy, e["decided_by"])
jsonEq(t, c.Name, "trigger", out.Trigger, e["trigger"])
case "downgrade":
base := RouteDecision{Route: str(c.Input, "base_route"), Floor: boolField(c.Input, "base_floor"), Reasons: []string{}}
out, err := downgradeRoute(base, str(c.Input, "new_route"), "trigger", "evidence")
e := c.Expected
if e["error"] != nil {
if err == nil {
t.Errorf("%s: expected FloorViolation, got none", c.Name)
} else if err.Error()[:14] != "FloorViolation" {
t.Errorf("%s: expected FloorViolation, got %v", c.Name, err)
}
continue
}
if err != nil {
t.Errorf("%s: unexpected error: %v", c.Name, err)
continue
}
jsonEq(t, c.Name, "route", out.Route, e["route"])
case "evidence_short":
base := RouteDecision{Route: "LOCAL_FIX", Reasons: []string{}}
_, err := EscalateOnVerificationFailure(base, str(c.Input, "evidence"))
got := "ACCEPTED"
if err != nil {
got = "REJECTED"
}
jsonEq(t, c.Name, "result", got, c.Expected["result"])
case "dispatch":
floored := ClassifyRoute(ClassifyInput{Body: "adopt a replacement for the memory index"})
d := BuildResearchDispatch(str(c.Input, "task_id"), str(c.Input, "description"), floored)
e := c.Expected
jsonEq(t, c.Name, "task_id", d["task_id"], e["task_id"])
jsonEq(t, c.Name, "title", d["title"], e["title"])
jsonEq(t, c.Name, "route", d["route"], e["route"])
jsonEq(t, c.Name, "floor", d["floor"], e["floor"])
jsonEq(t, c.Name, "executor_entry", d["executor_entry"], e["executor_entry"])
default:
t.Errorf("unknown truth fn %q", c.Fn)
}
}
}
BIN
View File
Binary file not shown.
-26
View File
@@ -1,26 +0,0 @@
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
@@ -1,73 +0,0 @@
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
@@ -1,154 +0,0 @@
// 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
@@ -1,37 +0,0 @@
// 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
@@ -1,197 +0,0 @@
// 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
@@ -1,64 +0,0 @@
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
@@ -1,3 +0,0 @@
module rune/cognition
go 1.21
-55
View File
@@ -1,55 +0,0 @@
// 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
@@ -1,50 +0,0 @@
// 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
@@ -1,27 +0,0 @@
// 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
@@ -1,53 +0,0 @@
// 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
@@ -1,52 +0,0 @@
// 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
@@ -1,43 +0,0 @@
// 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
@@ -1,27 +0,0 @@
// 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
@@ -1,37 +0,0 @@
// 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
@@ -1,26 +0,0 @@
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
@@ -1,73 +0,0 @@
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
@@ -1,94 +0,0 @@
// 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.
-17
View File
@@ -1,17 +0,0 @@
module rune/context-broker
go 1.26.0
require modernc.org/sqlite v1.60.1
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/sys v0.48.0 // indirect
modernc.org/libc v1.77.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.12.1 // indirect
)
-50
View File
@@ -1,50 +0,0 @@
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
@@ -1,414 +0,0 @@
// 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)
}
}
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
module rune/core-server
go 1.21
-92
View File
@@ -1,92 +0,0 @@
// 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())
}
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
module rune/cron-engine
go 1.21
-168
View File
@@ -1,168 +0,0 @@
// 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)
// Marshal ONLY the durable state — the embedded sync.Mutex must never
// be copied (go vet copies-lock), and it is not state anyway.
data, _ := json.MarshalIndent(struct{ Tasks map[string]*CronTask }{Tasks: ce.Tasks}, "", " ")
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)
}
}
-261
View File
@@ -1,261 +0,0 @@
package main
// candidates.go — batch 2: the candidate ledger (promote/decay) and the
// append-only monthly journal. PARITY ports of curator.py's
// promote_candidates / decay_candidates / journal_append, with the same
// constants and the same verdict semantics. The coherence gate and drift
// pre-commit gate are Go-side hooks (wired when those kernels' Go surfaces
// land); the ledger mechanics here are byte-faithful to the Python oracle.
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
const CandidateMin = 3 // CANDIDATE_MIN: sightings before promotion
const CandidateStaleRuns = 30 // CANDIDATE_STALE_RUNS: runs before decay
const CandidatesKey = "_candidates"
// Candidates — idx[CANDIDATES_KEY] shape: key → entry map.
type Candidates = map[string]map[string]interface{}
type Index = map[string]interface{}
// EnsureCandidates — get-or-create the ledger in the index.
func EnsureCandidates(idx Index) Candidates {
raw, ok := idx[CandidatesKey]
if !ok {
c := Candidates{}
idx[CandidatesKey] = c
return c
}
if c, ok := raw.(Candidates); ok {
return c
}
// JSON-decoded indexes arrive as map[string]interface{}
if m, ok := raw.(map[string]interface{}); ok {
c := Candidates{}
for k, v := range m {
if em, ok := v.(map[string]interface{}); ok {
c[k] = em
}
}
idx[CandidatesKey] = c
return c
}
c := Candidates{}
idx[CandidatesKey] = c
return c
}
// journalEntry — the op record shape (parity with Python journal_append).
type JournalEntry struct {
Op string
Target string
Before string
After string
Evidence string
Source string
Reason string
}
// JournalAppend — append one record to memory/journal/YYYY-MM.md.
// Byte-format parity: `ts` **OP** → `target` + indented fields.
func JournalAppend(journalDir string, entry JournalEntry) (string, error) {
month := time.Now().UTC().Format("2006-01")
path := filepath.Join(journalDir, month+".md")
ts := time.Now().UTC().Format("2006-01-02T15:04:05")
var b strings.Builder
fmt.Fprintf(&b, "`%s` **%s** → `%s`\n", ts, entry.Op, entry.Target)
fmt.Fprintf(&b, " - evidence: %s\n", entry.Evidence)
if entry.Before != "" {
fmt.Fprintf(&b, " - before: %s\n", entry.Before)
}
if entry.After != "" {
fmt.Fprintf(&b, " - after: %s\n", entry.After)
}
src := entry.Source
if src == "" {
src = "curator"
}
fmt.Fprintf(&b, " - source: %s\n", src)
if entry.Reason != "" {
fmt.Fprintf(&b, " - reason: %s\n", entry.Reason)
}
b.WriteString("\n")
if err := os.MkdirAll(journalDir, 0o755); 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.WriteString(b.String())
return path, err
}
// coherenceFn / driftGateFn — injectable gates (parity hooks). The Go port
// wires the real kernels when their surfaces land; nil = the Python
// no-kernel path (coherence check skipped in batch-2 oracle runs).
type CoherenceFn func(fact string, sources []string) bool
type DriftGateFn func(entry map[string]interface{}) (verdict string, reason string)
// PromoteCandidates — merge weak evidence into the ledger; promote at
// CANDIDATE_MIN sightings. Explicit ops and strength>=3 skip the ledger.
func PromoteCandidates(evidence []map[string]interface{}, idx Index, journalLines *[]JournalEntry, coherence CoherenceFn, driftGate DriftGateFn) []JournalEntry {
cands := EnsureCandidates(idx)
promoted := []JournalEntry{}
now := func() string { return time.Now().UTC().Format("2006-01-02T15:04:05") }
for _, item := range evidence {
if op, _ := item["op"].(string); op == "UPDATE" || op == "DELETE" {
continue
}
fact, _ := item["fact"].(string)
fact = strings.TrimSpace(fact)
if fact == "" {
continue
}
target, _ := item["target"].(string)
if target == "" {
target = "human"
}
strength := 1
if s, ok := item["strength"].(float64); ok {
strength = int(s)
} else if s, ok := item["strength"].(int); ok {
strength = s
}
if strength >= 3 {
continue
}
ck := CandidateKey(fact)
entry, exists := cands[ck]
if !exists {
entry = map[string]interface{}{
"text": fact, "target": target, "count": 0,
"last_seen": now(), "first_seen": now(),
"sources": []string{},
}
cands[ck] = entry
}
entry["text"] = fact
entry["target"] = target
count := 0
if c, ok := entry["count"].(float64); ok {
count = int(c)
} else if c, ok := entry["count"].(int); ok {
count = c
}
entry["count"] = count + 1
entry["last_seen"] = now()
src, _ := item["source"].(string)
if src == "" {
src = "curator"
}
sources, _ := entry["sources"].([]string)
found := false
for _, s := range sources {
if s == src {
found = true
break
}
}
if !found {
entry["sources"] = append(sources, src)
}
if count+1 >= CandidateMin {
// QUALITY GATE: persona/operating candidates need coherence.
if target == "persona" || target == "operating" {
if coherence != nil && !coherence(fact, []string{"sessions"}) {
*journalLines = append(*journalLines, JournalEntry{
Op: "CANDIDATE-PARKED", Target: target, After: truncate(fact, 140),
Evidence: "3 sightings but no transcript support (lens hallucination guard)",
Source: "coherence gate", Reason: "pattern not traceable to real session content",
})
entry["count"] = CandidateMin - 1
continue
}
}
// Drift-with-integrity: pre-commit gate.
if driftGate != nil {
verdict, reason := driftGate(map[string]interface{}{
"text": fact, "importance": 0.5, "wash_count": count + 1})
if verdict == "REJECT" {
*journalLines = append(*journalLines, JournalEntry{
Op: "CANDIDATE-REJECTED", Target: target, After: truncate(fact, 140),
Evidence: fmt.Sprintf("pre-commit gate rejected: %s", reason),
Source: "drift gate", Reason: "pre-commit gate failed",
})
continue
}
}
promoted = append(promoted, JournalEntry{
Op: "ADD", Target: target, After: fact,
Source: fmt.Sprintf("candidate ledger (%d sightings)", count+1),
Reason: fmt.Sprintf("candidate seen %d times across runs", count+1),
})
delete(cands, ck)
}
}
for _, p := range promoted {
*journalLines = append(*journalLines, p)
}
return promoted
}
// DecayCandidates — prune candidates not seen in CANDIDATE_STALE_RUNS/24 days.
func DecayCandidates(idx Index, journalLines *[]JournalEntry) bool {
cands := EnsureCandidates(idx)
nowT := time.Now().UTC()
var stale []string
for ck, entry := range cands {
lastSeen, _ := entry["last_seen"].(string)
last, err := time.Parse("2006-01-02T15:04:05", lastSeen)
if err != nil {
last, err = time.Parse(time.RFC3339, lastSeen)
if err != nil {
stale = append(stale, ck)
continue
}
}
days := nowT.Sub(last).Hours() / 24
if days > float64(CandidateStaleRuns)/24.0 {
stale = append(stale, ck)
}
}
for _, ck := range stale {
entry := cands[ck]
text, _ := entry["text"].(string)
target, _ := entry["target"].(string)
if target == "" {
target = "human"
}
count := 0
if c, ok := entry["count"].(float64); ok {
count = int(c)
} else if c, ok := entry["count"].(int); ok {
count = c
}
*journalLines = append(*journalLines, JournalEntry{
Op: "CANDIDATE-DROP", Target: target, Before: truncate(text, 120),
Evidence: fmt.Sprintf("candidate stale (not seen recently), had %d sightings", count),
Source: "candidate decay",
})
delete(cands, ck)
}
return len(stale) > 0
}
var _ = regexp.MustCompile // reserved for the coherence port
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n]
}
-75
View File
@@ -1,75 +0,0 @@
package main
// cli.go — the module-family CLI: one JSON op per invocation. Exposes the
// ported curator mechanics for shell/Go callers (the go loop builds every
// src/go module; a main package needs a main()).
import (
"encoding/json"
"fmt"
"os"
)
func main() {
var req map[string]interface{}
if err := json.NewDecoder(os.Stdin).Decode(&req); err != nil {
emit(map[string]interface{}{"error": "bad JSON input: " + err.Error()})
os.Exit(1)
}
op, _ := req["op"].(string)
switch op {
case "entry_key":
text, _ := req["text"].(string)
emit(map[string]interface{}{"key": EntryKey(text)})
case "candidate_key":
text, _ := req["text"].(string)
emit(map[string]interface{}{"key": CandidateKey(text)})
case "norm":
text, _ := req["text"].(string)
emit(map[string]interface{}{"norm": Norm(text)})
case "block_path":
label, _ := req["label"].(string)
emit(map[string]interface{}{"path": BlockPath(label)})
case "find_entry":
body, _ := req["body"].(string)
needle, _ := req["needle"].(string)
emit(map[string]interface{}{"line": FindEntry(body, needle)})
case "sanitize_slug":
name, _ := req["name"].(string)
emit(map[string]interface{}{"slug": SanitizeSlug(name)})
case "detect_conflict":
body, _ := req["body"].(string)
fact, _ := req["fact"].(string)
kind, line := DetectConflict(body, fact)
emit(map[string]interface{}{"kind": kind, "line": line})
case "le_promote_eligible":
proposal, _ := req["proposal"].(string)
existing, _ := req["existing"].(string)
strength := intOf(req["strength"], 1)
minS := intOf(req["min_strength"], 3)
wash := intOf(req["wash_count"], 0)
emit(LePromoteEligible(proposal, existing, strength, minS, wash))
default:
emit(map[string]interface{}{"error": "unknown op: " + op})
os.Exit(1)
}
}
func intOf(v interface{}, def int) int {
if f, ok := v.(float64); ok {
return int(f)
}
if i, ok := v.(int); ok {
return i
}
return def
}
func emit(v interface{}) {
b, err := json.Marshal(v)
if err != nil {
fmt.Fprintln(os.Stderr, "marshal:", err)
os.Exit(1)
}
fmt.Println(string(b))
}
-113
View File
@@ -1,113 +0,0 @@
package main
// coherence.go — batch 3: the semantic coherence gate (the anti-hallucination
// check for candidate promotion). PARITY port of curator.py's
// _grounded_in_sessions distinctive-word algorithm:
//
// - Significant words (4+ letters, minus the stop set) are extracted
// - The RAREST significant word (by corpus frequency) is the probe
// - Grounding requires the rarest word present in >= 2 DISTINCT sessions
// (independence beats multiplicity — one rich session can't dominate)
// - Any absent rarest word = not grounded (parking is never wrong for
// growth; applying wrongly is)
//
// The corpus-grounding half (_grounded_in_corpus) needs the memory kernel's
// chunk_recall — wired later through the Survivor membrane. This file covers
// the sessions half, which is the load-bearing gate for behaviour
// refinements.
import (
"regexp"
"strings"
"time"
)
var wordRE = regexp.MustCompile(`[A-Za-z]{4,}`)
// stopWords — the Python oracle's stop set (order-insensitive; membership only).
var stopWords = map[string]bool{
"should": true, "would": true, "could": true, "need": true,
"with": true, "that": true, "this": true, "about": true,
"before": true, "after": true, "from": true, "into": true,
"there": true, "their": true, "these": true, "those": true,
"while": true, "being": true, "have": true, "has": true,
"been": true, "will": true, "were": true, "when": true,
"then": true, "them": true, "they": true, "just": true,
"more": true, "also": true, "each": true, "than": true,
"very": true, "your": true, "the": true, "and": true,
"user": true, "value": true, "i": true, "we": true,
"you": true, "our": true, "its": true, "for": true,
}
// GroundedInSessions — the distinctive-word algorithm over in-memory session
// texts. The Python oracle filters by file mtime (window_days); the Go port
// takes pre-filtered texts (callers supply recent sessions) and the caller
// owns the recency policy. Decision logic is byte-faithful.
func GroundedInSessions(fact string, texts []string) bool {
if len(texts) == 0 {
return false
}
corpus := strings.Join(texts, " ")
lowCorpus := strings.ToLower(corpus)
words := wordRE.FindAllString(strings.ToLower(fact), -1)
var sig []string
for _, w := range words {
if !stopWords[w] {
sig = append(sig, w)
}
}
if len(sig) == 0 {
return false
}
freq := map[string]int{}
for _, w := range sig {
freq[w] = strings.Count(lowCorpus, w)
}
// Rarest significant word (Python: min(sig, key=freq.get, default 0)).
rarest := sig[0]
for _, w := range sig[1:] {
if freq[w] < freq[rarest] {
rarest = w
}
}
if !strings.Contains(lowCorpus, rarest) {
return false
}
// Independence: >= 2 DISTINCT sessions contain the rarest word.
hits := 0
for _, t := range texts {
if strings.Contains(strings.ToLower(t), rarest) {
hits++
}
}
return hits >= 2
}
// CoherenceCheck — the gate entry (parity with coherence_check's structure).
// Sources are checked in order; sessions grounding is in-process, corpus
// grounding delegates to corpusFn when wired (nil = skip, as the Python
// no-kernel path does).
func CoherenceCheck(fact string, sources []string, sessionTexts []string, corpusFn func(string) bool) bool {
if len(strings.TrimSpace(fact)) < 15 {
return true // non-claim — don't block cold start
}
for _, src := range sources {
if src == "sessions" && GroundedInSessions(fact, sessionTexts) {
return true
}
if src == "corpus" && corpusFn != nil && corpusFn(fact) {
return true
}
}
return false
}
var _ = time.Now // reserved for the mtime-window port
// timeNowUTC — test seam for the decay pass (mirrors Python's now()).
func timeNowUTC() time.Time {
return time.Now().UTC()
}
-176
View File
@@ -1,176 +0,0 @@
package main
// decay.go — batch 4: the decay pass and the token-overlap compress
// mechanics. PARITY ports of curator.py's decay_pass, _tokens, _overlap
// with the same constants (STALE_DAYS=90, DECAY_RATE=0.2, FORGET_FLOOR=0.2).
// The full compress_pass needs the store blocks read/write seam — the pure
// clustering pieces port here; the store seam lands with the memory kernel
// membrane.
import (
"fmt"
"math"
"sort"
"strings"
"time"
)
const StaleDays = 90
const DecayRate = 0.2
const ForgetFloor = 0.2
var compressStopwords = map[string]bool{
"the": true, "and": true, "for": true, "with": true, "from": true,
"that": true, "this": true, "has": true, "have": true, "was": true,
"were": true, "are": true, "you": true, "your": true, "our": true,
"not": true, "but": true, "also": true, "his": true, "her": true,
"how": true, "all": true, "any": true, "each": true, "own": true,
"same": true, "too": true, "very": true,
}
// Tokens — normalized significant words (norm'd, stopword-filtered).
func Tokens(text string) []string {
var out []string
for _, w := range strings.Fields(Norm(text)) {
if !compressStopwords[w] {
out = append(out, w)
}
}
return out
}
// Overlap — Jaccard over significant tokens (0.0 when either side is empty).
func Overlap(a, b string) float64 {
ta := tokenSet(a)
tb := tokenSet(b)
if len(ta) == 0 || len(tb) == 0 {
return 0.0
}
inter := 0
for w := range ta {
if tb[w] {
inter++
}
}
union := len(ta) + len(tb) - inter
return float64(inter) / float64(union)
}
func tokenSet(text string) map[string]bool {
m := map[string]bool{}
for _, w := range Tokens(text) {
m[w] = true
}
return m
}
// DecayPass — reduce stale entries' importance; FORGET below the floor.
// Skips _candidates and constitution (inviolable). Mutates idx in place.
// meta shape: {"importance": float, "last_referenced": iso-string}.
func DecayPass(idx Index, journalLines *[]JournalEntry) bool {
now := time.Now().UTC()
changed := false
for label, raw := range idx {
if label == CandidatesKey || label == "constitution" {
continue
}
entries, ok := raw.(map[string]map[string]interface{})
if !ok {
if m, isMap := raw.(map[string]interface{}); isMap {
entries = map[string]map[string]interface{}{}
for k, v := range m {
if em, ok := v.(map[string]interface{}); ok {
entries[k] = em
}
}
idx[label] = entries
} else {
continue
}
}
for key, meta := range entries {
lr, _ := meta["last_referenced"].(string)
ageDays := float64(StaleDays) + 1
if ref, err := time.Parse(time.RFC3339, lr); err == nil {
ageDays = now.Sub(ref).Hours() / 24
}
if ageDays < StaleDays {
continue
}
imp, _ := meta["importance"].(float64)
meta["importance"] = math.Max(0.0, imp-DecayRate)
changed = true
if meta["importance"].(float64) < ForgetFloor {
*journalLines = append(*journalLines, JournalEntry{
Op: "FORGET", Target: label,
Before: strField(meta, "text"),
Evidence: fmt.Sprintf("stale for %.0fd, importance %.2f", ageDays, meta["importance"].(float64)),
Source: "decay",
})
delete(entries, key)
}
}
}
return changed
}
// ClusterLines — greedy clustering by token overlap (>= 0.35 joins the best
// cluster; else a new cluster). PARITY with compress_pass's clustering loop.
func ClusterLines(entries []string) [][]string {
var clusters [][]string
for _, e := range entries {
bestI, bestS := -1, 0.35
for i, c := range clusters {
best := 0.0
for _, member := range c {
if s := Overlap(e, member); s > best {
best = s
}
}
if best > bestS {
bestI, bestS = i, best
}
}
if bestI >= 0 {
clusters[bestI] = append(clusters[bestI], e)
} else {
clusters = append(clusters, []string{e})
}
}
return clusters
}
// ClusterSummary — the representative is the LONGEST line; others noted.
// PARITY: rep + " (also: " + first-4 others (60-char each, '- ' stripped) + ")"
func ClusterSummary(cluster []string) string {
rep := cluster[0]
for _, c := range cluster {
if len(c) > len(rep) {
rep = c
}
}
var others []string
for _, m := range cluster {
if m != rep {
others = append(others, m)
}
}
sort.Strings(others)
var parts []string
for i, o := range others {
if i >= 4 {
break
}
parts = append(parts, truncate(strings.TrimPrefix(strings.TrimSpace(o), "- "), 60))
}
out := strings.TrimRight(rep, " \t\n")
if len(parts) > 0 {
out += " (also: " + strings.Join(parts, "; ") + ")"
}
return out
}
func strField(meta map[string]interface{}, key string) string {
s, _ := meta[key].(string)
return s
}
-3
View File
@@ -1,3 +0,0 @@
module rune/curator-core
go 1.21
-166
View File
@@ -1,166 +0,0 @@
// curator-core — DEEP PARITY PORT (batch 1) of src/rune/curator.py's
// mechanical core. The curator is the memory write-path gate: evidence →
// worthiness → grade → tier. Batch 1 covers the deterministic mechanics:
//
// BlockPath — store://topic/{label} descriptor
// EntryKey — sha256(text)[:16] (raw, NOT normalized)
// CandidateKey — sha256(_norm(text))[:16]
// Norm — markdown-strip + punctuation-drop + whitespace-collapse
// FindEntry — fuzzy needle-in-body line match
// LePromoteEligible — drift-with-integrity strength thresholds + degrade
// DetectConflict — normalized duplicate/containment detection
// SanitizeSlug — slug normalization ("procedure" fallback)
// EnsureEntryMeta — index metadata creation
// TouchEntry — reference-count / importance bump
//
// PARITY: truth.json carries 17 Python-oracle cases WITH inputs; the test
// replays every case. The lexicon_evolution degrade path is the truth
// column (the phantom module is absent, so Python degrades — the Go port
// reproduces the degrade verdicts, not an idealized lexicon path).
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"regexp"
"strings"
"time"
)
// BlockPath — blocks live in the store as always-on entries; the path is a
// descriptor, the DB is the source.
func BlockPath(label string) string {
return "store://topic/" + label
}
// EntryKey — raw sha256[:16] of the stripped text.
func EntryKey(text string) string {
sum := sha256.Sum256([]byte(strings.TrimSpace(text)))
return hex.EncodeToString(sum[:])[:16]
}
var (
normLead = regexp.MustCompile(`^[-*#>\s]+`)
normMarks = regexp.MustCompile(`[*_` + "`" + `#]`)
normPunct = regexp.MustCompile(`[^\w\s]`)
normSpaces = regexp.MustCompile(`\s+`)
slugInvalid = regexp.MustCompile(`[^a-z0-9]+`)
)
// Norm — lowercase, strip markdown bullets/bold/headers, drop punctuation,
// collapse whitespace.
func Norm(text string) string {
t := normLead.ReplaceAllString(strings.TrimSpace(text), "")
t = normMarks.ReplaceAllString(t, "")
t = normPunct.ReplaceAllString(t, " ")
return strings.ToLower(normSpaces.ReplaceAllString(strings.TrimSpace(t), " "))
}
// CandidateKey — sha256 of the NORMALIZED text, [:16].
func CandidateKey(text string) string {
sum := sha256.Sum256([]byte(Norm(text)))
return hex.EncodeToString(sum[:])[:16]
}
// FindEntry — the exact entry line containing needle, or "".
func FindEntry(body, needle string) string {
for _, line := range strings.Split(body, "\n") {
if strings.TrimSpace(line) == "" {
continue
}
if strings.Contains(line, needle) {
return line
}
}
return ""
}
// LePromoteEligible — drift-with-integrity: wash_count lowers the strength
// threshold (>=20 → 1, >=10 → 2, >=5 → 2, else min_strength). The lexicon
// module is a phantom — Python degrades to the strength-only gate with an
// explicit reason string; this port reproduces THAT verdict byte-identically.
func LePromoteEligible(proposal, existing string, strength, minStrength, washCount int) map[string]interface{} {
adjustedMin := minStrength
if washCount >= 20 {
adjustedMin = 1
} else if washCount >= 10 {
adjustedMin = 2
} else if washCount >= 5 {
adjustedMin = 2
}
return map[string]interface{}{
"eligible": strength >= adjustedMin,
"reason": fmt.Sprintf("lexicon module unavailable — strength gate only (adjusted_min=%d, wash_count=%d)",
adjustedMin, washCount),
}
}
// DetectConflict — conservative duplicate detection: exact normalized match
// or containment either direction. Returns ("duplicate", line) or ("" , "").
func DetectConflict(body, fact string) (string, string) {
nf := Norm(fact)
if nf == "" {
return "", ""
}
for _, line := range strings.Split(body, "\n") {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "---") {
continue
}
nl := Norm(line)
if nl == "" {
continue
}
if nf == nl || strings.Contains(nl, nf) || strings.Contains(nf, nl) {
return "duplicate", line
}
}
return "", ""
}
// SanitizeSlug — slug normalization with the "procedure" fallback.
func SanitizeSlug(name string) string {
slug := strings.Trim(slugInvalid.ReplaceAllString(strings.ToLower(name), "-"), "-")
if slug == "" {
return "procedure"
}
return slug
}
// EnsureEntryMeta — create index metadata if missing; returns the key.
func EnsureEntryMeta(idx map[string]map[string]map[string]interface{}, label, text string) string {
key := EntryKey(text)
if idx[label] == nil {
idx[label] = map[string]map[string]interface{}{}
}
if _, ok := idx[label][key]; !ok {
now := time.Now().UTC().Format("2006-01-02T15:04:05")
idx[label][key] = map[string]interface{}{
"created": now,
"last_updated": now,
"last_referenced": now,
"reference_count": 0,
"importance": 0.7,
}
}
return key
}
// TouchEntry — mark referenced: count+1, timestamp, importance capped at 1.0.
func TouchEntry(idx map[string]map[string]map[string]interface{}, label, text string) string {
key := EnsureEntryMeta(idx, label, text)
meta := idx[label][key]
meta["reference_count"] = meta["reference_count"].(int) + 1
meta["last_referenced"] = time.Now().UTC().Format("2006-01-02T15:04:05")
imp, _ := meta["importance"].(float64)
meta["importance"] = minFloat(1.0, imp+0.05)
return key
}
func minFloat(a, b float64) float64 {
if a < b {
return a
}
return b
}
-166
View File
@@ -1,166 +0,0 @@
[
{
"fn": "block_path",
"name": "bp",
"input": {
"label": "operating"
},
"expected": {
"path": "store://topic/operating"
}
},
{
"fn": "entry_key",
"name": "ek",
"input": {
"text": "the quick brown fox"
},
"expected": {
"key": "9ecb36561341d18e"
}
},
{
"fn": "entry_key",
"name": "ek_strip",
"input": {
"text": " padded "
},
"expected": {
"key": "c7f9b538b93ce513"
}
},
{
"fn": "find_entry",
"name": "fe_hit",
"input": {
"body": "first line\n\nsecond has the needle here\nthird",
"needle": "needle"
},
"expected": {
"line": "second has the needle here"
}
},
{
"fn": "find_entry",
"name": "fe_miss",
"input": {
"body": "first line\n\nsecond has the needle here\nthird",
"needle": "absent"
},
"expected": {
"line": null
}
},
{
"fn": "le_promote_eligible",
"name": "le_normal",
"input": {
"proposal": "add support for retries",
"existing": "support for retries in the fetch loop",
"strength": 3,
"wash_count": 0
},
"expected": {
"eligible": {
"eligible": true,
"reason": "lexicon module unavailable \u2014 strength gate only (adjusted_min=3, wash_count=0)"
}
}
},
{
"fn": "le_promote_eligible",
"name": "le_wash10",
"input": {
"proposal": "add support for retries",
"existing": "support for retries in the fetch loop",
"strength": 2,
"wash_count": 10
},
"expected": {
"eligible": {
"eligible": true,
"reason": "lexicon module unavailable \u2014 strength gate only (adjusted_min=2, wash_count=10)"
}
}
},
{
"fn": "le_promote_eligible",
"name": "le_wash20",
"input": {
"proposal": "add support for retries",
"existing": "support for retries in the fetch loop",
"strength": 1,
"wash_count": 20
},
"expected": {
"eligible": {
"eligible": true,
"reason": "lexicon module unavailable \u2014 strength gate only (adjusted_min=1, wash_count=20)"
}
}
},
{
"fn": "le_promote_eligible",
"name": "le_no_distinctive",
"input": {
"proposal": "quantum flux capacitor",
"existing": "totally unrelated memory about weather",
"strength": 5,
"wash_count": 0
},
"expected": {
"eligible": {
"eligible": true,
"reason": "lexicon module unavailable \u2014 strength gate only (adjusted_min=3, wash_count=0)"
}
}
},
{
"fn": "detect_conflict",
"name": "dc_conflict",
"input": {
"body": "the timeout is 30 seconds",
"fact": "the timeout is 60 seconds"
},
"expected": {
"conflict": [
null,
null
]
}
},
{
"fn": "detect_conflict",
"name": "dc_same",
"input": {
"body": "the timeout is 30 seconds",
"fact": "the timeout is 30 seconds"
},
"expected": {
"conflict": [
"duplicate",
"the timeout is 30 seconds"
]
}
},
{
"fn": "sanitize_slug",
"name": "slug",
"input": {
"name": "My Fancy Skill! (v2)"
},
"expected": {
"slug": "my-fancy-skill-v2"
}
},
{
"fn": "candidate_key",
"name": "ck",
"input": {
"text": "some fact text"
},
"expected": {
"key": "eb081bc793c68acd"
}
}
]
-57
View File
@@ -1,57 +0,0 @@
[
{
"fn": "promote_first_sighting",
"name": "p1",
"expected": {
"promoted": [],
"count": 1,
"target": "human"
}
},
{
"fn": "promote_third_sighting",
"name": "p3",
"expected": {
"promoted_ops": [
"ADD"
],
"source": "candidate ledger (3 sightings)",
"removed_from_ledger": true,
"journaled": 1
}
},
{
"fn": "explicit_ops_skip",
"name": "p4",
"expected": {
"promoted": [],
"ledger_empty": true
}
},
{
"fn": "strong_skips_ledger",
"name": "p5",
"expected": {
"promoted": [],
"ledger_empty": true
}
},
{
"fn": "decay_stale",
"name": "d1",
"expected": {
"changed": true,
"op": "CANDIDATE-DROP",
"ledger_empty": true
}
},
{
"fn": "decay_fresh_kept",
"name": "d2",
"expected": {
"changed": false,
"journaled": 0,
"still_present": true
}
}
]
-154
View File
@@ -1,154 +0,0 @@
package main
// truth_batch2_test.go — batch-2 parity gate: replays truth_batch2.json
// (generated from the LIVE Python oracle 2026-10-07) through the Go port.
import (
"encoding/json"
"os"
"testing"
)
func loadTruthB2(t *testing.T) []map[string]interface{} {
t.Helper()
b, err := os.ReadFile("truth_batch2.json")
if err != nil {
t.Fatalf("truth_batch2.json missing: %v", err)
}
var cases []map[string]interface{}
if err := json.Unmarshal(b, &cases); err != nil {
t.Fatalf("unparseable: %v", err)
}
return cases
}
func eqB2(t *testing.T, name, field string, got, want interface{}) {
t.Helper()
gs, _ := json.Marshal(got)
ws, _ := json.Marshal(want)
if string(gs) != string(ws) {
t.Errorf("%s.%s: got %s want %s", name, field, gs, ws)
}
}
func newIdx(seed map[string]map[string]interface{}) Index {
idx := Index{"_candidates": seed}
return idx
}
func TestBatch2Parity(t *testing.T) {
cases := loadTruthB2(t)
if len(cases) == 0 {
t.Fatal("empty truth battery")
}
for _, c := range cases {
name, _ := c["name"].(string)
switch c["fn"] {
case "promote_first_sighting":
idx := newIdx(map[string]map[string]interface{}{})
jl := []JournalEntry{}
promoted := PromoteCandidates([]map[string]interface{}{
{"fact": "weak fact one", "target": "human", "source": "s1", "strength": 1},
}, idx, &jl, nil, nil)
cands := EnsureCandidates(idx)
ck := CandidateKey("weak fact one")
entry := cands[ck]
e := c["expected"].(map[string]interface{})
eqB2(t, name, "promoted_empty", len(promoted) == 0, e["promoted"] == nil || len(e["promoted"].([]interface{})) == 0)
eqB2(t, name, "count", entry["count"].(int), 1)
eqB2(t, name, "target", entry["target"], e["target"])
case "promote_third_sighting":
idx := newIdx(map[string]map[string]interface{}{})
jl := []JournalEntry{}
var promoted []JournalEntry
for i := 0; i < 3; i++ {
promoted = PromoteCandidates([]map[string]interface{}{
{"fact": "recurring weak fact", "target": "human", "source": "s" + string(rune('0'+i)), "strength": 1},
}, idx, &jl, nil, nil)
}
final := EnsureCandidates(idx)
e := c["expected"].(map[string]interface{})
eqB2(t, name, "op", promoted[0].Op, "ADD")
eqB2(t, name, "source", promoted[0].Source, e["source"])
eqB2(t, name, "removed", final[CandidateKey("recurring weak fact")] == nil, e["removed_from_ledger"])
eqB2(t, name, "journaled", len(jl) > 0, true)
case "explicit_ops_skip":
idx := newIdx(map[string]map[string]interface{}{})
jl := []JournalEntry{}
promoted := PromoteCandidates([]map[string]interface{}{
{"op": "UPDATE", "fact": "x", "target": "human", "before": "a", "after": "b"},
}, idx, &jl, nil, nil)
e := c["expected"].(map[string]interface{})
eqB2(t, name, "promoted_empty", len(promoted) == 0, len(e["promoted"].([]interface{})) == 0)
eqB2(t, name, "ledger_empty", len(EnsureCandidates(idx)) == 0, e["ledger_empty"])
case "strong_skips_ledger":
idx := newIdx(map[string]map[string]interface{}{})
jl := []JournalEntry{}
promoted := PromoteCandidates([]map[string]interface{}{
{"fact": "strong fact", "target": "human", "strength": 3},
}, idx, &jl, nil, nil)
e := c["expected"].(map[string]interface{})
eqB2(t, name, "promoted_empty", len(promoted) == 0, len(e["promoted"].([]interface{})) == 0)
eqB2(t, name, "ledger_empty", len(EnsureCandidates(idx)) == 0, e["ledger_empty"])
case "decay_stale":
idx := newIdx(map[string]map[string]interface{}{
CandidateKey("old fact"): {"text": "old fact", "target": "human", "count": 2,
"last_seen": "2026-08-01T00:00:00+00:00", "first_seen": "2026-08-01T00:00:00+00:00",
"sources": []string{"s"}},
})
jl := []JournalEntry{}
changed := DecayCandidates(idx, &jl)
e := c["expected"].(map[string]interface{})
eqB2(t, name, "changed", changed, e["changed"])
eqB2(t, name, "op", jl[0].Op, e["op"])
eqB2(t, name, "ledger_empty", len(EnsureCandidates(idx)) == 0, e["ledger_empty"])
case "decay_fresh_kept":
idx := newIdx(map[string]map[string]interface{}{
CandidateKey("new fact"): {"text": "new fact", "target": "human", "count": 1,
"last_seen": "2026-10-07T00:00:00+00:00", "first_seen": "2026-10-07T00:00:00+00:00",
"sources": []string{"s"}},
})
jl := []JournalEntry{}
changed := DecayCandidates(idx, &jl)
e := c["expected"].(map[string]interface{})
eqB2(t, name, "changed", changed, e["changed"])
eqB2(t, name, "journaled", len(jl) == 0, e["journaled"].(float64) == 0)
eqB2(t, name, "still_present", EnsureCandidates(idx)[CandidateKey("new fact")] != nil, e["still_present"])
default:
t.Errorf("unknown batch-2 fn %q", c["fn"])
}
}
}
// TestJournalAppendFormat — byte-format parity with journal_append.
func TestJournalAppendFormat(t *testing.T) {
dir := t.TempDir()
path, err := JournalAppend(dir, JournalEntry{
Op: "ADD", Target: "operating", After: "the probe fact", Source: "test",
})
if err != nil {
t.Fatalf("append: %v", err)
}
b, _ := os.ReadFile(path)
s := string(b)
if want := "**ADD** → `operating`"; !contains(s, want) {
t.Errorf("journal format: missing %q in %q", want, s)
}
if want := " - source: test"; !contains(s, want) {
t.Errorf("journal format: missing %q", want)
}
if want := " - after: the probe fact"; !contains(s, want) {
t.Errorf("journal format: missing %q", want)
}
}
func contains(s, sub string) bool {
return len(s) >= len(sub) && (func() bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
})()
}
-57
View File
@@ -1,57 +0,0 @@
[
{
"fn": "grounded_sessions",
"name": "g_hit",
"input": {
"fact": "spine rebuild parity",
"sessions": [
"spine rebuild verified structural parity",
"spine parity held across the rebuild",
"unrelated session about railway timetables and tea"
]
},
"expected": {
"grounded": true
}
},
{
"fn": "grounded_sessions",
"name": "g_miss_rarest_absent",
"input": {
"fact": "the spine rebuild preserved structural identity",
"sessions": [
"spine rebuild verified structural parity",
"spine parity held across the rebuild",
"unrelated session about railway timetables and tea"
]
},
"expected": {
"grounded": false
}
},
{
"fn": "grounded_sessions",
"name": "g_miss_invention",
"input": {
"fact": "railway timetable scheduling optimization",
"sessions": [
"spine rebuild verified structural parity",
"spine parity held across the rebuild",
"unrelated session about railway timetables and tea"
]
},
"expected": {
"grounded": false
}
},
{
"fn": "coherence_short_circuit",
"name": "short",
"input": {
"fact": "too short"
},
"expected": {
"result": true
}
}
]
-78
View File
@@ -1,78 +0,0 @@
package main
// truth_batch3_test.go — batch-3 parity gate: the distinctive-word grounding
// algorithm against the live Python oracle (truth_batch3.json, verified
// 2026-10-07 — including the subtle miss cases the oracle taught us).
import (
"encoding/json"
"os"
"testing"
)
func TestBatch3Parity(t *testing.T) {
b, err := os.ReadFile("truth_batch3.json")
if err != nil {
t.Fatalf("truth_batch3.json missing: %v", err)
}
var cases []struct {
Fn string `json:"fn"`
Name string `json:"name"`
Input struct {
Fact string `json:"fact"`
Sessions []string `json:"sessions"`
} `json:"input"`
Expected map[string]interface{} `json:"expected"`
}
if err := json.Unmarshal(b, &cases); err != nil {
t.Fatalf("unparseable: %v", err)
}
for _, c := range cases {
switch c.Fn {
case "grounded_sessions":
got := GroundedInSessions(c.Input.Fact, c.Input.Sessions)
want := c.Expected["grounded"].(bool)
if got != want {
t.Errorf("%s: GroundedInSessions(%q) = %v, want %v", c.Name, c.Input.Fact, got, want)
}
case "coherence_short_circuit":
got := CoherenceCheck(c.Input.Fact, []string{"sessions"}, nil, nil)
want := c.Expected["result"].(bool)
if got != want {
t.Errorf("%s: CoherenceCheck short-circuit = %v, want %v", c.Name, got, want)
}
default:
t.Errorf("unknown batch-3 fn %q", c.Fn)
}
}
}
// TestDistinctiveWordSemantics — the algorithm's documented edge behaviors,
// each verified against the Python oracle during batch-3 development.
func TestDistinctiveWordSemantics(t *testing.T) {
s1 := "spine rebuild verified structural parity"
s2 := "spine parity held across the rebuild"
s3 := "unrelated session about railway timetables and tea"
// Hit: every significant word recurs; rarest in 2 distinct sessions.
if !GroundedInSessions("spine rebuild parity", []string{s1, s2, s3}) {
t.Error("recurring-word fact must ground")
}
// Miss: 'preserved' is rarest and absent from the corpus entirely.
if GroundedInSessions("the spine rebuild preserved structural identity", []string{s1, s2, s3}) {
t.Error("absent rarest word must NOT ground")
}
// Miss: unrelated invention.
if GroundedInSessions("railway timetable scheduling optimization", []string{s1, s2, s3}) {
t.Error("invention must NOT ground")
}
// Miss: rarest word appears in only ONE session (independence rule).
solo := []string{"spine is discussed here in rich detail", "totally different content about tea"}
if GroundedInSessions("spine alignment assessment", solo) {
t.Error("single-session rarest word must NOT ground (independence)")
}
// Empty sessions never ground.
if GroundedInSessions("spine rebuild parity", nil) {
t.Error("no sessions = not grounded")
}
}
-56
View File
@@ -1,56 +0,0 @@
[
{
"fn": "decay_fresh",
"name": "df",
"expected": {
"importance": 0.7,
"journaled": 0
}
},
{
"fn": "decay_stale",
"name": "ds",
"expected": {
"importance": 0.49999999999999994,
"journaled": 0
}
},
{
"fn": "decay_forget",
"name": "dg",
"expected": {
"removed": true,
"op": "FORGET"
}
},
{
"fn": "decay_constitution_skip",
"name": "dc",
"expected": {
"importance": 0.1,
"journaled": 0
}
},
{
"fn": "overlap",
"name": "ov1",
"input": {
"a": "spine rebuild verified parity",
"b": "spine parity verified today"
},
"expected": {
"ov": 0.6
}
},
{
"fn": "overlap",
"name": "ov_disjoint",
"input": {
"a": "railway timetable",
"b": "spine parity"
},
"expected": {
"ov": 0.0
}
}
]
-137
View File
@@ -1,137 +0,0 @@
package main
// truth_batch4_test.go — batch-4 parity gate: decay semantics and token
// overlap against the live Python oracle (truth_batch4.json, verified
// 2026-10-07).
import (
"encoding/json"
"math"
"os"
"testing"
)
func TestBatch4Parity(t *testing.T) {
b, err := os.ReadFile("truth_batch4.json")
if err != nil {
t.Fatalf("truth_batch4.json missing: %v", err)
}
var cases []struct {
Fn string `json:"fn"`
Name string `json:"name"`
Input map[string]interface{} `json:"input"`
Expected map[string]interface{} `json:"expected"`
}
if err := json.Unmarshal(b, &cases); err != nil {
t.Fatalf("unparseable: %v", err)
}
for _, c := range cases {
switch c.Fn {
case "decay_fresh", "decay_stale", "decay_forget", "decay_constitution_skip":
verifyDecayCase(t, c.Name, c.Fn, c.Expected)
case "overlap":
a, _ := c.Input["a"].(string)
bb, _ := c.Input["b"].(string)
got := Overlap(a, bb)
want := c.Expected["ov"].(float64)
if math.Abs(got-want) > 1e-9 {
t.Errorf("%s: Overlap = %f, want %f", c.Name, got, want)
}
}
}
}
func verifyDecayCase(t *testing.T, name, fn string, e map[string]interface{}) {
t.Helper()
now := timeNowUTC()
mkMeta := func(daysAgo int, imp float64) map[string]interface{} {
return map[string]interface{}{
"importance": imp,
"last_referenced": now.AddDate(0, 0, -daysAgo).Format("2006-01-02T15:04:05+00:00"),
}
}
switch name {
case "df": // fresh: untouched
idx := Index{"operating": map[string]map[string]interface{}{"k1": mkMeta(5, 0.7)}}
jl := []JournalEntry{}
DecayPass(idx, &jl)
got := idx["operating"].(map[string]map[string]interface{})["k1"]["importance"].(float64)
if got != 0.7 || len(jl) != 0 {
t.Errorf("%s: importance=%v journaled=%d, want 0.7/0", name, got, len(jl))
}
case "ds": // stale: DECAY_RATE off
idx := Index{"operating": map[string]map[string]interface{}{"k1": mkMeta(100, 0.7)}}
jl := []JournalEntry{}
DecayPass(idx, &jl)
got := idx["operating"].(map[string]map[string]interface{})["k1"]["importance"].(float64)
if math.Abs(got-0.5) > 1e-9 || len(jl) != 0 {
t.Errorf("%s: importance=%v journaled=%d, want ~0.5/0", name, got, len(jl))
}
case "dg": // below floor: FORGET + removed
idx := Index{"operating": map[string]map[string]interface{}{"k1": mkMeta(100, 0.3)}}
jl := []JournalEntry{}
DecayPass(idx, &jl)
_, still := idx["operating"].(map[string]map[string]interface{})["k1"]
removed := !still
if removed != true || len(jl) == 0 || jl[0].Op != "FORGET" {
t.Errorf("%s: removed=%v journaled=%d, want true/FORGET", name, removed, len(jl))
}
case "dc": // constitution: never decays
idx := Index{"constitution": map[string]map[string]interface{}{"k1": mkMeta(200, 0.1)}}
jl := []JournalEntry{}
DecayPass(idx, &jl)
got := idx["constitution"].(map[string]map[string]interface{})["k1"]["importance"].(float64)
if got != 0.1 || len(jl) != 0 {
t.Errorf("%s: constitution decayed! importance=%v", name, got)
}
}
}
// TestClusterSemantics — greedy clustering + representative summary parity.
func TestClusterSemantics(t *testing.T) {
entries := []string{
"- the spine rebuild verified structural parity",
"- spine parity held across the rebuild",
"- unrelated note about tea",
}
clusters := ClusterLines(entries)
// ORACLE-VERIFIED: ov(line0,line1)=0.429 > 0.35 → merge; the third
// spine-ish line was moved out entirely — pairwise overlaps vs the
// growing cluster were 0.333 < 0.35, so a 3-line cluster never forms
// with these inputs (the greedy check is against CLUSTER MEMBERS, and
// the threshold is strict >). line2 ("tea") is alone.
merged := 0
var longest []string
for _, c := range clusters {
if len(c) >= 2 {
merged++
longest = c
}
}
if merged != 1 || len(longest) != 2 {
t.Errorf("clusters: got %d merged (want 1), members %v", merged, clusters)
}
// Representative = longest member = the 46-char line0.
rep := longest[0]
for _, m := range longest {
if len(m) > len(rep) {
rep = m
}
}
if rep != "- the spine rebuild verified structural parity" {
t.Errorf("representative: got %q", rep)
}
summary := ClusterSummary(longest)
if want := "also:"; !containsStr(summary, want) {
t.Errorf("summary missing %q: %q", want, summary)
}
}
func containsStr(s, sub string) bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}
-125
View File
@@ -1,125 +0,0 @@
package main
// truth_test.go — the parity gate: replays truth.json (Python-oracle cases
// with inputs) against the ported functions. Divergence fails the port.
import (
"encoding/json"
"os"
"strings"
"testing"
)
type truthCase struct {
Fn string `json:"fn"`
Name string `json:"name"`
Input map[string]interface{} `json:"input"`
Expected map[string]interface{} `json:"expected"`
}
func loadTruth(t *testing.T) []truthCase {
t.Helper()
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 str(m map[string]interface{}, k string) string {
s, _ := m[k].(string)
return s
}
func num(m map[string]interface{}, k string) int {
f, _ := m[k].(float64)
return int(f)
}
func jsonEq(t *testing.T, name, field string, got, want interface{}) {
t.Helper()
gs, _ := json.Marshal(got)
ws, _ := json.Marshal(want)
if string(gs) != string(ws) {
t.Errorf("%s.%s: got %s want %s", name, field, gs, ws)
}
}
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 {
switch c.Fn {
case "block_path":
jsonEq(t, c.Name, "path", BlockPath(str(c.Input, "label")), c.Expected["path"])
case "entry_key":
jsonEq(t, c.Name, "key", EntryKey(str(c.Input, "text")), c.Expected["key"])
case "candidate_key":
jsonEq(t, c.Name, "key", CandidateKey(str(c.Input, "text")), c.Expected["key"])
case "find_entry":
got := FindEntry(str(c.Input, "body"), str(c.Input, "needle"))
want := str(c.Expected, "line")
jsonEq(t, c.Name, "line", got, want) // "" marshals vs null — normalize:
if got == "" && want == "null" || want == "" {
continue
}
case "sanitize_slug":
jsonEq(t, c.Name, "slug", SanitizeSlug(str(c.Input, "name")), c.Expected["slug"])
case "detect_conflict":
kind, line := DetectConflict(str(c.Input, "body"), str(c.Input, "fact"))
wantKind, _ := c.Expected["conflict"].([]interface{})
if len(wantKind) == 2 {
wk, _ := wantKind[0].(string)
wl, _ := wantKind[1].(string)
jsonEq(t, c.Name, "conflict.kind", kind, wk)
jsonEq(t, c.Name, "conflict.line", line, wl)
}
case "le_promote_eligible":
i := c.Input
got := LePromoteEligible(str(i, "proposal"), str(i, "existing"),
num(i, "strength"), 3, num(i, "wash_count"))
want, _ := c.Expected["eligible"].(map[string]interface{})
jsonEq(t, c.Name, "eligible", got["eligible"], want["eligible"])
jsonEq(t, c.Name, "reason", got["reason"], want["reason"])
default:
t.Errorf("unknown truth fn %q", c.Fn)
}
}
}
// TestNormParity — _norm's markdown semantics, spot-checked against the
// Python oracle's documented behavior.
func TestNormParity(t *testing.T) {
cases := map[string]string{
"- **bold** entry": "bold entry",
"### Header text": "header text",
" spaced out ": "spaced out",
"punctuation, gone!": "punctuation gone",
"> quoted line": "quoted line",
}
for in, want := range cases {
if got := Norm(in); got != want {
t.Errorf("Norm(%q): got %q want %q", in, got, want)
}
}
}
// TestEntryKeyVsCandidateKey — raw vs normalized hashing differ.
func TestEntryKeyVsCandidateKey(t *testing.T) {
raw := " padded text "
if EntryKey(raw) == CandidateKey(raw) {
t.Error("entry_key (raw) and candidate_key (normalized) must differ on padded text")
}
if !strings.Contains(SanitizeSlug("!!!"), "") {
// SanitizeSlug("!!!") → "procedure" fallback
}
if SanitizeSlug("!!!") != "procedure" {
t.Errorf("SanitizeSlug fallback: got %q want procedure", SanitizeSlug("!!!"))
}
}
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
module rune/curator
go 1.21
-217
View File
@@ -1,217 +0,0 @@
// 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
@@ -1,3 +0,0 @@
module rune/elastic-cognition
go 1.21
-257
View File
@@ -1,257 +0,0 @@
// 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)
}
}
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
module rune/event-bus
go 1.21
-328
View File
@@ -1,328 +0,0 @@
// 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,
}
}
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
module rune/failure-filer
go 1.21
-182
View File
@@ -1,182 +0,0 @@
// 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
@@ -1,3 +0,0 @@
module rune/hook-engine
go 1.21
Binary file not shown.
-200
View File
@@ -1,200 +0,0 @@
// 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
@@ -1,3 +0,0 @@
module rune/local-tool-runtime
go 1.21
Binary file not shown.
-256
View File
@@ -1,256 +0,0 @@
// 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
@@ -1,26 +0,0 @@
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
@@ -1,73 +0,0 @@
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
@@ -1,188 +0,0 @@
// 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)
}
}
Binary file not shown.
-26
View File
@@ -1,26 +0,0 @@
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
@@ -1,73 +0,0 @@
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=
-712
View File
@@ -1,712 +0,0 @@
// 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"
"crypto/sha256"
"encoding/hex"
"math"
_ "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)`)
// Batch-1 columns (parity with src/rune/memory_store.py)
db.Exec(`ALTER TABLE entries ADD COLUMN promotion_eligible INTEGER DEFAULT 0`)
db.Exec(`ALTER TABLE entries ADD COLUMN last_washed TEXT`)
db.Exec(`CREATE TABLE IF NOT EXISTS working (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
text TEXT NOT NULL,
created_at TEXT
)`)
db.Exec(`CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prev_hash TEXT NOT NULL,
hash TEXT NOT NULL,
claim TEXT NOT NULL,
verdict TEXT NOT NULL,
evidence TEXT DEFAULT '',
created_at TEXT
)`)
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 ──
// ── Helpers ──
// ---- Batch 1: wash lifecycle, CRUD, working set, audit chain, health ----
// PARITY ports of src/rune/memory_store.py (function names preserved in
// camelCase; the committed memory-store truth battery replays them).
const washPromoteThreshold = 20 // WASH_PROMOTE_THRESHOLD
const recencyHalfLifeDays = 30.0
func incrementWash(db *sql.DB, entryID int64) Result {
ts := nowISO()
db.Exec("UPDATE entries SET wash_count = wash_count + 1, last_washed = ? WHERE id = ?", ts, entryID)
var washCount int
var rockStatus string
err := db.QueryRow("SELECT wash_count, rock_status FROM entries WHERE id = ?", entryID).Scan(&washCount, &rockStatus)
if err == nil && washCount >= washPromoteThreshold && rockStatus == "washable" {
db.Exec("UPDATE entries SET promotion_eligible = 1, rock_status = 'established' WHERE id = ?", entryID)
}
db.Exec("COMMIT")
return Result{Success: true}
}
func getWashScore(db *sql.DB, entryID int64) Result {
var washCount int
var imp float64
var lastAccessed, createdAt sql.NullString
err := db.QueryRow("SELECT wash_count, importance, last_accessed, created_at FROM entries WHERE id = ?", entryID).
Scan(&washCount, &imp, &lastAccessed, &createdAt)
if err != nil {
return Result{Success: true, Data: 0.0}
}
if washCount < 0 {
washCount = 0
}
if imp == 0 {
imp = 0.5 // Python: (importance or 0.5) — only NULL/0-DEFAULT applies
}
recency := 1.0
if lastAccessed.Valid && lastAccessed.String != "" {
if last, perr := time.Parse("2006-01-02T15:04:05.000000+00:00", lastAccessed.String); perr == nil {
hoursSince := time.Since(last).Hours()
if hoursSince < 0.1 {
hoursSince = 0.1
}
recency = 1.0 / (1.0 + hoursSince/(recencyHalfLifeDays*24))
if recency < 0.1 {
recency = 0.1
}
} else {
recency = 0.5
}
}
washFactor := math.Min(1.0, math.Log1p(float64(washCount))/math.Log1p(float64(washPromoteThreshold)))
return Result{Success: true, Data: washFactor * imp * recency}
}
func promoteToRock(db *sql.DB, entryID int64) Result {
db.Exec("UPDATE entries SET rock_status = 'rock', promotion_eligible = 0 WHERE id = ?", entryID)
db.Exec("COMMIT")
return Result{Success: true}
}
func getRockEntries(db *sql.DB) Result {
rows, err := db.Query("SELECT id, text, importance, wash_count, rock_status FROM entries WHERE rock_status = 'rock' AND status = 'active'")
if err != nil {
return Result{Success: false, Error: err.Error()}
}
defer rows.Close()
return Result{Success: true, Data: rowsToMaps(rows)}
}
func getWashableEntries(db *sql.DB, limit int) Result {
rows, err := db.Query("SELECT id, text, importance, wash_count, rock_status FROM entries WHERE rock_status = 'washable' AND status = 'active' ORDER BY wash_count ASC LIMIT ?", limit)
if err != nil {
return Result{Success: false, Error: err.Error()}
}
defer rows.Close()
return Result{Success: true, Data: rowsToMaps(rows)}
}
func updateEntry(db *sql.DB, args map[string]interface{}) Result {
eid := getInt(args, "id", 0)
var sets []string
var params []interface{}
if text, ok := args["text"]; ok {
sets = append(sets, "text=?")
params = append(params, text)
}
if _, ok := args["importance"]; ok {
sets = append(sets, "importance=?")
params = append(params, getFloat(args, "importance", 0.5))
}
if vu, ok := args["valid_until"]; ok {
sets = append(sets, "valid_until=?")
params = append(params, vu)
}
if len(sets) == 0 {
return Result{Success: true, Data: false}
}
sets = append(sets, "last_accessed=?")
params = append(params, nowISO())
params = append(params, eid)
db.Exec("UPDATE entries SET "+strings.Join(sets, ", ")+" WHERE id=?", params...)
db.Exec("COMMIT")
return Result{Success: true, Data: true}
}
func deleteEntry(db *sql.DB, args map[string]interface{}) Result {
eid := getInt(args, "id", 0)
db.Exec("DELETE FROM entries WHERE id=?", eid)
db.Exec("DELETE FROM entries_vec WHERE rowid=?", eid)
db.Exec("COMMIT")
return Result{Success: true, Data: true}
}
func touchEntry(db *sql.DB, args map[string]interface{}) Result {
db.Exec("UPDATE entries SET last_accessed=? WHERE id=?", nowISO(), getInt(args, "id", 0))
db.Exec("COMMIT")
return Result{Success: true}
}
func workingSet(db *sql.DB, args map[string]interface{}) Result {
taskID := getString(args, "task_id")
text := getString(args, "text")
db.Exec("DELETE FROM working WHERE task_id=?", taskID)
res, err := db.Exec("INSERT INTO working (task_id, text, created_at) VALUES (?,?,?)", taskID, text, nowISO())
if err != nil {
return Result{Success: false, Error: err.Error()}
}
id, _ := res.LastInsertId()
db.Exec("COMMIT")
return Result{Success: true, Data: id}
}
func workingGet(db *sql.DB, args map[string]interface{}) Result {
rows, err := db.Query("SELECT text FROM working WHERE task_id=? ORDER BY id DESC LIMIT 5", getString(args, "task_id"))
if err != nil {
return Result{Success: false, Error: err.Error()}
}
defer rows.Close()
var texts []string
for rows.Next() {
var t string
rows.Scan(&t)
texts = append(texts, t)
}
if texts == nil {
texts = []string{}
}
return Result{Success: true, Data: texts}
}
func workingClear(db *sql.DB, args map[string]interface{}) Result {
db.Exec("DELETE FROM working WHERE task_id=?", getString(args, "task_id"))
db.Exec("COMMIT")
return Result{Success: true}
}
// auditAppend — hash-chained provenance ledger. PARITY-CRITICAL: the hash
// is sha256(f"{prev}|{claim}|{verdict}|{evidence}") truncated to 24 hex —
// byte-identical with the Python oracle.
func auditAppend(db *sql.DB, args map[string]interface{}) Result {
claim := getString(args, "claim")
verdict := getStringDefault(args, "verdict", "")
evidence := getStringDefault(args, "evidence", "")
var prev string
var lastHash sql.NullString
err := db.QueryRow("SELECT hash FROM audit_log ORDER BY id DESC LIMIT 1").Scan(&lastHash)
prev = "GENESIS"
if err == nil && lastHash.Valid {
prev = lastHash.String
}
payload := fmt.Sprintf("%s|%s|%s|%s", prev, claim, verdict, evidence)
sum := sha256.Sum256([]byte(payload))
h := hex.EncodeToString(sum[:])[:24]
db.Exec("INSERT INTO audit_log (prev_hash, hash, claim, verdict, evidence, created_at) VALUES (?,?,?,?,?,?)",
prev, h, claim, verdict, evidence, nowISO())
db.Exec("COMMIT")
return Result{Success: true, Data: h}
}
func auditVerify(db *sql.DB) Result {
rows, err := db.Query("SELECT id, prev_hash, hash, claim, verdict, evidence FROM audit_log ORDER BY id")
if err != nil {
return Result{Success: false, Error: err.Error()}
}
defer rows.Close()
prev := "GENESIS"
n := 0
for rows.Next() {
var id int64
var prevHash, h, claim, verdict string
var evidence sql.NullString
rows.Scan(&id, &prevHash, &h, &claim, &verdict, &evidence)
if prevHash != prev {
return Result{Success: true, Data: map[string]interface{}{"ok": false, "message": fmt.Sprintf("chain break at id %d", id)}}
}
payload := fmt.Sprintf("%s|%s|%s|%s", prev, claim, verdict, evidence.String)
sum := sha256.Sum256([]byte(payload))
recomputed := hex.EncodeToString(sum[:])[:24]
if recomputed != h {
return Result{Success: true, Data: map[string]interface{}{"ok": false, "message": fmt.Sprintf("hash mismatch at id %d (tampered)", id)}}
}
prev = h
n++
}
return Result{Success: true, Data: map[string]interface{}{"ok": true, "message": fmt.Sprintf("chain intact (%d entries)", n)}}
}
func getStats(db *sql.DB) Result {
var e, w, a int
db.QueryRow("SELECT COUNT(*) FROM entries WHERE status='active'").Scan(&e)
db.QueryRow("SELECT COUNT(*) FROM working").Scan(&w)
db.QueryRow("SELECT COUNT(*) FROM audit_log").Scan(&a)
rows, err := db.Query("SELECT topic, COUNT(*) AS n FROM entries WHERE status='active' GROUP BY topic ORDER BY n DESC LIMIT 6")
topics := map[string]int{}
if err == nil {
for rows.Next() {
var topic sql.NullString
var n int
rows.Scan(&topic, &n)
if topic.Valid && topic.String != "" {
topics[topic.String] = n
}
}
rows.Close()
}
return Result{Success: true, Data: map[string]interface{}{
"active_entries": e, "working": w, "audit": a, "top_topics": topics}}
}
func memoryHealthCheck(db *sql.DB, days int) Result {
cutoff := time.Now().UTC().AddDate(0, 0, -days).Format("2006-01-02T15:04:05.000000+00:00")
var total, stale, never int
db.QueryRow("SELECT COUNT(*) FROM entries WHERE status='active'").Scan(&total)
db.QueryRow("SELECT COUNT(*) FROM entries WHERE status='active' AND (last_accessed IS NULL OR last_accessed < ?)", cutoff).Scan(&stale)
db.QueryRow("SELECT COUNT(*) FROM entries WHERE status='active' AND (wash_count = 0 OR wash_count IS NULL)").Scan(&never)
rows, _ := db.Query("SELECT rock_status, COUNT(*) FROM entries WHERE status='active' GROUP BY rock_status")
trust := map[string]int{}
if rows != nil {
for rows.Next() {
var s sql.NullString
var c int
rows.Scan(&s, &c)
if s.Valid {
trust[s.String] = c
}
}
rows.Close()
}
stalePct, neverPct, health := 0.0, 0.0, 0.0
if total > 0 {
stalePct = float64(stale) / float64(total) * 100
neverPct = float64(never) / float64(total) * 100
health = 100 - (float64(stale)/float64(total))*50 - (float64(never)/float64(total))*50
if health < 0 {
health = 0
}
}
return Result{Success: true, Data: map[string]interface{}{
"total_entries": total, "stale_entries": stale, "stale_pct": stalePct,
"never_accessed": never, "never_accessed_pct": neverPct,
"trust_distribution": trust, "health_score": health}}
}
func rowsToMaps(rows *sql.Rows) []map[string]interface{} {
cols, _ := rows.Columns()
out := []map[string]interface{}{}
vals := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
for rows.Next() {
rows.Scan(ptrs...)
m := map[string]interface{}{}
for i, c := range cols {
v := vals[i]
if b, ok := v.([]byte); ok {
v = string(b)
}
m[c] = v
}
out = append(out, m)
}
return out
}
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 "increment-wash":
result = incrementWash(db, int64(getInt(args, "id", 0)))
case "wash-score":
result = getWashScore(db, int64(getInt(args, "id", 0)))
case "promote-rock":
result = promoteToRock(db, int64(getInt(args, "id", 0)))
case "rock-entries":
result = getRockEntries(db)
case "washable-entries":
result = getWashableEntries(db, getInt(args, "limit", 100))
case "update":
result = updateEntry(db, args)
case "delete":
result = deleteEntry(db, args)
case "touch":
result = touchEntry(db, args)
case "working-set":
result = workingSet(db, args)
case "working-get":
result = workingGet(db, args)
case "working-clear":
result = workingClear(db, args)
case "audit-append":
result = auditAppend(db, args)
case "audit-verify":
result = auditVerify(db)
case "health":
result = memoryHealthCheck(db, getInt(args, "days", 7))
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.
-130
View File
@@ -1,130 +0,0 @@
package main
// truth_test.go — parity tests against the Python oracle
// (src/rune/memory_store.py). Hash formats and wash-score formulas are
// deterministic; the expected values below are computed BY PYTHON
// (hashlib.sha256 of the documented payload format) and committed here.
// A divergence fails the port, not the oracle.
import (
"database/sql"
"os"
"path/filepath"
"testing"
)
func testDB(t *testing.T) *sql.DB {
t.Helper()
dir, _ := os.MkdirTemp("", "msd-parity")
t.Cleanup(func() { os.RemoveAll(dir) })
db, err := sql.Open("sqlite", filepath.Join(dir, "test.db"))
if err != nil {
t.Fatalf("open: %v", err)
}
if err := migrate(db); err != nil {
t.Fatalf("migrate: %v", err)
}
return db
}
func migrate(db *sql.DB) error {
if _, err := 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, last_washed 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', promotion_eligible INTEGER DEFAULT 0)`); err != nil {
return err
}
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS working (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL, text TEXT NOT NULL, created_at TEXT)`); err != nil {
return err
}
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prev_hash TEXT NOT NULL, hash TEXT NOT NULL, claim TEXT NOT NULL,
verdict TEXT NOT NULL, evidence TEXT DEFAULT '', created_at TEXT)`); err != nil {
return err
}
return nil
}
// TestAuditChainParity — the hash format is
// sha256(f"{prev}|{claim}|{verdict}|{evidence}")[:24]. Expected values
// computed by Python hashlib against identical payloads.
func TestAuditChainParity(t *testing.T) {
db := testDB(t)
r1 := auditAppend(db, map[string]interface{}{"claim": "probe claim", "verdict": "PROVEN", "evidence": "ev1"})
h1, _ := r1.Data.(string)
want1 := "17811d68d9ba3f2282084725" // PYTHON: sha256("GENESIS|probe claim|PROVEN|ev1")[:24]
if h1 != want1 {
t.Errorf("audit hash 1: got %s want %s", h1, want1)
}
r2 := auditAppend(db, map[string]interface{}{"claim": "second", "verdict": "FAILED", "evidence": "ev2"})
h2, _ := r2.Data.(string)
want2 := "619d12d1fbff58d015196b44" // PYTHON: sha256("17811d68d9ba3f2282084725|second|FAILED|ev2")[:24]
if h2 != want2 {
t.Errorf("audit hash 2: got %s want %s", h2, want2)
}
v := auditVerify(db)
data := v.Data.(map[string]interface{})
if data["ok"] != true {
t.Errorf("auditVerify: expected intact, got %v", data)
}
}
// TestWashLifecycleParity — increment/promote/established per Python
// increment_wash (WASH_PROMOTE_THRESHOLD=20), and the wash-score formula
// wash_factor * importance * recency with NULL last_accessed (recency=1).
func TestWashLifecycleParity(t *testing.T) {
db := testDB(t)
res, _ := db.Exec(`INSERT INTO entries (text, importance, status) VALUES ('lifecycle probe', 0.8, 'active')`)
id, _ := res.LastInsertId()
for i := 0; i < 20; i++ {
incrementWash(db, id)
}
var rockStatus string
var promo int
db.QueryRow("SELECT rock_status, promotion_eligible FROM entries WHERE id=?", id).Scan(&rockStatus, &promo)
if rockStatus != "established" || promo != 1 {
t.Errorf("threshold promotion: got rock_status=%s promo=%d, want established/1", rockStatus, promo)
}
// wash score with wash_count=20, NULL last_accessed (recency 1.0):
// PYTHON: min(1, log1p(20)/log1p(20)) * 0.8 * 1.0 = 0.8
score := getWashScore(db, id)
s, _ := score.Data.(float64)
if s < 0.79 || s > 0.81 {
t.Errorf("wash score: got %f want ~0.8", s)
}
promoteToRock(db, id)
db.QueryRow("SELECT rock_status FROM entries WHERE id=?", id).Scan(&rockStatus)
if rockStatus != "rock" {
t.Errorf("promoteToRock: got %s want rock", rockStatus)
}
rocks := getRockEntries(db)
if len(rocks.Data.([]map[string]interface{})) != 1 {
t.Errorf("getRockEntries: expected 1, got %v", rocks.Data)
}
}
// TestWorkingSetParity — working_set replaces prior entries for the task;
// working_get returns up to 5 texts newest-first.
func TestWorkingSetParity(t *testing.T) {
db := testDB(t)
workingSet(db, map[string]interface{}{"task_id": "T-9", "text": "first"})
workingSet(db, map[string]interface{}{"task_id": "T-9", "text": "second"})
got := workingGet(db, map[string]interface{}{"task_id": "T-9"})
texts := got.Data.([]string)
if len(texts) != 1 || texts[0] != "second" {
t.Errorf("working parity: got %v want [second] (working_set REPLACES per Python)", texts)
}
}
-3
View File
@@ -1,3 +0,0 @@
module rune/message-queue
go 1.21
-210
View File
@@ -1,210 +0,0 @@
// 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)
}
}
Binary file not shown.
Loaded 100 of 436 files, more files were not shown because too many files have changed in this diff. Show more