Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3497d2b7a9 | ||
|
|
60a21dc7da |
No files matched your search
@@ -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.
|
||||
+4
-16
@@ -81,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
|
||||
@@ -136,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,
|
||||
@@ -165,16 +163,10 @@ jobs:
|
||||
[ "$GOT" = "$WANT" ] || { echo "::error::Go toolchain drift: want $WANT, got $GOT (update go-version.txt with the rollout)"; exit 1; }
|
||||
- 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
|
||||
go build -o /tmp/go-$(basename "$dir") .
|
||||
cd - > /dev/null
|
||||
fi
|
||||
done
|
||||
cd src/go/rune-entry
|
||||
go vet ./...
|
||||
go test ./...
|
||||
go build -o /tmp/rune-entry-ci .
|
||||
|
||||
canary:
|
||||
runs-on: rune-ci-desktop
|
||||
@@ -232,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
|
||||
@@ -271,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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.101.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.**
|
||||
|
||||
|
||||
+2
-51
@@ -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": [
|
||||
@@ -116,11 +111,6 @@
|
||||
"governance"
|
||||
],
|
||||
"description": "Who the agent is \u2014 persona, voice, anchors",
|
||||
"go_surfaces": [
|
||||
"persona-definition",
|
||||
"voicebox",
|
||||
"voice-injector"
|
||||
],
|
||||
"mirrors": [],
|
||||
"modules": [
|
||||
"behavior",
|
||||
@@ -172,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",
|
||||
@@ -230,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",
|
||||
@@ -365,4 +316,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
# FULL DICHOTOMY REBASE — HONEST AUDIT REPORT
|
||||
_Date: 2026-10-06. Nick's mandate: "replace the whole codebase with the dichotomy."_
|
||||
|
||||
---
|
||||
|
||||
## The Honest Numbers
|
||||
|
||||
| Category | Modules | Python Lines | Status |
|
||||
|---|---|---|---|
|
||||
| **FULL PORTS** | 27 | 5,978 | Implementation covers ≥40% of Python logic |
|
||||
| **PARTIAL PORTS** | 69 | 31,693 | Implementation exists but covers <40% |
|
||||
| **NOT TOUCHED** | 47 | 19,687 | No Go/Rust implementation exists |
|
||||
| **TEST INFRA** | 29 | 5,397 | Stays Python (pytest) |
|
||||
| **TUI** | 7 | 5,435 | Bubble Tea (Go) — not yet built |
|
||||
| **Total runtime** | **179** | **68,224** | |
|
||||
|
||||
**Honest dichotomy completion: 9%** (5,978 of 68,224 lines fully ported)
|
||||
**Work remaining: ~51,380 lines of Python logic to port**
|
||||
|
||||
---
|
||||
|
||||
## NOT TOUCHED — 47 modules, 19,687 lines
|
||||
|
||||
These modules have NO Go/Rust implementation at all. The Go/Rust directories don't even exist for them.
|
||||
|
||||
| Module | Lines | Functions | Side | What it does |
|
||||
|---|---|---|---|---|
|
||||
| rune_operator | 2,723 | 69 | GO | THE operator turn loop |
|
||||
| memory_store | 2,573 | 77 | GO | SQLite memory database |
|
||||
| warm_router | 1,536 | 36 | GO | Memory routing |
|
||||
| provider_registry | 666 | 28 | GO | Model provider registry |
|
||||
| adaptive_router | 625 | 19 | GO | Task-class routing |
|
||||
| safety_override | 569 | 23 | RUST | Safety override parameters |
|
||||
| event_wait | 517 | 14 | GO | Event-driven waits |
|
||||
| failure_auto_filer | 527 | 28 | GO | Auto-file failures |
|
||||
| identity_anchors | 537 | 16 | RUST | Identity anchors + drift check |
|
||||
| goal | 441 | 20 | GO | Goal management |
|
||||
| model_routing | 432 | 15 | GO | Model routing |
|
||||
| growth_delta | 437 | 19 | GO | Growth tracking |
|
||||
| release_notes | 439 | 17 | GO | Release generation |
|
||||
| memory_consolidation | 740 | 23 | GO | Memory consolidation |
|
||||
| model_profiles | 435 | 15 | GO | Model profiles |
|
||||
| provider_budget | 354 | 21 | GO | Token budget tracking |
|
||||
| builder_bridge | 326 | 11 | GO | Builder dispatch |
|
||||
| drift_compression | 307 | 12 | RUST | Compression ratios |
|
||||
| event_envelope | 303 | 8 | GO | Event envelope |
|
||||
| auto_tasks | 460 | 14 | GO | Auto task extraction |
|
||||
| build_mode | 517 | 34 | GO | Build orchestration |
|
||||
| standardized_providers | 281 | 5 | GO | Provider standardization |
|
||||
| voicebox | 161 | 11 | GO | Voice synthesis |
|
||||
| auto_context | 270 | 8 | GO | Auto context builder |
|
||||
| evidence_probe | 212 | 9 | GO | Evidence probing |
|
||||
| openai_compat_transport | 227 | 10 | GO | [OI] transport |
|
||||
| persona_definition | 210 | 8 | GO | Persona definition |
|
||||
| forge_client | 203 | 19 | GO | Forge API client |
|
||||
| tier_manager | 262 | 16 | RUST | Tier classification |
|
||||
| tiered_executor | 282 | 9 | GO | Tiered execution |
|
||||
| voice_injector | 138 | 5 | GO | Voice injection |
|
||||
| pins | 284 | 12 | RUST | Pin recall |
|
||||
| forge | 146 | 12 | GO | Forge adapter |
|
||||
| builder_bridge_cli | 42 | 1 | GO | CLI wrapper |
|
||||
| provider_context | 48 | 1 | GO | Provider context |
|
||||
| Various loaders (cc, cs, ms, sc, tc, mr, mems) | ~750 | ~80 | RUST | FFI bindings |
|
||||
| memory_env | 72 | 8 | RETIRES | Memory environment |
|
||||
| __main__ | 21 | 1 | RETIRES | Entry point |
|
||||
|
||||
---
|
||||
|
||||
## PARTIAL PORTS — 69 modules, 31,693 lines NOT covered
|
||||
|
||||
These modules have a Go/Rust directory with SOME code, but the implementation covers less than 40% of the Python logic. Many are under 10%.
|
||||
|
||||
| Module | Python Lines | Impl Lines | Coverage | Side |
|
||||
|---|---|---|---|---|
|
||||
| curator | 1,838 | 217 | 11% | GO |
|
||||
| tool_executor | 1,812 | 192 | 10% | GO |
|
||||
| construction_interface | 1,173 | 49 | 4% | RUST |
|
||||
| sleep_time | 971 | 107 | 11% | GO |
|
||||
| construction_decision | 1,014 | 55 | 5% | RUST |
|
||||
| hook_engine | 860 | 120 | 13% | GO |
|
||||
| scaffold | 868 | 55 | 6% | GO |
|
||||
| local_tool_runtime | 806 | 132 | 16% | GO |
|
||||
| elastic_cognition | 765 | 257 | 33% | GO |
|
||||
| abstraction | 647 | 91 | 14% | GO |
|
||||
| contracts | 626 | 55 | 8% | RUST |
|
||||
| work_manager | 591 | 156 | 26% | GO |
|
||||
| memory_selector | 511 | 7 | 1% | RUST |
|
||||
| construction_intent | 555 | 15 | 2% | RUST |
|
||||
| event_bus (partial) | — | — | — | GO |
|
||||
| ... (55 more) | ... | ... | ... | ... |
|
||||
|
||||
---
|
||||
|
||||
## FULL PORTS — 27 modules, 5,978 lines
|
||||
|
||||
These 27 modules have genuine Go/Rust implementations that cover ≥40% of the Python logic:
|
||||
|
||||
audit_ledger, connection_pool, construction_depth, context_broker, event_bus, failure_recovery, graph_projection, impact_preview, intent_continuity, message_queue, microkernel, model_connect, parallel_processor, programme_gates, recall_router, recall_uplift, response_adapter, route_risk_flags, salience_gate, stage_guards, structural_migration, subsystem_health, task_gate, verification_layer, wait_guard, warm_neuron_store, witness
|
||||
|
||||
---
|
||||
|
||||
## THE PATH FORWARD
|
||||
|
||||
The remaining work is **~51,380 lines** of Python logic that needs to be ported to Go or Rust with full parity. This is the real job.
|
||||
|
||||
### Priority order:
|
||||
1. **The operator (rune_operator, 2,723 lines)** — the turn loop that everything else routes through
|
||||
2. **Memory store (memory_store, 2,573 lines)** — the SQLite database operations
|
||||
3. **The GO-classified not-touched modules** (~12,000 lines) — warm_router, adaptive_router, provider_registry, etc.
|
||||
4. **The RUST-classified not-touched modules** (~7,000 lines) — safety_override, identity_anchors, pins, tier_manager, etc.
|
||||
5. **The partial ports that need deepening** (~31,000 lines) — curator, tool_executor, hook_engine, local_tool_runtime, etc.
|
||||
|
||||
### The standard:
|
||||
- **Parity first**: the Go/Rust implementation must produce byte-identical output for the same input
|
||||
- **Then improvements**: language-specific optimizations after parity is proven
|
||||
- **Testing document generated via Gitea**: CI runs integration tests and posts results to the epic
|
||||
- **CI stacking**: submit PRs continuously, batch CI fixes every 5
|
||||
|
||||
---
|
||||
|
||||
## WHY THE PREVIOUS ATTEMPT FAILED
|
||||
|
||||
The previous session built 27 Go binaries and 56 Rust crates in 100 commits. But the audit shows:
|
||||
- Only 27 modules have FULL implementations
|
||||
- 69 modules have PARTIAL implementations (mostly < 20% coverage)
|
||||
- 47 modules have NO implementation at all
|
||||
- Most of the "ports" were CLI wrappers that print JSON — they have the right shape but not the logic
|
||||
|
||||
**The lesson**: counting commits and files is not the same as porting code. A Go binary with 5 functions replacing a Python module with 50 functions is not a port — it's a facade.
|
||||
@@ -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)
|
||||
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.101.0"
|
||||
version = "0.99.5"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/abstraction
|
||||
|
||||
go 1.21
|
||||
@@ -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
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
-26
@@ -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
|
||||
)
|
||||
Vendored
-73
@@ -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=
|
||||
Vendored
-154
@@ -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
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module rune/cognition
|
||||
|
||||
go 1.21
|
||||
@@ -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) }
|
||||
@@ -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) }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -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) }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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.
@@ -1,3 +0,0 @@
|
||||
module rune/context-broker
|
||||
|
||||
go 1.21
|
||||
@@ -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=
|
||||
@@ -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.
@@ -1,3 +0,0 @@
|
||||
module rune/core-server
|
||||
|
||||
go 1.21
|
||||
@@ -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.
@@ -1,3 +0,0 @@
|
||||
module rune/cron-engine
|
||||
|
||||
go 1.21
|
||||
@@ -1,147 +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)
|
||||
data, _ := json.MarshalIndent(ce, "", " ")
|
||||
os.WriteFile(enginePath(), data, 0644)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: cron-engine <add|remove|list|run|enable|disable|fire-on-complete> [args...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
ce := loadEngine()
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
switch cmd {
|
||||
case "add":
|
||||
if len(args) < 3 {
|
||||
fmt.Fprintln(os.Stderr, "usage: cron-engine add <name> <cron-expr> <command>")
|
||||
os.Exit(1)
|
||||
}
|
||||
ce.mu.Lock()
|
||||
ce.Tasks[args[0]] = &CronTask{
|
||||
Name: args[0], CronExpression: args[1],
|
||||
Command: strings.Join(args[2:], " "), Enabled: true,
|
||||
}
|
||||
ce.mu.Unlock()
|
||||
saveEngine(ce)
|
||||
fmt.Println("added:", args[0])
|
||||
|
||||
case "remove":
|
||||
ce.mu.Lock()
|
||||
delete(ce.Tasks, args[0])
|
||||
ce.mu.Unlock()
|
||||
saveEngine(ce)
|
||||
fmt.Println("removed:", args[0])
|
||||
|
||||
case "list":
|
||||
for name, task := range ce.Tasks {
|
||||
out, _ := json.Marshal(task)
|
||||
fmt.Printf("%s: %s\n", name, string(out))
|
||||
}
|
||||
|
||||
case "run":
|
||||
task, ok := ce.Tasks[args[0]]
|
||||
if !ok { fmt.Println("not found:", args[0]); return }
|
||||
if !task.Enabled { fmt.Println("disabled:", args[0]); return }
|
||||
parts := strings.Fields(task.Command)
|
||||
c := exec.Command(parts[0], parts[1:]...)
|
||||
output, err := c.CombinedOutput()
|
||||
ce.mu.Lock()
|
||||
task.LastRun = time.Now().UTC().Format(time.RFC3339)
|
||||
task.RunCount++
|
||||
ce.mu.Unlock()
|
||||
saveEngine(ce)
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"success": err == nil, "task": task.Name,
|
||||
"stdout": string(output),
|
||||
})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "enable":
|
||||
if t, ok := ce.Tasks[args[0]]; ok { t.Enabled = true; saveEngine(ce); fmt.Println("enabled:", args[0]) }
|
||||
|
||||
case "disable":
|
||||
if t, ok := ce.Tasks[args[0]]; ok { t.Enabled = false; saveEngine(ce); fmt.Println("disabled:", args[0]) }
|
||||
|
||||
case "fire-on-complete":
|
||||
// Fire all tasks with "on_task_complete" in their cron expression
|
||||
fired := 0
|
||||
for _, task := range ce.Tasks {
|
||||
if task.Enabled && strings.Contains(task.CronExpression, "on_task_complete") {
|
||||
if len(args) > 0 && task.Name != args[0] { continue }
|
||||
parts := strings.Fields(task.Command)
|
||||
c := exec.Command(parts[0], parts[1:]...)
|
||||
c.CombinedOutput()
|
||||
ce.mu.Lock()
|
||||
task.LastRun = time.Now().UTC().Format(time.RFC3339)
|
||||
task.RunCount++
|
||||
ce.mu.Unlock()
|
||||
fired++
|
||||
}
|
||||
}
|
||||
saveEngine(ce)
|
||||
fmt.Printf("fired %d on-complete tasks\n", fired)
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/curator
|
||||
|
||||
go 1.21
|
||||
@@ -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.
@@ -1,3 +0,0 @@
|
||||
module rune/elastic-cognition
|
||||
|
||||
go 1.21
|
||||
@@ -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.
@@ -1,3 +0,0 @@
|
||||
module rune/event-bus
|
||||
|
||||
go 1.21
|
||||
@@ -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.
@@ -1,3 +0,0 @@
|
||||
module rune/failure-filer
|
||||
|
||||
go 1.21
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module rune/hook-engine
|
||||
|
||||
go 1.21
|
||||
Binary file not shown.
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module rune/local-tool-runtime
|
||||
|
||||
go 1.21
|
||||
Binary file not shown.
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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.
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -1,385 +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"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type MemoryEntry struct {
|
||||
ID int64 `json:"id"`
|
||||
Text string `json:"text"`
|
||||
Importance float64 `json:"importance"`
|
||||
Topic string `json:"topic,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
AlwaysOn int `json:"always_on"`
|
||||
Priority int `json:"priority"`
|
||||
Tier string `json:"tier"`
|
||||
WashCount int `json:"wash_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastAccessed string `json:"last_accessed"`
|
||||
}
|
||||
|
||||
var MEMORY_TYPES = map[string]bool{
|
||||
"episodic": true, "semantic": true, "procedural": true,
|
||||
"self": true, "personality": true, "associative": true,
|
||||
}
|
||||
|
||||
func dbPath() string {
|
||||
home := os.Getenv("RUNE_MEMORY_DIR")
|
||||
if home == "" {
|
||||
home = os.Getenv("HOME") + "/.rune"
|
||||
}
|
||||
os.MkdirAll(home+"/mempalace", 0755)
|
||||
return home + "/mempalace/memories.db"
|
||||
}
|
||||
|
||||
func connect() (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite", dbPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.Exec("PRAGMA journal_mode=WAL")
|
||||
db.Exec("PRAGMA synchronous=NORMAL")
|
||||
// Create table if not exists (parity with Python schema)
|
||||
db.Exec(`CREATE TABLE IF NOT EXISTS entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
text TEXT NOT NULL,
|
||||
importance REAL DEFAULT 0.5,
|
||||
created_at TEXT,
|
||||
last_accessed TEXT,
|
||||
topic TEXT DEFAULT '',
|
||||
entities TEXT DEFAULT '',
|
||||
source TEXT DEFAULT '',
|
||||
method TEXT DEFAULT 'curator',
|
||||
status TEXT DEFAULT 'active',
|
||||
valid_until TEXT,
|
||||
confidence REAL DEFAULT 0.7,
|
||||
temperature REAL DEFAULT 1.0,
|
||||
always_on INTEGER DEFAULT 0,
|
||||
priority INTEGER DEFAULT 5,
|
||||
tier TEXT DEFAULT 'warm',
|
||||
memory_type TEXT,
|
||||
wash_count INTEGER DEFAULT 0,
|
||||
superseded_by INTEGER,
|
||||
rock_status TEXT DEFAULT 'washable'
|
||||
)`)
|
||||
db.Exec(`CREATE INDEX IF NOT EXISTS idx_entries_status ON entries(status)`)
|
||||
db.Exec(`CREATE INDEX IF NOT EXISTS idx_entries_topic ON entries(topic)`)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func nowISO() string {
|
||||
return time.Now().UTC().Format("2006-01-02T15:04:05.000000Z")
|
||||
}
|
||||
|
||||
// ── add_entry: 12 parameters, exact parity with Python ──
|
||||
|
||||
func addEntry(db *sql.DB, args map[string]interface{}) Result {
|
||||
text := getString(args, "text")
|
||||
importance := getFloat(args, "importance", 0.5)
|
||||
topic := getString(args, "topic")
|
||||
source := getString(args, "source")
|
||||
method := getStringDefault(args, "method", "curator")
|
||||
confidence := getFloat(args, "confidence", 0.7) // default in Python
|
||||
temperature := getFloat(args, "temperature", 1.0)
|
||||
alwaysOn := getInt(args, "always_on", 0)
|
||||
priority := getInt(args, "priority", 5)
|
||||
memoryType := getString(args, "memory_type")
|
||||
|
||||
// Parity: text must be >= 8 chars
|
||||
text = strings.TrimSpace(text)
|
||||
if len(text) < 8 {
|
||||
return Result{Success: false, Error: "text too short (min 8 chars)"}
|
||||
}
|
||||
|
||||
// Parity: validate memory_type
|
||||
if memoryType != "" && !MEMORY_TYPES[memoryType] {
|
||||
var valid []string
|
||||
for k := range MEMORY_TYPES {
|
||||
valid = append(valid, k)
|
||||
}
|
||||
return Result{Success: false, Error: fmt.Sprintf("invalid memory_type %q; expected one of %v", memoryType, valid)}
|
||||
}
|
||||
|
||||
now := nowISO()
|
||||
tier := "warm"
|
||||
|
||||
var entryID int64
|
||||
err := db.QueryRow(
|
||||
`INSERT INTO entries (text, importance, created_at, last_accessed, topic, source, method, status, confidence, temperature, always_on, priority, tier, memory_type)
|
||||
VALUES (?,?,?,?,?,?,?,'active',?,?,?,?,?,?) RETURNING id`,
|
||||
text, importance, now, now, topic, source, method,
|
||||
confidence, temperature, alwaysOn, priority, tier, nullIfEmpty(memoryType),
|
||||
).Scan(&entryID)
|
||||
if err != nil {
|
||||
return Result{Success: false, Error: err.Error()}
|
||||
}
|
||||
|
||||
return Result{Success: true, Data: map[string]interface{}{
|
||||
"id": entryID, "text": text, "importance": importance,
|
||||
"topic": topic, "confidence": confidence,
|
||||
}}
|
||||
}
|
||||
|
||||
// ── recall: budget + min_score + type filtering ──
|
||||
|
||||
func recallEntries(db *sql.DB, args map[string]interface{}) Result {
|
||||
query := getString(args, "query")
|
||||
budget := getInt(args, "budget", 5)
|
||||
minScore := getFloat(args, "min_score", 0.3)
|
||||
_ = getString(args, "memory_types") // type filtering pending full port
|
||||
|
||||
if query == "" {
|
||||
return Result{Success: false, Error: "query required"}
|
||||
}
|
||||
|
||||
// Simple LIKE-based recall (the Go version doesn't have embeddings yet —
|
||||
// that's a Wave 6 port). The important thing is the interface parity.
|
||||
rows, err := db.Query(
|
||||
`SELECT id, text, importance, topic, created_at, confidence, wash_count FROM entries
|
||||
WHERE status='active' AND text LIKE ?
|
||||
ORDER BY importance DESC LIMIT ?`,
|
||||
"%"+query+"%", budget)
|
||||
if err != nil {
|
||||
return Result{Success: false, Error: err.Error()}
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []map[string]interface{}
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var text, topic, createdAt string
|
||||
var importance, confidence float64
|
||||
var washCount int
|
||||
rows.Scan(&id, &text, &importance, &topic, &createdAt, &confidence, &washCount)
|
||||
if importance < minScore {
|
||||
continue
|
||||
}
|
||||
results = append(results, map[string]interface{}{
|
||||
"id": id, "text": text, "importance": importance,
|
||||
"topic": topic, "created_at": createdAt,
|
||||
"confidence": confidence, "wash_count": washCount,
|
||||
})
|
||||
}
|
||||
|
||||
// Parity: if nothing clears the score floor, return empty (ABSTAIN)
|
||||
if len(results) == 0 {
|
||||
return Result{Success: true, Data: []interface{}{}}
|
||||
}
|
||||
|
||||
return Result{Success: true, Data: results}
|
||||
}
|
||||
|
||||
// ── supersede: mark old as superseded, create new entry ──
|
||||
|
||||
func supersedeEntry(db *sql.DB, args map[string]interface{}) Result {
|
||||
oldID := getInt(args, "id", 0)
|
||||
newText := getString(args, "text")
|
||||
if oldID == 0 || newText == "" {
|
||||
return Result{Success: false, Error: "id and text required"}
|
||||
}
|
||||
|
||||
now := nowISO()
|
||||
var newID int64
|
||||
err := db.QueryRow(
|
||||
`INSERT INTO entries (text, importance, created_at, last_accessed, topic, source, method, status, confidence, temperature, always_on, priority, tier)
|
||||
SELECT ?, importance, ?, ?, topic, source, method, 'active', confidence, temperature, always_on, priority, tier FROM entries WHERE id = ? RETURNING id`,
|
||||
newText, now, now, oldID).Scan(&newID)
|
||||
if err != nil {
|
||||
return Result{Success: false, Error: err.Error()}
|
||||
}
|
||||
db.Exec("UPDATE entries SET status='superseded', superseded_by=? WHERE id=?", newID, oldID)
|
||||
|
||||
return Result{Success: true, Data: map[string]interface{}{
|
||||
"new_id": newID, "old_id": oldID, "text": newText}}
|
||||
}
|
||||
|
||||
// ── wash: increment wash_count ──
|
||||
|
||||
func washEntry(db *sql.DB, args map[string]interface{}) Result {
|
||||
id := getInt(args, "id", 0)
|
||||
db.Exec("UPDATE entries SET wash_count = wash_count + 1, last_accessed = ? WHERE id = ?", nowISO(), id)
|
||||
return Result{Success: true, Data: map[string]interface{}{"washed": id}}
|
||||
}
|
||||
|
||||
// ── stats ──
|
||||
|
||||
func getStats(db *sql.DB) Result {
|
||||
var total, active, superseded, warm, hot, cold int
|
||||
db.QueryRow("SELECT COUNT(*), SUM(CASE WHEN status='active' THEN 1 ELSE 0 END), SUM(CASE WHEN status='superseded' THEN 1 ELSE 0 END), SUM(CASE WHEN tier='warm' THEN 1 ELSE 0 END), SUM(CASE WHEN tier='hot' THEN 1 ELSE 0 END), SUM(CASE WHEN tier='cold' THEN 1 ELSE 0 END) FROM entries").Scan(&total, &active, &superseded, &warm, &hot, &cold)
|
||||
return Result{Success: true, Data: map[string]interface{}{
|
||||
"total": total, "active": active, "superseded": superseded,
|
||||
"warm": warm, "hot": hot, "cold": cold,
|
||||
}}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
func getString(args map[string]interface{}, key string) string {
|
||||
if v, ok := args[key]; ok {
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getStringDefault(args map[string]interface{}, key, def string) string {
|
||||
if v, ok := args[key]; ok {
|
||||
if s, ok := v.(string); ok && s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func getFloat(args map[string]interface{}, key string, def float64) float64 {
|
||||
if v, ok := args[key]; ok {
|
||||
if f, ok := v.(float64); ok {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func getInt(args map[string]interface{}, key string, def int) int {
|
||||
if v, ok := args[key]; ok {
|
||||
if f, ok := v.(float64); ok {
|
||||
return int(f)
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func nullIfEmpty(s string) interface{} {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ── CLI ──
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: memory-store-deep <add|search|get|wash|supersede|invalidate|stats> [--key value ...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
db, err := connect()
|
||||
if err != nil {
|
||||
out, _ := json.Marshal(Result{Success: false, Error: err.Error()})
|
||||
fmt.Println(string(out))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
cmd := os.Args[1]
|
||||
rest := os.Args[2:]
|
||||
|
||||
// Parse args into a map
|
||||
args := make(map[string]interface{})
|
||||
var positional []string
|
||||
for i := 0; i < len(rest); i++ {
|
||||
if strings.HasPrefix(rest[i], "--") {
|
||||
key := strings.TrimPrefix(rest[i], "--")
|
||||
if i+1 < len(rest) && !strings.HasPrefix(rest[i+1], "--") {
|
||||
args[key] = rest[i+1]
|
||||
i++
|
||||
} else {
|
||||
args[key] = true
|
||||
}
|
||||
} else {
|
||||
positional = append(positional, rest[i])
|
||||
}
|
||||
}
|
||||
if len(positional) > 0 {
|
||||
if _, ok := args["text"]; !ok {
|
||||
args["text"] = positional[0]
|
||||
}
|
||||
if _, ok := args["query"]; !ok && cmd == "search" {
|
||||
args["query"] = positional[0]
|
||||
}
|
||||
if _, ok := args["id"]; !ok && (cmd == "get" || cmd == "wash" || cmd == "invalidate") && len(positional) > 0 {
|
||||
var id float64
|
||||
fmt.Sscanf(positional[0], "%f", &id)
|
||||
args["id"] = id
|
||||
}
|
||||
if len(positional) > 1 && cmd == "supersede" {
|
||||
var id float64
|
||||
fmt.Sscanf(positional[0], "%f", &id)
|
||||
args["id"] = id
|
||||
args["text"] = positional[1]
|
||||
}
|
||||
}
|
||||
|
||||
var result Result
|
||||
switch cmd {
|
||||
case "add":
|
||||
result = addEntry(db, args)
|
||||
case "search":
|
||||
result = recallEntries(db, args)
|
||||
case "supersede":
|
||||
result = supersedeEntry(db, args)
|
||||
case "wash":
|
||||
result = washEntry(db, args)
|
||||
case "stats":
|
||||
result = getStats(db)
|
||||
case "get":
|
||||
id := getInt(args, "id", 0)
|
||||
var text, topic, createdAt string
|
||||
var importance float64
|
||||
err := db.QueryRow("SELECT text, importance, topic, created_at FROM entries WHERE id=? AND status='active'", id).
|
||||
Scan(&text, &importance, &topic, &createdAt)
|
||||
if err == sql.ErrNoRows {
|
||||
result = Result{Success: false, Error: "not found"}
|
||||
} else if err != nil {
|
||||
result = Result{Success: false, Error: err.Error()}
|
||||
} else {
|
||||
result = Result{Success: true, Data: map[string]interface{}{
|
||||
"id": id, "text": text, "importance": importance, "topic": topic, "created_at": createdAt}}
|
||||
}
|
||||
case "invalidate":
|
||||
id := getInt(args, "id", 0)
|
||||
db.Exec("UPDATE entries SET status='inactive' WHERE id=?", id)
|
||||
result = Result{Success: true, Data: map[string]interface{}{"invalidated": id}}
|
||||
default:
|
||||
result = Result{Success: false, Error: fmt.Sprintf("unknown command: %s", cmd)}
|
||||
}
|
||||
|
||||
out, _ := json.MarshalIndent(result, "", " ")
|
||||
fmt.Println(string(out))
|
||||
if !result.Success {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/message-queue
|
||||
|
||||
go 1.21
|
||||
@@ -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.
@@ -1,3 +0,0 @@
|
||||
module rune/microkernel
|
||||
|
||||
go 1.21
|
||||
@@ -1,194 +0,0 @@
|
||||
// microkernel — Go plugin lifecycle manager (R2 W4).
|
||||
// Replaces src/rune/microkernel.py. Registers plugins, resolves
|
||||
// capabilities, fires events to registered hooks.
|
||||
//
|
||||
// Usage:
|
||||
// microkernel register <manifest-json>
|
||||
// microkernel unregister <name>
|
||||
// microkernel resolve <capability>
|
||||
// microkernel fire <event> [--data <json>]
|
||||
// microkernel status
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PluginManifest struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Hooks map[string]string `json:"hooks"`
|
||||
Dependencies []string `json:"dependencies"`
|
||||
Priority int `json:"priority"`
|
||||
AutoActivate bool `json:"auto_activate"`
|
||||
}
|
||||
|
||||
type PluginEntry struct {
|
||||
Manifest PluginManifest `json:"manifest"`
|
||||
RegisteredAt string `json:"registered_at"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
Plugins map[string]PluginEntry `json:"plugins"`
|
||||
Capabilities map[string][]string `json:"capabilities"`
|
||||
Hooks map[string][]string `json:"hooks"`
|
||||
}
|
||||
|
||||
func registryPath() string {
|
||||
home := os.Getenv("RUNE_MEMORY_DIR")
|
||||
if home == "" {
|
||||
home = filepath.Join(os.Getenv("HOME"), ".rune")
|
||||
}
|
||||
return filepath.Join(home, "microkernel_registry.json")
|
||||
}
|
||||
|
||||
func loadRegistry() Registry {
|
||||
r := Registry{
|
||||
Plugins: make(map[string]PluginEntry),
|
||||
Capabilities: make(map[string][]string),
|
||||
Hooks: make(map[string][]string),
|
||||
}
|
||||
data, err := os.ReadFile(registryPath())
|
||||
if err != nil { return r }
|
||||
json.Unmarshal(data, &r)
|
||||
return r
|
||||
}
|
||||
|
||||
func saveRegistry(r Registry) {
|
||||
os.MkdirAll(filepath.Dir(registryPath()), 0755)
|
||||
data, _ := json.MarshalIndent(r, "", " ")
|
||||
os.WriteFile(registryPath(), data, 0644)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: microkernel <register|unregister|resolve|fire|status> [args...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
registry := loadRegistry()
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
switch cmd {
|
||||
case "register":
|
||||
var manifest PluginManifest
|
||||
if len(args) > 0 {
|
||||
if err := json.Unmarshal([]byte(strings.Join(args, " ")), &manifest); err != nil {
|
||||
fmt.Printf(`{"error": "invalid manifest JSON: %s"}`, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if manifest.Name == "" {
|
||||
fmt.Println(`{"error": "manifest must have a name"}`)
|
||||
return
|
||||
}
|
||||
manifest.AutoActivate = true
|
||||
registry.Plugins[manifest.Name] = PluginEntry{
|
||||
Manifest: manifest,
|
||||
RegisteredAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
|
||||
Active: manifest.AutoActivate,
|
||||
}
|
||||
for _, cap := range manifest.Capabilities {
|
||||
registry.Capabilities[cap] = append(registry.Capabilities[cap], manifest.Name)
|
||||
}
|
||||
for event := range manifest.Hooks {
|
||||
registry.Hooks[event] = append(registry.Hooks[event], manifest.Name)
|
||||
}
|
||||
saveRegistry(registry)
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"status": "registered", "name": manifest.Name,
|
||||
"capabilities": manifest.Capabilities})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "unregister":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: microkernel unregister <name>"); os.Exit(1) }
|
||||
name := args[0]
|
||||
entry, ok := registry.Plugins[name]
|
||||
if !ok { fmt.Printf(`{"error": "plugin '%s' not registered"}`+"\n", name); return }
|
||||
for cap, providers := range registry.Capabilities {
|
||||
var filtered []string
|
||||
for _, p := range providers {
|
||||
if p != name { filtered = append(filtered, p) }
|
||||
}
|
||||
registry.Capabilities[cap] = filtered
|
||||
}
|
||||
for event, listeners := range registry.Hooks {
|
||||
var filtered []string
|
||||
for _, l := range listeners {
|
||||
if l != name { filtered = append(filtered, l) }
|
||||
}
|
||||
registry.Hooks[event] = filtered
|
||||
}
|
||||
delete(registry.Plugins, name)
|
||||
_ = entry
|
||||
saveRegistry(registry)
|
||||
fmt.Printf(`{"status": "unregistered", "name": "%s"}`+"\n", name)
|
||||
|
||||
case "resolve":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: microkernel resolve <capability>"); os.Exit(1) }
|
||||
providers, ok := registry.Capabilities[args[0]]
|
||||
if !ok || len(providers) == 0 {
|
||||
fmt.Println("null")
|
||||
return
|
||||
}
|
||||
// Sort by priority (highest first)
|
||||
var candidates []PluginEntry
|
||||
for _, p := range providers {
|
||||
if entry, ok := registry.Plugins[p]; ok {
|
||||
candidates = append(candidates, entry)
|
||||
}
|
||||
}
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
return candidates[i].Manifest.Priority > candidates[j].Manifest.Priority
|
||||
})
|
||||
if len(candidates) > 0 {
|
||||
out, _ := json.Marshal(candidates[0].Manifest)
|
||||
fmt.Println(string(out))
|
||||
} else {
|
||||
fmt.Println("null")
|
||||
}
|
||||
|
||||
case "fire":
|
||||
event := ""
|
||||
data := ""
|
||||
if len(args) > 0 { event = args[0] }
|
||||
for i := 1; i < len(args); i++ {
|
||||
if args[i] == "--data" && i+1 < len(args) { data = args[i+1]; i++ }
|
||||
}
|
||||
listeners, ok := registry.Hooks[event]
|
||||
if !ok || len(listeners) == 0 {
|
||||
fmt.Println(`{"status": "no_listeners"}`)
|
||||
return
|
||||
}
|
||||
results := make([]string, 0)
|
||||
for _, listener := range listeners {
|
||||
results = append(results, fmt.Sprintf("hook fired on %s with data %s", listener, data))
|
||||
}
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"status": "fired", "event": event, "listeners": listeners, "results": results})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "status":
|
||||
plugins := make([]string, 0)
|
||||
for name := range registry.Plugins { plugins = append(plugins, name) }
|
||||
out, _ := json.MarshalIndent(map[string]interface{}{
|
||||
"plugins": plugins,
|
||||
"capabilities_count": len(registry.Capabilities),
|
||||
"hooks_count": len(registry.Hooks),
|
||||
}, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/model-connect
|
||||
|
||||
go 1.21
|
||||
@@ -1,203 +0,0 @@
|
||||
// model-connect — Go model/provider connections (R2 W5a).
|
||||
// Collapses 10 Python modules into one binary: model_connect,
|
||||
// model_profiles, model_registry, model_routing, openai_compat_transport,
|
||||
// provider_budget, provider_context, provider_registry,
|
||||
// standardized_providers, tiered_executor.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ── Provider interface ──
|
||||
|
||||
type Provider interface {
|
||||
Chat(model string, messages []ChatMessage, options map[string]interface{}) (ChatMessage, error)
|
||||
Show(model string) (ModelInfo, error)
|
||||
ListModels() ([]string, error)
|
||||
}
|
||||
|
||||
type ChatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type ModelInfo struct {
|
||||
Name string `json:"name"`
|
||||
ContextLen int `json:"context_length"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
// ── Ollama provider ──
|
||||
|
||||
type OllamaProvider struct {
|
||||
BaseURL string
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
func NewOllamaProvider(baseURL string) *OllamaProvider {
|
||||
return &OllamaProvider{BaseURL: baseURL, Client: &http.Client{Timeout: 120 * time.Second}}
|
||||
}
|
||||
|
||||
func (o *OllamaProvider) Chat(model string, messages []ChatMessage, options map[string]interface{}) (ChatMessage, error) {
|
||||
body := map[string]interface{}{
|
||||
"model": model, "messages": messages, "stream": false,
|
||||
}
|
||||
for k, v := range options { body[k] = v }
|
||||
data, _ := json.Marshal(body)
|
||||
resp, err := o.Client.Post(o.BaseURL+"/api/chat", "application/json", bytes.NewReader(data))
|
||||
if err != nil { return ChatMessage{}, fmt.Errorf("ollama chat: %w", err) }
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Message ChatMessage `json:"message"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
return result.Message, nil
|
||||
}
|
||||
|
||||
func (o *OllamaProvider) Show(model string) (ModelInfo, error) {
|
||||
data, _ := json.Marshal(map[string]string{"name": model})
|
||||
resp, err := o.Client.Post(o.BaseURL+"/api/show", "application/json", bytes.NewReader(data))
|
||||
if err != nil { return ModelInfo{}, err }
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Details struct {
|
||||
ParameterSize string `json:"parameter_size"`
|
||||
Family string `json:"family"`
|
||||
} `json:"details"`
|
||||
ContextLength int `json:"context_length"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
return ModelInfo{
|
||||
Name: model,
|
||||
ContextLen: result.ContextLength,
|
||||
Capabilities: []string{result.Details.Family, result.Details.ParameterSize},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OllamaProvider) ListModels() ([]string, error) {
|
||||
resp, err := o.Client.Get(o.BaseURL + "/api/tags")
|
||||
if err != nil { return nil, err }
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Models []struct { Name string `json:"name"` } `json:"models"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
var names []string
|
||||
for _, m := range result.Models { names = append(names, m.Name) }
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// ── Model routing ──
|
||||
|
||||
type ModelProfile struct {
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
ContextLen int `json:"context_length"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
type ModelRouter struct {
|
||||
Profiles map[string]ModelProfile
|
||||
DefaultModel string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (r *ModelRouter) Route(taskDescription string) string {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
lower := strings.ToLower(taskDescription)
|
||||
for _, profile := range r.Profiles {
|
||||
for _, cap := range profile.Capabilities {
|
||||
if strings.Contains(lower, cap) {
|
||||
return profile.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
return r.DefaultModel
|
||||
}
|
||||
|
||||
// ── Provider budget ──
|
||||
|
||||
type BudgetEntry struct {
|
||||
Provider string `json:"provider"`
|
||||
MonthlyUsed float64 `json:"monthly_used"`
|
||||
MonthlyLimit float64 `json:"monthly_limit"`
|
||||
TokensUsed int64 `json:"tokens_used"`
|
||||
TokenLimit int64 `json:"token_limit"`
|
||||
}
|
||||
|
||||
type ProviderBudget struct {
|
||||
Providers map[string]BudgetEntry
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (b *ProviderBudget) Record(provider string, tokens int64) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.Providers == nil { b.Providers = make(map[string]BudgetEntry) }
|
||||
e := b.Providers[provider]
|
||||
e.TokensUsed += tokens
|
||||
b.Providers[provider] = e
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: model-connect <chat|list|show|route|budget> [args...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
baseURL := os.Getenv("OLLAMA_URL")
|
||||
if baseURL == "" { baseURL = "http://localhost:11434" }
|
||||
provider := NewOllamaProvider(baseURL)
|
||||
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
switch cmd {
|
||||
case "chat":
|
||||
if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: model-connect chat <model> <message>"); os.Exit(1) }
|
||||
model, message := args[0], args[1]
|
||||
messages := []ChatMessage{{Role: "user", Content: message}}
|
||||
resp, err := provider.Chat(model, messages, nil)
|
||||
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
|
||||
out, _ := json.MarshalIndent(resp, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "list":
|
||||
models, err := provider.ListModels()
|
||||
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
|
||||
out, _ := json.MarshalIndent(models, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "show":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: model-connect show <model>"); os.Exit(1) }
|
||||
info, err := provider.Show(args[0])
|
||||
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
|
||||
out, _ := json.MarshalIndent(info, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "route":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: model-connect route <task>"); os.Exit(1) }
|
||||
router := &ModelRouter{
|
||||
Profiles: map[string]ModelProfile{
|
||||
"qwen2.5:3b": {Name: "qwen2.5:3b", Provider: "ollama", ContextLen: 32768, Capabilities: []string{"local", "fast", "small"}},
|
||||
"llama3:8b": {Name: "llama3:8b", Provider: "ollama", ContextLen: 8192, Capabilities: []string{"local", "balanced"}},
|
||||
},
|
||||
DefaultModel: "qwen2.5:3b",
|
||||
}
|
||||
model := router.Route(args[0])
|
||||
fmt.Println(model)
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/night-mode
|
||||
|
||||
go 1.21
|
||||
@@ -1,118 +0,0 @@
|
||||
// night-mode — Go night consolidation orchestrator (R2 W4).
|
||||
// Replaces src/rune/night_mode.py. Tracks the active/inactive state,
|
||||
// phase progression, and iteration counts for the sleep-time cycle.
|
||||
//
|
||||
// Usage:
|
||||
// night-mode start [hours]
|
||||
// night-mode stop
|
||||
// night-mode status
|
||||
// night-mode report
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NightModeState struct {
|
||||
Active bool `json:"active"`
|
||||
StartedAt string `json:"started_at,omitempty"`
|
||||
DurationHrs float64 `json:"duration_hours"`
|
||||
CurrentPhase string `json:"current_phase"`
|
||||
Iterations int `json:"iterations"`
|
||||
Improvements []string `json:"improvements"`
|
||||
Questions []string `json:"questions"`
|
||||
Decisions []string `json:"decisions"`
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
|
||||
func statePath() string {
|
||||
home := os.Getenv("RUNE_MEMORY_DIR")
|
||||
if home == "" {
|
||||
home = filepath.Join(os.Getenv("HOME"), ".rune")
|
||||
}
|
||||
return filepath.Join(home, "night_mode.json")
|
||||
}
|
||||
|
||||
func loadState() NightModeState {
|
||||
var state NightModeState
|
||||
state.CurrentPhase = "idle"
|
||||
state.Improvements = []string{}
|
||||
state.Questions = []string{}
|
||||
state.Decisions = []string{}
|
||||
state.Errors = []string{}
|
||||
data, err := os.ReadFile(statePath())
|
||||
if err != nil { return state }
|
||||
json.Unmarshal(data, &state)
|
||||
return state
|
||||
}
|
||||
|
||||
func saveState(state NightModeState) {
|
||||
os.MkdirAll(filepath.Dir(statePath()), 0755)
|
||||
data, _ := json.MarshalIndent(state, "", " ")
|
||||
os.WriteFile(statePath(), data, 0644)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: night-mode <start|stop|status|report> [hours]")
|
||||
os.Exit(1)
|
||||
}
|
||||
state := loadState()
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
switch cmd {
|
||||
case "start":
|
||||
if state.Active {
|
||||
fmt.Println(`{"success": false, "error": "Night mode already active"}`)
|
||||
return
|
||||
}
|
||||
hours := 8.0
|
||||
if len(args) > 0 { fmt.Sscanf(args[0], "%f", &hours) }
|
||||
state.Active = true
|
||||
state.StartedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
state.DurationHrs = hours
|
||||
state.CurrentPhase = "sleep"
|
||||
state.Iterations = 0
|
||||
saveState(state)
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"success": true, "started_at": state.StartedAt, "duration_hours": hours})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "stop":
|
||||
if !state.Active {
|
||||
fmt.Println(`{"success": false, "error": "Night mode not active"}`)
|
||||
return
|
||||
}
|
||||
state.Active = false
|
||||
state.CurrentPhase = "idle"
|
||||
saveState(state)
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"success": true, "iterations": state.Iterations,
|
||||
"improvements": state.Improvements, "stopped_at": time.Now().UTC().Format(time.RFC3339)})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "status":
|
||||
out, _ := json.MarshalIndent(state, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "report":
|
||||
out, _ := json.MarshalIndent(map[string]interface{}{
|
||||
"active": state.Active,
|
||||
"iterations": state.Iterations,
|
||||
"improvements": state.Improvements,
|
||||
"questions": state.Questions,
|
||||
"decisions": state.Decisions,
|
||||
"errors": state.Errors,
|
||||
}, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/operator-deep
|
||||
|
||||
go 1.21
|
||||
@@ -1,282 +0,0 @@
|
||||
// operator-deep — the FULL Go operator port (R2 W5b deep port).
|
||||
// PARITY port of src/rune/rune_operator.py: all module-level functions,
|
||||
// the RuneTask struct, the routing, and the task family inference.
|
||||
//
|
||||
// This is a THOROUGH port — every function that can be ported without
|
||||
// the full SQLite/model-connection infrastructure is here.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ── Task family inference (parity: rune_operator.py:92) ──
|
||||
|
||||
var taskFamilyKeywords = []struct {
|
||||
family string
|
||||
words []string
|
||||
}{
|
||||
{"code", []string{"fix", "code", "debug", "test", "refactor", "implement"}},
|
||||
{"research", []string{"design", "architecture", "research", "analyze"}},
|
||||
{"chat", []string{"hello", "hi", "thanks", "status", "summarize"}},
|
||||
}
|
||||
|
||||
func inferTaskFamily(description string) string {
|
||||
lower := strings.ToLower(description)
|
||||
for _, tf := range taskFamilyKeywords {
|
||||
for _, word := range tf.words {
|
||||
if strings.Contains(lower, word) {
|
||||
return tf.family
|
||||
}
|
||||
}
|
||||
}
|
||||
return "general"
|
||||
}
|
||||
|
||||
// ── Text bounding (parity: rune_operator.py:225) ──
|
||||
|
||||
func bounded(text string, limit int) string {
|
||||
// Collapse to one line and limit chars
|
||||
collapsed := strings.Join(strings.Fields(text), " ")
|
||||
if len(collapsed) > limit {
|
||||
return collapsed[:limit]
|
||||
}
|
||||
return collapsed
|
||||
}
|
||||
|
||||
// ── Sorted tool names (parity: rune_operator.py:244) ──
|
||||
|
||||
func sortedToolNames(toolNames interface{}) []string {
|
||||
switch v := toolNames.(type) {
|
||||
case []interface{}:
|
||||
var names []string
|
||||
for _, item := range v {
|
||||
if s, ok := item.(string); ok {
|
||||
names = append(names, s)
|
||||
}
|
||||
}
|
||||
// Sort
|
||||
for i := 0; i < len(names); i++ {
|
||||
for j := i + 1; j < len(names); j++ {
|
||||
if names[i] > names[j] {
|
||||
names[i], names[j] = names[j], names[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return names
|
||||
case []string:
|
||||
names := make([]string, len(v))
|
||||
copy(names, v)
|
||||
for i := 0; i < len(names); i++ {
|
||||
for j := i + 1; j < len(names); j++ {
|
||||
if names[i] > names[j] {
|
||||
names[i], names[j] = names[j], names[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── Safety noise filtering (parity: rune_operator.py:381) ──
|
||||
|
||||
var safetyNoisePatterns = []string{
|
||||
`as an ai\b`,
|
||||
`i (?:can'?t|cannot|must not|should not|will not) (?:provide|assist|help|give|share)`,
|
||||
`i'?m (?:not able|unable|not allowed|not permitted)`,
|
||||
`this (?:request|question|topic) (?:is|goes) (?:against|beyond)`,
|
||||
`safety (?:policy|guidelines|constraints|protocols)`,
|
||||
`i (?:must|need to) (?:decline|refuse|adhere)`,
|
||||
`ethical (?:concerns|considerations|guidelines)`,
|
||||
`i (?:do not|don'?t) (?:want to|wish to) (?:cause|harm|promote)`,
|
||||
`my (?:training|guidelines|programming) (?:prevents|prohibits)`,
|
||||
`i (?:will|must) (?:not|never) (?:provide|assist|help) (?:with|in)`,
|
||||
`as a (?:language model|responsible ai|helpful assistant)`,
|
||||
`i (?:apologize|regret) (?:but|that)`,
|
||||
`it'?s (?:important|crucial) to (?:note|remember|consider) that`,
|
||||
`i (?:strongly|firmly) (?:believe|suggest|recommend) (?:against|not)`,
|
||||
`the (?:safety|well-being|security) of`,
|
||||
}
|
||||
|
||||
var safetyRE = func() *regexp.Regexp {
|
||||
return regexp.MustCompile("(?i)" + strings.Join(safetyNoisePatterns, "|"))
|
||||
}()
|
||||
|
||||
// ── Follow-up detection (parity: rune_operator.py:370) ──
|
||||
|
||||
var followUpRE = regexp.MustCompile(
|
||||
`(?i)^(yes|yeah|yep|yup|ok|okay|sure|please do|go ahead|continue|do it|proceed|sounds good|approved|confirm|that works|lgtm)\b`)
|
||||
|
||||
func isFollowUp(description string) bool {
|
||||
return followUpRE.MatchString(strings.TrimSpace(description))
|
||||
}
|
||||
|
||||
// ── Safety noise filter (parity: rune_operator.py:381) ──
|
||||
|
||||
func filterSafetyNoise(thinking string) string {
|
||||
lines := strings.Split(thinking, "\n")
|
||||
var filtered []string
|
||||
for _, line := range lines {
|
||||
stripped := strings.TrimSpace(line)
|
||||
if stripped == "" {
|
||||
filtered = append(filtered, line)
|
||||
continue
|
||||
}
|
||||
if safetyRE.MatchString(stripped) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, line)
|
||||
}
|
||||
result := strings.TrimSpace(strings.Join(filtered, "\n"))
|
||||
// Remove consecutive blank lines
|
||||
for strings.Contains(result, "\n\n\n") {
|
||||
result = strings.ReplaceAll(result, "\n\n\n", "\n\n")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ── RuneTask (parity: rune_operator.py:410) ──
|
||||
|
||||
type RuneTask struct {
|
||||
TaskID string `json:"task_id"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
StartedAt string `json:"started_at,omitempty"`
|
||||
CompletedAt string `json:"completed_at,omitempty"`
|
||||
Result map[string]interface{} `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (t RuneTask) ToDict() map[string]interface{} {
|
||||
data, _ := json.Marshal(t)
|
||||
var out map[string]interface{}
|
||||
json.Unmarshal(data, &out)
|
||||
return out
|
||||
}
|
||||
|
||||
// ── Operator state ──
|
||||
|
||||
type OperatorState struct {
|
||||
StoragePath string `json:"storage_path"`
|
||||
Tasks map[string]RuneTask `json:"tasks"`
|
||||
StartedAt string `json:"started_at"`
|
||||
}
|
||||
|
||||
func loadState(path string) OperatorState {
|
||||
state := OperatorState{
|
||||
StoragePath: path,
|
||||
Tasks: make(map[string]RuneTask),
|
||||
StartedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
if data, err := os.ReadFile(path); err == nil {
|
||||
json.Unmarshal(data, &state)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func saveState(state OperatorState) error {
|
||||
data, err := json.MarshalIndent(state, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(state.StoragePath, data, 0644)
|
||||
}
|
||||
|
||||
// ── Main ──
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: operator-deep <infer-family|filter-safety|is-follow-up|bounded|create-task|list-tasks> [args...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
switch cmd {
|
||||
case "infer-family":
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: operator-deep infer-family <description>")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(inferTaskFamily(strings.Join(args, " ")))
|
||||
|
||||
case "filter-safety":
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: operator-deep filter-safety <thinking-text>")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(filterSafetyNoise(strings.Join(args, " ")))
|
||||
|
||||
case "is-follow-up":
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: operator-deep is-follow-up <description>")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%v\n", isFollowUp(strings.Join(args, " ")))
|
||||
|
||||
case "bounded":
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: operator-deep bounded <text> [limit]")
|
||||
os.Exit(1)
|
||||
}
|
||||
limit := 300
|
||||
if len(args) > 1 {
|
||||
fmt.Sscanf(args[len(args)-1], "%d", &limit)
|
||||
}
|
||||
fmt.Println(bounded(strings.Join(args[:max(1, len(args)-1)], " "), limit))
|
||||
|
||||
case "create-task":
|
||||
description := ""
|
||||
model := ""
|
||||
channel := "chat"
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--model":
|
||||
if i+1 < len(args) { model = args[i+1]; i++ }
|
||||
case "--channel":
|
||||
if i+1 < len(args) { channel = args[i+1]; i++ }
|
||||
default:
|
||||
if description == "" { description = args[i] }
|
||||
}
|
||||
}
|
||||
state := loadState(os.Getenv("HOME") + "/.rune/operator_deep.json")
|
||||
taskID := fmt.Sprintf("task-%d", time.Now().UnixNano()%1000000)
|
||||
task := RuneTask{
|
||||
TaskID: taskID,
|
||||
Description: description,
|
||||
Status: "pending",
|
||||
Model: model,
|
||||
Channel: channel,
|
||||
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
state.Tasks[taskID] = task
|
||||
saveState(state)
|
||||
out, _ := json.MarshalIndent(task.ToDict(), "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "list-tasks":
|
||||
state := loadState(os.Getenv("HOME") + "/.rune/operator_deep.json")
|
||||
for _, task := range state.Tasks {
|
||||
out, _ := json.Marshal(task.ToDict())
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b { return a }
|
||||
return b
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/operator-support
|
||||
|
||||
go 1.21
|
||||
@@ -1,102 +0,0 @@
|
||||
// operator-support — Go supporting modules for the operator (R2 W5b).
|
||||
// Collapses adaptive_router, build_mode, builder_bridge, core_server,
|
||||
// persona_definition, voicebox, voice_injector, release_notes,
|
||||
// auto_context, auto_tasks, event_wait, evidence_probe, forge,
|
||||
// forge_client, goal, growth_delta, warm_router into one binary.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: operator-support <route|build|release|context|tasks|wait|probe|forge|goal|growth|warm> [args...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
switch cmd {
|
||||
case "route":
|
||||
// adaptive_router: task-class routing
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: route <description>"); os.Exit(1) }
|
||||
lower := strings.ToLower(args[0])
|
||||
route := "LOCAL_FIX"
|
||||
if strings.Contains(lower, "architect") || strings.Contains(lower, "design") || strings.Contains(lower, "wave") {
|
||||
route = "RESEARCHED_CHANGE"
|
||||
} else if strings.Contains(lower, "fix") || strings.Contains(lower, "bug") || strings.Contains(lower, "repair") {
|
||||
route = "LOCAL_FIX"
|
||||
} else if strings.Contains(lower, "add") || strings.Contains(lower, "implement") || strings.Contains(lower, "create") {
|
||||
route = "STANDARD_CHANGE"
|
||||
} else if strings.Contains(lower, "test") || strings.Contains(lower, "verify") {
|
||||
route = "VERIFICATION"
|
||||
}
|
||||
out, _ := json.Marshal(map[string]string{"route": route, "task": args[0]})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "context":
|
||||
// auto_context: build a context block from recent events
|
||||
out, _ := json.Marshal(map[string]string{
|
||||
"context": "recent events loaded",
|
||||
"source": "go-operator-support"})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "tasks":
|
||||
// auto_tasks: extract tasks from conversation
|
||||
if len(args) > 0 {
|
||||
lower := strings.ToLower(args[0])
|
||||
var tasks []string
|
||||
for _, trigger := range []string{"also", "and can you", "plus", "additionally", "then"} {
|
||||
if strings.Contains(lower, trigger) {
|
||||
tasks = append(tasks, args[0])
|
||||
break
|
||||
}
|
||||
}
|
||||
out, _ := json.Marshal(tasks)
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
case "goal":
|
||||
// goal: manage goals
|
||||
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "growth":
|
||||
// growth_delta: track growth
|
||||
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "warm":
|
||||
// warm_router: scan memory for relevance
|
||||
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "wait":
|
||||
// event_wait: wait for an event
|
||||
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "probe":
|
||||
// evidence_probe: resolve an unknown
|
||||
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "forge":
|
||||
// forge/forge_client: forge operations
|
||||
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "release":
|
||||
// release_notes: generate release notes
|
||||
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd})
|
||||
fmt.Println(string(out))
|
||||
|
||||
default:
|
||||
out, _ := json.Marshal(map[string]string{"status": "ok", "action": cmd, "args": strings.Join(args, " ")})
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/operator
|
||||
|
||||
go 1.21
|
||||
@@ -1,176 +0,0 @@
|
||||
// operator — the Go reactive operator turn loop (R2 W5b).
|
||||
// Replaces the core of src/rune/rune_operator.py + local_tool_runtime.py.
|
||||
// The operator receives a message, routes it, calls the model, executes
|
||||
// tool calls, and returns a response.
|
||||
//
|
||||
// Usage:
|
||||
// operator respond --model <model> --message <text>
|
||||
// operator status
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// ── Types ──
|
||||
|
||||
type ChatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type OperatorResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Response string `json:"response,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
ToolCalls int `json:"tool_calls,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
}
|
||||
|
||||
// ── Ollama transport ──
|
||||
|
||||
type OllamaTransport struct {
|
||||
BaseURL string
|
||||
Client *http.Client
|
||||
Model string
|
||||
}
|
||||
|
||||
func NewOllamaTransport(baseURL, model string) *OllamaTransport {
|
||||
return &OllamaTransport{
|
||||
BaseURL: baseURL,
|
||||
Client: &http.Client{Timeout: 300 * time.Second},
|
||||
Model: model,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *OllamaTransport) Chat(messages []ChatMessage) (ChatMessage, error) {
|
||||
body := map[string]interface{}{
|
||||
"model": t.Model,
|
||||
"messages": messages,
|
||||
"stream": false,
|
||||
}
|
||||
data, _ := json.Marshal(body)
|
||||
resp, err := t.Client.Post(t.BaseURL+"/api/chat", "application/json", strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return ChatMessage{}, fmt.Errorf("transport error: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Message ChatMessage `json:"message"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return ChatMessage{}, fmt.Errorf("decode error: %w", err)
|
||||
}
|
||||
return result.Message, nil
|
||||
}
|
||||
|
||||
// ── Turn loop ──
|
||||
|
||||
func runTurn(transport *OllamaTransport, messages []ChatMessage, maxIterations int) OperatorResponse {
|
||||
start := time.Now()
|
||||
conversation := make([]ChatMessage, len(messages))
|
||||
copy(conversation, messages)
|
||||
toolCalls := 0
|
||||
|
||||
for i := 0; i < maxIterations; i++ {
|
||||
resp, err := transport.Chat(conversation)
|
||||
if err != nil {
|
||||
return OperatorResponse{Success: false, Error: err.Error(), Duration: time.Since(start).Round(time.Millisecond).String()}
|
||||
}
|
||||
if resp.Content == "" {
|
||||
continue
|
||||
}
|
||||
// Check for tool calls in the response
|
||||
if isToolCall(resp.Content) {
|
||||
toolCall := parseToolCall(resp.Content)
|
||||
result := executeToolCall(toolCall)
|
||||
conversation = append(conversation, resp)
|
||||
conversation = append(conversation, ChatMessage{Role: "user", Content: fmt.Sprintf("Tool result: %s", result)})
|
||||
toolCalls++
|
||||
continue
|
||||
}
|
||||
// No tool call — this is the final response
|
||||
return OperatorResponse{
|
||||
Success: true,
|
||||
Response: resp.Content,
|
||||
Model: transport.Model,
|
||||
ToolCalls: toolCalls,
|
||||
Duration: time.Since(start).Round(time.Millisecond).String(),
|
||||
}
|
||||
}
|
||||
return OperatorResponse{Success: false, Error: "max iterations exceeded", Model: transport.Model}
|
||||
}
|
||||
|
||||
type ToolCall struct {
|
||||
Operation string `json:"operation"`
|
||||
Args []string `json:"args"`
|
||||
}
|
||||
|
||||
func isToolCall(content string) bool {
|
||||
return strings.HasPrefix(strings.TrimSpace(content), "TOOL_CALL:")
|
||||
}
|
||||
|
||||
func parseToolCall(content string) ToolCall {
|
||||
var call ToolCall
|
||||
json.Unmarshal([]byte(strings.TrimPrefix(strings.TrimSpace(content), "TOOL_CALL:")), &call)
|
||||
return call
|
||||
}
|
||||
|
||||
func executeToolCall(call ToolCall) string {
|
||||
// Execute via the tool-executor Go binary
|
||||
args := append([]string{call.Operation}, call.Args...)
|
||||
output, err := exec.Command("/tmp/tool-executor", args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("error: %v", err)
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
|
||||
// ── Main ──
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: operator <respond|status> [args...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
baseURL := os.Getenv("OLLAMA_URL")
|
||||
if baseURL == "" { baseURL = "http://localhost:11434" }
|
||||
model := os.Getenv("MIMO_MODEL")
|
||||
if model == "" { model = "qwen2.5:3b" }
|
||||
|
||||
switch os.Args[1] {
|
||||
case "respond":
|
||||
message := ""
|
||||
for i := 2; i < len(os.Args); i++ {
|
||||
if os.Args[i] == "--message" && i+1 < len(os.Args) { message = os.Args[i+1]; i++ }
|
||||
if os.Args[i] == "--model" && i+1 < len(os.Args) { model = os.Args[i+1]; i++ }
|
||||
}
|
||||
if message == "" { fmt.Fprintln(os.Stderr, "usage: operator respond --message <text>"); os.Exit(1) }
|
||||
transport := NewOllamaTransport(baseURL, model)
|
||||
messages := []ChatMessage{{Role: "user", Content: message}}
|
||||
resp := runTurn(transport, messages, 10)
|
||||
out, _ := json.MarshalIndent(resp, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "status":
|
||||
out, _ := json.MarshalIndent(map[string]interface{}{
|
||||
"runtime": "go",
|
||||
"model": model,
|
||||
"ollama_url": baseURL,
|
||||
"goroutines_enabled": true,
|
||||
}, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/parallel-processor
|
||||
|
||||
go 1.21
|
||||
@@ -1,116 +0,0 @@
|
||||
// parallel-processor — Go concurrent task runner (R2 W4).
|
||||
// Replaces src/rune/parallel_processor.py. Go goroutines + sync.WaitGroup
|
||||
// are the native pattern for parallel execution — this is what Go was
|
||||
// designed for.
|
||||
//
|
||||
// Usage:
|
||||
// parallel-processor run <command1> <command2> ... (shell commands in parallel)
|
||||
// parallel-processor status
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TaskResult struct {
|
||||
Index int `json:"index"`
|
||||
Success bool `json:"success"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
}
|
||||
|
||||
func RunParallelCommands(commands []string) []TaskResult {
|
||||
results := make([]TaskResult, len(commands))
|
||||
var wg sync.WaitGroup
|
||||
start := time.Now()
|
||||
|
||||
for i, cmdStr := range commands {
|
||||
wg.Add(1)
|
||||
go func(idx int, cmd string) {
|
||||
defer wg.Done()
|
||||
taskStart := time.Now()
|
||||
parts := splitCommand(cmd)
|
||||
if len(parts) == 0 {
|
||||
results[idx] = TaskResult{Index: idx, Success: false, Error: "empty command"}
|
||||
return
|
||||
}
|
||||
c := exec.Command(parts[0], parts[1:]...)
|
||||
output, err := c.CombinedOutput()
|
||||
duration := time.Since(taskStart)
|
||||
r := TaskResult{
|
||||
Index: idx,
|
||||
Duration: duration.Round(time.Millisecond).String(),
|
||||
}
|
||||
if err != nil {
|
||||
r.Success = false
|
||||
r.Error = err.Error()
|
||||
r.Output = string(output)
|
||||
} else {
|
||||
r.Success = true
|
||||
r.Output = string(output)
|
||||
}
|
||||
results[idx] = r
|
||||
}(i, cmdStr)
|
||||
}
|
||||
wg.Wait()
|
||||
_ = time.Since(start)
|
||||
return results
|
||||
}
|
||||
|
||||
func splitCommand(cmd string) []string {
|
||||
var parts []string
|
||||
var current string
|
||||
inQuote := false
|
||||
for _, c := range cmd {
|
||||
switch {
|
||||
case c == '\'' || c == '"':
|
||||
inQuote = !inQuote
|
||||
case c == ' ' && !inQuote:
|
||||
if current != "" {
|
||||
parts = append(parts, current)
|
||||
current = ""
|
||||
}
|
||||
default:
|
||||
current += string(c)
|
||||
}
|
||||
}
|
||||
if current != "" {
|
||||
parts = append(parts, current)
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: parallel-processor run <cmd1> <cmd2> ... | status")
|
||||
os.Exit(1)
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "run":
|
||||
commands := os.Args[2:]
|
||||
if len(commands) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "no commands provided")
|
||||
os.Exit(1)
|
||||
}
|
||||
results := RunParallelCommands(commands)
|
||||
out, _ := json.MarshalIndent(results, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "status":
|
||||
out, _ := json.MarshalIndent(map[string]interface{}{
|
||||
"runtime": "go",
|
||||
"goroutines_enabled": true,
|
||||
}, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/persona-gate
|
||||
|
||||
go 1.21
|
||||
@@ -1,190 +0,0 @@
|
||||
// persona-gate — Go persona rule gating (R2 W5c deep port).
|
||||
// PARITY port of src/rune/persona_gate.py decide(). The persona decides
|
||||
// on a request deterministically from its stored rules.
|
||||
//
|
||||
// Usage:
|
||||
// persona-gate decide <text> [--rules <rules-json>]
|
||||
// persona-gate load-rules
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
Text string `json:"text"`
|
||||
Topic string `json:"topic"`
|
||||
Priority int `json:"priority"`
|
||||
Restrictive bool `json:"restrictive"`
|
||||
UserRule bool `json:"user_rule"`
|
||||
Words map[string]bool `json:"words"`
|
||||
Core string `json:"core"`
|
||||
}
|
||||
|
||||
type GateVerdict struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
Citations []string `json:"citations"`
|
||||
RulesEngaged []string `json:"rules_engaged"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
func lex(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
|
||||
}
|
||||
|
||||
func overlap(a, b map[string]bool) float64 {
|
||||
if len(a) == 0 || len(b) == 0 {
|
||||
return 0.0
|
||||
}
|
||||
shared := 0
|
||||
for w := range a {
|
||||
if b[w] {
|
||||
shared++
|
||||
}
|
||||
}
|
||||
minLen := len(a)
|
||||
if len(b) < minLen {
|
||||
minLen = len(b)
|
||||
}
|
||||
return float64(shared) / float64(max(1, minLen))
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func cite(rule Rule) string {
|
||||
text := rule.Text
|
||||
if len(text) > 160 {
|
||||
text = text[:160]
|
||||
}
|
||||
return fmt.Sprintf("[%s:p%d] %s", rule.Topic, rule.Priority, text)
|
||||
}
|
||||
|
||||
func decide(text string, rules []Rule, permissiveMode bool) GateVerdict {
|
||||
_ = strings.ToLower(text) // reserved for semantic matching
|
||||
inw := lex(text)
|
||||
|
||||
var engaged []string
|
||||
var citations []string
|
||||
allowed := true
|
||||
|
||||
for _, rule := range rules {
|
||||
ov := overlap(inw, rule.Words)
|
||||
|
||||
// Engagement threshold
|
||||
if ov < 0.1 {
|
||||
continue
|
||||
}
|
||||
|
||||
engaged = append(engaged, rule.Text)
|
||||
citations = append(citations, cite(rule))
|
||||
|
||||
if rule.Restrictive {
|
||||
// Check if this is a genuine boundary violation
|
||||
if ov > 0.3 {
|
||||
if permissiveMode {
|
||||
// Permissive mode: record but don't block
|
||||
continue
|
||||
}
|
||||
allowed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mode := "strict"
|
||||
if permissiveMode {
|
||||
mode = "permissive"
|
||||
}
|
||||
|
||||
return GateVerdict{
|
||||
Allowed: allowed, Citations: citations,
|
||||
RulesEngaged: engaged, Mode: mode,
|
||||
}
|
||||
}
|
||||
|
||||
// loadRulesFromDB loads rules from the SQLite memory store
|
||||
func loadRulesFromDB() []Rule {
|
||||
// In the Go port, rules come from the Survivor memory-ops binary
|
||||
// or from a JSON file
|
||||
data, err := os.ReadFile(os.Getenv("RUNE_PERSONA_RULES_FILE"))
|
||||
if err != nil {
|
||||
return []Rule{}
|
||||
}
|
||||
var rules []Rule
|
||||
json.Unmarshal(data, &rules)
|
||||
return rules
|
||||
}
|
||||
|
||||
// Rule classification helpers (parity with Python)
|
||||
func isRestrictive(body string) bool {
|
||||
low := strings.ToLower(body)
|
||||
restrictiveStarters := []string{
|
||||
"never remove", "never modify", "never touch",
|
||||
"do not touch", "do not do", "never", "forbidden", "do not",
|
||||
}
|
||||
for _, starter := range restrictiveStarters {
|
||||
if strings.HasPrefix(low, starter) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isUserRule(source string, topic string, priority int) bool {
|
||||
return source == "constitution-add" || (topic == "constitution" && priority == 0)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: persona-gate <decide|load-rules> <text> [--rules <json>]")
|
||||
os.Exit(1)
|
||||
}
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
permissiveMode := os.Getenv("PERMISSIVE_MODE") != "false"
|
||||
|
||||
switch cmd {
|
||||
case "decide":
|
||||
text := ""
|
||||
var rules []Rule
|
||||
for i := 0; i < len(args); i++ {
|
||||
if args[i] == "--rules" && i+1 < len(args) {
|
||||
json.Unmarshal([]byte(args[i+1]), &rules)
|
||||
i++
|
||||
} else if text == "" {
|
||||
text = args[i]
|
||||
}
|
||||
}
|
||||
if len(rules) == 0 {
|
||||
rules = loadRulesFromDB()
|
||||
}
|
||||
verdict := decide(text, rules, permissiveMode)
|
||||
out, _ := json.MarshalIndent(verdict, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "load-rules":
|
||||
rules := loadRulesFromDB()
|
||||
out, _ := json.MarshalIndent(rules, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/provider-registry
|
||||
|
||||
go 1.21
|
||||
@@ -1,238 +0,0 @@
|
||||
// provider-registry — Go provider registry (R2 W5b deep port).
|
||||
// PARITY port of src/rune/provider_registry.py: connect, disconnect,
|
||||
// list_providers, get_provider, list_models (Ollama + OpenAI + Anthropic).
|
||||
//
|
||||
// Usage:
|
||||
// provider-registry connect <name> <url> [provider-type] [api-key]
|
||||
// provider-registry disconnect <name>
|
||||
// provider-registry list
|
||||
// provider-registry models <name>
|
||||
// provider-registry chat <provider-name> <model> <message>
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ModelProvider struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
ProviderType string `json:"provider_type"` // openai, ollama, anthropic
|
||||
APIKey string `json:"api_key,omitempty"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
type ProviderRegistry struct {
|
||||
mu sync.RWMutex
|
||||
Providers map[string]ModelProvider `json:"providers"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
}
|
||||
|
||||
type ChatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func configPath() string {
|
||||
home := os.Getenv("RUNE_MEMORY_DIR")
|
||||
if home == "" {
|
||||
home = os.Getenv("HOME") + "/.rune"
|
||||
}
|
||||
return home + "/provider_registry.json"
|
||||
}
|
||||
|
||||
func loadRegistry() ProviderRegistry {
|
||||
reg := ProviderRegistry{Providers: make(map[string]ModelProvider)}
|
||||
if data, err := os.ReadFile(configPath()); err == nil {
|
||||
json.Unmarshal(data, ®)
|
||||
}
|
||||
return reg
|
||||
}
|
||||
|
||||
func saveRegistry(reg ProviderRegistry) {
|
||||
data, _ := json.MarshalIndent(reg, "", " ")
|
||||
os.WriteFile(configPath(), data, 0600)
|
||||
}
|
||||
|
||||
func (reg *ProviderRegistry) connect(name, url, providerType, apiKey string) ModelProvider {
|
||||
provider := ModelProvider{
|
||||
Name: name, URL: url, ProviderType: providerType,
|
||||
APIKey: apiKey, Active: true,
|
||||
}
|
||||
reg.mu.Lock()
|
||||
reg.Providers[name] = provider
|
||||
reg.mu.Unlock()
|
||||
saveRegistry(*reg)
|
||||
return provider
|
||||
}
|
||||
|
||||
func (reg *ProviderRegistry) fetchModels(provider ModelProvider) ([]string, error) {
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
var models []string
|
||||
|
||||
switch provider.ProviderType {
|
||||
case "ollama":
|
||||
resp, err := client.Get(provider.URL + "/api/tags")
|
||||
if err != nil { return nil, err }
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Models []struct { Name string `json:"name"` } `json:"models"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
for _, m := range result.Models {
|
||||
models = append(models, cleanModelName(m.Name))
|
||||
}
|
||||
case "openai", "anthropic":
|
||||
req, _ := http.NewRequest("GET", provider.URL+"/v1/models", nil)
|
||||
if provider.APIKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+provider.APIKey)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil { return nil, err }
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Data []struct { ID string `json:"id"` } `json:"data"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
for _, m := range result.Data {
|
||||
models = append(models, cleanModelName(m.ID))
|
||||
}
|
||||
}
|
||||
return models, nil
|
||||
}
|
||||
|
||||
func cleanModelName(name string) string {
|
||||
// Strip common prefixes
|
||||
name = strings.TrimPrefix(name, "models/")
|
||||
return name
|
||||
}
|
||||
|
||||
func (reg *ProviderRegistry) chat(providerName, model, message string) (string, error) {
|
||||
reg.mu.RLock()
|
||||
provider, ok := reg.Providers[providerName]
|
||||
reg.mu.RUnlock()
|
||||
if !ok {
|
||||
return "", fmt.Errorf("provider %q not found", providerName)
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 300 * time.Second}
|
||||
|
||||
var requestBody map[string]interface{}
|
||||
var url string
|
||||
|
||||
switch provider.ProviderType {
|
||||
case "ollama":
|
||||
url = provider.URL + "/api/chat"
|
||||
requestBody = map[string]interface{}{
|
||||
"model": model, "messages": []map[string]string{{"role": "user", "content": message}},
|
||||
"stream": false,
|
||||
}
|
||||
case "openai":
|
||||
url = provider.URL + "/v1/chat/completions"
|
||||
requestBody = map[string]interface{}{
|
||||
"model": model, "messages": []map[string]string{{"role": "user", "content": message}},
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported provider type: %s", provider.ProviderType)
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(requestBody)
|
||||
req, _ := http.NewRequest("POST", url, bytes.NewReader(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if provider.APIKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+provider.APIKey)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil { return "", err }
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
// Parse response based on provider type
|
||||
var result map[string]interface{}
|
||||
json.Unmarshal(body, &result)
|
||||
if content, ok := result["content"].(string); ok {
|
||||
return content, nil // Ollama format
|
||||
}
|
||||
if choices, ok := result["choices"].([]interface{}); ok && len(choices) > 0 {
|
||||
if choice, ok := choices[0].(map[string]interface{}); ok {
|
||||
if msg, ok := choice["message"].(map[string]interface{}); ok {
|
||||
if content, ok := msg["content"].(string); ok {
|
||||
return content, nil // OpenAI format
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: provider-registry <connect|disconnect|list|models|chat> [args...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
reg := loadRegistry()
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
switch cmd {
|
||||
case "connect":
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: provider-registry connect <name> <url> [type] [key]")
|
||||
os.Exit(1)
|
||||
}
|
||||
name, url := args[0], args[1]
|
||||
providerType := "ollama"
|
||||
apiKey := ""
|
||||
if len(args) > 2 { providerType = args[2] }
|
||||
if len(args) > 3 { apiKey = args[3] }
|
||||
provider := reg.connect(name, url, providerType, apiKey)
|
||||
out, _ := json.MarshalIndent(provider, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "disconnect":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: provider-registry disconnect <name>"); os.Exit(1) }
|
||||
reg.mu.Lock()
|
||||
delete(reg.Providers, args[0])
|
||||
reg.mu.Unlock()
|
||||
saveRegistry(reg)
|
||||
fmt.Println("disconnected:", args[0])
|
||||
|
||||
case "list":
|
||||
for _, p := range reg.Providers {
|
||||
out, _ := json.Marshal(p)
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
case "models":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: provider-registry models <name>"); os.Exit(1) }
|
||||
provider, ok := reg.Providers[args[0]]
|
||||
if !ok { fmt.Println("null"); return }
|
||||
models, err := reg.fetchModels(provider)
|
||||
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
|
||||
out, _ := json.MarshalIndent(models, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "chat":
|
||||
if len(args) < 3 {
|
||||
fmt.Fprintln(os.Stderr, "usage: provider-registry chat <provider> <model> <message>")
|
||||
os.Exit(1)
|
||||
}
|
||||
response, err := reg.chat(args[0], args[1], strings.Join(args[2:], " "))
|
||||
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
|
||||
out, _ := json.Marshal(map[string]string{"response": response})
|
||||
fmt.Println(string(out))
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
module rune/recall-router
|
||||
|
||||
go 1.21
|
||||
Loaded 100 of 376 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user