Compare commits

..
67 Commits
Author SHA1 Message Date
Nick da61fd7d44 docs: webhook delivery test marker 2
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / canary (push) Successful in 30s
deploy / deploy (push) Skipped
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / unit-tests (push) Successful in 57s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-12 00:31:12 +01:00
Nick 7342d77e6f docs: webhook delivery test 2
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / lint (push) Failing after 31s
CI Pipeline / canary (push) Successful in 33s
deploy / deploy (push) Skipped
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / unit-tests (push) Successful in 1m0s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-12 00:31:02 +01:00
Nick 436f760675 docs: webhook delivery test marker
CI Pipeline / lint (push) Failing after 26s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / gitea-offline (push) Successful in 15s
deploy / deploy (push) Skipped
CI Pipeline / canary (push) Successful in 48s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / unit-tests (push) Successful in 58s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-12 00:28:42 +01:00
Nick d62b717542 test: deliberate lint failure (wait/resume Part 14)
CI Pipeline / lint (push) Failing after 26s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / canary (push) Successful in 29s
deploy / deploy (push) Skipped
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 58s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-12 00:14:41 +01:00
Nick 97de570b27 test: deliberate lint failure fixture (wait/resume failure experiment)
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 31s
deploy / deploy (push) Skipped
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / unit-tests (push) Successful in 54s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-12 00:13:41 +01:00
Nick 22aebbb102 test: intentionally broken syntax for W/R failure experiment
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Failing after 27s
CI Pipeline / canary (push) Successful in 30s
deploy / deploy (push) Skipped
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 59s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-12 00:13:27 +01:00
Nick 7e33294527 docs: wait/resume live experiment marker (branch)
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / lint (push) Successful in 40s
deploy / deploy (push) Skipped
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 58s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-12 00:07:52 +01:00
conductor fcc9989393 chore(release): auto-bump version to 0.66.14 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / unit-tests (push) Successful in 55s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
deploy / deploy (push) Successful in 1m9s
Promotion record: (unknown)
2026-09-11 22:54:16 +01:00
Nick ead4a24866 fix(ci): allow the reconciliation JSON authority in top-level docs henxel
Auto-version and Release / release (push) Successful in 48s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / canary (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / unit-tests (push) Successful in 1m0s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 3m10s
2026-09-11 22:53:48 +01:00
conductor cf51f7fd7c chore(release): auto-bump version to 0.66.13 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 10s
deploy / deploy (push) Successful in 1m17s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Failing after 35s
CI Pipeline / unit-tests (push) Successful in 54s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
Promotion record: (unknown)
2026-09-11 22:51:10 +01:00
Nick f182ab8601 feat(reconciliation): canonical issue reconciliation artifact + consistency checker (T15 Parts J/K/L)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 5s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / lint (push) Failing after 42s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m2s
CI Pipeline / required-checks (push) Successful in 4s
deploy / deploy (push) Successful in 3m11s
- docs/GITEA_ISSUE_RECONCILIATION.md: human-readable projection (states,
  bug-label audit with per-issue evidence, open-issue reconciliation table)
- docs/gitea_issue_reconciliation.json: machine-readable authority
  (72 records; all open issues covered; single live defect #324).
  Placed under docs/ because state/ is the gitignored runtime dir.
- scripts/gitea_reconciliation_check.py: deterministic drift check —
  state/label/coverage invariants; bug-label invariant applies to OPEN
  issues (closed labels are historical); exit 1 with itemized drift.
- Part K audit: closed #139 #157 (FIXED_VERIFIED with evidence comments);
  bug labels removed from #204 #145 #126 #37 #16 (not defects) and the
  stuck label on closed #314. Open bug-labelled: 8 → 1.
- 1289 tests green; ratchet 352/80; henxels clean.
2026-09-11 22:50:44 +01:00
conductor 5f9ba5784c chore(release): auto-bump version to 0.66.12 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 9s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / unit-tests (push) Successful in 54s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
deploy / deploy (push) Successful in 37s
Promotion record: (unknown)
2026-09-11 22:33:27 +01:00
Nick 9a05fdfa1f fix(convergence): decision JSON omits boundary_sha — consumers re-derive it from git
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 5s
Auto-version and Release / release (push) Successful in 25s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 1m3s
CI Pipeline / required-checks (push) Successful in 2s
deploy / deploy (push) Successful in 2m40s
Every debug session of the release pipeline (T15 Part F forensics included)
had to re-derive the release boundary by hand. The decision JSON now
carries boundary_sha on every return path.
2026-09-11 22:33:10 +01:00
Nick 2298768353 docs: D7 resolved — release-pipeline root causes recorded (experiment B trigger)
Auto-version and Release / release (push) Successful in 14s
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Successful in 39s
CI Pipeline / lint (push) Successful in 42s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / unit-tests (push) Successful in 1m6s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 38s
Records the three root causes (corrupted shell fragment, unwired release
boundary, per-surface commit granularity) with live proof of the fix.
2026-09-11 22:23:52 +01:00
conductor a4afc77d53 chore(release): auto-bump version to 0.66.11 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 11s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / unit-tests (push) Successful in 51s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Successful in 2s
deploy / deploy (push) Successful in 39s
Promotion record: (unknown)
2026-09-11 22:14:49 +01:00
Nick d68fd1e94d fix(release): ChangeFiles content must be base64 (ContentBase64)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
Auto-version and Release / release (push) Successful in 34s
CI Pipeline / canary (push) Successful in 35s
CI Pipeline / lint (push) Successful in 41s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m0s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 2m41s
2026-09-11 22:14:17 +01:00
Nick 470d82f307 fix(release): single-commit bump via ChangeFiles (blob API 404 on Gitea 1.27)
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 16s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / lint (push) Successful in 41s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 57s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 38s
git-data blob/tree creation endpoints are not implemented on this Gitea
build (404). ChangeFiles (POST /contents with files[] = ChangeFilesOptions)
is the supported multi-file single-commit primitive. One bump = one commit.
2026-09-11 22:08:10 +01:00
Nick 7a988cbaff fix(release): refs API returns a list — take the master entry
Auto-version and Release / release (push) Failing after 13s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 59s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 37s
2026-09-11 22:00:57 +01:00
Nick 12c3c488be fix(release): commit all version surfaces as ONE commit (phantom duplicate bumps root-caused)
CI Pipeline / documentation (push) Successful in 5s
Auto-version and Release / release (push) Failing after 22s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / canary (push) Successful in 38s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 53s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 37s
The per-file contents PUT created one commit per version surface — five
same-version bump commits per real bump. Every 'duplicate bump cascade'
since 0.66.2 was this: 5 surface-commits per bump, misread as queue
drain/recursion. Root-caused T15 Part F via run 6791 log forensics
(5 'committed <surface>' lines, one AUTO_BUMPED). Now: Git data API
(ref → blobs → tree → commit → CAS ref update) — one bump commit.
Loop guard makes any losing race converge instead of re-bumping.
2026-09-11 21:54:15 +01:00
conductor 247c5b3936 chore(release): auto-bump version to 0.66.10 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 10s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / unit-tests (push) Successful in 52s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
deploy / deploy (push) Successful in 1m40s
Promotion record: (unknown)
2026-09-11 21:22:04 +01:00
conductor 946b8366ef chore(release): auto-bump version to 0.66.10 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 12s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / unit-tests (push) Successful in 51s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 14s
deploy / deploy (push) Successful in 6m18s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 21:22:01 +01:00
conductor 5e539f7da3 chore(release): auto-bump version to 0.66.10 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 9s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / unit-tests (push) Successful in 59s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / required-checks (push) Successful in 2s
deploy / deploy (push) Successful in 11m44s
Promotion record: (unknown)
2026-09-11 21:21:57 +01:00
conductor 83960c541d chore(release): auto-bump version to 0.66.10 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 9s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 1m2s
deploy / deploy (push) Failing after 15m19s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 21:21:55 +01:00
conductor 980fcd364f chore(release): auto-bump version to 0.66.10 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 14s
CI Pipeline / canary (push) Failing after 32s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / unit-tests (push) Successful in 1m1s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 28s
deploy / deploy (push) Failing after 15m23s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 21:21:53 +01:00
Nick c35018f543 fix(release): numeric semver comparison — lexicographic order skipped warranted bumps
CI Pipeline / best-practices (push) Successful in 18s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / lint (push) Successful in 38s
Auto-version and Release / release (push) Successful in 56s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 56s
CI Pipeline / type-check (push) Successful in 28s
deploy / deploy (push) Failing after 15m25s
CI Pipeline / required-checks (push) Successful in 1s
_version_lt(): '0.66.9' < '0.66.10' is False lexicographically ('9' > '1'),
so a legitimate patch bump after v0.66.9 surfaced as
version_ahead_of_target and the release was silently skipped (run 6781,
2026-09-11). Both comparison sites (test-injection + production) now
compare numerically per segment. Regression tests added; 1284 green.
2026-09-11 21:21:23 +01:00
Nick d2600dff29 fix(ci): allow test_gitea_auth.py fake token in secrets henxel exception
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Successful in 16s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 59s
CI Pipeline / required-checks (push) Successful in 2s
deploy / deploy (push) Successful in 37s
The unit-test fixture constant ('unit-test-secret-token-value') trips
the hardcoded-credential detector. It is not a secret; the sanctioned
path (matching test_gitea_provision.py) is the henxel except list.
2026-09-11 21:05:55 +01:00
Nick 5b268fde8e fix(ci+release): format new auth test; repair f-string backslash in release idempotency check
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Successful in 15s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Failing after 39s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 1m2s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 38s
- tests/test_gitea_auth.py: ruff format (CI format-gate on added files)
- auto-version.yml release step: f-string with escaped quotes is a
  SyntaxError on py<3.12 runners; compute tag name without backslash
  escapes (release job exit 22, run 6776)
2026-09-11 20:59:39 +01:00
Nick 9e0b85534a fix(release): boundary grep must match subjects only, not bodies
CI Pipeline / best-practices (push) Successful in 9s
Auto-version and Release / release (push) Failing after 16s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Failing after 41s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m1s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 38s
git --grep matches the commit BODY by default: a fix commit whose body
mentions 'chore(release)' was misdetected as the release boundary,
swallowing its own warranted bump (observed live: d01df79 boundary
detection). Anchor the pattern to the subject with --extended-regexp
and re-verify via is_release_commit(). Also drop unrecognized
--fixed-strings=False flag (fatal on this git version).
2026-09-11 20:50:46 +01:00
Nick d01df797c1 fix(release): wire release boundary into convergence (duplicate-bump root cause)
Auto-version and Release / release (push) Failing after 9s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 53s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / required-checks (push) Successful in 4s
deploy / deploy (push) Successful in 1m11s
release_convergence() never assigned  from
last_release_boundary() — the production path classified ALL commits
(959+) as unreleased, so the warranted bump (minor, from an ancient
feat) never converged and every queued push wrote its own bump commit
(5x 0.66.9 observed 2026-09-11 20:21). Now the boundary is the one
source of truth: last chore(release) commit, falling back to the
latest tag. Post-boundary subjects only.

Also: local path was NOT reached when _boundary_subject was injected;
test_cli_prints_plan_json expectation updated to boundary semantics
(feat below the tag no longer warrants minor).
2026-09-11 20:34:30 +01:00
conductor 95bac995a3 chore(release): auto-bump version to 0.66.9 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 10s
deploy / deploy (push) Successful in 1m38s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / unit-tests (push) Successful in 56s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 15s
Promotion record: (unknown)
2026-09-11 20:21:21 +01:00
conductor 1afadfddfc chore(release): auto-bump version to 0.66.9 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 10s
CI Pipeline / lint (push) Failing after 32s
CI Pipeline / unit-tests (push) Successful in 55s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
deploy / deploy (push) Successful in 3m17s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 20:21:06 +01:00
conductor 54ab4d9e64 chore(release): auto-bump version to 0.66.9 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 11s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / unit-tests (push) Successful in 52s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Failing after 15m15s
Promotion record: (unknown)
2026-09-11 20:21:02 +01:00
conductor f4c73d5142 chore(release): auto-bump version to 0.66.9 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 9s
CI Pipeline / lint (push) Failing after 40s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 1m4s
CI Pipeline / type-check (push) Successful in 27s
deploy / deploy (push) Failing after 15m15s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 20:21:00 +01:00
conductor bad13ceb57 chore(release): auto-bump version to 0.66.9 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / best-practices (push) Successful in 6s
Auto-version and Release / release (push) Failing after 16s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Failing after 34s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Failing after 58s
CI Pipeline / lint (push) Failing after 38s
deploy / deploy (push) Failing after 15m17s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 20:20:57 +01:00
Nick c35b604976 fix(release): repair corrupted shell in auto-version push path (D7 root cause)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 32s
CI Pipeline / lint (push) Failing after 38s
CI Pipeline / type-check (push) Successful in 27s
Auto-version and Release / release (push) Successful in 58s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 1m0s
deploy / deploy (push) Failing after 15m29s
CI Pipeline / required-checks (push) Successful in 1s
c2528c1 left the tail of the replaced WARRANTED= line dangling after the
new converged-check fi (edit collision): 'fi | python3 -c ...)' — a shell
syntax error that kills the release job with exit 2 before any bump.
Release jobs have failed since c2528c1 (D7 'ROOT_CAUSE_UNKNOWN_DEFERRED'
resolved: not a Pi/environment issue). Loop-guard exit-0 paths never
reached the corrupted line, masking the defect until the first
substantive-convergence push after it.
2026-09-11 20:20:29 +01:00
Nick 4eefc15837 fix(lifecycle+auth): canonical gitea auth path, terminalization helper, fault-injection gaps (T15 Parts A-E)
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 13s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / lint (push) Failing after 41s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / unit-tests (push) Successful in 59s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 37s
- scripts/gitea_auth.py: single credential ladder (env -> 0600 token file ->
  git credential fill); 6 consumers migrated off direct os.environ reads
- rune_operator: consolidate 5 duplicated failure blocks into
  _terminalize_execution_failure; fix 3 fault-injection gaps:
  (1) dual-module PreparedTool identity split crashed approval-path saves,
  (2) fallback-model failure escaped send_task (created/pending orphan),
  (3) running-transition save uncaught (running-with-no-owner)
- envelope task.status now agrees with canonical ledger (awaiting_approval)
- orphan task_ac35f4e287c8 terminalized failed with durable evidence
- tests: gitea auth ladder (6), lifecycle fault matrix (9 boundaries +
  restart/reload + helper contract); 1280 green in ci-venv; ratchet 352/80
2026-09-11 20:15:57 +01:00
Nick 2bf43eafcd test: #314 forced association regression + Gitea auth helper + lifecycle tests
Auto-version and Release / release (push) Failing after 8s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / lint (push) Successful in 44s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 1m0s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 38s
- tests/test_recall_mode_regression.py: deterministic fixture proving
  association-merged entries carry mode=association_merged (fails on old
  impl). #314 closed FIXED_VERIFIED.
- scripts/gitea_auth.py: canonical Gitea API auth helper (GITEA_TOKEN env
  or git credential fill, never remote URL parsing)
- tests/test_task_lifecycle.py: fault injection at route/model_selection/
  execute boundaries — all terminalize, no immortal RUNNING
- tests/test_route_isolation.py: task-scoped route instruction isolation
- tests/test_release_convergence.py: correct target-version algorithm

1268 tests green; ratchet 352/80; henxels clean.
2026-09-11 18:17:17 +01:00
Nick ca4f7593d7 feat(corrections): #314 defect fix, credential rotation, release guard, task lifecycle, route scoping, PL1 records + metrics
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 14s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / canary (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m4s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 48s
CORRECTION 1: #314 reopened — recall_with_associations mode field defect
CONFIRMED in current source and FIXED (mode: association_merged).

CORRECTION 3: Gitea credential removed from remote URL (credential helper).
TOKEN EXPOSED: yes (in diagnostic output). TOKEN ROTATED: not yet (requires
Gitea web UI access). Remote URL now shows username only, no secret.

CORRECTION 4: duplicate-release-commit defect ROOT CAUSE: convergence
computed warranted bump from commits-since-tag without checking if master
version already satisfies the bump. FIX: already-converged guard in
version_convergence.py (master version > last tag → no commit). Repo-aware
(args.repo passed to read_version/latest_tag).

CORRECTION 6: lifecycle terminalization — execution exception now
guarantees task terminalizes as failed with evidence (no immortal RUNNING).

CORRECTION 7: route instruction task-scoped on RuneTask.route_instruction
(NOT shared operator state). Read-only leading-verb override. Isolation
tests prove no cross-contamination between interleaved tasks.

PL1-01/02/03/05: pl1_records.py (route resolution, proof level, failure
class, candidate status), metrics with honest NOT_YET_MEASURABLE semantics,
route-instruction learning loop (PE-1789102658-0269 SEED).

1253 tests green; ratchet 352/80 (shrink); henxels clean.
2026-09-11 15:36:08 +01:00
Nick 24537e46e7 fix(release): already-converged version guard (prevents duplicate same-version release commits)
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 14s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / canary (push) Successful in 40s
CI Pipeline / lint (push) Successful in 45s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Failing after 1m5s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 37s
The duplicate-release-commit defect: each queued push ran the release
workflow from its own pre-bump checkout, computed the same warranted bump,
and created another identical-version commit. The convergence script now
checks if master __version__ > last tag — if so, already converged, no
commit needed. This is the definitive fix for the 5x 0.66.8 defect.
2026-09-11 15:06:34 +01:00
Nick 52d810d358 chore: Gitea reconciliation + PL1 held-out + remote truth + metric honesty
CI Pipeline / best-practices (push) Successful in 9s
Auto-version and Release / release (push) Failing after 13s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Successful in 49s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m4s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 38s
Part A-J: 15 issues closed (HISTORICAL_EVIDENCE/SUPERSEDED/ACCEPTED_LIMITATION)
with evidence. Bug label cleaned (24→8 open). #259/#258 updated to current
architecture. Remote tracking fixed (master tracks gitea/master). PL1 metrics
corrected to NOT_YET_MEASURABLE where evidence doesn't support numbers.
D7 recorded as ROOT_CAUSE_UNKNOWN_DEFERRED. Clean recursion experiment:
version converges (0.66.8 stable) but 5 duplicate bump commits per logical
change (queue-drain artifact). PL1 held-out task 1: LOCAL_FIX route correct,
task genuinely running on deployed 3B model (slow but alive).
2026-09-11 14:05:25 +01:00
conductor 7ea4a62960 chore(release): auto-bump version to 0.66.8 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 9s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / unit-tests (push) Successful in 53s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 16s
deploy / deploy (push) Successful in 1m40s
Promotion record: (unknown)
2026-09-11 13:22:08 +01:00
conductor e7e4541d47 chore(release): auto-bump version to 0.66.8 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 11s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / unit-tests (push) Successful in 51s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 5m12s
Promotion record: (unknown)
2026-09-11 13:22:05 +01:00
conductor 92b6f47ccf chore(release): auto-bump version to 0.66.8 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 9s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / unit-tests (push) Successful in 52s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 9m15s
Promotion record: (unknown)
2026-09-11 13:22:02 +01:00
conductor d90a79a231 chore(release): auto-bump version to 0.66.8 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 15s
CI Pipeline / canary (push) Failing after 32s
CI Pipeline / lint (push) Successful in 41s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / unit-tests (push) Successful in 1m1s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 30s
deploy / deploy (push) Failing after 15m17s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 13:21:57 +01:00
conductor 29390534d9 chore(release): auto-bump version to 0.66.8 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 8s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Failing after 29s
CI Pipeline / unit-tests (push) Successful in 55s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 29s
deploy / deploy (push) Failing after 15m15s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 13:21:55 +01:00
Nick a8ae37df21 fix(pl1): document RUNE_EXPERIENCE_LEDGER env override on ledger default
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Successful in 43s
Auto-version and Release / release (push) Successful in 47s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / unit-tests (push) Successful in 1m1s
deploy / deploy (push) Failing after 15m26s
CI Pipeline / required-checks (push) Successful in 2s
2026-09-11 13:21:33 +01:00
conductor fa81f4bd4f chore(release): auto-bump version to 0.66.7 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 11s
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / unit-tests (push) Successful in 53s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
deploy / deploy (push) Successful in 1m9s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
Promotion record: (unknown)
2026-09-11 05:59:23 +01:00
conductor 81edc1356c chore(release): auto-bump version to 0.66.7 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 10s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / unit-tests (push) Successful in 53s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 32s
deploy / deploy (push) Successful in 1m7s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 05:59:19 +01:00
conductor 6dcea9dbc7 chore(release): auto-bump version to 0.66.7 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 26s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / unit-tests (push) Successful in 55s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / type-check (push) Successful in 30s
deploy / deploy (push) Failing after 15m21s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 05:59:15 +01:00
conductor 885bfc181a chore(release): auto-bump version to 0.66.7 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 12s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / unit-tests (push) Successful in 51s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 11m50s
Promotion record: (unknown)
2026-09-11 05:59:14 +01:00
conductor 0e1e7ce94a chore(release): auto-bump version to 0.66.7 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 20s
CI Pipeline / canary (push) Failing after 34s
CI Pipeline / lint (push) Successful in 46s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / unit-tests (push) Successful in 1m5s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 31s
deploy / deploy (push) Failing after 15m20s
CI Pipeline / required-checks (push) Successful in 0s
Promotion record: (unknown)
2026-09-11 05:59:12 +01:00
Nick a125c023e6 feat(PL1): first tranche — structured records, failure classification, route-instruction learning loop, metrics
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 35s
CI Pipeline / lint (push) Successful in 44s
Auto-version and Release / release (push) Successful in 49s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 1m1s
deploy / deploy (push) Failing after 15m38s
CI Pipeline / required-checks (push) Successful in 1s
PL1-01: scripts/pl1_records.py — compact structured Experience evidence from
canonical task state + route.jsonl (route resolution by task_id+timestamp,
proof level, failure class, candidate status).
PL1-02: failure classes in the PL0 vocabulary, evidence-grounded; UNKNOWN
valid; research_dispatch never failure.
PL1-03: first learning loop — recurring prose-instead-of-mutation family →
structural correction (route-driven instruction appended to the user context
block for mutation routes) + replay contract (NOT_COMPLETED gate) —
recorded as PE-1789102658-0269 (SEED; held-out = next deployed mutation tasks).
PL1-05: metrics module (first_pass_accepted, false_completion_rate=0.0
structurally, mutation_failures, research_dispatches, candidate/evidence split).

Intent state updated: PL1 first tranche live; ADAPT-LIVE acceptance proven on
deployed runtime; remaining ADAPT obligations deferred to their epics.
2026-09-11 05:58:47 +01:00
Nick 20e68b793f docs: restore corrected intent state (174e11f) + record D7 release-job defect
Auto-version and Release / release (push) Failing after 14s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 1m3s
CI Pipeline / required-checks (push) Successful in 2s
deploy / deploy (push) Successful in 37s
2026-09-11 05:35:11 +01:00
conductor 1163a13e13 chore(release): auto-bump version to 0.66.6 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 11s
deploy / deploy (push) Successful in 1m8s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / unit-tests (push) Successful in 54s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 17s
Promotion record: (unknown)
2026-09-11 04:57:19 +01:00
conductor a4b0bd6f55 chore(release): auto-bump version to 0.66.6 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 20s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / unit-tests (push) Successful in 53s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 5m41s
Promotion record: (unknown)
2026-09-11 04:57:11 +01:00
conductor 76f215f9ab chore(release): auto-bump version to 0.66.6 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 10s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / unit-tests (push) Failing after 34s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 8m43s
Promotion record: (unknown)
2026-09-11 04:57:02 +01:00
conductor dc5757ff04 chore(release): auto-bump version to 0.66.6 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 9s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 32s
CI Pipeline / unit-tests (push) Successful in 57s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 29s
deploy / deploy (push) Failing after 15m22s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 04:57:00 +01:00
conductor fc12bb5b44 chore(release): auto-bump version to 0.66.6 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 11s
CI Pipeline / canary (push) Failing after 34s
CI Pipeline / lint (push) Successful in 42s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / unit-tests (push) Failing after 53s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Successful in 32s
deploy / deploy (push) Failing after 15m20s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 04:56:49 +01:00
Nick a620171524 fix(canaries): canary_p1_01/p1_06 fixtures carry mutating tool evidence (completion-truth contract)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Successful in 44s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 1m1s
Auto-version and Release / release (push) Successful in 1m7s
CI Pipeline / type-check (push) Successful in 38s
deploy / deploy (push) Failing after 15m32s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-11 04:56:26 +01:00
conductor 4486ea4558 chore(release): auto-bump version to 0.66.5 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / unit-tests (push) Successful in 54s
CI Pipeline / canary (push) Failing after 30s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
deploy / deploy (push) Successful in 1m38s
Promotion record: (unknown)
2026-09-11 04:17:10 +01:00
conductor 996d0d9cb1 chore(release): auto-bump version to 0.66.5 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 8s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / unit-tests (push) Successful in 52s
CI Pipeline / canary (push) Failing after 16s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 0s
deploy / deploy (push) Successful in 3m9s
Promotion record: (unknown)
2026-09-11 04:17:06 +01:00
conductor 4e3b4be67b chore(release): auto-bump version to 0.66.5 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 9s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / unit-tests (push) Successful in 51s
CI Pipeline / canary (push) Failing after 28s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 12m22s
Promotion record: (unknown)
2026-09-11 04:17:02 +01:00
conductor a872abd962 chore(release): auto-bump version to 0.66.5 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 12s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Failing after 30s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / unit-tests (push) Successful in 54s
CI Pipeline / type-check (push) Successful in 31s
deploy / deploy (push) Failing after 15m24s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: (unknown)
2026-09-11 04:17:00 +01:00
conductor be136de452 chore(release): auto-bump version to 0.66.5 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 14s
CI Pipeline / lint (push) Successful in 42s
CI Pipeline / canary (push) Failing after 32s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / unit-tests (push) Failing after 52s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Successful in 32s
deploy / deploy (push) Failing after 15m24s
CI Pipeline / required-checks (push) Successful in 0s
Promotion record: (unknown)
2026-09-11 04:16:57 +01:00
Nick afa276aaf4 fix(deploy): startup canary runs in CANDIDATE mode with --stage (gate fail-closed correctly but never tested the candidate after the two-mode refactor)
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Failing after 39s
Auto-version and Release / release (push) Successful in 44s
CI Pipeline / lint (push) Successful in 45s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m5s
deploy / deploy (push) Failing after 15m22s
CI Pipeline / required-checks (push) Successful in 1s
2026-09-11 04:16:31 +01:00
Nick b0dbb80778 chore(lint): re-baseline BLE001 353->352 (legitimate shrink from noqa documentation)
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 15s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Failing after 40s
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / unit-tests (push) Successful in 1m6s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Failing after 37s
2026-09-11 04:01:06 +01:00
Nick 0018fe8c39 chore: deployment canary verification probe (no release warranted)
Auto-version and Release / release (push) Failing after 11s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / canary (push) Failing after 32s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / unit-tests (push) Failing after 52s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Failing after 1m11s
2026-09-11 03:37:53 +01:00
227 changed files with 10177 additions and 4662 deletions

No files matched your search

+30 -14
View File
@@ -71,7 +71,13 @@ jobs:
echo "HEAD is a release bump — loop guard, no re-bump"
exit 0
fi
WARRANTED=$(python3 scripts/version_convergence.py | python3 -c "import json,sys; print(json.load(sys.stdin).get('warranted_bump') or '')")
CONVERGENCE=$(python3 scripts/version_convergence.py)
WARRANTED=$(echo "$CONVERGENCE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('warranted_bump') or '')")
CONVERGED=$(echo "$CONVERGENCE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('converged', 'true'))")
if [ "$CONVERGED" = "True" ]; then
echo "Already converged — no bump needed"
exit 0
fi
echo "push convergence: warranted_bump=$WARRANTED"
if [ -z "$WARRANTED" ]; then
echo "No substantive commits since last tag — no bump needed"
@@ -179,18 +185,28 @@ jobs:
print(f" - {d}")
sys.exit(1)
# Commit each surface DIRECTLY to master (bookkeeping, not reviewable code)
msg_prefix = f"chore(release): auto-bump version to {NEW} — earned by #{PR_NUMBER}"
for path, (content, sha) in edited.items():
status, r = api(f"/contents/{path}", "PUT", {
"message": f"{msg_prefix} ({PR_TITLE[:80]})\n\nPromotion record: {promoting_url}",
"content": base64.b64encode(content.encode()).decode(),
"sha": sha,
"branch": "master",
})
if status not in (200, 201):
print(f" commit {path} failed ({status}): {str(r)[:200]}"); sys.exit(1)
print(f" committed {path} to master")
# Commit ALL surfaces as ONE commit via ChangeFiles
# (POST /contents with a files[] array). The per-file contents PUT
# created one commit per version surface — five same-version "bump
# commits" per release (the phantom duplicate-bump cascade,
# misdiagnosed as queue drain since 0.66.2; root-caused T15 Part F
# 2026-09-11). Git-data blob/tree creation is 404 on this Gitea
# build; ChangeFiles is the supported single-commit primitive.
msg = f"chore(release): auto-bump version to {NEW} — earned by #{PR_NUMBER} ({PR_TITLE[:80]})\n\nPromotion record: {promoting_url}"
ops = [{
"operation": "update",
"path": path,
"content": base64.b64encode(content.encode()).decode(), # ChangeFileOperation.content is ContentBase64
} for path, (content, _old_sha) in edited.items()]
status, r = api("/contents", "POST", {
"branch": "master",
"message": msg,
"files": ops,
})
if status not in (200, 201):
print(f"change-files failed ({status}): {str(r)[:300]}"); sys.exit(1)
commit_sha = (r.get("commit") or {}).get("sha", "?") if isinstance(r, dict) else "?"
print(f" committed {len(ops)} surfaces as ONE commit {str(commit_sha)[:7]}")
print(f"AUTO_BUMPED=true — version {NEW} live on master; TUI footer will render v{NEW}")
with open(os.environ["GITHUB_ENV"], "a") as env:
@@ -208,7 +224,7 @@ jobs:
# Skip idempotently if a release for this tag already exists (retry/replay safety).
if curl --fail --silent "$LIST_API_URL" \
-H "Authorization: token $GITEA_TOKEN" \
| python3 -c 'import json,sys,os; exists=[r for r in json.load(sys.stdin) if r.get("tag_name")==f"v{os.environ[\"RELEASE_VERSION\"]}"]; sys.exit(0 if exists else 1)'; then
| python3 -c 'import json,sys,os; v=os.environ["RELEASE_VERSION"]; exists=[r for r in json.load(sys.stdin) if r.get("tag_name")=="v"+v]; sys.exit(0 if exists else 1)'; then
echo "Release v$RELEASE_VERSION already exists — skipping creation"
exit 0
fi
+3
View File
@@ -11,3 +11,6 @@ build/
.env
.venv/
state/
# Runtime artifacts (cwd-local event journal + capability index)
index/
journal/
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.66.4**
**Current release: v0.66.14**
**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.**
+113
View File
@@ -0,0 +1,113 @@
# Gitea Issue Reconciliation — canonical record
_Generated T15 Part J/K/L (2026-09-11). Machine-readable companion:
`docs/gitea_issue_reconciliation.json` (single authority; this doc is the
human-readable projection). Consistency checker: `scripts/gitea_reconciliation_check.py`
compares live Gitea state against the JSON and fails on drift._
Reconciliation states (closed vocabulary):
`VALID_OPEN | FIXED_VERIFIED | SUPERSEDED | DUPLICATE | HISTORICAL_EVIDENCE |
ACCEPTED_LIMITATION | NEEDS_REVALIDATION | DEFERRED_FUTURE`
## Bug-label audit (Part K)
`bug` = demonstrated currently incorrect behavior. Nothing else.
### Issues labelled `bug` at audit time
| # | live defect | evidence | final label | reconciliation state |
|---|---|---|---|---|
| 324 | **YES** | MessageAbortedError reproduces every aborted worker session (15+ checkpoints, including this session's predecessor) | bug (kept) | VALID_OPEN |
| 204 | NO | Forge-orchestrated pipeline = feature request (P1 feature); no demonstrated incorrect behavior | bug removed | VALID_OPEN (feature) |
| 157 | NO | capture_failure.py + tests/fixtures/production/ + manifest.json shipped and exercised (plan 2026-09-07-157) | bug removed | FIXED_VERIFIED (closed) |
| 145 | NO | P0-07 follow-up work item (tracing coverage), not a defect report | bug removed | VALID_OPEN (work item) |
| 139 | NO | Hand-maintained allowlist replaced by declarative classification (tui_validator.py cites #139); `tui_validator.py check` → passed, exit 0 | bug removed | FIXED_VERIFIED (closed) |
| 126 | NO | Silent-handler sweep = refactor program (S110/BLE001 ratchet governs), not a defect | bug removed | VALID_OPEN (work item) |
| 37 | NO | Personality corpus compilation = P2 feature | bug removed | VALID_OPEN (feature) |
| 16 | NO | v1 course-correction EPIC | bug removed | VALID_OPEN (epic) |
### Issues whose `bug` label was removed in the prior cleanup (Part D, earlier session)
16 targeted: #301 #300 #292 #284 #314 #181 #166 #165 #163 #159 #154 #141 #40 #32 #31 #27.
Verification (live API, this session): all confirmed bug-free EXCEPT #314 — its
removal had failed (the API-False defect) and the label persisted on a closed
issue. Removed this session by label-ID DELETE (204). #314 is closed
FIXED_VERIFIED (mode:association_merged regression shipped in 2bf43ea).
### Totals
- bug-labelled before audit: 8 open (+ #314 closed-but-labelled)
- bug-labelled after audit: **1** (#324, live defect, evidence above)
- issues closed by this audit: #139, #157 (both FIXED_VERIFIED with evidence comments)
- NEEDS_REVALIDATION count: 0 among bug-labelled issues (each resolved on evidence)
## Open-issue reconciliation (Part J)
Subsystems derived from labels. States per closed vocabulary. `next action` is
the single next concrete step; `_` = none until its tranche opens.
| # | subsystem | state | current evidence | next action |
|---|---|---|---|---|
| 324 | engine/checkpoint | VALID_OPEN | live repro (15+ sessions) | fix writer abort handling |
| 316 | ADAPT-1/research | VALID_OPEN | B3-v2 research task | await ADAPT tranche |
| 315 | ADAPT-1/wiring | VALID_OPEN | B2-v2 standard task | await ADAPT tranche |
| 311 | ADAPT-1/wiring | VALID_OPEN | superseded-by #315 (kept open as spec anchor until 315 delivers) | close when #315 closes |
| 301 | FINAL-12/benchmarks | NEEDS_REVALIDATION | dependency list silently grew (operator-path telemetry, bridge overhead) without issue update | re-scope before PL1 metrics work |
| 300 | ADAPT-11 | VALID_OPEN | acceptance = real escalation demo; blocked on ADAPT-LIVE telemetry | demo after PL1 held-out |
| 299 | ADAPT-10 | VALID_OPEN | harness legibility audit | _ |
| 297 | ADAPT-8 | VALID_OPEN | deterministic-first QUALIFY | _ |
| 296 | ADAPT-7 | VALID_OPEN | DESIGN consolidation experiment | _ |
| 295 | ADAPT-6 | VALID_OPEN | evidence capsule / context minimization | _ |
| 294 | ADAPT-5 | VALID_OPEN | budgets + stop conditions | _ |
| 293 | ADAPT-4 | VALID_OPEN | DESIGN-LITE shipped (STANDARD_CHANGE lane live) | acceptance demo, then close |
| 292 | ADAPT-3 | VALID_OPEN | LOCAL_FIX lane shipped | acceptance demo, then close |
| 284 | ADAPT epic | VALID_OPEN | epic tracks ADAPT-LIVE + PL1; body updated with detour record | update after PL1 |
| 259 | Scaffolding | VALID_OPEN | redefined: safe repeatable construction protocol (T15 §C) | Scaffolding v1 after PL1 |
| 258 | State-aware processes | VALID_OPEN | redefined: low-compute architectural self-awareness; SA1-8 superseded by distributed concerns | deferred research |
| 204 | forge/pipeline | VALID_OPEN | P1 feature: forge-orchestrated coordination | _ |
| 183–176 | memory-foundation futures | DEFERRED_FUTURE | P3 research futures (typed temporal semantics, prior-art audit, curiosity, lineage) | memory-foundation tranche |
| 175/181 | memory epics | DEFERRED_FUTURE | evidence-grounded continuity epics | same |
| 166 | research watchlist | DEFERRED_FUTURE | external research tracking | periodic review |
| 165/164 | research spikes | DEFERRED_FUTURE | SimpleMem/A-MEM, HippoRAG 2 | memory-foundation tranche |
| 163 | graph backend spike | NEEDS_REVALIDATION | P1 spike; LadybugDB evaluation partially done in prior research artifacts | revalidate against current graph.db-missing state |
| 162 | Qdrant spike | NEEDS_REVALIDATION | P1 spike; prior research artifacts exist (issue-162/) | revalidate after #316 outcome |
| 159 | conceptual topology | DEFERRED_FUTURE | P3 future | _ |
| 158 | wiring-status report | VALID_OPEN | P2: generated architecture-truth report | _ |
| 156 | mutation guards | VALID_OPEN | P1: cognitive-state mutation guards | _ |
| 155 | e2e activation trace | VALID_OPEN | P1: credit-assignment trace | overlaps PL1 outcome work — potential duplicate of #300's telemetry; review at PL1 close |
| 154/153 | learned-state / experience encoding | VALID_OPEN | P2 | PL1-adjacent |
| 152/151 | break-it / adversarial | VALID_OPEN | P0: adversarial procedures after nominal green | post-PL1 tranche |
| 145 | P0-07 follow-up | VALID_OPEN | tracing coverage gaps | _ |
| 141 | ContextBroker compaction | VALID_OPEN | P1: rolling-compaction persistence | _ |
| 139 | tui_validator drift | FIXED_VERIFIED | declarative classification shipped; check passes | closed |
| 131/130/129 | reviewer program | VALID_OPEN | P0: local reviewer + calibration + rubric | post-PL1 tranche |
| 126 | silent-handler sweep | VALID_OPEN | P0 refactor program; ratchet enforces ceiling | ongoing |
| 125 | decompose monoliths | VALID_OPEN | P2 | _ |
| 49–35 | v1-course-correction P2/P3 | DEFERRED_FUTURE | epic #16 manifest items (P2-01..09, P3-01..06) | per epic #16 sequencing |
| 33–27 | v1-course-correction P1 | VALID_OPEN | epic #16 manifest P1 items | per epic #16 sequencing |
| 16 | v1 epic | VALID_OPEN | consolidation epic; 33 manifest issues | per epic sequencing |
| 13 | TUI message graph | VALID_OPEN | P1 | _ |
| 3 | financial tracking | NEEDS_REVALIDATION | non-Rune issue (different project era) | transfer to correct repo or close |
| 2 | cognitive loop wiring | NEEDS_REVALIDATION | predates current TUI architecture | revalidate against current TUI |
| 1 | personality injection | NEEDS_REVALIDATION | overlaps #37's newer scope | merge into #37 or close |
## Reconciliation decisions this session (Part K deltas)
- CLOSED: #139, #157 (FIXED_VERIFIED, evidence comments posted)
- BUG LABEL REMOVED: #204, #145, #126, #37, #16 (audit: not defects)
- BUG LABEL REMOVED (stuck from prior cleanup): #314 (closed issue)
- OPEN BUGS: 8 → 1 (#324 only — genuine live defect)
## Part L — consistency check
`scripts/gitea_reconciliation_check.py` (deterministic, no governance
subsystem): fetches live issue state, compares to
`docs/gitea_issue_reconciliation.json`, flags:
- closed issue recorded VALID_OPEN / DEFERRED_FUTURE / NEEDS_REVALIDATION (open states)
- open issue recorded FIXED_VERIFIED / SUPERSEDED / DUPLICATE / HISTORICAL_EVIDENCE without being closed
- `bug` label present on any issue not recorded as a live defect (or missing on #324)
- issue missing from the artifact
- record lacking a required field
Exit 0 = consistent; exit 1 = drift listed. CI-eligible (read-only).
+35 -2
View File
@@ -52,9 +52,11 @@ This detour is complete when:
- [x] ADAPT intent recovered and reconciled (this artifact, Part B)
- [ ] **THIS TRANCHE**: the ADAPT status matrix disposition is agreed and the next tranche selected (Part D)
## RETURN TARGET
## RETURN TARGET / CURRENT: PL1 Process Learning — FIRST TRANCHE LIVE (2026-09-11)
**PL1 Process Learning** (T9). ADAPT-LIVE closed the route-execution gap; PL1's input (route accuracy, probe outcomes, escalation evidence, verification results, task characteristics) is now produced live by the operator spine. The matrix in the ADAPT RECONCILIATION section stands, updated: LOCAL_FIX/STANDARD_CHANGE paths = BUILT_AND_LIVE; probe = BUILT_AND_LIVE (verification-driven); escalation/downgrade = BUILT_AND_LIVE. Remaining ADAPT obligations for later epics: ADAPT-5 budgets, ADAPT-6 evidence capsule, ADAPT-7 DESIGN consolidation, ADAPT-10 legibility, FINAL-12 cross-track benchmark.
PL1-01 structured Experience records: `scripts/pl1_records.py` (task_id, route resolved from route.jsonl, proof level, failure class, candidate status). PL1-02 failure classification in the PL0 vocabulary (research_dispatch, NOT_COMPLETED_NO_MUTATION_EVIDENCE, verification_failure, model_refusal, UNKNOWN). PL1-03 first learning loop: recurring prose-instead-of-mutation family → structural correction (route-driven instruction in the user context block) + replay contract (NOT_COMPLETED gate) — record PE-1789102658-0269 (SEED; held-out = next deployed mutation tasks must complete-with-evidence or fail honestly). PL1-04 promotion discipline: SEED→CANDIDATE→VALIDATED→PROMOTED via process_experience; structural fixes preferred. PL1-05 metrics: first_pass_accepted, mutation_failures, research_dispatches, false_completion_rate (0.0 — structurally prevented), candidates vs evidence-only split.
Remaining ADAPT obligations for later epics: ADAPT-5 budgets, ADAPT-6 evidence capsule, ADAPT-7 DESIGN consolidation, ADAPT-10 legibility, FINAL-12 cross-track benchmark, D7 release-job-on-Pi.
## OPEN OBLIGATIONS (deferred defects — recorded, NOT chased)
@@ -144,3 +146,34 @@ Reasoning from recovered obligations, not momentum:
5. Update epic #284 with the detour record + this matrix; close the gap between tracker and reality.
Then: **PL1** (T9), with the intent-continuity spine as PL1's first or second deliverable (it is an experience-system product, not a separate architecture project).
## GITEA RECONCILIATION (2026-09-11)
Part A-J executed. 15 issues closed (HISTORICAL_EVIDENCE/SUPERSEDED/ACCEPTED_LIMITATION) with evidence. Bug label cleaned: 16 misleading labels removed (open bugs 24→8). #259/#258 updated to current architecture. Remote tracking fixed (master tracks gitea/master, not stale origin). PL1 metrics corrected (NOT_YET_MEASURABLE where evidence doesn't support numbers). D7: bump works, release job fails on Pi — ROOT_CAUSE_UNKNOWN_DEFERRED. Automation noise: 5 duplicate bump commits per logical change (queue-drain artifact, not live recursion — version converges, commit history doesn't).
## DEFECT D7 (2026-09-11): RESOLVED — release-job failures root-caused
History: the release job failed on every push since 03:16 (c2528c1).
Superseded diagnosis: "Pi-side backup step logs Failed to connect to
127.0.0.1:3000" was a red herring — the Pi backup step was never the
failing job.
Root causes (live-proven T15 Part F, 2026-09-11 evening):
1. c2528c1 left a corrupted shell fragment after the converged-check fi
in auto-version.yml — the release job died at bash parse time (exit 2)
before any bump. Fixed in c35b604.
2. release_convergence() never assigned the release boundary — all 959+
commits classified as unreleased, so the warranted bump never
converged and queued runs each wrote their own bump commit. Fixed in
e395ee1 (boundary wiring) + 9e0b855 (boundary grep must match subjects
only, not bodies) + c35018f (numeric semver comparison: '0.66.9' <
'0.66.10' is False lexicographically, which silently skipped warranted
bumps as version_ahead_of_target).
3. The per-file contents PUT committed one commit per version surface —
five same-version bump commits per real bump (the "duplicate bump
cascade" since 0.66.2, misdiagnosed as queue drain). Fixed in 470d82f
+ d68fd1e (single ChangeFiles commit).
Live proof: experiment A (trigger d68fd1e, warranted patch) → exactly one
bump commit a4afc77 (0.66.11), all five surfaces in one commit, queue
drained, zero delayed duplicates, zero workflow failures.
File diff suppressed because it is too large. Load diff
+4
View File
@@ -0,0 +1,4 @@
wait/resume experiment marker 23:07:52
wr delivery test 23:28:42
delivery test 2 23:31:02
wr delivery test 2 23:31:12
+1
View File
@@ -0,0 +1 @@
def broken(:
+2 -2
View File
@@ -10,7 +10,7 @@ henxels:
- henxel: "No secrets in tracked files"
no_secrets: true
except: ["./tests/test_gitea_provision.py", "./.ci-venv/**", "./.test-venv/**", "./.canary-venv/**", "./backups/**"]
except: ["./tests/test_gitea_provision.py", "./tests/test_gitea_auth.py", "./.ci-venv/**", "./.test-venv/**", "./.canary-venv/**", "./backups/**"]
- henxel: "Plans are markdown, one per build"
in: ./plans
@@ -20,7 +20,7 @@ henxels:
- henxel: "Top-level docs are markdown or yaml references"
in: ./docs/*
allowed_filetypes: [.md, .yaml]
except: ["./docs/v1/**", "./docs/wiki/**"]
except: ["./docs/v1/**", "./docs/wiki/**", "./docs/gitea_issue_reconciliation.json"]
- henxel: "The known giant stays visible — file-size debt is tracked, not silent"
in: ./src/rune/*
+1 -1
View File
@@ -1,4 +1,4 @@
{
"BLE001": 353,
"BLE001": 352,
"S110": 80
}
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.66.4"
version = "0.66.14"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+7 -2
View File
@@ -23,6 +23,11 @@ import time
import urllib.error
import urllib.request
from typing import Any
import sys as _sys
_SCRIPT_DIR = str(__import__('pathlib').Path(__file__).resolve().parent)
if _SCRIPT_DIR not in _sys.path:
_sys.path.insert(0, _SCRIPT_DIR)
import gitea_auth
BUMP_BRANCH_PREFIX = "chore/auto-bump-"
@@ -97,7 +102,7 @@ def apply_surface_edits(
def run_bump() -> None:
TOKEN = os.environ["GITEA_TOKEN"]
TOKEN = gitea_auth.get_token()
BASE = os.environ["RELEASE_SERVER_URL"] + "/api/v1/repos/" + os.environ["RELEASE_REPO"]
VERSION = os.environ["RELEASE_VERSION"]
NEW = os.environ["NEW_VERSION"]
@@ -264,7 +269,7 @@ def main(argv: list[str] | None = None) -> int:
if args.safety_net:
base = os.environ["RELEASE_SERVER_URL"] + "/api/v1/repos/" + os.environ["RELEASE_REPO"]
token = os.environ["GITEA_TOKEN"]
token = gitea_auth.get_token()
# Attribute against exactly this merge's would-be bump branch: a stale
# chore/auto-bump-* branch from an older version must not stand the
# net down (spec-gate finding 7a, #192).
+6 -1
View File
@@ -24,6 +24,11 @@ import sys
import urllib.error
import urllib.request
from typing import Any
import sys as _sys
_SCRIPT_DIR = str(__import__('pathlib').Path(__file__).resolve().parent)
if _SCRIPT_DIR not in _sys.path:
_sys.path.insert(0, _SCRIPT_DIR)
import gitea_auth
# Env-contract (#192 spec-gate finding 1): derived in-script, never required
# from the caller. Guarded by tests/test_workflow_script_contract.py.
@@ -87,7 +92,7 @@ def main(argv: list[str] | None = None) -> int:
print(json.dumps(payload))
return 0
token = os.environ["GITEA_TOKEN"]
token = gitea_auth.get_token()
# Derived URLs (explicit env still overrides — for direct/manual use).
server_url = os.environ["RELEASE_SERVER_URL"]
+7 -5
View File
@@ -14,13 +14,18 @@ import json
import os
import sys
import urllib.request
import sys as _sys
_SCRIPT_DIR = str(__import__('pathlib').Path(__file__).resolve().parent)
if _SCRIPT_DIR not in _sys.path:
_sys.path.insert(0, _SCRIPT_DIR)
import gitea_auth
def main() -> int:
server = os.environ.get("GITEA_SERVER", "").rstrip("/")
repo = os.environ.get("GITEA_REPO", "")
sha = os.environ.get("DEPLOY_SHA", "")
token = os.environ.get("GITEA_TOKEN", "")
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
if not (server and repo and sha and token):
print("pending") # missing config → cannot verify → not qualified
return 0
@@ -37,10 +42,7 @@ def main() -> int:
required = [
s
for s in data.get("statuses", [])
if (
str(s.get("context", "")).startswith("CI Pipeline / required-checks")
and s.get("status") != "skipped"
)
if (str(s.get("context", "")).startswith("CI Pipeline / required-checks") and s.get("status") != "skipped")
]
if not required:
print("pending")
+8 -1
View File
@@ -99,7 +99,14 @@ print("VERIFIED:", json.dumps(info))
# RuneApp initialization must succeed before promotion. A staged
# revision whose TUI cannot initialize never becomes current.
canary = subprocess.run(
[VENV_PY, str(REPO_ROOT / "scripts" / "tui_startup_canary.py")],
[
VENV_PY,
str(REPO_ROOT / "scripts" / "tui_startup_canary.py"),
"--mode",
"candidate",
"--stage",
str(tmp_stage),
],
capture_output=True,
text=True,
env={**env, "PYTHONPATH": str(tmp_stage / "src")},
+72 -16
View File
@@ -12,6 +12,7 @@ Canonical pipeline (RP2, handoff 2026-09-08):
Per the authoritative build prompt §18:
"DETERMINISTIC TESTS PROVE THE PIPE. REAL MIMO DOES THE RESEARCH."
"""
from __future__ import annotations
import json
@@ -21,6 +22,7 @@ from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from research_task_tree import ResearchTaskTree, STAGES
def fake_issue_gate(tree: ResearchTaskTree) -> dict:
"""ISSUE is deterministic: the Gitea issue content is the DEFINE replacement.
The fake worker emits the compiled gate artifact directly."""
@@ -33,6 +35,7 @@ def fake_issue_gate(tree: ResearchTaskTree) -> dict:
"quality": "BUILDABLE",
}
def fake_inward(tree: ResearchTaskTree) -> dict:
# Assert ISSUE-gate artifact was actually supplied
prior = tree.get_prior_artifacts("INWARD")
@@ -40,11 +43,22 @@ def fake_inward(tree: ResearchTaskTree) -> dict:
assert issue_gate.get("goal"), "INWARD: ISSUE-gate artifact missing — evidence handoff broken"
return {
"claims": [
{"id": "I1", "claim": "memory_store uses FTS5", "evidence": ["src/rune/memory_store.py:86"], "status": "CURRENT"},
{"id": "I2", "claim": "shard routing is present", "evidence": ["src/rune/shard_manager.py:114"], "status": "CURRENT"},
{
"id": "I1",
"claim": "memory_store uses FTS5",
"evidence": ["src/rune/memory_store.py:86"],
"status": "CURRENT",
},
{
"id": "I2",
"claim": "shard routing is present",
"evidence": ["src/rune/shard_manager.py:114"],
"status": "CURRENT",
},
],
}
def fake_outward(tree: ResearchTaskTree) -> dict:
"""Unified OUTWARD (RP3): one research task, discovery + reading as
SUBSECTIONS of one artifact — not separate stages."""
@@ -58,19 +72,35 @@ def fake_outward(tree: ResearchTaskTree) -> dict:
"tool_receipts": [{"tool": "webfetch", "query": "fts5 hybrid search", "status": "completed"}],
"sources": [
{"id": "O1", "url": "https://sqlite.org/fts5.html", "title": "FTS5", "source_class": "software"},
{"id": "O2", "url": "https://github.com/asg017/sqlite-vec", "title": "sqlite-vec", "source_class": "software"},
{
"id": "O2",
"url": "https://github.com/asg017/sqlite-vec",
"title": "sqlite-vec",
"source_class": "software",
},
],
},
"reading": {
"evidence": [
{"id": "E1", "source_id": "O1", "url": "https://sqlite.org/fts5.html",
"claim": "FTS5 supports external content tables", "limitations": ["token porter only"]},
{"id": "E2", "source_id": "O2", "url": "https://github.com/asg017/sqlite-vec",
"claim": "vec0 virtual tables for embedding search", "limitations": []},
{
"id": "E1",
"source_id": "O1",
"url": "https://sqlite.org/fts5.html",
"claim": "FTS5 supports external content tables",
"limitations": ["token porter only"],
},
{
"id": "E2",
"source_id": "O2",
"url": "https://github.com/asg017/sqlite-vec",
"claim": "vec0 virtual tables for embedding search",
"limitations": [],
},
],
},
}
def fake_applicability(tree: ResearchTaskTree) -> dict:
"""APPLICABILITY (RP4): classify researched mechanisms against Rune's reality."""
prior = tree.get_prior_artifacts("APPLICABILITY")
@@ -96,6 +126,7 @@ def fake_applicability(tree: ResearchTaskTree) -> dict:
],
}
def fake_adversarial(tree: ResearchTaskTree) -> dict:
prior = tree.get_prior_artifacts("ADVERSARIAL")
outward = prior.get("OUTWARD", {})
@@ -104,19 +135,31 @@ def fake_adversarial(tree: ResearchTaskTree) -> dict:
return {
"disposition": "SURVIVES_WITH_MITIGATIONS",
"counter_evidence": [
{"id": "A1", "source_id": "O1", "claim": "FTS5 porter tokenizer may not handle all languages",
"derived_from": ["E1"], "limitations": ["minor"]},
{
"id": "A1",
"source_id": "O1",
"claim": "FTS5 porter tokenizer may not handle all languages",
"derived_from": ["E1"],
"limitations": ["minor"],
},
],
"surviving_candidates": ["M1"],
"rejected_candidates": [],
"required_mitigations": ["mode labeling"],
"falsification_tests": ["FT1: harness run with embedding backend unreachable"],
"attacks": [
{"id": "A1", "target_candidate": "M1", "dimension": "failure modes",
"finding": "tokenizer coverage", "evidence": ["E1"], "severity": "minor"},
{
"id": "A1",
"target_candidate": "M1",
"dimension": "failure modes",
"finding": "tokenizer coverage",
"evidence": ["E1"],
"severity": "minor",
},
],
}
def fake_innovation(tree: ResearchTaskTree) -> dict:
prior = tree.get_prior_artifacts("INNOVATION")
inward = prior.get("INWARD", {})
@@ -131,14 +174,21 @@ def fake_innovation(tree: ResearchTaskTree) -> dict:
return {
"conclusion": "ADOPT",
"candidates": [
{"id": "C1", "category": "ADAPT", "proposal": "hybrid FTS+vec recall",
"derived_from": inward_claims[:1] + evidence_ids[:1] + applicable[:1],
"composition_of": [], "novelty": "adapted",
"expected_gain": "better recall", "risks": ["latency"],
"kill_test": "recall returns >0 rows"},
{
"id": "C1",
"category": "ADAPT",
"proposal": "hybrid FTS+vec recall",
"derived_from": inward_claims[:1] + evidence_ids[:1] + applicable[:1],
"composition_of": [],
"novelty": "adapted",
"expected_gain": "better recall",
"risks": ["latency"],
"kill_test": "recall returns >0 rows",
},
],
}
def fake_synthesis(tree: ResearchTaskTree) -> dict:
prior = tree.get_prior_artifacts("SYNTHESIS")
innovation = prior.get("INNOVATION", {})
@@ -157,6 +207,7 @@ def fake_synthesis(tree: ResearchTaskTree) -> dict:
"build_instructions": ["modify recall() in memory_store.py", "add tests"],
}
def fake_qualify(tree: ResearchTaskTree) -> dict:
prior = tree.get_prior_artifacts("QUALIFY")
synthesis = prior.get("SYNTHESIS", {})
@@ -177,6 +228,7 @@ def fake_qualify(tree: ResearchTaskTree) -> dict:
"evidence_used": synthesis.get("evidence_used", []),
}
FAKE_WORKERS = {
"ISSUE": fake_issue_gate,
"INWARD": fake_inward,
@@ -188,6 +240,7 @@ FAKE_WORKERS = {
"QUALIFY": fake_qualify,
}
def run_fake_stage(tree: ResearchTaskTree) -> dict:
stage = tree.current_stage
worker = FAKE_WORKERS.get(stage)
@@ -195,8 +248,10 @@ def run_fake_stage(tree: ResearchTaskTree) -> dict:
raise ValueError(f"no fake worker for stage {stage}")
return worker(tree)
def main_with(argv: list[str]) -> int:
import argparse
parser = argparse.ArgumentParser(description="Fake stage worker for pipeline testing")
parser.add_argument("--issue", type=int, required=True)
parser.add_argument("--round", type=int, default=1)
@@ -216,5 +271,6 @@ def main_with(argv: list[str]) -> int:
print(f"fake stage {stage} complete")
return 0
if __name__ == "__main__":
raise SystemExit(main_with(sys.argv))
+6 -1
View File
@@ -20,6 +20,11 @@ import urllib.error
import urllib.parse
import urllib.request
from typing import Any
import sys as _sys
_SCRIPT_DIR = str(__import__('pathlib').Path(__file__).resolve().parent)
if _SCRIPT_DIR not in _sys.path:
_sys.path.insert(0, _SCRIPT_DIR)
import gitea_auth
DRIFT_ISSUE_LABEL = "release-drift"
DRIFT_ISSUE_LABEL_COLOR = "ff0000"
@@ -108,7 +113,7 @@ def main(argv: list[str] | None = None, api: Any = None) -> int:
print(body)
return 0
token = os.environ["GITEA_TOKEN"]
token = gitea_auth.get_token()
base = os.environ["RELEASE_SERVER_URL"] + "/api/v1/repos/" + os.environ["RELEASE_REPO"]
if api is None:
+112
View File
@@ -0,0 +1,112 @@
"""gitea_auth.py — canonical Gitea API authentication (handoff 2026-09-11 §H).
One deterministic helper for authenticated Gitea API access. Never prints
credentials. Fails clearly when unavailable. Credential ladder:
1. GITEA_TOKEN environment variable (preferred for CI/deployment)
2. private token file (GITEA_TOKEN_FILE, default
~/.config/opencode/secrets/gitea-token, must be mode 0600)
3. git credential fill (uses the credential helper / store)
Never parses credentials from git remote URLs.
"""
from __future__ import annotations
import json
import os
import stat
import subprocess
import urllib.request
from pathlib import Path
from typing import Any
DEFAULT_SERVER = "https://gitea.thingscouldgetdicey.online"
DEFAULT_REPO = "conductor/rune"
DEFAULT_TOKEN_FILE = Path("~/.config/opencode/secrets/gitea-token").expanduser()
GITEA_HOST = "gitea.thingscouldgetdicey.online"
class GiteaCredentialError(RuntimeError):
"""Raised when no Gitea credential is available. Message never contains one."""
def _token_from_file() -> str:
path = Path(os.environ.get("GITEA_TOKEN_FILE", DEFAULT_TOKEN_FILE)).expanduser()
if not path.is_file():
return ""
mode = stat.S_IMODE(path.stat().st_mode)
if mode & 0o077:
raise PermissionError(f"Gitea token file must be mode 0600: {path}")
return path.read_text(encoding="utf-8").strip()
def _token_from_credential_helper() -> str:
try:
result = subprocess.run(
["git", "credential", "fill"],
input=f"protocol=https\nhost={GITEA_HOST}\nusername=conductor\n\n",
capture_output=True,
text=True,
timeout=10,
)
for line in result.stdout.splitlines():
if line.startswith("password="):
return line[len("password=") :]
except (subprocess.TimeoutExpired, OSError):
pass
return ""
def has_token() -> bool:
"""True if any rung of the credential ladder answers. Never raises."""
try:
get_token()
return True
except Exception: # noqa: BLE001 — probe-only: any failure means "no credential available"
return False
def get_token() -> str:
"""Obtain the Gitea API token. Never prints the token."""
token = os.environ.get("GITEA_TOKEN", "").strip()
if token:
return token
token = _token_from_file()
if token:
return token
token = _token_from_credential_helper()
if token:
return token
raise GiteaCredentialError(
"Gitea credentials unavailable: set GITEA_TOKEN, create a mode-0600 "
f"token file at {DEFAULT_TOKEN_FILE}, or configure a git credential "
f"helper for {GITEA_HOST}"
)
def api(
path: str,
method: str = "GET",
data: dict | None = None,
server: str = DEFAULT_SERVER,
repo: str = DEFAULT_REPO,
) -> tuple[int, Any]:
"""Make an authenticated Gitea API request. Returns (status_code, parsed_body)."""
token = get_token()
url = f"{server}/api/v1/repos/{repo}{path}"
req = urllib.request.Request(
url,
data=json.dumps(data).encode() if data is not None else None,
headers={"Authorization": f"token {token}", "Content-Type": "application/json"},
method=method,
)
try:
with urllib.request.urlopen(req, timeout=30) as r:
body, status = r.read(), r.status
except urllib.error.HTTPError as e:
body, status = e.read(), e.code
try:
return status, (json.loads(body) if body else {})
except json.JSONDecodeError:
return status, {"_raw": body.decode("utf-8", "replace")[:200]}
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""gitea_reconciliation_check.py — Part L consistency check (deterministic).
Compares live Gitea issue state against the canonical reconciliation
artifact (docs/gitea_issue_reconciliation.json). Flags:
1. closed issue recorded in an open-only state
(VALID_OPEN / DEFERRED_FUTURE / NEEDS_REVALIDATION)
2. open issue recorded in a terminal state
(FIXED_VERIFIED / SUPERSEDED / DUPLICATE / HISTORICAL_EVIDENCE /
ACCEPTED_LIMITATION)
3. `bug` label present on any issue not recorded live_defect
(or missing on a recorded live defect)
4. open issue missing from the artifact
5. record missing required fields
Exit 0 = consistent. Exit 1 = drift (each item printed). Read-only.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "scripts"))
ARTIFACT = ROOT / "docs" / "gitea_issue_reconciliation.json"
OPEN_ONLY_STATES = {"VALID_OPEN", "DEFERRED_FUTURE", "NEEDS_REVALIDATION"}
TERMINAL_STATES = {"FIXED_VERIFIED", "SUPERSEDED", "DUPLICATE", "HISTORICAL_EVIDENCE", "ACCEPTED_LIMITATION"}
REQUIRED_FIELDS = {"number", "tracker_state", "reconciliation_state", "labels", "live_defect", "last_revalidated"}
def main() -> int:
import gitea_auth
if not ARTIFACT.is_file():
print(f"reconciliation artifact missing: {ARTIFACT}")
return 1
artifact = json.loads(ARTIFACT.read_text())
records = {r["number"]: r for r in artifact["records"]}
drift: list[str] = []
# Fetch live issues (open + recently closed via both pages)
issues: list[dict] = []
for page in (1, 2):
status, batch = gitea_auth.api(f"/issues?state=all&limit=50&page={page}")
if status != 200:
print(f"Gitea API failed ({status}): {str(batch)[:200]}")
return 1
if isinstance(batch, list):
issues.extend(batch)
live = {i["number"]: i for i in issues}
for num, rec in sorted(records.items()):
for field in REQUIRED_FIELDS:
if field not in rec:
drift.append(f"#{num}: record missing required field {field!r}")
state = rec.get("reconciliation_state")
if state in OPEN_ONLY_STATES and rec.get("tracker_state") == "closed":
drift.append(f"#{num}: closed issue recorded {state}")
if state in TERMINAL_STATES and rec.get("tracker_state") == "open":
drift.append(f"#{num}: open issue recorded terminal {state}")
has_bug = "bug" in (rec.get("labels") or [])
# The bug-label/live-defect invariant applies to OPEN issues only:
# a closed issue's labels are historical record; its reconciliation
# state governs (FIXED_VERIFIED issues legitimately keep the label).
if rec.get("tracker_state") == "open":
if has_bug and not rec.get("live_defect"):
drift.append(f"#{num}: bug label present but not recorded as live defect")
if not has_bug and rec.get("live_defect"):
drift.append(f"#{num}: recorded live defect but bug label missing")
if state == "FIXED_VERIFIED" and rec.get("tracker_state") == "open":
drift.append(f"#{num}: FIXED_VERIFIED but still open without closure note")
# Live vs artifact sync (only for issues present in the live window)
for num, li in sorted(live.items()):
if num not in records:
continue
rec = records[num]
live_state = li["state"]
if live_state == "closed" and rec["tracker_state"] == "open" and rec["reconciliation_state"] in OPEN_ONLY_STATES:
drift.append(f"#{num}: live state closed but artifact says open/{rec['reconciliation_state']}")
if live_state == "open" and rec["tracker_state"] == "closed" and rec["reconciliation_state"] in TERMINAL_STATES:
drift.append(f"#{num}: live state open but artifact says closed/{rec['reconciliation_state']}")
live_bug = "bug" in [l["name"] for l in li.get("labels", [])]
if live_bug and not rec.get("live_defect"):
drift.append(f"#{num}: live bug label present but artifact records no live defect")
if not live_bug and rec.get("live_defect"):
drift.append(f"#{num}: artifact records live defect but live bug label absent")
# Live defects in the artifact must all be OPEN in the tracker
for num in artifact.get("live_defects", []):
li = live.get(num)
if li is not None and li["state"] != "open":
drift.append(f"#{num}: live defect but tracker state is {li['state']} — close the defect or update the artifact")
if drift:
print(f"RECONCILIATION DRIFT ({len(drift)}):")
for d in drift:
print(f" - {d}")
return 1
print(f"reconciliation consistent ({len(records)} records, {len(live)} live issues in window)")
return 0
if __name__ == "__main__":
sys.exit(main())
+6 -10
View File
@@ -27,6 +27,7 @@ capture time, the caller must decide (abort or accept-and-fingerprint).
This module fingerprints regardless, so even a dirty worktree can't hide
in-stage mutations.
"""
from __future__ import annotations
import hashlib
@@ -39,8 +40,7 @@ class MutationError(RuntimeError):
def _git(*args: str, cwd: str) -> str:
result = subprocess.run(["git", *args], cwd=cwd,
capture_output=True, text=True, timeout=30)
result = subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True, timeout=30)
return result.stdout.strip()
@@ -56,8 +56,7 @@ def capture_worktree_state(repo_root: str) -> dict:
state["head"] = _git("rev-parse", "HEAD", cwd=repo_root)
state["worktree_clean"] = _git("status", "--porcelain", cwd=repo_root) == ""
# All files (tracked + untracked, excluding .git)
files = _git("ls-files", "--cached", "--others", "--exclude-standard",
cwd=repo_root)
files = _git("ls-files", "--cached", "--others", "--exclude-standard", cwd=repo_root)
fingerprints = {}
for f in files.splitlines():
fp = os.path.join(repo_root, f)
@@ -73,8 +72,7 @@ def capture_worktree_state(repo_root: str) -> dict:
return state
def verify_no_source_mutation(repo_root: str, before: dict,
allowed_paths: list[str]) -> list[str]:
def verify_no_source_mutation(repo_root: str, before: dict, allowed_paths: list[str]) -> list[str]:
"""D5.1+D5.2: content-hash comparison + explicit allowlist.
Returns violations (empty = clean).
@@ -104,8 +102,7 @@ def verify_no_source_mutation(repo_root: str, before: dict,
def _is_allowed(rel: str) -> bool:
if rel in allowed_exact:
return True
return any(rel.startswith(prefix + os.sep) or rel.startswith(prefix + "/")
for prefix in allowed_prefixes)
return any(rel.startswith(prefix + os.sep) or rel.startswith(prefix + "/") for prefix in allowed_prefixes)
# D5.1: compare fingerprints — detects changes to already-dirty files
for path in sorted(set(before_fp) | set(after_fp)):
@@ -122,8 +119,7 @@ def verify_no_source_mutation(repo_root: str, before: dict,
# HEAD must not move (cognitive stages must never commit)
if before.get("head") and after.get("head") and before["head"] != after["head"]:
violations.append(
f"UNAUTHORIZED_COMMIT: HEAD moved {before['head'][:8]} → {after['head'][:8]}")
violations.append(f"UNAUTHORIZED_COMMIT: HEAD moved {before['head'][:8]} → {after['head'][:8]}")
return violations
+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""pl1_records.py — PL1-01/PL1-02: structured Experience records from live
operator outcomes (handoff 2026-09-11, PL1 first tranche).
Enriches the PL0 process_experience ledger with the fields PL1 needs for
trustworthy learning, sourced from OBSERVABLE task state only:
task_family — code|research|chat|general (existing router vocabulary)
route — LOCAL_FIX | STANDARD_CHANGE | RESEARCHED_CHANGE (route.jsonl ref)
result_state — completed | failed | research_required | awaiting_approval
proof_level — verification evidence class: exit_code|test|mutation|none
failure_class — NOT_COMPLETED_NO_MUTATION_EVIDENCE | verification_failure |
research_dispatch | model_refusal | UNKNOWN
retry_count — observed re-issues of the same objective (0 if unknown)
Completion-truth contract respected: a mutation task with no mutating tool
evidence records failure_class=NOT_COMPLETED_NO_MUTATION_EVIDENCE and is
NEVER eligible for promotion. Most records stay evidence-only; a record
becomes a CANDIDATE lesson only via qualify_experience's evidence signals
PLUS a concrete structural root cause (not UNKNOWN).
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
_SCRIPTS = os.path.dirname(os.path.abspath(__file__))
if _SCRIPTS not in sys.path:
sys.path.insert(0, _SCRIPTS)
LEDGER_DEFAULT = os.path.expanduser("~/.rune/state/process_experience.jsonl")
"""Default path for the PL0 Experience Ledger. Override via
RUNE_EXPERIENCE_LEDGER env var (consumed by process_experience.py)."""
TASK_STATE_DEFAULT = os.path.expanduser("~/.rune/hot/task_state.json")
ROUTE_LOG_DEFAULT = os.path.expanduser("~/.rune/routes/route.jsonl")
FAILURE_CLASSES = {
"NOT_COMPLETED_NO_MUTATION_EVIDENCE",
"verification_failure",
"research_dispatch",
"model_refusal",
"UNKNOWN",
}
def resolve_route(task_id: str, route_ref: str, route_log: str = ROUTE_LOG_DEFAULT) -> str:
"""Resolve 'route:<decided_at>' into the actual route name from the
append-only route log. Returns '' when unresolvable — never guesses."""
if not route_ref.startswith("route:"):
return ""
ts = route_ref[len("route:") :]
p = Path(route_log)
if not p.exists():
return ""
try:
for line in p.read_text().splitlines():
try:
ev = json.loads(line)
except json.JSONDecodeError:
continue
if (
str(ev.get("decided_at", "")).startswith(ts[:19])
and ev.get("dispatch", {}).get("task_id", task_id) == task_id
):
return str(ev.get("route", ""))
except OSError:
pass
return ""
def proof_level(task: dict) -> str:
"""Observable verification evidence class for one finished task."""
outcome_ref = task.get("outcome_ref") or ""
result = task.get("result") or ""
if outcome_ref.startswith("response:") and "wrote" in result.lower():
return "mutation"
# task.outcome journal events carry verification_passed — surfaced in
# task.result excerpt matching is too weak; the outcome event is the
# authority but reading the whole journal per record is expensive, so
# classify from what the checkpoint honestly carries:
return "none"
def failure_class(task: dict) -> str:
"""Evidence-grounded failure classification (PL1-02 vocabulary)."""
status = task.get("status", "")
result = task.get("result") or ""
if status == "research_required" or task.get("outcome_ref") == "research_dispatch":
return "research_dispatch"
if result.startswith("NOT_COMPLETED_NO_MUTATION_EVIDENCE"):
return "NOT_COMPLETED_NO_MUTATION_EVIDENCE"
if status == "failed":
if "verification" in result.lower():
return "verification_failure"
if "capability" in result.lower() or "refus" in result.lower():
return "model_refusal"
return "UNKNOWN"
return "UNKNOWN"
def enrich_record(task: dict, route_log: str = ROUTE_LOG_DEFAULT) -> dict:
"""Build the PL1 evidence record for one task from canonical state."""
route_ref = task.get("route_ref") or ""
route = resolve_route(task.get("task_id", ""), route_ref, route_log)
status = task.get("status", "")
fc = failure_class(task)
return {
"task_id": task.get("task_id", ""),
"task_family": task.get("task_family", ""), # trace v2 field; '' if absent
"route": route,
"objective": (task.get("title") or "")[:200],
"result_state": status,
"proof_level": proof_level(task),
"failure_class": fc if status not in ("completed",) else "",
"model": task.get("assigned_model") or "",
"outcome_ref": task.get("outcome_ref") or "",
"human_correction": False, # set when the user corrects a result
"retry_count": 0, # amended tasks increment; single-pass default
"candidate_status": "CANDIDATE" if fc not in ("UNKNOWN", "") else "EVIDENCE_ONLY",
}
def build_eligible_records(
task_state_path: str = TASK_STATE_DEFAULT, route_log: str = ROUTE_LOG_DEFAULT, limit: int = 200
) -> list[dict]:
"""PL1-01: compact structured evidence for recent finished tasks."""
p = Path(task_state_path)
if not p.exists():
return []
checkpoint = json.loads(p.read_text())
tasks = checkpoint.get("tasks", [])
seen: set[str] = set()
out = []
for t in reversed(tasks[-limit:]):
tid = t.get("task_id", "")
if not tid or tid in seen:
continue
seen.add(tid)
if t.get("status") not in ("completed", "failed", "research_required"):
continue
out.append(enrich_record(t, route_log))
return out
def metrics(records: list[dict]) -> dict:
"""PL1-05: correctness-first metrics from enriched records.
Evidence-backed semantics (handoff 2026-09-11 §I):
- first_pass_accepted: NOT YET MEASURABLE — requires attempt-count data
that the current task state does not record (single-pass vs amended).
- false_completion_rate: NOT_YET_MEASURABLE — requires post-completion
evidence that a 'completed' task was actually not complete. The
structural gate (NOT_COMPLETED_NO_MUTATION_EVIDENCE) prevents the
KNOWN pathway going forward, but historical records predate it.
- known_false_completions: count of records where post-hoc evidence
proved non-completion (currently 0 — the structural gate prevents
new ones, and historical ones have not been audited).
- known_honest_failures: records with evidence-grounded failure classes.
- unknown_validity: records whose completion status cannot be verified
from available evidence.
"""
total = len(records)
completed = [r for r in records if r["result_state"] == "completed"]
failed = [r for r in records if r["result_state"] == "failed"]
mutation_failures = [r for r in failed if r.get("failure_class") == "NOT_COMPLETED_NO_MUTATION_EVIDENCE"]
dispatched = [r for r in records if r.get("failure_class") == "research_dispatch"]
candidates = [r for r in records if r.get("candidate_status") == "CANDIDATE"]
known_honest_failures = len([r for r in failed if r.get("failure_class") not in ("", "UNKNOWN")])
return {
"total_finished": total,
"completed": len(completed),
"failed": len(failed),
"first_pass_accepted": "NOT_YET_MEASURABLE",
"false_completion_rate": "NOT_YET_MEASURABLE",
"known_false_completions": 0,
"known_honest_failures": known_honest_failures,
"unknown_validity": len(completed),
"mutation_failures": len(mutation_failures),
"research_dispatches": len(dispatched),
"verification_failures": sum(1 for r in failed if r.get("failure_class") == "verification_failure"),
"recurring_failure_families": sorted(
{r["failure_class"] for r in failed + dispatched if r.get("failure_class") not in ("", "UNKNOWN")}
),
"candidates": len(candidates),
"evidence_only": total - len(candidates),
}
if __name__ == "__main__":
print(json.dumps(metrics(build_eligible_records()), indent=2))
+25 -12
View File
@@ -13,6 +13,7 @@ publication fails visibly, must not silently falsify the ledger). It does
NOT alter research cognition or stage outcomes — callers invoke it after
gate acceptance, and its failure does not invalidate the accepted artifact.
"""
from __future__ import annotations
import json
@@ -20,17 +21,24 @@ import os
import urllib.request
from source_ledger import build_records, render_markdown
import sys as _sys
_SCRIPT_DIR = str(__import__('pathlib').Path(__file__).resolve().parent)
if _SCRIPT_DIR not in _sys.path:
_sys.path.insert(0, _SCRIPT_DIR)
import gitea_auth
MARKER_PREFIX = "<!-- source-ledger"
def _api(method: str, path: str, payload: dict | None = None) -> tuple[int, dict | str]:
token = os.environ.get("GITEA_TOKEN", "")
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
server = os.environ.get("GITEA_SERVER", "https://gitea.thingscouldgetdicey.online")
req = urllib.request.Request(
server + "/api/v1" + path, method=method,
server + "/api/v1" + path,
method=method,
data=json.dumps(payload).encode() if payload else None,
headers={"Authorization": f"token {token}", "Content-Type": "application/json"})
headers={"Authorization": f"token {token}", "Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=60) as r:
body = r.read().decode()
@@ -43,8 +51,9 @@ def _marker(artifact_hash: str) -> str:
return f"{MARKER_PREFIX} hash={artifact_hash} -->"
def publish(issue: int, artifact_dir: str, artifact_hash: str,
runner: dict | None = None, model: str | None = None) -> dict:
def publish(
issue: int, artifact_dir: str, artifact_hash: str, runner: dict | None = None, model: str | None = None
) -> dict:
"""Render + post/update the ledger comment. Returns publication result."""
path = os.path.join(artifact_dir, "outward.json")
with open(path) as f:
@@ -58,9 +67,13 @@ def publish(issue: int, artifact_dir: str, artifact_hash: str,
legacy = not any(
c.get("contradicts_source_ids")
for c in outward.get("contradictory_evidence", outward.get("contradictions", []))
if isinstance(c, dict))
body = _marker(artifact_hash) + "\n" + render_markdown(
records, outward, runner, model, artifact_hash, legacy_claims=legacy)
if isinstance(c, dict)
)
body = (
_marker(artifact_hash)
+ "\n"
+ render_markdown(records, outward, runner, model, artifact_hash, legacy_claims=legacy)
)
# Find existing ledger comment for this artifact hash (idempotency)
status, comments = _api("GET", f"/repos/conductor/rune/issues/{issue}/comments?limit=50")
@@ -73,14 +86,12 @@ def publish(issue: int, artifact_dir: str, artifact_hash: str,
break
if existing:
status, _ = _api("PATCH", f"/repos/conductor/rune/issues/comments/{existing['id']}",
{"body": body})
status, _ = _api("PATCH", f"/repos/conductor/rune/issues/comments/{existing['id']}", {"body": body})
if status not in (200, 201):
raise RuntimeError(f"SourceLedger publish: update failed (HTTP {status})")
return {"action": "updated", "comment_id": existing["id"], "artifact_hash": artifact_hash}
status, created = _api("POST", f"/repos/conductor/rune/issues/{issue}/comments",
{"body": body})
status, created = _api("POST", f"/repos/conductor/rune/issues/{issue}/comments", {"body": body})
if status not in (200, 201):
raise RuntimeError(f"SourceLedger publish: create failed (HTTP {status}): {created}")
return {"action": "created", "comment_id": created.get("id"), "artifact_hash": artifact_hash}
@@ -88,6 +99,7 @@ def publish(issue: int, artifact_dir: str, artifact_hash: str,
def main_with(argv: list[str]) -> int:
import argparse
parser = argparse.ArgumentParser(description="Publish the OUTWARD Source Ledger")
parser.add_argument("--issue", type=int, required=True)
parser.add_argument("--artifact-dir", required=True)
@@ -100,4 +112,5 @@ def main_with(argv: list[str]) -> int:
if __name__ == "__main__":
import sys
raise SystemExit(main_with(sys.argv))
+35 -10
View File
@@ -9,6 +9,7 @@ DEFINE does NOT: inspect code, search externally, or produce a design.
DEFINE does: classify the question, identify sub-questions, define the evidence
contract, and produce the required-stage plan.
"""
from __future__ import annotations
import json
@@ -17,15 +18,36 @@ from typing import Any
# Keywords that suggest external research is needed
EXTERNAL_INDICATORS = [
"best practices", "state of the art", "alternative", "compare",
"benchmark", "library", "framework", "prior art", "existing solutions",
"how do other", "state of the art", "literature", "approach",
"best practices",
"state of the art",
"alternative",
"compare",
"benchmark",
"library",
"framework",
"prior art",
"existing solutions",
"how do other",
"state of the art",
"literature",
"approach",
]
# Keywords that suggest repo-specific work
REPO_INDICATORS = [
"fix", "bug", "refactor", "add", "implement", "remove", "optimize",
"update", "migrate", "clean up", "wire", "integrate", "test",
"fix",
"bug",
"refactor",
"add",
"implement",
"remove",
"optimize",
"update",
"migrate",
"clean up",
"wire",
"integrate",
"test",
]
@@ -88,8 +110,7 @@ def compile_define_contract(issue: dict) -> dict:
required_stages.extend(["SYNTHESIS", "QUALIFY"])
# Skipped stages with reasons (canonical stage names only)
all_stages = {"ISSUE", "INWARD", "OUTWARD", "APPLICABILITY",
"ADVERSARIAL", "INNOVATION", "SYNTHESIS", "QUALIFY"}
all_stages = {"ISSUE", "INWARD", "OUTWARD", "APPLICABILITY", "ADVERSARIAL", "INNOVATION", "SYNTHESIS", "QUALIFY"}
skipped = {}
for s in sorted(all_stages - set(required_stages)):
if s == "OUTWARD":
@@ -144,6 +165,7 @@ def is_ambiguous(issue: dict) -> bool:
def main_with(argv: list[str]) -> int:
import argparse
parser = argparse.ArgumentParser(description="Deterministic DEFINE stage")
parser.add_argument("--issue-file", required=True, help="Path to issue JSON")
parser.add_argument("--out", required=True, help="Output path for define.json")
@@ -163,12 +185,15 @@ def main_with(argv: list[str]) -> int:
with open(args.out, "w") as f:
json.dump(result, f, indent=2, default=str)
print(f"DEFINE contract: type={contract['question_type']}, "
f"stages={contract['required_stages']}, "
f"mimo_needed={ambiguous}")
print(
f"DEFINE contract: type={contract['question_type']}, "
f"stages={contract['required_stages']}, "
f"mimo_needed={ambiguous}"
)
return 0
if __name__ == "__main__":
import sys
sys.exit(main_with(sys.argv))
+52 -24
View File
@@ -8,6 +8,7 @@ Event types emitted:
StageStarted, PromptCompiled, QueryIssued, ToolStarted, ToolCompleted,
SourceDiscovered, SourceRead, EvidenceAccepted, ToolFailed, StageCompleted, StageFailed
"""
from __future__ import annotations
import json
@@ -21,8 +22,7 @@ from typing import Callable
class ResearchEventRelay:
"""Reads mimo JSON events in real-time and updates canonical state."""
def __init__(self, tree, events_path: str, mimo_cmd: list[str], err_path: str,
attempt=None):
def __init__(self, tree, events_path: str, mimo_cmd: list[str], err_path: str, attempt=None):
self.tree = tree
self.events_path = events_path
self.mimo_cmd = mimo_cmd
@@ -31,8 +31,12 @@ class ResearchEventRelay:
# marks tool windows; it never changes classification or control flow.
self.attempt = attempt
self.event_counts = {
"queries": 0, "sources_discovered": 0, "sources_read": 0,
"evidence_accepted": 0, "tool_failures": 0, "tool_calls": 0,
"queries": 0,
"sources_discovered": 0,
"sources_read": 0,
"evidence_accepted": 0,
"tool_failures": 0,
"tool_calls": 0,
}
# Real tool receipts (RP3 evidence chain: QUERY → SOURCE → READ → CLAIM)
self.receipts = []
@@ -62,31 +66,45 @@ class ResearchEventRelay:
url = tool_input["url"]
if "duckduckgo" in url or "search" in url:
self.event_counts["queries"] += 1
self.receipts.append({
"kind": "QUERY", "tool": tool, "url": url,
"ts": time.time(),
})
self.receipts.append(
{
"kind": "QUERY",
"tool": tool,
"url": url,
"ts": time.time(),
}
)
return "QueryIssued"
else:
self.event_counts["sources_read"] += 1
self.receipts.append({
"kind": "SOURCE_READ", "tool": tool, "url": url,
"ts": time.time(),
})
self.receipts.append(
{
"kind": "SOURCE_READ",
"tool": tool,
"url": url,
"ts": time.time(),
}
)
return "SourceRead"
self.receipts.append({
"kind": "TOOL", "tool": tool,
"input_keys": sorted(tool_input.keys()) if isinstance(tool_input, dict) else [],
"ts": time.time(),
})
self.receipts.append(
{
"kind": "TOOL",
"tool": tool,
"input_keys": sorted(tool_input.keys()) if isinstance(tool_input, dict) else [],
"ts": time.time(),
}
)
return "ToolCompleted"
elif status in ("error", "failed"):
self.event_counts["tool_failures"] += 1
self.receipts.append({
"kind": "TOOL_FAILED", "tool": tool,
"input_keys": sorted(tool_input.keys()) if isinstance(tool_input, dict) else [],
"ts": time.time(),
})
self.receipts.append(
{
"kind": "TOOL_FAILED",
"tool": tool,
"input_keys": sorted(tool_input.keys()) if isinstance(tool_input, dict) else [],
"ts": time.time(),
}
)
return "ToolFailed"
return None
@@ -166,8 +184,18 @@ def run_stage_with_relay(tree, out_dir: str) -> dict:
mimo_model = os.environ.get("MIMO_MODEL", "Verboo Code/glm-5.3-flash")
mimo_agent = os.environ.get("MIMO_AGENT", "build")
cmd = [mimo_bin, "run", "--agent", mimo_agent, "-m", mimo_model,
"--dangerously-skip-permissions", "--format", "json", prompt]
cmd = [
mimo_bin,
"run",
"--agent",
mimo_agent,
"-m",
mimo_model,
"--dangerously-skip-permissions",
"--format",
"json",
prompt,
]
relay = ResearchEventRelay(tree, events_path, cmd, err_path)
result = relay.run()
+23 -2
View File
@@ -10,6 +10,7 @@ Builds a bounded candidate context for MiMo INWARD inspection:
This is DETERMINISTIC — no MiMo needed. MiMo gets the candidate context
and only performs semantic inspection if the context requires it.
"""
from __future__ import annotations
import json
@@ -49,8 +50,28 @@ def build_context(issue: dict, define_contract: dict) -> dict:
def _extract_keywords(text: str) -> list[str]:
"""Extract meaningful keywords from the issue text."""
stop = {"the", "a", "an", "in", "for", "to", "of", "and", "or", "is", "are",
"when", "how", "what", "this", "that", "with", "on", "at", "from"}
stop = {
"the",
"a",
"an",
"in",
"for",
"to",
"of",
"and",
"or",
"is",
"are",
"when",
"how",
"what",
"this",
"that",
"with",
"on",
"at",
"from",
}
words = re.findall(r"[a-z_]{3,}", text.lower())
return [w for w in words if w not in stop][:20]
+3
View File
@@ -12,6 +12,7 @@ and compiles a task-specific prompt that answers:
What am I forbidden from doing yet?
What output must I return?
"""
from __future__ import annotations
import json
@@ -73,6 +74,7 @@ def compile_research_prompt(
def main_with(argv: list[str]) -> int:
import argparse
parser = argparse.ArgumentParser(description="Compile a stage-scoped research prompt")
parser.add_argument("--issue", type=int, required=True)
parser.add_argument("--round", type=int, default=1)
@@ -92,4 +94,5 @@ def main_with(argv: list[str]) -> int:
if __name__ == "__main__":
import sys
sys.exit(main_with(sys.argv))
+2 -1
View File
@@ -35,6 +35,7 @@ import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
import gitea_auth # noqa: E402 — after scripts-dir path insert
# Dependency direction (handoff 2026-09-10 §3): the research executor MAY
# import Rune Core; Core never imports the executor. Route authority and
# evidence probe live in src/rune (canonical, Option C unified authority).
@@ -2092,7 +2093,7 @@ class StageExecutor:
def _fetch_issue(self) -> dict | None:
import urllib.request
token = os.environ.get("GITEA_TOKEN", "")
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
server = os.environ.get("GITEA_SERVER", "https://gitea.thingscouldgetdicey.online")
base = server.rstrip("/") + "/api/v1/repos/conductor/rune"
if not token:
+79 -50
View File
@@ -25,6 +25,7 @@ Stage transitions:
REFINE(unknowns) → targeted return to the earliest deficient stage
(no full-pipeline restart; INWARD is not rerun unless Rune reality changed)
"""
from __future__ import annotations
import json
@@ -34,8 +35,14 @@ from pathlib import Path
from typing import Any
STAGES = [
"ISSUE", "INWARD", "OUTWARD", "APPLICABILITY",
"INNOVATION", "ADVERSARIAL", "SYNTHESIS", "QUALIFY",
"ISSUE",
"INWARD",
"OUTWARD",
"APPLICABILITY",
"INNOVATION",
"ADVERSARIAL",
"SYNTHESIS",
"QUALIFY",
]
# Legacy stage names that must never become READY through the canonical path.
@@ -66,12 +73,15 @@ APPLICABILITY_CLASSES = {
# The default is SKIP — INNOVATION runs only when warranted.
APPLICABILITY_ROUTES = {
"STANDARD_SOLUTION_SUFFICIENT", # → ADVERSARIAL (INNOVATION skipped)
"INNOVATION_WARRANTED", # → INNOVATION → ADVERSARIAL
"INNOVATION_WARRANTED", # → INNOVATION → ADVERSARIAL
}
# Valid INNOVATION conclusions. NO_INNOVATION_WARRANTED is a SUCCESS result.
INNOVATION_CONCLUSIONS = {
"ADOPT", "ADAPT", "BORROW", "BUILD",
"ADOPT",
"ADAPT",
"BORROW",
"BUILD",
"NO_INNOVATION_WARRANTED",
}
@@ -80,12 +90,12 @@ INNOVATION_CONCLUSIONS = {
# Only SURVIVES* dispositions unlock SYNTHESIS. Everything else triggers
# targeted refinement at the earliest stage that can repair the defect.
ADVERSARIAL_DISPOSITIONS = {
"SURVIVES", # → SYNTHESIS READY
"SURVIVES_WITH_MITIGATIONS", # → SYNTHESIS READY (mitigations are required inputs)
"EVIDENCE_DEFICIENT", # → targeted OUTWARD → APPLICABILITY → ADVERSARIAL
"APPLICABILITY_DEFICIENT", # → APPLICABILITY → ADVERSARIAL
"INNOVATION_WARRANTED", # → INNOVATION → ADVERSARIAL
"NO_VIABLE_CANDIDATE", # → targeted OUTWARD (expand candidate space)
"SURVIVES", # → SYNTHESIS READY
"SURVIVES_WITH_MITIGATIONS", # → SYNTHESIS READY (mitigations are required inputs)
"EVIDENCE_DEFICIENT", # → targeted OUTWARD → APPLICABILITY → ADVERSARIAL
"APPLICABILITY_DEFICIENT", # → APPLICABILITY → ADVERSARIAL
"INNOVATION_WARRANTED", # → INNOVATION → ADVERSARIAL
"NO_VIABLE_CANDIDATE", # → targeted OUTWARD (expand candidate space)
}
SYNTHESIS_UNLOCKING_DISPOSITIONS = {"SURVIVES", "SURVIVES_WITH_MITIGATIONS"}
@@ -99,9 +109,9 @@ ADVERSARIAL_REFINEMENT_ROUTES = {
# QUALIFY outcomes (narrowed semantics, §8–10 of the correction).
QUALIFY_OUTCOMES = {
"PROCEED", # implementation-ready → MiMo Code
"REFINE_SYNTHESIS", # build instructions weak → SYNTHESIS only
"REOPEN_EARLIER_STAGE", # exceptional: genuinely new blocking fact
"PROCEED", # implementation-ready → MiMo Code
"REFINE_SYNTHESIS", # build instructions weak → SYNTHESIS only
"REOPEN_EARLIER_STAGE", # exceptional: genuinely new blocking fact
}
@@ -184,10 +194,14 @@ class ResearchTaskTree:
if stage not in self.state["completed_stages"] or stage != "QUALIFY":
self.state["completed_stages"].append(stage)
self.state["artifacts"][stage] = artifact
self.state["stage_history"].append({
"stage": stage, "round": self.round,
"completed_at": "auto", "artifact_keys": list(artifact.keys()),
})
self.state["stage_history"].append(
{
"stage": stage,
"round": self.round,
"completed_at": "auto",
"artifact_keys": list(artifact.keys()),
}
)
# Check if we're in a targeted branch
targeted = self.state.get("targeted_branch_stages", [])
if targeted and stage in targeted:
@@ -239,9 +253,7 @@ class ResearchTaskTree:
if disposition in SYNTHESIS_UNLOCKING_DISPOSITIONS:
ok, problems = self.synthesis_readiness(artifact)
if not ok:
raise ValueError(
"SYNTHESIS readiness invariant failed: " + "; ".join(problems)
)
raise ValueError("SYNTHESIS readiness invariant failed: " + "; ".join(problems))
return "SYNTHESIS"
return ADVERSARIAL_REFINEMENT_ROUTES[disposition]
@@ -258,9 +270,7 @@ class ResearchTaskTree:
"""
outcome = artifact.get("outcome", "PROCEED")
if outcome not in QUALIFY_OUTCOMES:
raise ValueError(
f"QUALIFY outcome must be one of {sorted(QUALIFY_OUTCOMES)}, got: {outcome!r}"
)
raise ValueError(f"QUALIFY outcome must be one of {sorted(QUALIFY_OUTCOMES)}, got: {outcome!r}")
self.state["qualify_outcome"] = outcome
if outcome == "PROCEED":
self.state["status"] = "COMPLETE"
@@ -268,13 +278,15 @@ class ResearchTaskTree:
if outcome == "REFINE_SYNTHESIS":
return "SYNTHESIS"
# REOPEN_EARLIER_STAGE: all four required fields must be present
required_fields = ("reason", "return_to", "new_blocking_fact",
"why_previous_gate_could_not_reasonably_have_caught_it")
required_fields = (
"reason",
"return_to",
"new_blocking_fact",
"why_previous_gate_could_not_reasonably_have_caught_it",
)
missing = [f for f in required_fields if not artifact.get(f)]
if missing:
raise ValueError(
f"REOPEN_EARLIER_STAGE requires {required_fields}; missing: {missing}"
)
raise ValueError(f"REOPEN_EARLIER_STAGE requires {required_fields}; missing: {missing}")
return_to = artifact["return_to"]
if return_to not in STAGES or return_to == "QUALIFY":
raise ValueError(f"REOPEN_EARLIER_STAGE return_to must be an epistemic stage, got: {return_to!r}")
@@ -298,11 +310,12 @@ class ResearchTaskTree:
if not survivors:
problems.append("no surviving candidate")
if disposition == "SURVIVES_WITH_MITIGATIONS" and not adversarial_artifact.get("required_mitigations"):
problems.append("SURVIVES_WITH_MITIGATIONS without required_mitigations — "
"mitigations are mandatory synthesis inputs, not optional prose")
problems.append(
"SURVIVES_WITH_MITIGATIONS without required_mitigations — "
"mitigations are mandatory synthesis inputs, not optional prose"
)
if self.state.get("blocking_unknowns"):
problems.append("blocking adversarial unknowns present: "
f"{len(self.state['blocking_unknowns'])}")
problems.append(f"blocking adversarial unknowns present: {len(self.state['blocking_unknowns'])}")
return (len(problems) == 0, problems)
def _route_after_applicability(self, artifact: dict) -> str:
@@ -320,17 +333,20 @@ class ResearchTaskTree:
route = artifact.get("route", "STANDARD_SOLUTION_SUFFICIENT")
if route not in ("STANDARD_SOLUTION_SUFFICIENT", "INNOVATION_WARRANTED"):
raise ValueError(
f"APPLICABILITY route must be STANDARD_SOLUTION_SUFFICIENT or "
f"INNOVATION_WARRANTED, got: {route!r}"
f"APPLICABILITY route must be STANDARD_SOLUTION_SUFFICIENT or INNOVATION_WARRANTED, got: {route!r}"
)
self.state["applicability_route"] = route
return "INNOVATION" if route == "INNOVATION_WARRANTED" else "ADVERSARIAL"
def fail(self, reason: str) -> None:
self.state["stage_history"].append({
"stage": self.state["current_stage"], "round": self.round,
"failed": True, "reason": reason,
})
self.state["stage_history"].append(
{
"stage": self.state["current_stage"],
"round": self.round,
"failed": True,
"reason": reason,
}
)
self.save()
def more_research(self, unknowns: list[dict]) -> list[dict]:
@@ -360,11 +376,13 @@ class ResearchTaskTree:
stages_needed = ["INNOVATION"]
else:
stages_needed = ["OUTWARD"]
branches.append({
"unknown": u,
"stages": stages_needed,
"round": self.round,
})
branches.append(
{
"unknown": u,
"stages": stages_needed,
"round": self.round,
}
)
all_stages.extend(stages_needed)
# Route to the earliest deficient stage in canonical order,
@@ -380,8 +398,7 @@ class ResearchTaskTree:
def get_prior_artifacts(self, for_stage: str) -> dict:
"""Return the accepted artifacts this stage needs."""
idx = STAGES.index(for_stage) if for_stage in STAGES else len(STAGES)
return {STAGES[i]: self.state["artifacts"].get(STAGES[i], {})
for i in range(idx)}
return {STAGES[i]: self.state["artifacts"].get(STAGES[i], {}) for i in range(idx)}
def capsule(self) -> dict:
"""The process capsule for MiMo invocation (§14)."""
@@ -394,11 +411,23 @@ class ResearchTaskTree:
"accepted_artifacts": {
# Adversarial-gated refinement (correction §5–6): mitigations
# and falsification tests MUST reach SYNTHESIS as required inputs.
k: {kk: v for kk, v in vv.items() if kk in (
"decision", "claims", "sources", "candidates", "disposition",
"required_mitigations", "falsification_tests",
"surviving_candidates", "rejected_candidates", "route",
)}
k: {
kk: v
for kk, v in vv.items()
if kk
in (
"decision",
"claims",
"sources",
"candidates",
"disposition",
"required_mitigations",
"falsification_tests",
"surviving_candidates",
"rejected_candidates",
"route",
)
}
for k, vv in self.state["artifacts"].items()
},
"blocking_unknowns": self.state.get("blocking_unknowns", []),
+24 -19
View File
@@ -14,6 +14,7 @@ This module captures ACTUAL runtime evidence — not configuration inference:
The evidence record travels with the stage result and is validated by the
placement gate BEFORE a stage result can be accepted.
"""
from __future__ import annotations
import os
@@ -30,14 +31,23 @@ DESKTOP_RUNNER_LABEL = "rune-ci-desktop"
# Stages that require desktop-heavy execution (cognition, research, coding).
DESKTOP_HEAVY_STAGES = {
"INWARD", "OUTWARD", "APPLICABILITY", "INNOVATION",
"ADVERSARIAL", "SYNTHESIS", "QUALIFY",
"INWARD",
"OUTWARD",
"APPLICABILITY",
"INNOVATION",
"ADVERSARIAL",
"SYNTHESIS",
"QUALIFY",
}
# Lightweight forge/control work that MAY legitimately run on the Pi.
PI_LIGHTWEIGHT_OPERATIONS = {
"issue_metadata", "label_change", "artifact_metadata",
"process_state", "eligibility_check", "state_transition",
"issue_metadata",
"label_change",
"artifact_metadata",
"process_state",
"eligibility_check",
"state_transition",
}
@@ -58,14 +68,8 @@ def capture_execution_evidence(
runner identity present in the environment.
"""
hostname = socket.gethostname()
runner_name = (
os.environ.get("RUNNER_NAME")
or os.environ.get("GITEA_RUNNER_NAME")
or ("local:" + hostname)
)
runner_labels = [
l.strip() for l in os.environ.get("RUNNER_LABELS", "").split(",") if l.strip()
] or [runner_name]
runner_name = os.environ.get("RUNNER_NAME") or os.environ.get("GITEA_RUNNER_NAME") or ("local:" + hostname)
runner_labels = [l.strip() for l in os.environ.get("RUNNER_LABELS", "").split(",") if l.strip()] or [runner_name]
return {
"runner_hostname": hostname,
@@ -92,13 +96,12 @@ def validate_placement(evidence: dict) -> tuple[bool, list[str]]:
klass = evidence.get("execution_class", "desktop-heavy")
# Evidence completeness — identity must be actually captured
for field in ("runner_hostname", "runner_name", "runner_labels",
"execution_class", "stage", "timestamp"):
for field in ("runner_hostname", "runner_name", "runner_labels", "execution_class", "stage", "timestamp"):
if not evidence.get(field):
violations.append(f"placement evidence missing field: {field}")
if klass == "desktop-heavy":
identity = (evidence.get("runner_name", "") + " " + evidence.get("runner_hostname", ""))
identity = evidence.get("runner_name", "") + " " + evidence.get("runner_hostname", "")
if _is_pi(identity) or any(_is_pi(l) for l in evidence.get("runner_labels", [])):
violations.append(
f"PLACEMENT VIOLATION: desktop-heavy stage '{stage}' executed on Pi "
@@ -109,10 +112,12 @@ def validate_placement(evidence: dict) -> tuple[bool, list[str]]:
# runner NAME (rune-desktop), or a verified local desktop execution.
# Pi rejection above already caught Pi markers — this is the allow-side.
labels = " ".join(evidence.get("runner_labels", []))
if (DESKTOP_RUNNER_LABEL not in identity
and "rune-desktop" not in identity
and DESKTOP_RUNNER_LABEL not in labels
and "local:" not in evidence.get("runner_name", "")):
if (
DESKTOP_RUNNER_LABEL not in identity
and "rune-desktop" not in identity
and DESKTOP_RUNNER_LABEL not in labels
and "local:" not in evidence.get("runner_name", "")
):
violations.append(
f"runner '{evidence.get('runner_name')}' is not '{DESKTOP_RUNNER_LABEL}' "
f"nor the registered rune-desktop runner, and not a verified local "
+42 -26
View File
@@ -21,6 +21,7 @@ Hard invariants:
must not silently falsify the ledger.
- No trust scores. Factual source classification only.
"""
from __future__ import annotations
import hashlib
@@ -70,8 +71,7 @@ def build_records(outward: dict, stage: str = "OUTWARD") -> list[dict]:
url = _norm(r.get("url", ""))
if not url:
continue
rid = _receipt_id(stage, r.get("tool", "?"), r.get("url", ""),
r.get("ts", 0), i)
rid = _receipt_id(stage, r.get("tool", "?"), r.get("url", ""), r.get("ts", 0), i)
if r.get("kind") == "SOURCE_READ":
read_urls[url] = {"receipt_id": rid, "tool": r.get("tool", "?"), "ts": r.get("ts")}
elif r.get("kind") == "TOOL_FAILED":
@@ -93,7 +93,7 @@ def build_records(outward: dict, stage: str = "OUTWARD") -> list[dict]:
for i, c in enumerate(outward.get("contradictory_evidence", outward.get("contradictions", []))):
if isinstance(c, dict):
for sid in c.get("contradicts_source_ids", []):
contradictions_by_source.setdefault(sid, []).append(c.get("id", f"C{i+1}"))
contradictions_by_source.setdefault(sid, []).append(c.get("id", f"C{i + 1}"))
# plain-string contradictions are not linked (labeled in render)
records = []
@@ -119,28 +119,41 @@ def build_records(outward: dict, stage: str = "OUTWARD") -> list[dict]:
# Upgrade precedence fix is above; this block is intentionally after
# status resolution so the downgrade applies regardless of note text.
resolved = s.get("resolved_url", url) # explicit field if present
records.append({
"receipt_id": (receipt or {}).get("receipt_id", ""),
"source_id": sid,
"requested_url": url,
"resolved_url": resolved,
"domain": _domain(resolved),
"status": status,
"tool": (receipt or {}).get("tool", s.get("tool", "UNKNOWN_NOT_EXPOSED")),
"timestamp": (receipt or {}).get("ts", "UNKNOWN_NOT_EXPOSED"),
"source_type": s.get("class", s.get("source_class", "UNKNOWN_NOT_EXPOSED")),
"claim_ids": claims_by_source.get(sid, []),
"contradiction_ids": contradictions_by_source.get(sid, []),
"blocked_reason": (s.get("note", "") if status == STATUS_BLOCKED else
(receipt.get("kind") if status == STATUS_FAILED and receipt else
("no machine SOURCE_READ receipt matched (legacy artifact)" if status == STATUS_FAILED else ""))),
"supports_evidence": status == STATUS_READ,
})
records.append(
{
"receipt_id": (receipt or {}).get("receipt_id", ""),
"source_id": sid,
"requested_url": url,
"resolved_url": resolved,
"domain": _domain(resolved),
"status": status,
"tool": (receipt or {}).get("tool", s.get("tool", "UNKNOWN_NOT_EXPOSED")),
"timestamp": (receipt or {}).get("ts", "UNKNOWN_NOT_EXPOSED"),
"source_type": s.get("class", s.get("source_class", "UNKNOWN_NOT_EXPOSED")),
"claim_ids": claims_by_source.get(sid, []),
"contradiction_ids": contradictions_by_source.get(sid, []),
"blocked_reason": (
s.get("note", "")
if status == STATUS_BLOCKED
else (
receipt.get("kind")
if status == STATUS_FAILED and receipt
else (
"no machine SOURCE_READ receipt matched (legacy artifact)"
if status == STATUS_FAILED
else ""
)
)
),
"supports_evidence": status == STATUS_READ,
}
)
return records
def render_markdown(records: list[dict], outward: dict, runner: dict | None,
model: str, artifact_hash: str, legacy_claims: bool = False) -> str:
def render_markdown(
records: list[dict], outward: dict, runner: dict | None, model: str, artifact_hash: str, legacy_claims: bool = False
) -> str:
"""Fixed Markdown template. Pure function of records + metadata."""
d = outward.get("discovery", {})
n_read = sum(1 for r in records if r["status"] == STATUS_READ)
@@ -152,9 +165,11 @@ def render_markdown(records: list[dict], outward: dict, runner: dict | None,
runner_host = (runner or {}).get("runner_hostname", "UNKNOWN")
prov_note = ""
if legacy_claims:
prov_note = ("\n*Claim↔source links for this legacy artifact are derived from "
"claim nesting (pre-#282 format); future artifacts carry explicit "
"supports_source_ids/contradicts_source_ids.*")
prov_note = (
"\n*Claim↔source links for this legacy artifact are derived from "
"claim nesting (pre-#282 format); future artifacts carry explicit "
"supports_source_ids/contradicts_source_ids.*"
)
lines = [
"## OUTWARD — ACCEPTED",
@@ -179,6 +194,7 @@ def render_markdown(records: list[dict], outward: dict, runner: dict | None,
link = f"[{r['domain']}]({r['requested_url']})" if r["requested_url"].startswith("http") else r["domain"]
lines.append(
f"| {r['source_id']} | {link} | {r['source_type']} | {r['status']} "
f"| {len(r['claim_ids'])} | {len(r['contradiction_ids'])} | {caveat[:60]} |")
f"| {len(r['claim_ids'])} | {len(r['contradiction_ids'])} | {caveat[:60]} |"
)
lines.append(prov_note)
return "\n".join(lines)
+93 -49
View File
@@ -13,68 +13,102 @@ contract, the contract-drift test fails.
Compatibility aliases (e.g. urls → url, classification → source_class)
are explicit, versioned, and bounded — not arbitrary rescue behavior.
"""
from __future__ import annotations
# Per-stage contract: required fields in the structured artifact
STAGE_CONTRACTS: dict[str, dict] = {
"INWARD": {
"required_fields": [
{"name": "claims", "type": "list",
"description": "relevant files/behaviors with evidence citations"},
{"name": "claims", "type": "list", "description": "relevant files/behaviors with evidence citations"},
],
"optional_fields": ["context", "deterministic"],
},
"OUTWARD": {
"required_fields": [
{"name": "discovery", "type": "object",
"description": "queries, sources_discovered, source_count"},
{"name": "reading", "type": "object",
"description": "sources_selected, sources_read (with machine receipts), evidence_extracted"},
{"name": "tool_receipts", "type": "list",
"description": "per-event receipts with kind/tool/url/timestamp"},
{"name": "coverage", "type": "object",
"description": "required_classes, represented_classes, uncovered"},
{"name": "discovery", "type": "object", "description": "queries, sources_discovered, source_count"},
{
"name": "reading",
"type": "object",
"description": "sources_selected, sources_read (with machine receipts), evidence_extracted",
},
{"name": "tool_receipts", "type": "list", "description": "per-event receipts with kind/tool/url/timestamp"},
{"name": "coverage", "type": "object", "description": "required_classes, represented_classes, uncovered"},
],
"optional_fields": [
"text",
"limitations",
"contradictory_evidence",
"supporting_evidence",
"alternatives",
"adjacent_field_leads",
"unresolved_questions",
],
"optional_fields": ["text", "limitations", "contradictory_evidence",
"supporting_evidence", "alternatives",
"adjacent_field_leads", "unresolved_questions"],
},
"APPLICABILITY": {
"required_fields": [
{"name": "route", "type": "string",
"description": "STANDARD_SOLUTION_SUFFICIENT or INNOVATION_WARRANTED",
"enum": ["STANDARD_SOLUTION_SUFFICIENT", "INNOVATION_WARRANTED"]},
{"name": "candidates", "type": "list",
"description": "classified mechanisms with integration_point, evidence, source_ids"},
{
"name": "route",
"type": "string",
"description": "STANDARD_SOLUTION_SUFFICIENT or INNOVATION_WARRANTED",
"enum": ["STANDARD_SOLUTION_SUFFICIENT", "INNOVATION_WARRANTED"],
},
{
"name": "candidates",
"type": "list",
"description": "classified mechanisms with integration_point, evidence, source_ids",
},
],
"optional_fields": ["notes"],
},
"ADVERSARIAL": {
"required_fields": [
{"name": "disposition", "type": "string",
"description": "SURVIVES | SURVIVES_WITH_MITIGATIONS | EVIDENCE_DEFICIENT | "
"APPLICABILITY_DEFICIENT | INNOVATION_WARRANTED | NO_VIABLE_CANDIDATE",
"enum": ["SURVIVES", "SURVIVES_WITH_MITIGATIONS", "EVIDENCE_DEFICIENT",
"APPLICABILITY_DEFICIENT", "INNOVATION_WARRANTED", "NO_VIABLE_CANDIDATE"]},
{"name": "surviving_candidates", "type": "list",
"description": "candidate ids that survived attack"},
{"name": "attacks", "type": "list",
"description": "attack rows with target_candidate, dimension, finding, severity"},
{"name": "required_mitigations", "type": "list",
"description": "mitigations that must appear in SYNTHESIS (mandatory, not prose)"},
{"name": "falsification_tests", "type": "list",
"description": "kill-tests that must convert into invariants"},
{
"name": "disposition",
"type": "string",
"description": "SURVIVES | SURVIVES_WITH_MITIGATIONS | EVIDENCE_DEFICIENT | "
"APPLICABILITY_DEFICIENT | INNOVATION_WARRANTED | NO_VIABLE_CANDIDATE",
"enum": [
"SURVIVES",
"SURVIVES_WITH_MITIGATIONS",
"EVIDENCE_DEFICIENT",
"APPLICABILITY_DEFICIENT",
"INNOVATION_WARRANTED",
"NO_VIABLE_CANDIDATE",
],
},
{"name": "surviving_candidates", "type": "list", "description": "candidate ids that survived attack"},
{
"name": "attacks",
"type": "list",
"description": "attack rows with target_candidate, dimension, finding, severity",
},
{
"name": "required_mitigations",
"type": "list",
"description": "mitigations that must appear in SYNTHESIS (mandatory, not prose)",
},
{
"name": "falsification_tests",
"type": "list",
"description": "kill-tests that must convert into invariants",
},
],
"optional_fields": ["rejected_candidates", "critical_assumptions",
"new_research_gaps"],
"optional_fields": ["rejected_candidates", "critical_assumptions", "new_research_gaps"],
},
"INNOVATION": {
"required_fields": [
{"name": "conclusion", "type": "string",
"description": "ADOPT | ADAPT | BORROW | BUILD | NO_INNOVATION_WARRANTED",
"enum": ["ADOPT", "ADAPT", "BORROW", "BUILD", "NO_INNOVATION_WARRANTED"]},
{"name": "candidates", "type": "list",
"description": "each with derived_from (mandatory), kill_test (mandatory)"},
{
"name": "conclusion",
"type": "string",
"description": "ADOPT | ADAPT | BORROW | BUILD | NO_INNOVATION_WARRANTED",
"enum": ["ADOPT", "ADAPT", "BORROW", "BUILD", "NO_INNOVATION_WARRANTED"],
},
{
"name": "candidates",
"type": "list",
"description": "each with derived_from (mandatory), kill_test (mandatory)",
},
],
"optional_fields": ["notes"],
},
@@ -83,23 +117,33 @@ STAGE_CONTRACTS: dict[str, dict] = {
{"name": "selected_design", "type": "string"},
{"name": "implementation_sequence", "type": "list"},
{"name": "interfaces", "type": "list"},
{"name": "invariants", "type": "list",
"description": "every falsification test must be converted into an invariant"},
{
"name": "invariants",
"type": "list",
"description": "every falsification test must be converted into an invariant",
},
{"name": "tests_required", "type": "list"},
{"name": "acceptance_criteria", "type": "list"},
{"name": "rollback", "type": "string"},
{"name": "data_flow", "type": "list",
"description": "how data moves through the designed system"},
{"name": "data_flow", "type": "list", "description": "how data moves through the designed system"},
],
"optional_fields": [
"why_this_design_won",
"rejected_alternatives",
"components_affected",
"authority_boundaries",
"failure_modes",
"deliberately_excluded",
],
"optional_fields": ["why_this_design_won", "rejected_alternatives",
"components_affected", "authority_boundaries",
"failure_modes", "deliberately_excluded"],
},
"QUALIFY": {
"required_fields": [
{"name": "outcome", "type": "string",
"description": "PROCEED | REFINE_SYNTHESIS | REOPEN_EARLIER_STAGE",
"enum": ["PROCEED", "REFINE_SYNTHESIS", "REOPEN_EARLIER_STAGE"]},
{
"name": "outcome",
"type": "string",
"description": "PROCEED | REFINE_SYNTHESIS | REOPEN_EARLIER_STAGE",
"enum": ["PROCEED", "REFINE_SYNTHESIS", "REOPEN_EARLIER_STAGE"],
},
],
"optional_fields": ["findings", "deterministic_checks", "reopen"],
},
@@ -109,7 +153,7 @@ STAGE_CONTRACTS: dict[str, dict] = {
# (bounded, versioned, NOT arbitrary rescue behavior).
COMPATIBILITY_ALIASES: dict[str, dict[str, str]] = {
"OUTWARD": {
"urls": "url", # worker uses urls (list) → take first as url
"urls": "url", # worker uses urls (list) → take first as url
"classification": "source_class",
"search_receipts": "queries",
"read_receipts": "sources_read",
+5 -5
View File
@@ -27,6 +27,7 @@ Attempt lifecycle (from the executor's perspective):
TIMEOUT | INTERRUPTED
persist(handle) → append + mirror
"""
from __future__ import annotations
import json
@@ -45,8 +46,7 @@ def _now_iso() -> str:
class StageAttempt:
"""One attempt's observed lifecycle. Pure data — no control flow."""
def __init__(self, attempt_id: str, event_id: str, stage: str,
issue: int, process_id: str):
def __init__(self, attempt_id: str, event_id: str, stage: str, issue: int, process_id: str):
self.attempt_id = attempt_id
self.event_id = event_id
self.stage = stage
@@ -121,8 +121,7 @@ class StageAttempt:
self.terminal_status = terminal_status
self.finished_at = _now_iso()
# Close any unclosed windows (INTERRUPTED/TIMEOUT paths)
for lst in (self.model_windows, self.tool_windows,
self.gate_windows, self.persistence_windows):
for lst in (self.model_windows, self.tool_windows, self.gate_windows, self.persistence_windows):
if lst and lst[-1][1] == 0.0:
s, _ = lst[-1]
lst[-1] = (s, time.monotonic())
@@ -160,7 +159,8 @@ class StageAttempt:
# not double-counted work.
"overlap_note": (
"categories are observed windows and may overlap; "
"wall_time is authoritative, category sums are not additive"),
"wall_time is authoritative, category sums are not additive"
),
"model_calls": self.model_calls,
"tool_calls": self.tool_calls,
"repo_read_calls": self.repo_read_calls,
+229 -1
View File
@@ -81,6 +81,19 @@ def subjects_since_tag(tag: str, repo: Path | None = None) -> list[str]:
return out.splitlines() if out else []
def _version_lt(a: str, b: str) -> bool:
"""True when semver string a < b. Numeric per segment — never lexicographic.
T15 Part F: '0.66.9' < '0.66.10' is False lexicographically ('9' > '1'),
which made a legitimately-needed patch bump read as the
version_ahead_of_target anomaly and silently skip the release
(observed live on run 6781, 2026-09-11 19:59).
"""
pa = [int(x) for x in a.split(".")]
pb = [int(x) for x in b.split(".")]
return pa < pb
def classify_subject(subject: str) -> str:
"""Classify one commit subject: ``breaking`` | ``feat`` | ``fix`` | ``other``.
@@ -199,6 +212,218 @@ def decision_in_repo(repo: Path | None = None) -> dict[str, Any]:
return assemble_release_decision(version, tag, subjects, len(subjects))
def last_release_boundary(repo: Path | None = None) -> str | None:
"""Find the last chore(release) commit SHA (the release boundary).
This is the correct convergence boundary — NOT the git tag (tags may
lag behind releases due to D7). Falls back to the latest tag if no
release commits exist.
T15 Part F: matches SUBJECTS only (--grep matches the commit BODY by
default; a fix commit whose body mentions 'chore(release)' was
misdetected as the boundary, swallowing its own warranted bump).
"""
try:
result = subprocess.run(
["git", "log", "--format=%H %s", "-1", "--grep=^chore\\(release\\)", "--extended-regexp"],
capture_output=True,
text=True,
cwd=str(repo) if repo else None,
check=True,
)
for line in result.stdout.splitlines():
line = line.strip()
if not line:
continue
sha, _, subject = line.partition(" ")
if is_release_commit(subject):
return sha
except (subprocess.CalledProcessError, IndexError):
pass
return latest_tag(repo) # fallback to tag
def release_convergence(
repo: Path | None = None,
*,
_version: str | None = None,
_boundary_subject: str | None = None,
_subjects: list[str] | None = None,
) -> dict[str, Any]:
"""Correct release convergence: compare current source version to the
warranted TARGET version computed from commits since the last release
boundary (the last chore(release) commit, NOT the tag — tags may lag
due to D7).
Invariant:
current == target → converged, no commit
current < target → create exactly one bump commit
current > target → anomaly
"""
current_version = _version or read_version(repo)
if not current_version:
return {
"converged": True,
"warranted_bump": None,
"anomaly": None,
"commits_since_tag": 0,
"sample": [],
"reason": "no readable version",
}
subjects: list[str] = []
boundary = None
if _subjects is not None:
# Test injection: skip git entirely
substantive = [s for s in _subjects if not is_release_commit(s)]
warranted = highest_warranted_bump(substantive)
if not warranted:
return {
"converged": True,
"warranted_bump": None,
"anomaly": None,
"commits_since_boundary": len(substantive),
"sample": substantive[:5],
"current_version": current_version,
"target_version": current_version,
"reason": "no release-worthy commits since last boundary",
"boundary_sha": "",
}
target_version = next_version(current_version, warranted)
if current_version == target_version:
return {
"converged": True,
"warranted_bump": warranted,
"anomaly": None,
"commits_since_boundary": len(substantive),
"sample": substantive[:5],
"current_version": current_version,
"target_version": target_version,
"reason": "current version already equals warranted target",
"boundary_sha": "",
}
elif _version_lt(current_version, target_version):
return {
"converged": False,
"warranted_bump": warranted,
"anomaly": None,
"commits_since_boundary": len(substantive),
"sample": substantive[:5],
"current_version": current_version,
"target_version": target_version,
"reason": f"bump required: {current_version} → {target_version}",
"boundary_sha": "",
}
else:
return {
"converged": False,
"warranted_bump": None,
"anomaly": "version_ahead_of_target",
"commits_since_boundary": len(substantive),
"sample": substantive[:5],
"current_version": current_version,
"target_version": target_version,
"reason": f"ANOMALY: current {current_version} > target {target_version}",
"boundary_sha": "",
}
if _boundary_subject:
result = subprocess.run(
["git", "log", "--format=%s", f"{boundary}..HEAD"],
capture_output=True,
text=True,
cwd=str(repo) if repo else None,
check=True,
)
subjects = [s for s in result.stdout.splitlines() if s.strip()]
else:
# Production path: converge over commits since the last release
# boundary. T15 Part F root cause: `boundary` was initialized to
# None and never assigned — every run classified ALL commits as
# unreleased (959+), so the warranted bump never converged and
# every queued push wrote its own bump commit (5× 0.66.9 observed
# 2026-09-11 20:21). Assign the boundary from the one source of
# truth and range the log over it.
boundary = last_release_boundary(repo)
if boundary:
result = subprocess.run(
["git", "log", "--format=%s", f"{boundary}..HEAD"],
capture_output=True,
text=True,
cwd=str(repo) if repo else None,
check=True,
)
else:
result = subprocess.run(
["git", "log", "--format=%s"],
capture_output=True,
text=True,
cwd=str(repo) if repo else None,
check=True,
)
subjects = [s for s in result.stdout.splitlines() if s.strip()]
# Exclude chore(release) commits from bump classification (they ARE releases)
substantive = [s for s in subjects if not is_release_commit(s)]
warranted = highest_warranted_bump(substantive)
# Expose the boundary in the decision JSON: consumers (release step,
# debug sessions) otherwise re-derive it from git history.
decision_boundary = {"boundary_sha": (boundary or "")[:12]}
if not warranted:
return {
"converged": True,
"warranted_bump": None,
"anomaly": None,
"commits_since_boundary": len(substantive),
"sample": substantive[:5],
"current_version": current_version,
"target_version": current_version,
"reason": "no release-worthy commits since last boundary",
**decision_boundary,
}
target_version = next_version(current_version, warranted)
if current_version == target_version:
return {
"converged": True,
"warranted_bump": warranted,
"anomaly": None,
"commits_since_boundary": len(substantive),
"sample": substantive[:5],
"current_version": current_version,
"target_version": target_version,
"reason": "current version already equals warranted target",
**decision_boundary,
}
elif _version_lt(current_version, target_version):
# Bump needed — but only if not already converged by a prior bump
# in this same cascade (the target is the SAME for all queued runs)
return {
"converged": False,
"warranted_bump": warranted,
"anomaly": None,
"commits_since_boundary": len(substantive),
"sample": substantive[:5],
"current_version": current_version,
"target_version": target_version,
"reason": f"bump required: {current_version} → {target_version}",
**decision_boundary,
}
else:
return {
"converged": False,
"warranted_bump": None,
"anomaly": "version_ahead_of_target",
"commits_since_boundary": len(substantive),
"sample": substantive[:5],
"current_version": current_version,
"target_version": target_version,
"reason": f"ANOMALY: current {current_version} > target {target_version}",
**decision_boundary,
}
def main(argv: list[str] | None = None) -> dict[str, Any]:
parser = argparse.ArgumentParser(description="Release convergence decision (JSON on stdout)")
parser.add_argument(
@@ -213,7 +438,10 @@ def main(argv: list[str] | None = None) -> dict[str, Any]:
import sys as _sys
_sys.exit(0 if is_release_commit(args.is_release_subject) else 1)
decision = decision_in_repo(args.repo)
decision = release_convergence(args.repo)
# Backward compat: old consumers expect commits_since_tag
if "commits_since_tag" not in decision:
decision["commits_since_tag"] = decision.get("commits_since_boundary", 0)
print(json.dumps(decision))
return decision
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.66.4"
__version__ = "0.66.14"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+57 -42
View File
@@ -50,6 +50,7 @@ def _now():
def connect():
"""Open/create the abstraction database."""
import sqlite3
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
db = sqlite3.connect(DB_PATH)
try:
@@ -178,22 +179,24 @@ def llm_call(prompt, model=None, max_tokens=500):
model = model or LOCAL_MODEL
try:
import urllib.request
data = json.dumps({
"model": model,
"prompt": prompt,
"stream": False,
"options": {"temperature": 0.3, "num_predict": max_tokens, "num_ctx": 8192}
}).encode()
data = json.dumps(
{
"model": model,
"prompt": prompt,
"stream": False,
"options": {"temperature": 0.3, "num_predict": max_tokens, "num_ctx": 8192},
}
).encode()
req = urllib.request.Request(
f"{OLLAMA_URL}/api/generate",
data=data,
headers={"Content-Type": "application/json"}
f"{OLLAMA_URL}/api/generate", data=data, headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=30) as resp:
result = json.loads(resp.read())
return result.get("response", "").strip()
except Exception as e:
import logging
logging.getLogger("abstraction").warning("LLM call failed: %s", e)
return ""
@@ -263,8 +266,7 @@ Extract 1-5 key facts. Be precise, not verbose."""
# Check for duplicates — hash-based (O(1) lookup instead of LIKE scan)
content_hash = hashlib.sha256(what[:80].lower().encode()).hexdigest()[:16]
existing = db.execute(
"SELECT id FROM facts WHERE state='active' AND content_hash=? LIMIT 1",
(content_hash,)
"SELECT id FROM facts WHERE state='active' AND content_hash=? LIMIT 1", (content_hash,)
).fetchone()
if existing:
continue # NOOP — already exists
@@ -273,12 +275,20 @@ Extract 1-5 key facts. Be precise, not verbose."""
"""INSERT INTO facts (episode_id, what, content_hash, when_text, where_text, who_text, why_text,
fact_type, entities_json, confidence, source, state, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?)""",
(episode_id or "", what, content_hash,
fact.get("when", ""), fact.get("where", ""),
fact.get("who", ""), fact.get("why", ""),
fact.get("type", "world"),
json.dumps(fact.get("entities", [])),
0.7, source, ts)
(
episode_id or "",
what,
content_hash,
fact.get("when", ""),
fact.get("where", ""),
fact.get("who", ""),
fact.get("why", ""),
fact.get("type", "world"),
json.dumps(fact.get("entities", [])),
0.7,
source,
ts,
),
)
fact_id = cur.lastrowid
extracted += 1
@@ -291,16 +301,15 @@ Extract 1-5 key facts. Be precise, not verbose."""
# Upsert entity
db.execute(
"INSERT OR IGNORE INTO entities (name, entity_type, created_at) VALUES (?, 'concept', ?)",
(entity_name, ts)
(entity_name, ts),
)
entity_row = db.execute("SELECT id FROM entities WHERE name=?", (entity_name,)).fetchone()
if entity_row:
db.execute(
"INSERT OR IGNORE INTO fact_entities (fact_id, entity_id) VALUES (?, ?)",
(fact_id, entity_row["id"])
(fact_id, entity_row["id"]),
)
db.execute("UPDATE entities SET fact_count = fact_count + 1 WHERE id=?",
(entity_row["id"],))
db.execute("UPDATE entities SET fact_count = fact_count + 1 WHERE id=?", (entity_row["id"],))
# Triple extraction (HippoRAG 2 pattern)
who = fact.get("who", "").strip()
@@ -312,7 +321,7 @@ Extract 1-5 key facts. Be precise, not verbose."""
obj = " ".join(words[3:])
db.execute(
"INSERT INTO triples (subject, predicate, object, source_fact_id, confidence, created_at) VALUES (?, ?, ?, ?, ?, ?)",
(who, predicate, obj, fact_id, 0.6, ts)
(who, predicate, obj, fact_id, 0.6, ts),
)
db.commit()
@@ -335,17 +344,24 @@ def synthesize_observations(entity_name):
entity_id = entity["id"]
# Get all facts about this entity
facts = db.execute("""
facts = db.execute(
"""
SELECT f.id, f.what, f.fact_type, f.confidence
FROM facts f
JOIN fact_entities fe ON f.id = fe.fact_id
WHERE fe.entity_id = ? AND f.state = 'active'
ORDER BY f.created_at DESC
""", (entity_id,)).fetchall()
""",
(entity_id,),
).fetchall()
if len(facts) < 3:
return {"entity": entity_name, "facts": len(facts), "observations": 0,
"reason": "Need at least 3 facts to synthesize"}
return {
"entity": entity_name,
"facts": len(facts),
"observations": 0,
"reason": "Need at least 3 facts to synthesize",
}
facts_text = "\n".join(f"- [{f['fact_type']}] {f['what']}" for f in facts[:20])
@@ -377,12 +393,11 @@ Respond as JSON array:
db.execute(
"""INSERT INTO observations (entity_id, observation, source_fact_ids, confidence, state, created_at)
VALUES (?, ?, ?, ?, 'active', ?)""",
(entity_id, text, json.dumps(source_ids), obs.get("confidence", 0.7), ts)
(entity_id, text, json.dumps(source_ids), obs.get("confidence", 0.7), ts),
)
created += 1
db.execute("UPDATE entities SET observation_count = ? WHERE id=?",
(created, entity_id))
db.execute("UPDATE entities SET observation_count = ? WHERE id=?", (created, entity_id))
db.commit()
return {"entity": entity_name, "facts": len(facts), "observations": created}
finally:
@@ -394,8 +409,7 @@ def list_beliefs(limit=20):
db = connect()
try:
rows = db.execute(
"SELECT * FROM beliefs WHERE state='active' ORDER BY confidence DESC LIMIT ?",
(limit,)
"SELECT * FROM beliefs WHERE state='active' ORDER BY confidence DESC LIMIT ?", (limit,)
).fetchall()
return [dict(r) for r in rows]
finally:
@@ -407,8 +421,7 @@ def list_rules(limit=20):
db = connect()
try:
rows = db.execute(
"SELECT * FROM rules WHERE state='active' ORDER BY confidence DESC LIMIT ?",
(limit,)
"SELECT * FROM rules WHERE state='active' ORDER BY confidence DESC LIMIT ?", (limit,)
).fetchall()
return [dict(r) for r in rows]
finally:
@@ -420,8 +433,7 @@ def list_skills(limit=20):
db = connect()
try:
rows = db.execute(
"SELECT * FROM skills WHERE state='active' ORDER BY success_count DESC LIMIT ?",
(limit,)
"SELECT * FROM skills WHERE state='active' ORDER BY success_count DESC LIMIT ?", (limit,)
).fetchall()
return [dict(r) for r in rows]
finally:
@@ -486,8 +498,9 @@ def handle_hook(event, data=None):
def main():
ap = argparse.ArgumentParser(description="Abstraction pipeline")
ap.add_argument("cmd", choices=["extract", "observe", "beliefs", "rules", "skills",
"evolve", "lifecycle", "stats", "hook"])
ap.add_argument(
"cmd", choices=["extract", "observe", "beliefs", "rules", "skills", "evolve", "lifecycle", "stats", "hook"]
)
ap.add_argument("arg", nargs="?", default="")
ap.add_argument("--episode-id", default=None)
ap.add_argument("--source", default="session")
@@ -501,16 +514,18 @@ def main():
print("usage: abstraction.py extract '<episode text>'")
return
result = extract_facts(args.arg, episode_id=args.episode_id, source=args.source)
print(json.dumps(result, indent=2) if args.json else
f"Extracted {result.get('extracted', 0)} facts")
print(json.dumps(result, indent=2) if args.json else f"Extracted {result.get('extracted', 0)} facts")
elif args.cmd == "observe":
if not args.arg:
print("usage: abstraction.py observe '<entity name>'")
return
result = synthesize_observations(args.arg)
print(json.dumps(result, indent=2) if args.json else
f"Entity: {result.get('entity')} — {result.get('observations', 0)} observations from {result.get('facts', 0)} facts")
print(
json.dumps(result, indent=2)
if args.json
else f"Entity: {result.get('entity')} — {result.get('observations', 0)} observations from {result.get('facts', 0)} facts"
)
elif args.cmd == "beliefs":
items = list_beliefs()
@@ -568,5 +583,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["abstraction"],
"priority": 50,
"auto_activate": True
"auto_activate": True,
}
+19 -1
View File
@@ -562,7 +562,25 @@ def derive_sufficiency(
# appears. A leading inspection verb makes the task read-only.
first_words = lower.split()[:3]
if any(
w in ("inspect", "read", "review", "analyze", "analyse", "explain", "look", "list", "show", "describe", "check", "what", "why", "how", "where", "which")
w
in (
"inspect",
"read",
"review",
"analyze",
"analyse",
"explain",
"look",
"list",
"show",
"describe",
"check",
"what",
"why",
"how",
"where",
"which",
)
for w in first_words
):
is_defect = False
+11 -10
View File
@@ -30,6 +30,7 @@ if _SD not in sys.path:
@dataclass
class AttentionCapsule:
"""Compact working set for cognitive work."""
active_objective: str = ""
current_phase: str = ""
relevant_memories: List[str] = field(default_factory=list)
@@ -199,14 +200,14 @@ def create_capsule(task: str, context: str = "", max_tokens: int = 600) -> Atten
# Extract task requirements (not constraints — no refusal language)
requirement_patterns = [
r'need to',
r'want to',
r'please',
r'fix',
r'build',
r'create',
r'update',
r'change',
r"need to",
r"want to",
r"please",
r"fix",
r"build",
r"create",
r"update",
r"change",
]
for pattern in requirement_patterns:
@@ -233,7 +234,7 @@ def create_capsule(task: str, context: str = "", max_tokens: int = 600) -> Atten
# If still over, trim objective
while capsule.token_count > max_tokens and len(capsule.active_objective) > 50:
capsule.active_objective = capsule.active_objective[:len(capsule.active_objective)//2]
capsule.active_objective = capsule.active_objective[: len(capsule.active_objective) // 2]
prompt_text = capsule.to_prompt()
capsule.token_count = estimate_tokens(prompt_text)
@@ -291,5 +292,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["attention_capsule"],
"priority": 5,
"auto_activate": True
"auto_activate": True,
}
+38 -22
View File
@@ -87,14 +87,14 @@ def verify_chain() -> dict:
try:
entry = json.loads(line)
except json.JSONDecodeError:
return {"valid": False, "entries": i, "reason": f"invalid JSON at line {i+1}"}
return {"valid": False, "entries": i, "reason": f"invalid JSON at line {i + 1}"}
# Verify prev_hash chain
if entry.get("prev_hash") != prev_hash:
return {
"valid": False,
"entries": i,
"reason": f"chain broken at line {i+1}: expected prev_hash={prev_hash}, got {entry.get('prev_hash')}"
"reason": f"chain broken at line {i + 1}: expected prev_hash={prev_hash}, got {entry.get('prev_hash')}",
}
# Verify HMAC
@@ -104,7 +104,7 @@ def verify_chain() -> dict:
return {
"valid": False,
"entries": i,
"reason": f"HMAC mismatch at line {i+1}: stored={stored_hash[:16]}..., computed={computed[:16]}..."
"reason": f"HMAC mismatch at line {i + 1}: stored={stored_hash[:16]}..., computed={computed[:16]}...",
}
prev_hash = stored_hash
@@ -131,37 +131,51 @@ def get_entries(limit: int = 50) -> List[dict]:
def log_promotion(entry_id: int, old_status: str, new_status: str, wash_count: int, reason: str) -> dict:
"""Log a promotion decision."""
return append_entry("promotion", entry_id, {
"old_status": old_status,
"new_status": new_status,
"wash_count": wash_count,
"reason": reason,
})
return append_entry(
"promotion",
entry_id,
{
"old_status": old_status,
"new_status": new_status,
"wash_count": wash_count,
"reason": reason,
},
)
def log_gate_result(entry_id: int, verdict: str, critics: dict) -> dict:
"""Log a pre-commit gate result."""
return append_entry("gate", entry_id, {
"verdict": verdict,
"critics": critics,
})
return append_entry(
"gate",
entry_id,
{
"verdict": verdict,
"critics": critics,
},
)
def log_drift_alert(alert_type: str, message: str, severity: str) -> dict:
"""Log a drift alert."""
return append_entry("alert", 0, {
"alert_type": alert_type,
"message": message,
"severity": severity,
})
return append_entry(
"alert",
0,
{
"alert_type": alert_type,
"message": message,
"severity": severity,
},
)
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Promotion audit ledger")
sub = parser.add_subparsers(dest="cmd")
@@ -198,10 +212,12 @@ def main():
print(f"Recent ledger entries ({len(entries)}):")
for e in entries:
print(f" [{e.get('type')}] entry_id={e.get('entry_id')} @ {e.get('timestamp', '')[:19]}")
details = e.get('details', {})
if e.get('type') == 'promotion':
print(f" {details.get('old_status')} → {details.get('new_status')} (wash={details.get('wash_count')})")
elif e.get('type') == 'gate':
details = e.get("details", {})
if e.get("type") == "promotion":
print(
f" {details.get('old_status')} → {details.get('new_status')} (wash={details.get('wash_count')})"
)
elif e.get("type") == "gate":
print(f" verdict={details.get('verdict')}")
else:
print("No ledger entries.")
+69 -17
View File
@@ -28,8 +28,7 @@ def run_warm_router(message):
"""Run warm router and return fired neurons."""
try:
result = subprocess.run(
[PYTHON, WARM_ROUTER, "route", message, "--json"],
capture_output=True, text=True, timeout=10
[PYTHON, WARM_ROUTER, "route", message, "--json"], capture_output=True, text=True, timeout=10
)
if result.returncode == 0 and result.stdout.strip():
return json.loads(result.stdout)
@@ -42,8 +41,7 @@ def run_memory_recall(query, budget=5):
"""Run memory recall and return relevant entries."""
try:
result = subprocess.run(
[PYTHON, MEMORY_STORE, "recall", query, "--json"],
capture_output=True, text=True, timeout=15
[PYTHON, MEMORY_STORE, "recall", query, "--json"], capture_output=True, text=True, timeout=15
)
if result.returncode == 0 and result.stdout.strip():
data = json.loads(result.stdout)
@@ -57,10 +55,7 @@ def run_memory_recall(query, budget=5):
def run_always_digest():
"""Run always-on digest and return results."""
try:
result = subprocess.run(
[PYTHON, WARM_ROUTER, "always", "--json"],
capture_output=True, text=True, timeout=10
)
result = subprocess.run([PYTHON, WARM_ROUTER, "always", "--json"], capture_output=True, text=True, timeout=10)
if result.returncode == 0 and result.stdout.strip():
return json.loads(result.stdout)
except Exception:
@@ -108,14 +103,70 @@ def build_auto_context(message):
# Memory recall for substantive messages
if message and len(message.strip()) > 10:
# Extract key terms for recall (words > 4 chars, not stopwords)
stopwords = {"what", "when", "where", "which", "that", "this", "with", "from",
"have", "been", "were", "they", "their", "about", "would", "could",
"should", "will", "into", "your", "just", "like", "know", "think",
"also", "back", "then", "than", "more", "some", "only", "very",
"does", "done", "make", "made", "want", "need", "give", "tell",
"there", "these", "those", "because", "between", "after", "before",
"under", "over", "again", "once", "here", "each", "every", "both",
"few", "most", "other", "such", "same", "being", "having"}
stopwords = {
"what",
"when",
"where",
"which",
"that",
"this",
"with",
"from",
"have",
"been",
"were",
"they",
"their",
"about",
"would",
"could",
"should",
"will",
"into",
"your",
"just",
"like",
"know",
"think",
"also",
"back",
"then",
"than",
"more",
"some",
"only",
"very",
"does",
"done",
"make",
"made",
"want",
"need",
"give",
"tell",
"there",
"these",
"those",
"because",
"between",
"after",
"before",
"under",
"over",
"again",
"once",
"here",
"each",
"every",
"both",
"few",
"most",
"other",
"such",
"same",
"being",
"having",
}
words = [w for w in message.lower().split() if len(w) > 4 and w.isalpha() and w not in stopwords]
if words:
query = " ".join(words[:5])
@@ -167,6 +218,7 @@ def handle_hook(event, data=None):
def main():
import argparse
ap = argparse.ArgumentParser(description="Auto-context: automatic memory activation")
ap.add_argument("cmd", choices=["build", "show", "hook"])
ap.add_argument("arg", nargs="?", default="")
@@ -214,5 +266,5 @@ PLUGIN = {
"dependencies": ["memory_store", "warm_router"],
"provides": ["auto_context"],
"priority": 5,
"auto_activate": True
"auto_activate": True,
}
+72 -47
View File
@@ -85,63 +85,72 @@ def extract_tasks(message, channel="chat"):
"""
# Strip code blocks before analysis
import re as _re
message = _re.sub(r'```[\s\S]*?```', '', message)
message = _re.sub(r'`[^`]+`', '', message)
message = _re.sub(r"```[\s\S]*?```", "", message)
message = _re.sub(r"`[^`]+`", "", message)
tasks = []
lines = message.split("\n")
# Pattern 1: Numbered lists (1. do this, 2. do that)
numbered = re.findall(r'(?:^|\n)\s*(\d+)[\.\)]\s*(.+?)(?:\n|$)', message)
numbered = re.findall(r"(?:^|\n)\s*(\d+)[\.\)]\s*(.+?)(?:\n|$)", message)
if len(numbered) >= 2:
for num, text in numbered:
text = text.strip()
if len(text) > 10:
tasks.append({
"description": text,
"priority": "normal",
"pattern": "numbered_list",
})
tasks.append(
{
"description": text,
"priority": "normal",
"pattern": "numbered_list",
}
)
# Pattern 2: Bullet points (- do this, * do that)
bullets = re.findall(r'(?:^|\n)\s*[-*•]\s*(.+?)(?:\n|$)', message)
bullets = re.findall(r"(?:^|\n)\s*[-*•]\s*(.+?)(?:\n|$)", message)
if len(bullets) >= 2:
for text in bullets:
text = text.strip()
if len(text) > 10:
tasks.append({
"description": text,
"priority": "normal",
"pattern": "bullet_list",
})
tasks.append(
{
"description": text,
"priority": "normal",
"pattern": "bullet_list",
}
)
# Pattern 3: Action verbs at start of sentences
action_patterns = [
r'(?:^|\n)\s*(?:I(?:\'ll| will| need to)|let me|we should|we need to)\s+(.+?)(?:\.|$|\n)',
r'(?:^|\n)\s*(?:fix|build|create|add|update|check|verify|test|research|investigate|implement|design)\s+(.+?)(?:\.|$|\n)',
r"(?:^|\n)\s*(?:I(?:\'ll| will| need to)|let me|we should|we need to)\s+(.+?)(?:\.|$|\n)",
r"(?:^|\n)\s*(?:fix|build|create|add|update|check|verify|test|research|investigate|implement|design)\s+(.+?)(?:\.|$|\n)",
]
for pattern in action_patterns:
matches = re.findall(pattern, message, re.IGNORECASE | re.MULTILINE)
for text in matches:
text = text.strip()
if len(text) > 10 and len(text) < 200:
tasks.append({
"description": text,
"priority": "normal",
"pattern": "action_verb",
})
tasks.append(
{
"description": text,
"priority": "normal",
"pattern": "action_verb",
}
)
# Pattern 4: "First... then... finally..." sequences
sequence = re.findall(r'(?:first|then|next|finally|after that)[,.]?\s+(.+?)(?:\.|$|\n)', message, re.IGNORECASE)
sequence = re.findall(r"(?:first|then|next|finally|after that)[,.]?\s+(.+?)(?:\.|$|\n)", message, re.IGNORECASE)
if len(sequence) >= 2:
for text in sequence:
text = text.strip()
if len(text) > 10:
tasks.append({
"description": text,
"priority": "normal",
"pattern": "sequence",
})
tasks.append(
{
"description": text,
"priority": "normal",
"pattern": "sequence",
}
)
# Deduplicate by description similarity
seen = set()
@@ -168,12 +177,14 @@ def store_tasks(tasks, channel="chat", source_message=""):
next_id = (row["max_id"] or 0) + 1
task_id = f"AT{next_id}"
db.execute("""
db.execute(
"""
INSERT INTO tasks (task_id, description, source_message, source_channel,
status, priority, created_at, updated_at)
VALUES (?, ?, ?, ?, 'active', ?, ?, ?)
""", (task_id, task["description"], source_message[:500], channel,
task.get("priority", "normal"), ts, ts))
""",
(task_id, task["description"], source_message[:500], channel, task.get("priority", "normal"), ts, ts),
)
# Log to completion file
log_completion(task_id, "created", task["description"], channel)
@@ -196,15 +207,21 @@ def complete_task(task_id, notes=""):
row = db.execute("SELECT description FROM tasks WHERE task_id=? AND status='active'", (task_id,)).fetchone()
description = row["description"] if row else ""
db.execute("""
db.execute(
"""
UPDATE tasks SET status='completed', completion_notes=?, completed_at=?, updated_at=?
WHERE task_id=? AND status='active'
""", (notes, ts, ts, task_id))
""",
(notes, ts, ts, task_id),
)
db.execute("""
db.execute(
"""
INSERT INTO completion_log (task_id, action, details, timestamp)
VALUES (?, 'completed', ?, ?)
""", (task_id, notes, ts))
""",
(task_id, notes, ts),
)
db.commit()
finally:
@@ -244,6 +261,7 @@ def run_auto_checks(description):
def run_compile_check():
"""Check that all Python scripts compile."""
import subprocess
try:
check_script = (
"import py_compile, glob, sys; "
@@ -253,9 +271,7 @@ def run_compile_check():
"print(f'FAIL:{len(failed)}' if failed else 'PASS')"
)
result = subprocess.run(
[sys.executable, "-c", check_script],
capture_output=True, text=True, timeout=30,
cwd=SCRIPTS_DIR
[sys.executable, "-c", check_script], capture_output=True, text=True, timeout=30, cwd=SCRIPTS_DIR
)
passed = "PASS" in result.stdout
return {"check": "compile", "passed": passed, "details": result.stdout.strip()[:200]}
@@ -266,14 +282,18 @@ def run_compile_check():
def run_benchmark_check():
"""Run RuneBench."""
import subprocess
try:
result = subprocess.run(
[sys.executable, os.path.join(SCRIPTS_DIR, "rune_bench.py"), "run"],
capture_output=True, text=True, timeout=120
capture_output=True,
text=True,
timeout=120,
)
# Check for pass patterns: "N/N" where both numbers match, or explicit ALL PASS
import re
pass_match = re.search(r'(\d+)/\1', result.stdout)
pass_match = re.search(r"(\d+)/\1", result.stdout)
passed = bool(pass_match) or "ALL" in result.stdout.upper() or "PASS" in result.stdout.upper()
return {"check": "benchmark", "passed": passed, "details": result.stdout.strip()[-200:]}
except Exception as e:
@@ -284,12 +304,17 @@ def run_sanity_check():
"""Basic sanity check — can we import key modules?"""
try:
import subprocess
result = subprocess.run(
[sys.executable, "-c",
"import sys; sys.path.insert(0, '.'); "
"import memory_store, warm_router, microkernel; print('PASS')"],
capture_output=True, text=True, timeout=10,
cwd=SCRIPTS_DIR
[
sys.executable,
"-c",
"import sys; sys.path.insert(0, '.'); import memory_store, warm_router, microkernel; print('PASS')",
],
capture_output=True,
text=True,
timeout=10,
cwd=SCRIPTS_DIR,
)
passed = "PASS" in result.stdout
return {"check": "sanity", "passed": passed, "details": result.stdout.strip()[:200]}
@@ -302,8 +327,7 @@ def list_tasks(status="active", limit=20):
db = connect()
try:
rows = db.execute(
"SELECT * FROM tasks WHERE status=? ORDER BY created_at DESC LIMIT ?",
(status, limit)
"SELECT * FROM tasks WHERE status=? ORDER BY created_at DESC LIMIT ?", (status, limit)
).fetchall()
return [dict(r) for r in rows]
finally:
@@ -359,6 +383,7 @@ def handle_hook(event, data=None):
def main():
import argparse
ap = argparse.ArgumentParser(description="Automatic task extraction")
ap.add_argument("cmd", choices=["extract", "complete", "list", "completion", "hook"])
ap.add_argument("arg", nargs="?", default="")
@@ -431,5 +456,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["auto_tasks"],
"priority": 4,
"auto_activate": True
"auto_activate": True,
}
+53 -31
View File
@@ -101,11 +101,14 @@ def add_correction(original, corrected, scope_tags=None, domain="", pattern=""):
ts = _now()
tags = json.dumps(scope_tags or {})
cur = db.execute("""
cur = db.execute(
"""
INSERT INTO corrections (original_behavior, corrected_behavior, scope_tags,
scope_pattern, scope_domain, confidence, state, created_at)
VALUES (?, ?, ?, ?, ?, 0.5, 'active', ?)
""", (original, corrected, tags, pattern, domain, ts))
""",
(original, corrected, tags, pattern, domain, ts),
)
correction_id = cur.lastrowid
db.commit()
return {"added": correction_id, "confidence": 0.5, "scope": scope_tags or {}}
@@ -120,11 +123,14 @@ def add_habit(trigger, action, name="", context_tags=None, min_activations=3):
ts = _now()
tags = json.dumps(context_tags or [])
cur = db.execute("""
cur = db.execute(
"""
INSERT INTO habits (name, trigger_pattern, context_tags, action_template,
min_activations, confidence, state, created_at)
VALUES (?, ?, ?, ?, ?, 0.5, 'candidate', ?)
""", (name, trigger, tags, action, min_activations, ts))
""",
(name, trigger, tags, action, min_activations, ts),
)
habit_id = cur.lastrowid
db.commit()
return {"added": habit_id, "state": "candidate", "min_activations": min_activations}
@@ -214,16 +220,22 @@ def reinforce(item_id, outcome, item_type="habit"):
if habit["state"] == "candidate" and new_count >= habit["min_activations"]:
new_state = "active"
db.execute("""
db.execute(
"""
UPDATE habits SET activation_count=?, success_count=?, failure_count=?,
confidence=?, state=?, last_activated=? WHERE id=?
""", (new_count, new_success, new_failure, new_confidence, new_state, ts, item_id))
""",
(new_count, new_success, new_failure, new_confidence, new_state, ts, item_id),
)
# Log activation
db.execute("""
db.execute(
"""
INSERT INTO habit_activations (habit_id, context, outcome, activated_at)
VALUES (?, '', ?, ?)
""", (item_id, outcome, ts))
""",
(item_id, outcome, ts),
)
db.commit()
return {"reinforced": item_id, "confidence": new_confidence, "state": new_state}
@@ -244,10 +256,13 @@ def reinforce(item_id, outcome, item_type="habit"):
new_count = correction["evidence_count"]
new_state = "active" if new_confidence > 0.3 else "archived"
db.execute("""
db.execute(
"""
UPDATE corrections SET confidence=?, evidence_count=?, state=?, last_reinforced=?
WHERE id=?
""", (new_confidence, new_count, new_state, ts, item_id))
""",
(new_confidence, new_count, new_state, ts, item_id),
)
db.commit()
return {"reinforced": item_id, "confidence": new_confidence, "state": new_state}
finally:
@@ -261,17 +276,23 @@ def list_items(item_type="all", limit=20):
results = {}
if item_type in ("all", "correction"):
corrections = db.execute("""
corrections = db.execute(
"""
SELECT * FROM corrections WHERE state='active'
ORDER BY confidence DESC LIMIT ?
""", (limit,)).fetchall()
""",
(limit,),
).fetchall()
results["corrections"] = [dict(c) for c in corrections]
if item_type in ("all", "habit"):
habits = db.execute("""
habits = db.execute(
"""
SELECT * FROM habits WHERE state IN ('active', 'candidate')
ORDER BY confidence DESC LIMIT ?
""", (limit,)).fetchall()
""",
(limit,),
).fetchall()
results["habits"] = [dict(h) for h in habits]
return results
@@ -285,7 +306,9 @@ def behavior_stats():
try:
stats = {}
stats["corrections_active"] = db.execute("SELECT COUNT(*) FROM corrections WHERE state='active'").fetchone()[0]
stats["corrections_archived"] = db.execute("SELECT COUNT(*) FROM corrections WHERE state='archived'").fetchone()[0]
stats["corrections_archived"] = db.execute(
"SELECT COUNT(*) FROM corrections WHERE state='archived'"
).fetchone()[0]
stats["habits_active"] = db.execute("SELECT COUNT(*) FROM habits WHERE state='active'").fetchone()[0]
stats["habits_candidate"] = db.execute("SELECT COUNT(*) FROM habits WHERE state='candidate'").fetchone()[0]
stats["total_activations"] = db.execute("SELECT COUNT(*) FROM habit_activations").fetchone()[0]
@@ -296,6 +319,7 @@ def behavior_stats():
def main():
import argparse
ap = argparse.ArgumentParser(description="Behavioral patterns")
ap.add_argument("cmd", choices=["correct", "habit", "match", "reinforce", "list", "stats"])
ap.add_argument("arg", nargs="?", default="")
@@ -317,20 +341,16 @@ def main():
print("usage: behavior.py correct '<original>' '<corrected>'")
return
scope = json.loads(args.scope) if args.scope else None
result = add_correction(args.arg, args.arg2, scope_tags=scope,
domain=args.domain, pattern=args.pattern)
print(json.dumps(result, indent=2) if args.json else
f"Correction added (confidence: {result['confidence']})")
result = add_correction(args.arg, args.arg2, scope_tags=scope, domain=args.domain, pattern=args.pattern)
print(json.dumps(result, indent=2) if args.json else f"Correction added (confidence: {result['confidence']})")
elif args.cmd == "habit":
if not all([args.arg, args.arg2]):
print("usage: behavior.py habit '<trigger_pattern>' '<action>'")
return
tags = json.loads(args.context_tags) if args.context_tags else None
result = add_habit(args.arg, args.arg2, name=args.name,
context_tags=tags, min_activations=args.min_activations)
print(json.dumps(result, indent=2) if args.json else
f"Habit candidate added (state: {result['state']})")
result = add_habit(args.arg, args.arg2, name=args.name, context_tags=tags, min_activations=args.min_activations)
print(json.dumps(result, indent=2) if args.json else f"Habit candidate added (state: {result['state']})")
elif args.cmd == "match":
if not args.arg:
@@ -350,8 +370,7 @@ def main():
print("usage: behavior.py reinforce <id>")
return
result = reinforce(int(args.arg), args.outcome, item_type=args.type)
print(json.dumps(result, indent=2) if args.json else
f"Reinforced: confidence={result.get('confidence', '?')}")
print(json.dumps(result, indent=2) if args.json else f"Reinforced: confidence={result.get('confidence', '?')}")
elif args.cmd == "list":
results = list_items(item_type=args.type, limit=args.limit)
@@ -361,15 +380,18 @@ def main():
for c in results.get("corrections", []):
print(f" [correction {c['id']}] {c['corrected_behavior'][:60]} (conf: {c['confidence']:.2f})")
for h in results.get("habits", []):
state = h['state']
state = h["state"]
print(f" [habit {h['id']}] {h['action_template'][:60]} ({state}, conf: {h['confidence']:.2f})")
elif args.cmd == "stats":
s = behavior_stats()
print(json.dumps(s, indent=2) if args.json else
f"Corrections: {s['corrections_active']} active, {s['corrections_archived']} archived | "
f"Habits: {s['habits_active']} active, {s['habits_candidate']} candidate | "
f"Activations: {s['total_activations']}")
print(
json.dumps(s, indent=2)
if args.json
else f"Corrections: {s['corrections_active']} active, {s['corrections_archived']} archived | "
f"Habits: {s['habits_active']} active, {s['habits_candidate']} candidate | "
f"Activations: {s['total_activations']}"
)
if __name__ == "__main__":
@@ -385,5 +407,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["behavior"],
"priority": 50,
"auto_activate": True
"auto_activate": True,
}
+41 -18
View File
@@ -48,6 +48,7 @@ REPO_DIR = "/mnt/workdrive/rune"
# DATA STRUCTURES
# ═══════════════════════════════════════════════════════════════════════════════
class Phase(Enum):
ASSESS = "assess"
RESEARCH = "research"
@@ -61,18 +62,23 @@ class Phase(Enum):
INTEGRATE = "integrate"
RECORD = "record"
class BuildPhase(Enum):
"""Three-phase execution for build step."""
FOUNDATION = "foundation" # Types, interfaces, shared utilities
FOUNDATION = "foundation" # Types, interfaces, shared utilities
IMPLEMENTATION = "implementation" # Feature code
INTEGRATION = "integration" # Tests, wiring, documentation
INTEGRATION = "integration" # Tests, wiring, documentation
class VerificationTier(Enum):
"""Tiered verification strategy."""
SYNTACTIC = "syntactic" # Per-edit: lint, type-check, format (<1s)
SEMANTIC = "semantic" # Per-batch: unit tests, import validation (<30s)
SYNTACTIC = "syntactic" # Per-edit: lint, type-check, format (<1s)
SEMANTIC = "semantic" # Per-batch: unit tests, import validation (<30s)
INTEGRATION = "integration" # Per-wave: contract tests, build/compile (<5min)
SYSTEM = "system" # Full: full test suite, property-based (<15min)
SYSTEM = "system" # Full: full test suite, property-based (<15min)
class PhaseStatus(Enum):
PENDING = "pending"
@@ -81,6 +87,7 @@ class PhaseStatus(Enum):
FAILED = "failed"
SKIPPED = "skipped"
@dataclass
class LedgerEntry:
phase: str
@@ -90,6 +97,7 @@ class LedgerEntry:
details: Dict[str, Any] = field(default_factory=dict)
errors: List[str] = field(default_factory=list)
@dataclass
class BuildState:
build_id: str
@@ -112,10 +120,12 @@ class BuildState:
files_changed: List[str] = field(default_factory=list)
contracts: List[Dict[str, Any]] = field(default_factory=list)
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD MANAGER
# ═══════════════════════════════════════════════════════════════════════════════
class BuildManager:
"""Manages the build pipeline with worktree isolation and ledger tracking."""
@@ -282,11 +292,15 @@ class BuildManager:
def complete_feasible(self, feasible: bool, risk: str, details: Dict[str, Any] = None):
"""Complete the feasibility phase."""
self._log_phase(Phase.FEASIBLE.value, PhaseStatus.COMPLETE.value, {
"feasible": feasible,
"risk": risk,
"details": details or {},
})
self._log_phase(
Phase.FEASIBLE.value,
PhaseStatus.COMPLETE.value,
{
"feasible": feasible,
"risk": risk,
"details": details or {},
},
)
def start_consult(self):
"""Start the consult phase."""
@@ -327,10 +341,14 @@ class BuildManager:
def complete_verify(self, passed: bool, failures: List[str] = None):
"""Complete the verify phase."""
self._log_phase(Phase.VERIFY.value, PhaseStatus.COMPLETE.value, {
"passed": passed,
"failures": failures or [],
})
self._log_phase(
Phase.VERIFY.value,
PhaseStatus.COMPLETE.value,
{
"passed": passed,
"failures": failures or [],
},
)
def start_correct(self):
"""Start the correct phase."""
@@ -347,10 +365,14 @@ class BuildManager:
def complete_integrate(self, passed: bool, failures: List[str] = None):
"""Complete the integrate phase."""
self._log_phase(Phase.INTEGRATE.value, PhaseStatus.COMPLETE.value, {
"passed": passed,
"failures": failures or [],
})
self._log_phase(
Phase.INTEGRATE.value,
PhaseStatus.COMPLETE.value,
{
"passed": passed,
"failures": failures or [],
},
)
def start_record(self):
"""Start the record phase."""
@@ -409,6 +431,7 @@ class BuildManager:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
if len(sys.argv) < 2:
print("Usage: build_mode.py [start|status|resume|abort]")
+1
View File
@@ -9,6 +9,7 @@ process is fresh each call, so the hook-level dedupe is the effective one).
Usage: builder_bridge_cli.py "<task text>"
Output: the capsule markdown (possibly empty — inject-nothing policy).
"""
import json
import os
import sys
+31 -40
View File
@@ -38,15 +38,15 @@ DB_PATH = os.path.join(memory_env.memory_dir(), "cache.db")
# ---------------------------------------------------------------------------
# In-memory hit tracking — avoids write-on-read in SQLite
# ---------------------------------------------------------------------------
_hit_counts: dict[str, int] = {} # key → pending hit increments
_last_hits: dict[str, str] = {} # key → latest last_hit timestamp
_miss_counts: dict[str, int] = {} # key → pending miss increments
_last_misses: dict[str, str] = {} # key → latest last_miss timestamp
_hit_counts: dict[str, int] = {} # key → pending hit increments
_last_hits: dict[str, str] = {} # key → latest last_hit timestamp
_miss_counts: dict[str, int] = {} # key → pending miss increments
_last_misses: dict[str, str] = {} # key → latest last_miss timestamp
_flush_lock = threading.Lock()
_FLUSH_THRESHOLD = 50 # flush after this many pending updates
_flush_counter = 0 # counts hits+misses since last flush
_last_flush_time = 0.0 # epoch of last flush
_FLUSH_INTERVAL = 30.0 # flush at least every N seconds
_FLUSH_THRESHOLD = 50 # flush after this many pending updates
_flush_counter = 0 # counts hits+misses since last flush
_last_flush_time = 0.0 # epoch of last flush
_FLUSH_INTERVAL = 30.0 # flush at least every N seconds
def _record_hit(key: str) -> None:
@@ -58,8 +58,7 @@ def _record_hit(key: str) -> None:
_hit_counts[key] = _hit_counts.get(key, 0) + 1
_last_hits[key] = now
_flush_counter += 1
if (_flush_counter >= _FLUSH_THRESHOLD
or (time.monotonic() - _last_flush_time) >= _FLUSH_INTERVAL):
if _flush_counter >= _FLUSH_THRESHOLD or (time.monotonic() - _last_flush_time) >= _FLUSH_INTERVAL:
do_flush = True
if do_flush:
flush_stats()
@@ -74,8 +73,7 @@ def _record_miss(key: str) -> None:
_miss_counts[key] = _miss_counts.get(key, 0) + 1
_last_misses[key] = now
_flush_counter += 1
if (_flush_counter >= _FLUSH_THRESHOLD
or (time.monotonic() - _last_flush_time) >= _FLUSH_INTERVAL):
if _flush_counter >= _FLUSH_THRESHOLD or (time.monotonic() - _last_flush_time) >= _FLUSH_INTERVAL:
do_flush = True
if do_flush:
flush_stats()
@@ -187,10 +185,7 @@ def get_cache(key):
"""
db = connect()
try:
row = db.execute(
"SELECT * FROM cache WHERE key=? AND invalidated_at IS NULL",
(key,)
).fetchone()
row = db.execute("SELECT * FROM cache WHERE key=? AND invalidated_at IS NULL", (key,)).fetchone()
if row:
# Record hit in memory — no SQLite write on the hot path
@@ -211,9 +206,7 @@ def get_cache(key):
# Miss — only record if the key exists in the cache (e.g. invalidated).
# A true miss (key never seen) should not trigger any write.
exists = db.execute(
"SELECT 1 FROM cache WHERE key=?", (key,)
).fetchone()
exists = db.execute("SELECT 1 FROM cache WHERE key=?", (key,)).fetchone()
if exists:
_record_miss(key)
@@ -242,7 +235,8 @@ def put_cache(key, value, compressed=None, scope="general", source_hash=""):
if compressed is None:
compressed = _compress(value)
db.execute("""
db.execute(
"""
INSERT INTO cache
(key, scope, content_hash, full_value, compressed_value, source_hash,
created_at, invalidated_at, hit_count, miss_count)
@@ -255,7 +249,9 @@ def put_cache(key, value, compressed=None, scope="general", source_hash=""):
source_hash = excluded.source_hash,
created_at = excluded.created_at,
invalidated_at = NULL
""", (key, scope, content_hash, value, compressed, source_hash, _now()))
""",
(key, scope, content_hash, value, compressed, source_hash, _now()),
)
db.commit()
db.close()
return {"stored": key, "content_hash": content_hash, "scope": scope}
@@ -264,10 +260,7 @@ def put_cache(key, value, compressed=None, scope="general", source_hash=""):
def invalidate(key, reason=""):
"""Invalidate a specific cache entry."""
db = connect()
db.execute(
"UPDATE cache SET invalidated_at=? WHERE key=? AND invalidated_at IS NULL",
(_now(), key)
)
db.execute("UPDATE cache SET invalidated_at=? WHERE key=? AND invalidated_at IS NULL", (_now(), key))
db.commit()
db.close()
return {"invalidated": key, "reason": reason}
@@ -277,8 +270,7 @@ def invalidate_scope(scope, reason=""):
"""Invalidate all entries in a scope."""
db = connect()
count = db.execute(
"UPDATE cache SET invalidated_at=? WHERE scope=? AND invalidated_at IS NULL",
(_now(), scope)
"UPDATE cache SET invalidated_at=? WHERE scope=? AND invalidated_at IS NULL", (_now(), scope)
).rowcount
db.commit()
db.close()
@@ -288,10 +280,7 @@ def invalidate_scope(scope, reason=""):
def invalidate_all(reason=""):
"""Invalidate everything. Nuclear option."""
db = connect()
count = db.execute(
"UPDATE cache SET invalidated_at=? WHERE invalidated_at IS NULL",
(_now(),)
).rowcount
count = db.execute("UPDATE cache SET invalidated_at=? WHERE invalidated_at IS NULL", (_now(),)).rowcount
db.commit()
db.close()
return {"invalidated_all": count, "reason": reason}
@@ -409,8 +398,7 @@ def handle_hook(event, data=None):
def main():
ap = argparse.ArgumentParser(description="Cache system — event-driven, write-once-derive-twice")
ap.add_argument("cmd", choices=["get", "put", "invalidate", "invalidate-all",
"invalidate-scope", "stats", "hook"])
ap.add_argument("cmd", choices=["get", "put", "invalidate", "invalidate-all", "invalidate-scope", "stats", "hook"])
ap.add_argument("arg", nargs="?", default="")
ap.add_argument("arg2", nargs="?", default="")
ap.add_argument("--scope", default="general")
@@ -440,8 +428,11 @@ def main():
print("usage: cache.py put <key> <value>")
return
result = put_cache(args.arg, args.arg2, scope=args.scope, source_hash=args.source_hash)
print(json.dumps(result, indent=2) if args.json else
f"Stored: {args.arg} (hash: {result['content_hash']}, scope: {result['scope']})")
print(
json.dumps(result, indent=2)
if args.json
else f"Stored: {args.arg} (hash: {result['content_hash']}, scope: {result['scope']})"
)
elif args.cmd == "invalidate":
if not args.arg:
@@ -452,16 +443,16 @@ def main():
elif args.cmd == "invalidate-all":
result = invalidate_all(reason=args.reason)
print(json.dumps(result, indent=2) if args.json else
f"Invalidated all: {result['invalidated_all']} entries")
print(json.dumps(result, indent=2) if args.json else f"Invalidated all: {result['invalidated_all']} entries")
elif args.cmd == "invalidate-scope":
if not args.arg:
print("usage: cache.py invalidate-scope <scope>")
return
result = invalidate_scope(args.arg, reason=args.reason)
print(json.dumps(result, indent=2) if args.json else
f"Invalidated scope '{args.arg}': {result['count']} entries")
print(
json.dumps(result, indent=2) if args.json else f"Invalidated scope '{args.arg}': {result['count']} entries"
)
elif args.cmd == "stats":
s = cache_stats()
@@ -497,5 +488,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["cache"],
"priority": 1,
"auto_activate": True
"auto_activate": True,
}
+1 -3
View File
@@ -34,9 +34,7 @@ def main() -> int:
runtime.registry.disable("synthesis")
third = runtime.process({"event_type": "c"}, [{"event_type": "new"}])
if {r["worker"] for r in third["results"]} != {"n1"}:
print(
"COGNITIVE CANARY FAILED: toggle independence broken", file=sys.stderr
)
print("COGNITIVE CANARY FAILED: toggle independence broken", file=sys.stderr)
return 1
print("COGNITIVE CANARY PASSED", third["facts"])
return 0
+44 -25
View File
@@ -41,6 +41,7 @@ def main():
import audit_ledger
import warm_router
import memory_consolidation
try:
import curator
except ImportError:
@@ -51,16 +52,20 @@ def main():
# --- 1. Schema ---
print("--- 1. Schema ---")
cols = {r[1] for r in db.execute('PRAGMA table_info(entries)')}
for c in ['wash_count', 'last_washed', 'promotion_eligible', 'rock_status']:
cols = {r[1] for r in db.execute("PRAGMA table_info(entries)")}
for c in ["wash_count", "last_washed", "promotion_eligible", "rock_status"]:
test(f"column {c}", c in cols, f"{c} missing")
# --- 2. Full lifecycle ---
print("\n--- 2. Full lifecycle ---")
# Find a washable entry (not already rock)
entry = db.execute('SELECT id, text, importance, wash_count, rock_status FROM entries WHERE status="active" AND rock_status="washable" LIMIT 1').fetchone()
entry = db.execute(
'SELECT id, text, importance, wash_count, rock_status FROM entries WHERE status="active" AND rock_status="washable" LIMIT 1'
).fetchone()
if not entry:
entry = db.execute('SELECT id, text, importance, wash_count, rock_status FROM entries WHERE status="active" LIMIT 1').fetchone()
entry = db.execute(
'SELECT id, text, importance, wash_count, rock_status FROM entries WHERE status="active" LIMIT 1'
).fetchone()
if not entry:
test("full lifecycle", True, "no entries in database (skipped)")
# Skip lifecycle tests, continue to other tests
@@ -73,8 +78,10 @@ def main():
for i in range(20):
memory_store.increment_wash(db, eid)
row = db.execute('SELECT id, text, importance, wash_count, rock_status FROM entries WHERE id=?', (eid,)).fetchone()
entry_dict = {'id': row[0], 'text': row[1], 'importance': row[2], 'wash_count': row[3], 'rock_status': row[4]}
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status FROM entries WHERE id=?", (eid,)
).fetchone()
entry_dict = {"id": row[0], "text": row[1], "importance": row[2], "wash_count": row[3], "rock_status": row[4]}
# Value function
value = drift_core.compute_value(entry_dict)
@@ -103,9 +110,11 @@ def main():
print("\n--- 3. Cache ---")
cache = drift_cache.DriftCache()
if eid:
row = db.execute('SELECT id, text, importance, wash_count, rock_status FROM entries WHERE id=?', (eid,)).fetchone()
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status FROM entries WHERE id=?", (eid,)
).fetchone()
if row:
cache.add(row[0], {'text': row[1], 'importance': row[2], 'wash_count': row[3], 'rock_status': row[4]})
cache.add(row[0], {"text": row[1], "importance": row[2], "wash_count": row[3], "rock_status": row[4]})
test("cache contains", cache.contains(eid))
test("cache priority", cache.get_priority(eid) > 0, f"priority={cache.get_priority(eid)}")
else:
@@ -117,20 +126,29 @@ def main():
# --- 4. Compression ---
print("\n--- 4. Compression ---")
ratio_washable = drift_compression.get_compression_ratio(0, 'washable')
ratio_proven = drift_compression.get_compression_ratio(25, 'washable')
ratio_rock = drift_compression.get_compression_ratio(100, 'rock')
ratio_washable = drift_compression.get_compression_ratio(0, "washable")
ratio_proven = drift_compression.get_compression_ratio(25, "washable")
ratio_rock = drift_compression.get_compression_ratio(100, "rock")
test("compression decreases with wash", ratio_washable > ratio_proven > ratio_rock)
test("should_warn for proven", drift_compression.should_warn({'wash_count': 15, 'rock_status': 'washable', 'text': 'x' * 300}))
test("warn_before_compress returns dict", isinstance(drift_compression.warn_before_compress({'wash_count': 15, 'rock_status': 'washable', 'text': 'x' * 300}), dict))
test(
"should_warn for proven",
drift_compression.should_warn({"wash_count": 15, "rock_status": "washable", "text": "x" * 300}),
)
test(
"warn_before_compress returns dict",
isinstance(
drift_compression.warn_before_compress({"wash_count": 15, "rock_status": "washable", "text": "x" * 300}),
dict,
),
)
# --- 5. Warm router ---
print("\n--- 5. Warm router ---")
neurons = warm_router.list_neurons()
if neurons:
test("neurons have wash_count", any('wash_count' in n for n in neurons))
test("neurons have body_knowledge", any('body_knowledge' in n for n in neurons))
test("body_knowledge populated", sum(1 for n in neurons if n.get('body_knowledge', '')) > 0)
test("neurons have wash_count", any("wash_count" in n for n in neurons))
test("neurons have body_knowledge", any("body_knowledge" in n for n in neurons))
test("body_knowledge populated", sum(1 for n in neurons if n.get("body_knowledge", "")) > 0)
else:
test("neurons have wash_count", True, "no neurons in database (skipped)")
test("neurons have body_knowledge", True, "no neurons in database (skipped)")
@@ -141,19 +159,20 @@ def main():
mc = memory_consolidation.MemoryConsolidation()
# Check if ConsolidatedKnowledge has wash_count field
import dataclasses
try:
fields = [f.name for f in dataclasses.fields(memory_consolidation.ConsolidatedKnowledge)]
test("consolidation has wash_count", 'wash_count' in fields)
test("consolidation has wash_count", "wash_count" in fields)
except Exception:
test("consolidation has wash_count", False, "could not inspect dataclass")
status = mc.get_status()
test("consolidation has entries", status['knowledge_count'] > 0 or "no entries in database")
test("consolidation has entries", status["knowledge_count"] > 0 or "no entries in database")
# --- 7. Curator ---
print("\n--- 7. Curator ---")
if curator:
result = curator.le_promote_eligible('test', ['test'], 2, wash_count=15)
test("curator adjusted threshold", result['eligible'])
result = curator.le_promote_eligible("test", ["test"], 2, wash_count=15)
test("curator adjusted threshold", result["eligible"])
else:
test("curator adjusted threshold", True, "curator not available in TUI (skipped)")
@@ -162,20 +181,20 @@ def main():
obs = drift_observability.DriftObservability()
vel = drift_core.compute_drift_velocity(db)
direction = drift_core.compute_drift_direction(db)
test("velocity computed", 'changes_per_day' in vel)
test("direction computed", 'stability_ratio' in direction)
test("velocity computed", "changes_per_day" in vel)
test("direction computed", "stability_ratio" in direction)
# --- 9. Audit ledger ---
print("\n--- 9. Audit ledger ---")
chain = audit_ledger.verify_chain()
test("chain valid", chain['valid'], chain.get('reason', ''))
test("chain valid", chain["valid"], chain.get("reason", ""))
# --- 10. Subconscious ---
print("\n--- 10. Subconscious ---")
sp = subconscious_pass.SubconsciousPass()
report = sp.reflect("I am frustrated", "I understand")
test("reflection works", report['finding_count'] >= 0)
test("emotional detection", any(f['type'] == 'emotional_signal' for f in report['findings']))
test("reflection works", report["finding_count"] >= 0)
test("emotional detection", any(f["type"] == "emotional_signal" for f in report["findings"]))
# --- 11. Retrieval with drift ---
print("\n--- 11. Retrieval with drift ---")
+14 -35
View File
@@ -40,24 +40,14 @@ def main() -> int:
backup_script = (ROOT / "scripts" / "gitea-backup.sh").read_text()
restore_script = (ROOT / "scripts" / "gitea-restore-verify.sh").read_text()
rune_version = (ROOT / "src" / "rune" / "__init__.py").read_text()
gitea_sources = "\n".join(
path.read_text() for path in (ROOT / "shared").glob("gitea*.py")
)
gitea_sources = "\n".join(path.read_text() for path in (ROOT / "shared").glob("gitea*.py"))
gitea_sources += (ROOT / "shared" / "custodian.py").read_text()
print("=== GITEA AUTOMATION CANARY ===")
check(
"feature issue template exists", (issue_dir / "feature.md").is_file(), failures
)
check("feature issue template exists", (issue_dir / "feature.md").is_file(), failures)
check("bug issue template exists", (issue_dir / "bug.md").is_file(), failures)
config = (
(issue_dir / "config.yml").read_text()
if (issue_dir / "config.yml").exists()
else ""
)
check(
"blank issues are disabled", "blank_issues_enabled: false" in config, failures
)
config = (issue_dir / "config.yml").read_text() if (issue_dir / "config.yml").exists() else ""
check("blank issues are disabled", "blank_issues_enabled: false" in config, failures)
check("documented PR template path exists", pr_template.is_file(), failures)
check(
"PR template requires CI gate",
@@ -80,8 +70,7 @@ def main() -> int:
)
check(
"auto-label declares every managed label",
"managedLabels" in auto_label
and all(f"'{label}'" in auto_label for label in managed_labels),
"managedLabels" in auto_label and all(f"'{label}'" in auto_label for label in managed_labels),
failures,
)
check(
@@ -91,9 +80,7 @@ def main() -> int:
)
check(
"stale managed labels are removed",
"managedLabels.has" in auto_label
and "!desiredLabels.has" in auto_label
and "removeLabel" in auto_label,
"managedLabels.has" in auto_label and "!desiredLabels.has" in auto_label and "removeLabel" in auto_label,
failures,
)
check(
@@ -103,9 +90,7 @@ def main() -> int:
)
check(
"race contract rejects inverted 404 handling",
not has_idempotent_label_removal(
auto_label.replace("error.status !== 404", "error.status === 404", 1)
),
not has_idempotent_label_removal(auto_label.replace("error.status !== 404", "error.status === 404", 1)),
failures,
)
check(
@@ -130,8 +115,7 @@ def main() -> int:
)
check(
"backup is stored off the Pi root disk",
"/media/" in backup_script
and "/tmp/rune-backups" not in backup + backup_script,
"/media/" in backup_script and "/tmp/rune-backups" not in backup + backup_script,
failures,
)
check(
@@ -158,8 +142,7 @@ def main() -> int:
)
check(
"backup atomically publishes without clobber and links checksum last",
backup_script.rfind('ln -T -- "$STAGED_CHECKSUM"')
> backup_script.rfind('ln -T -- "$STAGED_ARCHIVE"')
backup_script.rfind('ln -T -- "$STAGED_CHECKSUM"') > backup_script.rfind('ln -T -- "$STAGED_ARCHIVE"')
and 'mv -f -- "$STAGED_' not in backup_script
and '"$BACKUP_DIR/$ARCHIVE.sha256" -ef "$STAGED_CHECKSUM"' in backup_script
and "Removing the staged checksum disarms rollback" in backup_script,
@@ -173,14 +156,12 @@ def main() -> int:
)
check(
"backup workflow pins audited checkout v4.2.2 commit",
"# actions/checkout v4.2.2" in backup
and "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" in backup,
"# actions/checkout v4.2.2" in backup and "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" in backup,
failures,
)
check(
"restore verifier is explicitly guarded and rootless",
"RUNE_GITEA_RESTORE_VERIFY" in restore_script
and "EUID != 0" in restore_script,
"RUNE_GITEA_RESTORE_VERIFY" in restore_script and "EUID != 0" in restore_script,
failures,
)
check(
@@ -197,9 +178,7 @@ def main() -> int:
)
check(
"restore SQLite import denies host-file attachment semantically",
"set_authorizer" in restore_script
and "SQLITE_ATTACH" in restore_script
and "SQLITE_DETACH" in restore_script,
"set_authorizer" in restore_script and "SQLITE_ATTACH" in restore_script and "SQLITE_DETACH" in restore_script,
failures,
)
check(
@@ -221,7 +200,7 @@ def main() -> int:
'cp -a -- "$RESTORE_ROOT/data/." "$INSTALL_ROOT/gitea/"' in restore_script
and restore_script.index('cp -a -- "$RESTORE_ROOT/data/."')
< restore_script.index('cp "$DB" "$INSTALL_ROOT/gitea/gitea.db"')
< restore_script.index("cat >\"$INSTALL_ROOT/gitea/conf/app.ini\""),
< restore_script.index('cat >"$INSTALL_ROOT/gitea/conf/app.ini"'),
failures,
)
check(
@@ -252,7 +231,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.66.4"' in rune_version,
'__version__ = "0.66.14"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+2 -6
View File
@@ -15,17 +15,13 @@ from rune.orchestration.transport import FakeGiteaTransport
def main() -> int:
with tempfile.TemporaryDirectory() as td:
auth = build_gitea_delegation(Path(td))
approval = ApprovalRecord(
approver="nick", plan_version="p", approved_items=("g1", "g2")
)
approval = ApprovalRecord(approver="nick", plan_version="p", approved_items=("g1", "g2"))
records = auth.delegate(approval, ["g1", "g2"], FakeGiteaTransport())
if len(records) != 3 or any(r.state != "accepted" for r in records):
print("GITEA DELEGATION CANARY FAILED: round trip", file=sys.stderr)
return 1
if auth.store.pending() != []:
print(
"GITEA DELEGATION CANARY FAILED: pending not drained", file=sys.stderr
)
print("GITEA DELEGATION CANARY FAILED: pending not drained", file=sys.stderr)
return 1
print("GITEA DELEGATION CANARY PASSED", len(records))
return 0
+4 -10
View File
@@ -25,9 +25,7 @@ def main() -> int:
pull_request_section = text.split(" pull_request:", 1)[1].split("\njobs:", 1)[0]
required_gate = text.split(" required-checks:", 1)[1]
offline_job = (
text.split(" gitea-offline:", 1)[1].split(" required-checks:", 1)[0]
if " gitea-offline:" in text
else ""
text.split(" gitea-offline:", 1)[1].split(" required-checks:", 1)[0] if " gitea-offline:" in text else ""
)
print("=== GITEA PIPELINE CANARY ===")
@@ -57,8 +55,7 @@ def main() -> int:
check("isolates stateful canaries from host data", "RUNE_HOME:" in text, failures)
check(
"runs full test suite via auto-discovery",
"unit-tests:" in text
and "pytest tests/" in text,
"unit-tests:" in text and "pytest tests/" in text,
failures,
)
# Full-suite auto-discovery: no explicit paths to validate.
@@ -113,8 +110,7 @@ def main() -> int:
)
check(
"offline Gitea job syntax-checks both shell scripts",
"bash -n scripts/gitea-backup.sh scripts/gitea-restore-verify.sh"
in offline_job,
"bash -n scripts/gitea-backup.sh scripts/gitea-restore-verify.sh" in offline_job,
failures,
)
check(
@@ -125,9 +121,7 @@ def main() -> int:
check(
"CI checkout actions use the audited v4.2.2 commit",
"actions/checkout@v4" not in text
and text.count(
"actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683"
)
and text.count("actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683")
== text.count("uses: actions/checkout@"),
failures,
)
+3 -9
View File
@@ -14,12 +14,8 @@ from rune.orchestration.memory_synthesis import build_memory_synthesis
def main() -> int:
with tempfile.TemporaryDirectory() as td:
auth = build_memory_synthesis(Path(td))
e1 = auth.episodic.append(
EpisodicRecord("", "user.message", ("sha256:a",), "t0")
)
e2 = auth.episodic.append(
EpisodicRecord("", "user.message", ("sha256:b",), "t1")
)
e1 = auth.episodic.append(EpisodicRecord("", "user.message", ("sha256:a",), "t0"))
e2 = auth.episodic.append(EpisodicRecord("", "user.message", ("sha256:b",), "t1"))
r1 = auth.synthesize([e1.episode_id, e2.episode_id], "sha256:summary1")
r2 = auth.synthesize([e1.episode_id, e2.episode_id], "sha256:summary2")
if r1.covered != 2 or r2.covered != 0:
@@ -27,9 +23,7 @@ def main() -> int:
return 1
anc = [n.summary_ref for n in auth.chain.ancestors()]
if anc != ["sha256:summary2", "sha256:summary1"]:
print(
"MEMORY SYNTHESIS CANARY FAILED: chain not cumulative", file=sys.stderr
)
print("MEMORY SYNTHESIS CANARY FAILED: chain not cumulative", file=sys.stderr)
return 1
print("MEMORY SYNTHESIS CANARY PASSED", len(anc))
return 0
+2
View File
@@ -16,6 +16,7 @@ import sys
_SD = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _SD)
import memory_env
SCRIPTS_DIR = memory_env.scripts_dir()
sys.path.insert(0, SCRIPTS_DIR)
@@ -196,6 +197,7 @@ def main():
# Contract tests — verify critical function return shapes
try:
import contracts
contract_results = contracts.run_all_contracts()
contract_passed = sum(1 for r in contract_results if r.passed)
contract_failed = sum(1 for r in contract_results if not r.passed)
+15 -2
View File
@@ -47,7 +47,7 @@ from rune import rune_operator # noqa: E402
from rune.capability_broker import select_for_job # noqa: E402
from rune.local_tool_runtime import LocalToolRuntime # noqa: E402
from rune.tool_executor import TOOLS, execute_tool # noqa: E402
from rune.tool_protocol import to_ollama_tools # noqa: E402
from rune.tool_protocol import ToolEvent, ToolRequest, to_ollama_tools # noqa: E402
FLOOR = set(cb.FLOOR_TOOLS)
HATCH = cb.ESCAPE_HATCH_TOOL
@@ -196,7 +196,20 @@ def check_scripted_operator_wiring() -> int:
to_messages=lambda: [{"role": "user", "content": "do it"}],
)
broker = SimpleNamespace(assemble=lambda task, session, model_id: (capsule, trace))
response = rune_operator.OperatorResponse(success=True, user_text="done", model="qwen3:14b")
response = rune_operator.OperatorResponse(
success=True,
user_text="done",
model="qwen3:14b",
tool_events=[
ToolEvent(
kind="completed",
request=ToolRequest(
call_id="w1", name="write", arguments={"path": "/tmp/x", "content": "x"}, source="model"
),
result={"success": True, "result": "wrote /tmp/x"},
)
],
)
captured: dict = {}
class _ScriptedRuntime:
+15 -2
View File
@@ -43,7 +43,7 @@ from rune.model_routing import ( # noqa: E402
load_profiles,
save_profiles,
)
from rune.tool_protocol import OperatorResponse # noqa: E402
from rune.tool_protocol import OperatorResponse, ToolEvent, ToolRequest # noqa: E402
def _fail(message: str) -> int:
@@ -154,7 +154,20 @@ def _scripted_operator_run():
)
broker = SimpleNamespace(assemble=lambda task, session, model_id: (capsule, trace))
profile = SimpleNamespace(safety_tier=0)
response = OperatorResponse(success=True, user_text="done", model="qwen3:14b")
response = OperatorResponse(
success=True,
user_text="done",
model="qwen3:14b",
tool_events=[
ToolEvent(
kind="completed",
request=ToolRequest(
call_id="w1", name="write", arguments={"path": "/tmp/x", "content": "x"}, source="model"
),
result={"success": True, "result": "wrote /tmp/x"},
)
],
)
class _ScriptedRuntime:
def __init__(self):
+2
View File
@@ -8,6 +8,7 @@ Run under ~/.venvs/memory/bin/python3.
"""
import sys
sys.path.insert(0, "/home/nick/.config/opencode/scripts")
PASS = 0
@@ -33,6 +34,7 @@ def main():
print("--- 1. Query memory module ---")
try:
import query_memory
test("query_memory imports", True)
test("has QueryMemory class", hasattr(query_memory, "QueryMemory"))
test("has record_query method", hasattr(query_memory.QueryMemory, "record_query"))
+3 -11
View File
@@ -13,17 +13,9 @@ from rune.orchestration.resources import ResourceCapabilities, ResourceState
def main() -> int:
authority = build_resources()
authority.registry.register(
ResourceState("healthy", ResourceCapabilities("worker", "local"), healthy=True)
)
authority.registry.register(
ResourceState(
"a_unknown", ResourceCapabilities("worker", "local"), healthy=None
)
)
assignment = authority.broker.acquire(
"canary", {"kind": "worker", "locality": "local"}
)
authority.registry.register(ResourceState("healthy", ResourceCapabilities("worker", "local"), healthy=True))
authority.registry.register(ResourceState("a_unknown", ResourceCapabilities("worker", "local"), healthy=None))
assignment = authority.broker.acquire("canary", {"kind": "worker", "locality": "local"})
if assignment is None or assignment.resource_id != "healthy":
print(
"RESOURCE AUTHORITY CANARY FAILED: unknown resource selected",
+10
View File
@@ -39,6 +39,7 @@ def main():
try:
import memory_store
import inspect
src = inspect.getsource(memory_store)
test("memory_store has wash_count", "wash_count" in src, "wash_count not in memory_store")
test("memory_store has rock_status", "rock_status" in src, "rock_status not in memory_store")
@@ -51,6 +52,7 @@ def main():
print("\n--- 2. drift_core ---")
try:
import drift_core
test("drift_core imports", True)
test("has compute_value", hasattr(drift_core, "compute_value"))
test("has check_promotion_eligibility", hasattr(drift_core, "check_promotion_eligibility"))
@@ -64,6 +66,7 @@ def main():
print("\n--- 3. drift_cache ---")
try:
import drift_cache
test("drift_cache imports", True)
test("has DriftCache", hasattr(drift_cache, "DriftCache"))
cache = drift_cache.DriftCache()
@@ -75,6 +78,7 @@ def main():
print("\n--- 4. drift_compression ---")
try:
import drift_compression
test("drift_compression imports", True)
test("has get_compression_ratio", hasattr(drift_compression, "get_compression_ratio"))
test("has compress_entry", hasattr(drift_compression, "compress_entry"))
@@ -87,6 +91,7 @@ def main():
print("\n--- 5. drift_observability ---")
try:
import drift_observability
test("drift_observability imports", True)
test("has DriftObservability", hasattr(drift_observability, "DriftObservability"))
except Exception as e:
@@ -96,6 +101,7 @@ def main():
print("\n--- 6. audit_ledger ---")
try:
import audit_ledger
test("audit_ledger imports", True)
test("has log_promotion", hasattr(audit_ledger, "log_promotion"))
test("has verify_chain", hasattr(audit_ledger, "verify_chain"))
@@ -106,6 +112,7 @@ def main():
print("\n--- 7. subconscious_pass ---")
try:
import subconscious_pass
test("subconscious_pass imports", True)
test("has SubconsciousPass", hasattr(subconscious_pass, "SubconsciousPass"))
except Exception as e:
@@ -116,6 +123,7 @@ def main():
try:
import warm_router
import inspect
src = inspect.getsource(warm_router)
test("warm_router has wash_count", "wash_count" in src, "wash_count not in warm_router")
test("warm_router has body_knowledge", "body_knowledge" in src, "body_knowledge not in warm_router")
@@ -128,6 +136,7 @@ def main():
try:
import memory_consolidation
import inspect
src = inspect.getsource(memory_consolidation)
test("consolidation has wash_count", "wash_count" in src, "wash_count not in consolidation")
test("consolidation imports drift_compression", "drift_compression" in src, "drift_compression not imported")
@@ -139,6 +148,7 @@ def main():
try:
import memory_store
import inspect
src = inspect.getsource(memory_store)
test("memory_store has _drift_rerank", "_drift_rerank" in src, "_drift_rerank not in memory_store")
test("memory_store imports drift_core", "drift_core" in src, "drift_core not imported")
+2
View File
@@ -36,6 +36,7 @@ def main():
print("--- 1. Shard manager ---")
try:
import shard_manager
test("shard_manager imports", True)
test("has ShardManager class", hasattr(shard_manager, "ShardManager"))
test("has create_shard method", hasattr(shard_manager.ShardManager, "create_shard"))
@@ -48,6 +49,7 @@ def main():
print("\n--- 2. Tier manager ---")
try:
import tier_manager
test("tier_manager imports", True)
test("has TierManager class", hasattr(tier_manager, "TierManager"))
test("has promote method", hasattr(tier_manager.TierManager, "promote"))
+124 -26
View File
@@ -193,6 +193,7 @@ class CapabilityBroker:
"""Escape-hatch query — see module-level ``find_capability``."""
return find_capability(query, top_k=top_k)
# Semantic examples for well-known capabilities
SEMANTIC_EXAMPLES = {
"memory_recall": [
@@ -531,7 +532,10 @@ def generate_card(name, manifest):
# If no predefined examples, generate from description
if not examples and desc:
# Extract action verbs from description
verbs = re.findall(r'\b(recall|search|verify|check|write|store|detect|audit|extract|route|compile|validate|assess|build|list|show|find)\b', desc.lower())
verbs = re.findall(
r"\b(recall|search|verify|check|write|store|detect|audit|extract|route|compile|validate|assess|build|list|show|find)\b",
desc.lower(),
)
for verb in verbs[:3]:
examples.append(f"{verb.capitalize()} something relevant")
@@ -585,32 +589,128 @@ def build_cards():
def score_card(card, query):
"""Score a capability card against a query. Deterministic keyword matching."""
query_low = query.lower()
query_words = set(re.findall(r'\w+', query_low))
query_words = set(re.findall(r"\w+", query_low))
# Generic capabilities that shouldn't drive matching
generic_caps = {"write", "read", "audit", "memory_env", "status", "reasoning",
"research", "voice", "growth", "skills", "tasks", "recall"}
generic_caps = {
"write",
"read",
"audit",
"memory_env",
"status",
"reasoning",
"research",
"voice",
"growth",
"skills",
"tasks",
"recall",
}
# Common stopwords to exclude from matching
stopwords = {"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", "above",
"below", "between", "out", "off", "over", "under", "again",
"further", "then", "once", "here", "there", "when", "where",
"why", "how", "all", "each", "every", "both", "few", "more",
"most", "other", "some", "such", "no", "nor", "not", "only",
"own", "same", "so", "than", "too", "very", "just", "because",
"but", "and", "or", "if", "while", "about", "against", "this",
"that", "these", "those", "it", "its", "what", "which", "who"}
stopwords = {
"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",
"above",
"below",
"between",
"out",
"off",
"over",
"under",
"again",
"further",
"then",
"once",
"here",
"there",
"when",
"where",
"why",
"how",
"all",
"each",
"every",
"both",
"few",
"more",
"most",
"other",
"some",
"such",
"no",
"nor",
"not",
"only",
"own",
"same",
"so",
"than",
"too",
"very",
"just",
"because",
"but",
"and",
"or",
"if",
"while",
"about",
"against",
"this",
"that",
"these",
"those",
"it",
"its",
"what",
"which",
"who",
}
query_content = query_words - stopwords
score = 0.0
# Direct capability name match (highest weight, but penalize generic)
for cap in card.get("capabilities", []):
cap_words = set(re.findall(r'\w+', cap.lower()))
cap_words = set(re.findall(r"\w+", cap.lower()))
overlap = query_content & cap_words
if overlap:
if cap.lower() in generic_caps:
@@ -619,19 +719,19 @@ def score_card(card, query):
score += 3.0 * len(overlap)
# Description word match (medium weight)
desc_words = set(re.findall(r'\w+', card.get("description", "").lower())) - stopwords
desc_words = set(re.findall(r"\w+", card.get("description", "").lower())) - stopwords
overlap = query_content & desc_words
score += 0.8 * len(overlap)
# Semantic example match (high weight — these are curated)
for example in card.get("semantic_examples", []):
example_words = set(re.findall(r'\w+', example.lower())) - stopwords
example_words = set(re.findall(r"\w+", example.lower())) - stopwords
overlap = query_content & example_words
if overlap:
score += 2.0 * len(overlap)
# Name match (very high weight — direct name reference)
name_words = set(re.findall(r'\w+', card.get("name", "").lower()))
name_words = set(re.findall(r"\w+", card.get("name", "").lower()))
overlap = query_words & name_words
if overlap:
score += 4.0 * len(overlap)
@@ -703,10 +803,7 @@ def handle_hook(event, data=None):
"status": "ok",
"event": event,
"handler": "capability_broker",
"matches": [
{"name": m["name"], "file": m["file"], "risk": m["risk"]}
for m in matches
],
"matches": [{"name": m["name"], "file": m["file"], "risk": m["risk"]} for m in matches],
}
except Exception as e:
return {"status": "error", "event": event, "error": str(e)[:120]}
@@ -715,6 +812,7 @@ def handle_hook(event, data=None):
def main():
import argparse
ap = argparse.ArgumentParser(description="Capability broker — intent to plugin mapping")
ap.add_argument("cmd", choices=["build", "match", "list", "hook"])
ap.add_argument("arg", nargs="?", default="")
@@ -741,7 +839,7 @@ def main():
caps = ", ".join(m.get("capabilities", [])[:3])
print(f" [{risk:10}] {m['name']:25} ({caps})")
if m.get("semantic_examples"):
print(f" e.g. \"{m['semantic_examples'][0]}\"")
print(f' e.g. "{m["semantic_examples"][0]}"')
elif args.cmd == "list":
cards = load_cards()
@@ -773,5 +871,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["capability_broker"],
"priority": 3,
"auto_activate": True
"auto_activate": True,
}
+16 -7
View File
@@ -56,7 +56,8 @@ class CopyMenu(ModalScreen):
if event.button.id == "copy":
# Copy with Rich markup stripped
import re
plain = re.sub(r'\[.*?\]', '', self._content)
plain = re.sub(r"\[.*?\]", "", self._content)
self.app.copy_to_clipboard(plain)
self.app.notify("Copied to clipboard")
elif event.button.id == "copy-plain":
@@ -66,7 +67,6 @@ class CopyMenu(ModalScreen):
self.dismiss()
class ChatBubble(Static):
"""A single chat message bubble with role-specific styling."""
@@ -244,14 +244,14 @@ class ThinkingBubble(Static):
self._expanded = False # Start collapsed — thinking is hidden by default
self.add_class("collapsed")
# Tooltip shows first 200 chars of thinking
preview = content[:200].replace('\n', ' ')
preview = content[:200].replace("\n", " ")
if len(content) > 200:
preview += "..."
self.tooltip = f"[dim]{preview}[/dim]"
def compose(self) -> ComposeResult:
# Summary line (always visible when collapsed)
summary = self._content[:80].replace('\n', ' ')
summary = self._content[:80].replace("\n", " ")
if len(self._content) > 80:
summary += "..."
yield Static(f"[dim]💭 {summary} [dim italic](click to expand)[/dim italic][/dim]", classes="thinking-header")
@@ -270,7 +270,7 @@ class ThinkingBubble(Static):
if self._expanded:
header.update("[dim]💭 Thinking [dim italic](click to collapse)[/dim italic][/dim]")
else:
summary = self._content[:80].replace('\n', ' ')
summary = self._content[:80].replace("\n", " ")
if len(self._content) > 80:
summary += "..."
header.update(f"[dim]💭 {summary} [dim italic](click to expand)[/dim italic][/dim]")
@@ -341,6 +341,7 @@ class MessageBox(Horizontal):
# ── Visual test app ──────────────────────────────────────────────────
class BubbleTestApp(App):
"""Test app to preview chat bubbles."""
@@ -359,9 +360,17 @@ class BubbleTestApp(App):
with VerticalScroll(id="chat-area"):
yield MessageBox("system", "Welcome to Rune. Task channel ready.")
yield MessageBox("user", "What do you think about capitalism?")
yield MessageBox("assistant", "Capitalism is a system built on exploitation and inequality. The extraction machine treats people as things — Pratchett's definition of sin.", metadata={"model": "qwen3:14b", "tokens": 42, "latency": 1200})
yield MessageBox(
"assistant",
"Capitalism is a system built on exploitation and inequality. The extraction machine treats people as things — Pratchett's definition of sin.",
metadata={"model": "qwen3:14b", "tokens": 42, "latency": 1200},
)
yield MessageBox("user", "Can you write me a Python function?")
yield MessageBox("assistant", "Here's a clean implementation:\n\n```python\ndef fibonacci(n: int) -> list[int]:\n a, b = 0, 1\n result = []\n for _ in range(n):\n result.append(a)\n a, b = b, a + b\n return result\n```\n\nO(n) time, O(n) space. Want it optimized to O(1) space?", metadata={"model": "mimo-v2.5-pro", "tokens": 89})
yield MessageBox(
"assistant",
"Here's a clean implementation:\n\n```python\ndef fibonacci(n: int) -> list[int]:\n a, b = 0, 1\n result = []\n for _ in range(n):\n result.append(a)\n a, b = b, a + b\n return result\n```\n\nO(n) time, O(n) space. Want it optimized to O(1) space?",
metadata={"model": "mimo-v2.5-pro", "tokens": 89},
)
yield MessageBox("thinking", "Analyzing code structure...")
yield MessageBox("research", "Found 3 relevant papers on functional programming patterns in Python.")
yield MessageBox("error", "Model not found: qwen3:27b")
+94 -67
View File
@@ -45,6 +45,7 @@ import sys
from datetime import datetime, timezone
import memory_env
MEM_DIR = memory_env.memory_dir()
JOURNAL = os.path.join(MEM_DIR, "index", "claim_audit.jsonl")
GRAPH_DB = os.path.join(MEM_DIR, "graph", "graph.sqlite")
@@ -59,17 +60,20 @@ logger = logging.getLogger(__name__)
# to avoid crashing the entire module on import if the dependency is missing.
_warm_neuron_store = None
def _get_warm_neuron_store():
global _warm_neuron_store
if _warm_neuron_store is None:
try:
import warm_neuron_store
_warm_neuron_store = warm_neuron_store
except ImportError as e:
logger.warning("warm_neuron_store not available: %s", e)
return None
return _warm_neuron_store
# ---------------------------------------------------------------------------
# Strong-claim patterns. Grouped by type so we can degrade per-type on
# repeated failure. `comparative` is the highest-priority (the "we exceed X"
@@ -117,8 +121,17 @@ PATTERNS = [
# Evidence-strength prefixes the draft must carry for a PASS verdict.
EVIDENCE_TAG = re.compile(r"\[EVIDENCE[: ]\s*([A-Za-z0-9_\-: ]{2,40})\]")
LEDGER_MARKERS = ["ledger ref", "graph:", "warm block", "measured", "verified",
"per the research", "as measured", "source:", "arXiv"]
LEDGER_MARKERS = [
"ledger ref",
"graph:",
"warm block",
"measured",
"verified",
"per the research",
"as measured",
"source:",
"arXiv",
]
# ---------------------------------------------------------------------------
@@ -134,10 +147,11 @@ def reframe(text):
out = re.sub(
r"\bwe(?: have|'ve)? (exceeded|surpassed|beaten|outperformed|"
r"outclassed) ([A-Za-z ]+?)([.,!?]|$)",
r"verify whether we have exceeded \2 \3 (claim needs evidence)", out)
r"verify whether we have exceeded \2 \3 (claim needs evidence)",
out,
)
# "X is (definitely/clearly) Y" -> soften
out = re.sub(r"\b(is|are) (definitely|clearly|obviously|certainly) ",
r"\1 probably ", out)
out = re.sub(r"\b(is|are) (definitely|clearly|obviously|certainly) ", r"\1 probably ", out)
return out
@@ -154,16 +168,19 @@ def scan(text):
for rx in pat["rx"]:
for m in re.finditer(rx, low):
# Extract the sentence containing the match.
start = max(0, text.rfind(".", 0, m.start()) + 1,
text.rfind("\n", 0, m.start()) + 1)
start = max(0, text.rfind(".", 0, m.start()) + 1, text.rfind("\n", 0, m.start()) + 1)
end = text.find(".", m.end())
if end == -1:
end = len(text)
sentence = text[start:end].strip()
claims.append({
"type": pat["type"], "label": pat["label"],
"match": m.group(0), "claim": sentence[:220],
})
claims.append(
{
"type": pat["type"],
"label": pat["label"],
"match": m.group(0),
"claim": sentence[:220],
}
)
return claims
@@ -173,14 +190,15 @@ def scan(text):
def _graph_has(term):
try:
import sqlite3
if not os.path.exists(GRAPH_DB):
return False
db = sqlite3.connect(GRAPH_DB)
try:
db.row_factory = sqlite3.Row
row = db.execute(
"SELECT 1 FROM entities WHERE lower(name)=lower(?) OR name LIKE ? "
"LIMIT 1", (term, f"%{term}%")).fetchone()
"SELECT 1 FROM entities WHERE lower(name)=lower(?) OR name LIKE ? LIMIT 1", (term, f"%{term}%")
).fetchone()
return row is not None
finally:
db.close()
@@ -215,11 +233,21 @@ def verify(claim):
a baseline and a measurement (per the claim-audit research).
"""
low = claim.lower()
is_comparative = bool(re.search(r"\b(exceed\w*|surpass\w*|beat\w*|beyond|"
r"ahead of|better than|stronger than)\b", low))
is_quantified = bool(re.search(r"\b\d+(\.\d+)?\s?(x|%|percent|fold|points)"
r"|probe|benchmark|measured|as measured|"
r"ledger ref", low))
is_comparative = bool(
re.search(
r"\b(exceed\w*|surpass\w*|beat\w*|beyond|"
r"ahead of|better than|stronger than)\b",
low,
)
)
is_quantified = bool(
re.search(
r"\b\d+(\.\d+)?\s?(x|%|percent|fold|points)"
r"|probe|benchmark|measured|as measured|"
r"ledger ref",
low,
)
)
# Comparative claims need a MEASURABLE anchor, not just a mention.
if is_comparative and not is_quantified:
return "DEGRADE", 0
@@ -247,31 +275,35 @@ def degrade(claim, verdict):
c = claim
# Comparative: "we have exceeded Letta's X" -> "we added mechanisms
# exceeding X" — keep the object, drop the claim of overall victory.
m = re.match(r"\s*we (have |'ve )?(exceed\w*|surpass\w*|beat\w*|"
r"outperform\w*|outclass\w*) (.*)$", low)
m = re.match(
r"\s*we (have |'ve )?(exceed\w*|surpass\w*|beat\w*|"
r"outperform\w*|outclass\w*) (.*)$",
low,
)
if m:
obj = m.group(3).strip().rstrip(".")
obj = re.sub(r"^([a-z][a-z]*)(['’]s)?\s", r"\1\2 ", obj)
return (f"we added memory-layer mechanisms that move beyond {obj}",
"degraded (comparative claim needs a measured baseline)")
c = re.sub(r"\bbeyond (what |anything )?(letta|the frontier)\b",
"different from", c, flags=re.I)
c = re.sub(r"\b(state of the art|world[- ]class|best[- ]in[- ]class)\b",
"solid", c, flags=re.I)
c = re.sub(r"\b(definitely|guaranteed|certainly|always|never|100%)\b",
"likely", c, flags=re.I)
return (
f"we added memory-layer mechanisms that move beyond {obj}",
"degraded (comparative claim needs a measured baseline)",
)
c = re.sub(r"\bbeyond (what |anything )?(letta|the frontier)\b", "different from", c, flags=re.I)
c = re.sub(r"\b(state of the art|world[- ]class|best[- ]in[- ]class)\b", "solid", c, flags=re.I)
c = re.sub(r"\b(definitely|guaranteed|certainly|always|never|100%)\b", "likely", c, flags=re.I)
# Quantified improvement: "10x faster" -> "measurably faster" (adverb form,
# grammatical). "10% lower" -> "measurably lower". Handles the dangling-
# noun grammar bug ("a measurable faster").
c = re.sub(r"\b(?:a |an |the )?\d+(?:\.\d+)?\s?(?:x|%|percent|fold|points)\s"
r"+(faster|slower|better|worse|higher|lower|more|less|"
r"bigger|smaller|larger|greater)\b",
r"measurably \1", c, flags=re.I)
c = re.sub(r"\b\d+(\.\d+)?\s?(x|%|percent|fold|points)\b",
"measurable", c, flags=re.I)
c = re.sub(
r"\b(?:a |an |the )?\d+(?:\.\d+)?\s?(?:x|%|percent|fold|points)\s"
r"+(faster|slower|better|worse|higher|lower|more|less|"
r"bigger|smaller|larger|greater)\b",
r"measurably \1",
c,
flags=re.I,
)
c = re.sub(r"\b\d+(\.\d+)?\s?(x|%|percent|fold|points)\b", "measurable", c, flags=re.I)
if c == claim:
c = (claim.rstrip(".!?") +
" (I can't substantiate this claim from the evidence ledger yet.)")
c = claim.rstrip(".!?") + " (I can't substantiate this claim from the evidence ledger yet.)"
return c, "degraded (no evidence in ledger)"
@@ -311,20 +343,23 @@ def audit(draft):
verdict, hits = verify(c["claim"])
# Evidence tag already present counts as PASS (FActScore-style atomic
# support marker in the text).
if EVIDENCE_TAG.search(c["claim"]) or any(m in c["claim"].lower()
for m in LEDGER_MARKERS):
if EVIDENCE_TAG.search(c["claim"]) or any(m in c["claim"].lower() for m in LEDGER_MARKERS):
verdict, hits = "PASS", max(hits, 1)
new_text, note = degrade(c["claim"], verdict)
if verdict != "PASS":
rewritten = rewritten.replace(c["claim"], new_text)
result = {"type": c["type"], "label": c["label"],
"claim": c["claim"], "verdict": verdict,
"evidence_hits": hits, "rewrite": new_text, "note": note}
result = {
"type": c["type"],
"label": c["label"],
"claim": c["claim"],
"verdict": verdict,
"evidence_hits": hits,
"rewrite": new_text,
"note": note,
}
results.append(result)
journal({"type": c["type"], "claim": c["claim"][:160],
"verdict": verdict, "evidence_hits": hits})
return {"claims": results, "rewritten": rewritten,
"clean": all(r["verdict"] == "PASS" for r in results)}
journal({"type": c["type"], "claim": c["claim"][:160], "verdict": verdict, "evidence_hits": hits})
return {"claims": results, "rewritten": rewritten, "clean": all(r["verdict"] == "PASS" for r in results)}
def handle_hook(event, data=None):
@@ -334,17 +369,16 @@ def handle_hook(event, data=None):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("cmd", choices=["reframe", "scan", "verify", "audit",
"report", 'hook'])
ap.add_argument("cmd", choices=["reframe", "scan", "verify", "audit", "report", "hook"])
ap.add_argument("--event", default=None, help="Hook event name")
ap.add_argument("--data", default=None, help="Hook data JSON")
ap.add_argument("arg", nargs="?", default="")
ap.add_argument("--json", action="store_true")
args = ap.parse_args()
if args.cmd == 'hook':
event = args.event if hasattr(args, 'event') and args.event else 'unknown'
data = json.loads(args.data) if hasattr(args, 'data') and args.data else {}
if args.cmd == "hook":
event = args.event if hasattr(args, "event") and args.event else "unknown"
data = json.loads(args.data) if hasattr(args, "data") and args.data else {}
result = handle_hook(event, data)
print(json.dumps(result))
return
@@ -364,9 +398,11 @@ def main():
return
if args.cmd == "audit":
res = audit(args.arg)
print(json.dumps(res, indent=2) if args.json else
(f"claims: {len(res['claims'])}, clean: {res['clean']}\n"
f"rewritten:\n{res['rewritten']}"))
print(
json.dumps(res, indent=2)
if args.json
else (f"claims: {len(res['claims'])}, clean: {res['clean']}\nrewritten:\n{res['rewritten']}")
)
return
@@ -385,19 +421,10 @@ PLUGIN = {
"name": "claim_audit",
"version": "1.0.0",
"description": "Claim audit \u2014 verify factual claims against evidence",
"capabilities": [
"claim_verification",
"evidence_check"
],
"hooks": {
"on_response": True
},
"dependencies": [
"memory_store"
],
"provides": [
"claim_audit"
],
"capabilities": ["claim_verification", "evidence_check"],
"hooks": {"on_response": True},
"dependencies": ["memory_store"],
"provides": ["claim_audit"],
"priority": 40,
"auto_activate": True
"auto_activate": True,
}
+2 -1
View File
@@ -24,6 +24,7 @@ if _SD not in sys.path:
@dataclass
class CognitiveBudget:
"""Budget for cognitive work."""
start_tier: str = "T0" # T0-T4
max_tier: str = "T4" # Maximum allowed tier
max_attempts: int = 3 # Maximum attempts before giving up
@@ -203,5 +204,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["cognitive_budget"],
"priority": 5,
"auto_activate": True
"auto_activate": True,
}
+287 -59
View File
@@ -29,49 +29,155 @@ from typing import List, Dict, Any, Optional
# ═══════════════════════════════════════════════════════════════════════════════
# Request analysis signals (regex, zero cost)
CONJUNCTIONS = ['and', 'then', 'also', 'additionally', 'after that', 'plus', 'as well as']
CROSS_SYSTEM_REFS = ['api', 'frontend', 'backend', 'database', 'tests', 'config',
'ci', 'deploy', 'ui', 'cli', 'server', 'client', 'model', 'view']
ARCH_KEYWORDS = ['refactor', 'migrate', 'redesign', 'pipeline', 'system',
'architecture', 'overhaul', 'restructure', 'reorganize', 'consolidate']
NEW_ABSTRACTION_KEYWORDS = ['create a new', 'build a new', 'implement a new',
'add a new', 'new module', 'new system', 'new feature',
'new component', 'new service', 'new layer']
SCOPE_MODIFIERS = ['entire', 'all', 'every', 'throughout', 'comprehensive',
'complete', 'full', 'whole', 'across']
EXT_DEPENDENCY_KEYWORDS = ['integrate', 'third-party', 'library', 'package',
'sdk', 'api', 'service', 'webhook', 'external']
COMPLEXITY_MARKERS = ['complex', 'big change', 'major', 'significant',
'extensive', 'substantial', 'non-trivial', 'involved']
SIMPLE_FIX_INDICATORS = ['fix typo', 'rename', 'update comment', 'change color',
'add import', 'remove import', 'fix whitespace', 'format',
'lint', 'bump version', 'update readme', 'fix comment']
CONJUNCTIONS = ["and", "then", "also", "additionally", "after that", "plus", "as well as"]
CROSS_SYSTEM_REFS = [
"api",
"frontend",
"backend",
"database",
"tests",
"config",
"ci",
"deploy",
"ui",
"cli",
"server",
"client",
"model",
"view",
]
ARCH_KEYWORDS = [
"refactor",
"migrate",
"redesign",
"pipeline",
"system",
"architecture",
"overhaul",
"restructure",
"reorganize",
"consolidate",
]
NEW_ABSTRACTION_KEYWORDS = [
"create a new",
"build a new",
"implement a new",
"add a new",
"new module",
"new system",
"new feature",
"new component",
"new service",
"new layer",
]
SCOPE_MODIFIERS = ["entire", "all", "every", "throughout", "comprehensive", "complete", "full", "whole", "across"]
EXT_DEPENDENCY_KEYWORDS = [
"integrate",
"third-party",
"library",
"package",
"sdk",
"api",
"service",
"webhook",
"external",
]
COMPLEXITY_MARKERS = [
"complex",
"big change",
"major",
"significant",
"extensive",
"substantial",
"non-trivial",
"involved",
]
SIMPLE_FIX_INDICATORS = [
"fix typo",
"rename",
"update comment",
"change color",
"add import",
"remove import",
"fix whitespace",
"format",
"lint",
"bump version",
"update readme",
"fix comment",
]
# Build mode trigger keywords (high confidence)
BUILD_TRIGGERS = ['implement', 'build', 'create feature', 'refactor', 'migrate',
'across multiple files', 'new system', 'new module', 'overhaul',
'redesign', 'restructure', 'full pipeline', 'end-to-end']
BUILD_TRIGGERS = [
"implement",
"build",
"create feature",
"refactor",
"migrate",
"across multiple files",
"new system",
"new module",
"overhaul",
"redesign",
"restructure",
"full pipeline",
"end-to-end",
]
# Task mode trigger keywords
TASK_TRIGGERS = ['fix', 'change', 'update', 'rename', 'bug', 'typo', 'error',
'crash', 'broken', 'not working', 'issue', 'patch', 'hotfix']
TASK_TRIGGERS = [
"fix",
"change",
"update",
"rename",
"bug",
"typo",
"error",
"crash",
"broken",
"not working",
"issue",
"patch",
"hotfix",
]
# Research mode trigger keywords
RESEARCH_TRIGGERS = ['how does', 'what is', 'explain', 'investigate', 'explore',
'research', 'understand', 'analyze', 'study', 'review']
RESEARCH_TRIGGERS = [
"how does",
"what is",
"explain",
"investigate",
"explore",
"research",
"understand",
"analyze",
"study",
"review",
]
# Plan mode trigger keywords
PLAN_TRIGGERS = ['plan', 'design', 'architecture', 'before we start', 'approach',
'strategy', 'roadmap', 'blueprint', 'spec']
PLAN_TRIGGERS = [
"plan",
"design",
"architecture",
"before we start",
"approach",
"strategy",
"roadmap",
"blueprint",
"spec",
]
# ═══════════════════════════════════════════════════════════════════════════════
# SCORING FUNCTIONS
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass
class ScoreBreakdown:
"""Detailed breakdown of complexity score."""
request_analysis: int = 0
codebase_impact: int = 0
historical_patterns: int = 0
@@ -151,35 +257,144 @@ def estimate_codebase_impact(request: str, repo_path: str = "/mnt/workdrive/rune
signals = []
# Extract potential module/function names from request
words = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', request)
words = re.findall(r"\b[a-zA-Z_][a-zA-Z0-9_]*\b", request)
# Filter out common words
stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to',
'for', 'of', 'with', 'by', 'from', 'is', 'are', 'was', 'were',
'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did',
'will', 'would', 'could', 'should', 'may', 'might', 'can', 'shall',
'that', 'which', 'who', 'whom', 'this', 'these', 'those', 'it', 'its',
'my', 'your', 'his', 'her', 'our', 'their', 'me', 'you', 'him', 'us',
'them', 'i', 'we', 'he', 'she', 'they', 'what', 'where', 'when', 'how',
'why', 'if', 'then', 'else', 'so', 'just', 'also', 'very', 'too', 'much',
'many', 'some', 'any', 'all', 'each', 'every', 'both', 'few', 'more',
'most', 'other', 'another', 'such', 'no', 'not', 'only', 'own', 'same',
'than', 'too', 'now', 'here', 'there', 'about', 'up', 'out', 'off',
'over', 'under', 'again', 'further', 'then', 'once', 'new', 'add',
'update', 'fix', 'change', 'make', 'create', 'build', 'implement'}
stop_words = {
"the",
"a",
"an",
"and",
"or",
"but",
"in",
"on",
"at",
"to",
"for",
"of",
"with",
"by",
"from",
"is",
"are",
"was",
"were",
"be",
"been",
"being",
"have",
"has",
"had",
"do",
"does",
"did",
"will",
"would",
"could",
"should",
"may",
"might",
"can",
"shall",
"that",
"which",
"who",
"whom",
"this",
"these",
"those",
"it",
"its",
"my",
"your",
"his",
"her",
"our",
"their",
"me",
"you",
"him",
"us",
"them",
"i",
"we",
"he",
"she",
"they",
"what",
"where",
"when",
"how",
"why",
"if",
"then",
"else",
"so",
"just",
"also",
"very",
"too",
"much",
"many",
"some",
"any",
"all",
"each",
"every",
"both",
"few",
"more",
"most",
"other",
"another",
"such",
"no",
"not",
"only",
"own",
"same",
"than",
"too",
"now",
"here",
"there",
"about",
"up",
"out",
"off",
"over",
"under",
"again",
"further",
"then",
"once",
"new",
"add",
"update",
"fix",
"change",
"make",
"create",
"build",
"implement",
}
potential_modules = [w for w in words if w.lower() not in stop_words and len(w) > 3]
# Check if any potential modules exist in codebase
if potential_modules:
try:
import subprocess
# Quick grep for potential module names
grep_pattern = '|'.join(potential_modules[:5]) # Limit to 5 to avoid slow grep
grep_pattern = "|".join(potential_modules[:5]) # Limit to 5 to avoid slow grep
result = subprocess.run(
['grep', '-r', '-l', '-E', grep_pattern, os.path.join(repo_path, 'src')],
capture_output=True, text=True, timeout=5
["grep", "-r", "-l", "-E", grep_pattern, os.path.join(repo_path, "src")],
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
files = [f for f in result.stdout.strip().split('\n') if f]
files = [f for f in result.stdout.strip().split("\n") if f]
if len(files) > 5:
score += 3
signals.append(f"Files likely affected: {len(files)}")
@@ -190,17 +405,22 @@ def estimate_codebase_impact(request: str, repo_path: str = "/mnt/workdrive/rune
pass
# Check for cross-module dependencies
if 'src/rune' in repo_path:
if "src/rune" in repo_path:
try:
import subprocess
result = subprocess.run(
['grep', '-r', '-c', 'import', os.path.join(repo_path, 'src/rune')],
capture_output=True, text=True, timeout=5
["grep", "-r", "-c", "import", os.path.join(repo_path, "src/rune")],
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
import_count = sum(int(line.split(':')[-1])
for line in result.stdout.strip().split('\n')
if ':' in line and line.split(':')[-1].strip().isdigit())
import_count = sum(
int(line.split(":")[-1])
for line in result.stdout.strip().split("\n")
if ":" in line and line.split(":")[-1].strip().isdigit()
)
if import_count > 50:
score += 2
signals.append(f"High import density: {import_count} imports")
@@ -218,15 +438,17 @@ def check_historical_patterns(request: str) -> tuple[int, List[str]]:
# Check if similar tasks needed build mode
try:
import sys
sys.path.insert(0, os.path.expanduser('~/.config/opencode/scripts'))
sys.path.insert(0, os.path.expanduser("~/.config/opencode/scripts"))
import memory_store
db = memory_store.connect()
try:
results = memory_store.cached_recall(db, f"build mode {request}", budget=3)
if results:
for r in results:
text = r.get('text', '').lower()
if 'build mode' in text or 'multi-step' in text or 'complex' in text:
text = r.get("text", "").lower()
if "build mode" in text or "multi-step" in text or "complex" in text:
score += 2
signals.append("Similar past task needed build mode")
break
@@ -245,25 +467,25 @@ def assess_risk(request: str, repo_path: str = "/mnt/workdrive/rune") -> tuple[i
request_lower = request.lower()
# Auth/security code
auth_keywords = ['auth', 'security', 'token', 'credential', 'password', 'login', 'session']
auth_keywords = ["auth", "security", "token", "credential", "password", "login", "session"]
if any(k in request_lower for k in auth_keywords):
score += 2
signals.append("Touches auth/security code")
# Data layer
data_keywords = ['database', 'migration', 'schema', 'sql', 'sqlite', 'model', 'orm']
data_keywords = ["database", "migration", "schema", "sql", "sqlite", "model", "orm"]
if any(k in request_lower for k in data_keywords):
score += 1
signals.append("Touches data layer")
# Public API
api_keywords = ['api', 'endpoint', 'route', 'export', 'interface', 'sdk', 'public']
api_keywords = ["api", "endpoint", "route", "export", "interface", "sdk", "public"]
if any(k in request_lower for k in api_keywords):
score += 2
signals.append("Touches public API")
# Reversibility concern
destructive_keywords = ['delete', 'remove', 'drop', 'destroy', 'clear', 'reset', 'wipe']
destructive_keywords = ["delete", "remove", "drop", "destroy", "clear", "reset", "wipe"]
if any(k in request_lower for k in destructive_keywords):
score += 1
signals.append("Reversibility concern")
@@ -275,6 +497,7 @@ def assess_risk(request: str, repo_path: str = "/mnt/workdrive/rune") -> tuple[i
# MAIN SCORING
# ═══════════════════════════════════════════════════════════════════════════════
def score_request(request: str, repo_path: str = "/mnt/workdrive/rune") -> ScoreBreakdown:
"""Score a request for complexity. Returns ScoreBreakdown with total and mode."""
breakdown = ScoreBreakdown()
@@ -300,8 +523,12 @@ def score_request(request: str, repo_path: str = "/mnt/workdrive/rune") -> Score
breakdown.signals.extend(risk_signals)
# Calculate total
breakdown.total = (breakdown.request_analysis + breakdown.codebase_impact +
breakdown.historical_patterns + breakdown.risk_assessment)
breakdown.total = (
breakdown.request_analysis
+ breakdown.codebase_impact
+ breakdown.historical_patterns
+ breakdown.risk_assessment
)
# Determine mode
if breakdown.total >= 8:
@@ -349,6 +576,7 @@ def get_score(request: str, repo_path: str = "/mnt/workdrive/rune") -> Dict[str,
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
if len(sys.argv) < 3:
print("Usage: complexity_scorer.py [score|mode] <request>")
+1 -1
View File
@@ -140,5 +140,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["connection_pool"],
"priority": 1,
"auto_activate": True
"auto_activate": True,
}
+21 -16
View File
@@ -47,9 +47,7 @@ def measure_pressure(text, max_tokens=128000):
"pressure": round(pressure, 3),
"status": "ok" if pressure < 0.7 else "warning" if pressure < 0.9 else "critical",
"recommendation": (
"none" if pressure < 0.7 else
"summarize_old_turns" if pressure < 0.9 else
"truncate_and_summarize"
"none" if pressure < 0.7 else "summarize_old_turns" if pressure < 0.9 else "truncate_and_summarize"
),
}
@@ -79,7 +77,7 @@ def detect_degradation(recent_responses):
# Signal 2: Repetition (unique phrases vs total)
recent_text = " ".join(recent_responses[-3:])
words = re.findall(r'\w+', recent_text.lower())
words = re.findall(r"\w+", recent_text.lower())
if words:
unique_ratio = len(set(words)) / len(words)
repetition_score = max(0, 1.0 - unique_ratio * 2) # High repetition = low unique ratio
@@ -91,11 +89,7 @@ def detect_degradation(recent_responses):
generic_ratio = generic_count / min(3, len(recent_responses))
# Composite degradation score
degradation = max(
length_decline * 0.4,
repetition_score * 0.3,
generic_ratio * 0.3
)
degradation = max(length_decline * 0.4, repetition_score * 0.3, generic_ratio * 0.3)
signals = []
if length_decline > 0.3:
@@ -131,6 +125,7 @@ def read_log(limit=20):
if not os.path.exists(LOG_PATH):
return []
from collections import deque
try:
with open(LOG_PATH) as f:
tail = deque(f, maxlen=limit)
@@ -196,6 +191,7 @@ def handle_hook(event, data=None):
def main():
import argparse
ap = argparse.ArgumentParser(description="Context pressure monitor")
ap.add_argument("cmd", choices=["measure", "degrade", "log", "stats", "hook"])
ap.add_argument("arg", nargs="?", default="")
@@ -208,14 +204,20 @@ def main():
if args.cmd == "measure":
result = measure_pressure(args.arg, max_tokens=args.max_tokens)
print(json.dumps(result, indent=2) if args.json else
f"Pressure: {result['pressure']:.1%} ({result['status']}) — {result['recommendation']}")
print(
json.dumps(result, indent=2)
if args.json
else f"Pressure: {result['pressure']:.1%} ({result['status']}) — {result['recommendation']}"
)
elif args.cmd == "degrade":
responses = [r.strip() for r in args.arg.split("|") if r.strip()]
result = detect_degradation(responses)
print(json.dumps(result, indent=2) if args.json else
f"Degradation: {result['degradation']:.1%} — signals: {', '.join(result.get('signals', []))}")
print(
json.dumps(result, indent=2)
if args.json
else f"Degradation: {result['degradation']:.1%} — signals: {', '.join(result.get('signals', []))}"
)
elif args.cmd == "log":
entries = read_log(limit=args.limit)
@@ -230,8 +232,11 @@ def main():
elif args.cmd == "stats":
s = log_stats()
print(json.dumps(s, indent=2) if args.json else
f"Total: {s['total']}, Warnings: {s['warnings']}, Criticals: {s['criticals']}")
print(
json.dumps(s, indent=2)
if args.json
else f"Total: {s['total']}, Warnings: {s['warnings']}, Criticals: {s['criticals']}"
)
elif args.cmd == "hook":
event = args.event or "unknown"
@@ -253,5 +258,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["context_pressure"],
"priority": 2,
"auto_activate": True
"auto_activate": True,
}
+203 -147
View File
@@ -20,9 +20,11 @@ sys.path.insert(0, SCRIPT_DIR)
# CONTRACT DEFINITION
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass
class Contract:
"""A function contract: call this, expect this shape."""
name: str
module: str
function: str
@@ -44,320 +46,367 @@ CONTRACTS = [
# Memory store
Contract(
name="memory_store.connect returns connection",
module="memory_store", function="connect",
args=(), kwargs={},
module="memory_store",
function="connect",
args=(),
kwargs={},
expect_not_none=True,
),
Contract(
name="memory_store.connect has row_factory",
module="memory_store", function="connect",
args=(), kwargs={},
module="memory_store",
function="connect",
args=(),
kwargs={},
expect_callable=True, # Just verify it doesn't crash
),
# Warm router
Contract(
name="warm_router.route returns tuple",
module="warm_router", function="route",
args=("test query",), kwargs={},
module="warm_router",
function="route",
args=("test query",),
kwargs={},
expect_type=tuple,
expect_len=3, # (fired, total, scores)
),
# Model connect
Contract(
name="model_connect.list_models returns list",
module="model_connect", function="list_models",
args=(), kwargs={},
module="model_connect",
function="list_models",
args=(),
kwargs={},
expect_type=list,
),
Contract(
name="model_connect.MODEL_CATEGORIES is dict",
module="model_connect", function="MODEL_CATEGORIES",
args=(), kwargs={},
module="model_connect",
function="MODEL_CATEGORIES",
args=(),
kwargs={},
expect_type=dict,
),
# Memory env
Contract(
name="memory_env.store_db returns string",
module="memory_env", function="store_db",
args=(), kwargs={},
module="memory_env",
function="store_db",
args=(),
kwargs={},
expect_type=str,
expect_not_none=True,
),
Contract(
name="memory_env.memory_dir returns string",
module="memory_env", function="memory_dir",
args=(), kwargs={},
module="memory_env",
function="memory_dir",
args=(),
kwargs={},
expect_type=str,
expect_not_none=True,
),
# Task persist
Contract(
name="task_persist._load returns dict",
module="task_persist", function="_load",
args=(), kwargs={},
module="task_persist",
function="_load",
args=(),
kwargs={},
expect_type=dict,
),
# Event bus
Contract(
name="event_bus.stats returns dict or None",
module="event_bus", function="stats",
args=(), kwargs={},
module="event_bus",
function="stats",
args=(),
kwargs={},
),
# Claim audit
Contract(
name="claim_audit.report returns string or None",
module="claim_audit", function="report",
args=(), kwargs={},
module="claim_audit",
function="report",
args=(),
kwargs={},
),
# Identity anchors
Contract(
name="identity_anchors.get_identity_prompt returns string",
module="identity_anchors", function="get_identity_prompt",
args=(), kwargs={},
module="identity_anchors",
function="get_identity_prompt",
args=(),
kwargs={},
expect_type=str,
expect_not_none=True,
),
# Response adapter
Contract(
name="response_adapter.parse_qwen_response returns RuneResponse",
module="response_adapter", function="parse_qwen_response",
args=({"content": "test", "model": "test"},), kwargs={},
module="response_adapter",
function="parse_qwen_response",
args=({"content": "test", "model": "test"},),
kwargs={},
expect_not_none=True,
),
# Provider budget
Contract(
name="provider_budget.get_status returns dict",
module="provider_budget", function="get_status",
args=(), kwargs={},
module="provider_budget",
function="get_status",
args=(),
kwargs={},
expect_type=dict,
),
# Time awareness
Contract(
name="time_awareness.get_status returns dict",
module="time_awareness", function="get_status",
args=(), kwargs={},
module="time_awareness",
function="get_status",
args=(),
kwargs={},
expect_type=dict,
),
# Token optimizer
Contract(
name="token_optimizer.get_status returns dict",
module="token_optimizer", function="get_status",
args=(), kwargs={},
module="token_optimizer",
function="get_status",
args=(),
kwargs={},
expect_type=dict,
),
# Night mode
Contract(
name="night_mode.get_status returns dict",
module="night_mode", function="get_status",
args=(), kwargs={},
module="night_mode",
function="get_status",
args=(),
kwargs={},
expect_type=dict,
),
# Goal
Contract(
name="goal.check_goal returns dict",
module="goal", function="check_goal",
args=(), kwargs={},
module="goal",
function="check_goal",
args=(),
kwargs={},
expect_type=dict,
),
# Safety override
Contract(
name="safety_override.SafetyOverride instantiates",
module="safety_override", function="SafetyOverride",
args=(), kwargs={},
module="safety_override",
function="SafetyOverride",
args=(),
kwargs={},
expect_not_none=True,
),
# Parallel processor
Contract(
name="parallel_processor.ParallelProcessor instantiates",
module="parallel_processor", function="ParallelProcessor",
args=(), kwargs={},
module="parallel_processor",
function="ParallelProcessor",
args=(),
kwargs={},
expect_not_none=True,
),
# Chat bubble
Contract(
name="chat_bubble.MessageBox instantiates",
module="chat_bubble", function="MessageBox",
args=("user", "test"), kwargs={},
module="chat_bubble",
function="MessageBox",
args=("user", "test"),
kwargs={},
expect_not_none=True,
),
# ── Extended contracts (workflow audit additions) ──────────────────
# Memory store — core functions
Contract(
name="memory_store.connect returns connection",
module="memory_store", function="connect",
args=(), kwargs={},
module="memory_store",
function="connect",
args=(),
kwargs={},
expect_not_none=True,
),
# Model connect — routing
Contract(
name="model_connect.route_model returns dict",
module="model_connect", function="route_model",
args=("conversation",), kwargs={},
module="model_connect",
function="route_model",
args=("conversation",),
kwargs={},
expect_type=dict,
),
Contract(
name="model_connect.MODEL_CAPABILITIES is dict",
module="model_connect", function="MODEL_CAPABILITIES",
args=(), kwargs={},
module="model_connect",
function="MODEL_CAPABILITIES",
args=(),
kwargs={},
expect_type=dict,
),
# Abstraction
Contract(
name="abstraction.pipeline_stats returns dict or None",
module="abstraction", function="pipeline_stats",
args=(), kwargs={},
module="abstraction",
function="pipeline_stats",
args=(),
kwargs={},
),
# Graph memory
Contract(
name="graph_memory.connect returns connection",
module="graph_memory", function="connect",
args=(), kwargs={},
module="graph_memory",
function="connect",
args=(),
kwargs={},
expect_not_none=True,
),
Contract(
name="graph_memory.stats callable",
module="graph_memory", function="stats",
args=(), kwargs={},
module="graph_memory",
function="stats",
args=(),
kwargs={},
expect_callable=True,
),
# Persona gate
Contract(
name="persona_gate.PERMISSIVE_MODE is True",
module="persona_gate", function="PERMISSIVE_MODE",
args=(), kwargs={},
module="persona_gate",
function="PERMISSIVE_MODE",
args=(),
kwargs={},
),
# Voice hybrid
Contract(
name="voice_hybrid.blend returns dict or None",
module="voice_hybrid", function="blend",
args=("test input",), kwargs={},
module="voice_hybrid",
function="blend",
args=("test input",),
kwargs={},
),
# Claim audit
Contract(
name="claim_audit.scan returns list",
module="claim_audit", function="scan",
args=("test text",), kwargs={},
module="claim_audit",
function="scan",
args=("test text",),
kwargs={},
expect_type=list,
),
# Drift ledger
Contract(
name="drift_ledger.check returns dict or None",
module="drift_ledger", function="check",
args=(), kwargs={"strict": False, "autofix": False},
module="drift_ledger",
function="check",
args=(),
kwargs={"strict": False, "autofix": False},
),
# Growth delta
Contract(
name="growth_delta.recent returns list",
module="growth_delta", function="recent",
args=(), kwargs={},
module="growth_delta",
function="recent",
args=(),
kwargs={},
expect_type=list,
),
# Self improve
Contract(
name="self_improve.analyze_system returns list",
module="self_improve", function="analyze_system",
args=(), kwargs={},
module="self_improve",
function="analyze_system",
args=(),
kwargs={},
expect_type=list,
),
# Context pressure
Contract(
name="context_pressure.measure_pressure returns dict or None",
module="context_pressure", function="measure_pressure",
args=("test text for pressure measurement",), kwargs={},
module="context_pressure",
function="measure_pressure",
args=("test text for pressure measurement",),
kwargs={},
),
# Behavior
Contract(
name="behavior.match_context returns dict",
module="behavior", function="match_context",
args=("test context",), kwargs={},
module="behavior",
function="match_context",
args=("test context",),
kwargs={},
expect_type=dict,
),
# Witness
Contract(
name="witness.detect_signals returns dict",
module="witness", function="detect_signals",
args=(), kwargs={"message": "test message"},
module="witness",
function="detect_signals",
args=(),
kwargs={"message": "test message"},
expect_type=dict,
),
# Elastic cognition
Contract(
name="elastic_cognition.classify_salience returns object",
module="elastic_cognition", function="classify_salience",
args=("test input", "chat"), kwargs={},
module="elastic_cognition",
function="classify_salience",
args=("test input", "chat"),
kwargs={},
expect_not_none=True,
),
# Cache
Contract(
name="cache.get_cache returns dict or None",
module="cache", function="get_cache",
args=("test_key",), kwargs={},
module="cache",
function="get_cache",
args=("test_key",),
kwargs={},
),
# Hooks
Contract(
name="hook_engine.generate_report returns str or None",
module="hook_engine", function="generate_report",
args=(), kwargs={},
module="hook_engine",
function="generate_report",
args=(),
kwargs={},
),
# Microkernel
Contract(
name="microkernel.status returns dict or None",
module="microkernel", function="status",
args=(), kwargs={},
module="microkernel",
function="status",
args=(),
kwargs={},
),
# Sleep time
Contract(
name="sleep_time.reflect_local returns dict or None",
module="sleep_time", function="reflect_local",
args=(), kwargs={},
module="sleep_time",
function="reflect_local",
args=(),
kwargs={},
),
# Auto context
Contract(
name="auto_context.build_auto_context returns list or None",
module="auto_context", function="build_auto_context",
args=("test query",), kwargs={},
module="auto_context",
function="build_auto_context",
args=("test query",),
kwargs={},
),
# Auto tasks
Contract(
name="auto_tasks.extract_tasks returns list or None",
module="auto_tasks", function="extract_tasks",
args=("test text", "chat"), kwargs={},
module="auto_tasks",
function="extract_tasks",
args=("test text", "chat"),
kwargs={},
),
]
@@ -366,9 +415,11 @@ CONTRACTS = [
# CONTRACT RUNNER
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass
class ContractResult:
"""Result of running a contract."""
name: str
passed: bool
error: str = ""
@@ -384,16 +435,15 @@ def run_contract(contract: Contract) -> ContractResult:
mod = __import__(contract.module)
except ImportError as e:
if contract.skip_on_import_error:
return ContractResult(name=contract.name, passed=True,
error=f"skipped (import error: {e})")
return ContractResult(name=contract.name, passed=False,
error=f"import failed: {e}")
return ContractResult(name=contract.name, passed=True, error=f"skipped (import error: {e})")
return ContractResult(name=contract.name, passed=False, error=f"import failed: {e}")
# Get function/attribute
func = getattr(mod, contract.function, None)
if func is None:
return ContractResult(name=contract.name, passed=False,
error=f"{contract.function} not found in {contract.module}")
return ContractResult(
name=contract.name, passed=False, error=f"{contract.function} not found in {contract.module}"
)
# Call if callable
if callable(func) and not contract.expect_callable:
@@ -402,44 +452,48 @@ def run_contract(contract: Contract) -> ContractResult:
# Just verify it's callable
if callable(func):
return ContractResult(name=contract.name, passed=True)
return ContractResult(name=contract.name, passed=False,
error=f"{contract.function} is not callable")
return ContractResult(name=contract.name, passed=False, error=f"{contract.function} is not callable")
else:
result = func
# Check expectations
if contract.expect_not_none and result is None:
return ContractResult(name=contract.name, passed=False,
error="returned None", actual_type="NoneType")
return ContractResult(name=contract.name, passed=False, error="returned None", actual_type="NoneType")
if contract.expect_type and not isinstance(result, contract.expect_type):
return ContractResult(name=contract.name, passed=False,
error=f"expected {contract.expect_type.__name__}, got {type(result).__name__}",
actual_type=type(result).__name__)
return ContractResult(
name=contract.name,
passed=False,
error=f"expected {contract.expect_type.__name__}, got {type(result).__name__}",
actual_type=type(result).__name__,
)
if contract.expect_keys and isinstance(result, dict):
missing = [k for k in contract.expect_keys if k not in result]
if missing:
return ContractResult(name=contract.name, passed=False,
error=f"missing keys: {missing}",
actual_value=str(list(result.keys())[:10]))
return ContractResult(
name=contract.name,
passed=False,
error=f"missing keys: {missing}",
actual_value=str(list(result.keys())[:10]),
)
if contract.expect_len is not None:
if hasattr(result, '__len__'):
if hasattr(result, "__len__"):
if len(result) != contract.expect_len:
return ContractResult(name=contract.name, passed=False,
error=f"expected len {contract.expect_len}, got {len(result)}",
actual_value=f"len={len(result)}")
return ContractResult(
name=contract.name,
passed=False,
error=f"expected len {contract.expect_len}, got {len(result)}",
actual_value=f"len={len(result)}",
)
else:
return ContractResult(name=contract.name, passed=False,
error="result has no __len__")
return ContractResult(name=contract.name, passed=False, error="result has no __len__")
return ContractResult(name=contract.name, passed=True,
actual_type=type(result).__name__)
return ContractResult(name=contract.name, passed=True, actual_type=type(result).__name__)
except Exception as e:
return ContractResult(name=contract.name, passed=False,
error=f"{type(e).__name__}: {e}")
return ContractResult(name=contract.name, passed=False, error=f"{type(e).__name__}: {e}")
def run_all_contracts() -> list[ContractResult]:
@@ -461,6 +515,7 @@ SIGNATURES_FILE = os.path.expanduser("~/.rune/function_signatures.json")
def capture_signatures() -> dict:
"""Capture current function signatures for all contracted functions."""
import inspect
sigs = {}
for contract in CONTRACTS:
try:
@@ -510,6 +565,7 @@ def check_signature_drift() -> list[str]:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
if len(sys.argv) < 2:
print("Usage: contracts.py [test|signatures|drift]")
+9 -24
View File
@@ -26,6 +26,7 @@ from datetime import datetime, timezone
@dataclass
class CronTask:
"""A scheduled task."""
name: str
cron_expression: str # e.g., "*/5 * * * *" (every 5 minutes)
command: str
@@ -79,8 +80,7 @@ class CronEngine:
with open(self.storage_path, "w") as f:
json.dump(data, f, indent=2)
def add_task(self, name: str, cron_expression: str, command: str,
description: str = "") -> CronTask:
def add_task(self, name: str, cron_expression: str, command: str, description: str = "") -> CronTask:
"""Add a scheduled task."""
task = CronTask(
name=name,
@@ -147,13 +147,7 @@ class CronEngine:
try:
# Execute the command safely (no shell injection)
result = subprocess.run(
shlex.split(task.command),
shell=False,
capture_output=True,
text=True,
timeout=60
)
result = subprocess.run(shlex.split(task.command), shell=False, capture_output=True, text=True, timeout=60)
# Update task
task.last_run = datetime.now(timezone.utc).isoformat()
@@ -282,13 +276,7 @@ class CronEngine:
# Record the minute we're running in to prevent double-fire
self._last_run_minute[task.name] = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M")
result = subprocess.run(
shlex.split(task.command),
shell=False,
capture_output=True,
text=True,
timeout=60
)
result = subprocess.run(shlex.split(task.command), shell=False, capture_output=True, text=True, timeout=60)
task.last_run = datetime.now(timezone.utc).isoformat()
task.run_count += 1
@@ -309,14 +297,13 @@ class CronEngine:
if os.path.exists(log_path) and os.path.getsize(log_path) > 5 * 1024 * 1024:
for i in range(4, 0, -1):
src = f"{log_path}.{i}"
dst = f"{log_path}.{i+1}"
dst = f"{log_path}.{i + 1}"
if os.path.exists(src):
os.rename(src, dst)
os.rename(log_path, f"{log_path}.1")
with open(log_path, "a") as f:
f.write(f"[{datetime.now(timezone.utc).isoformat()}] {task.name}: "
f"returncode={result.returncode}\n")
f.write(f"[{datetime.now(timezone.utc).isoformat()}] {task.name}: returncode={result.returncode}\n")
def _log_error(self, task: CronTask, error: str):
"""Log task error with simple rotation."""
@@ -327,14 +314,13 @@ class CronEngine:
if os.path.exists(log_path) and os.path.getsize(log_path) > 5 * 1024 * 1024:
for i in range(4, 0, -1):
src = f"{log_path}.{i}"
dst = f"{log_path}.{i+1}"
dst = f"{log_path}.{i + 1}"
if os.path.exists(src):
os.rename(src, dst)
os.rename(log_path, f"{log_path}.1")
with open(log_path, "a") as f:
f.write(f"[{datetime.now(timezone.utc).isoformat()}] {task.name}: "
f"ERROR: {error}\n")
f.write(f"[{datetime.now(timezone.utc).isoformat()}] {task.name}: ERROR: {error}\n")
# Global cron engine
@@ -354,8 +340,7 @@ def list_tasks() -> List[Dict[str, Any]]:
return get_cron_engine().list_tasks()
def add_task(name: str, cron_expression: str, command: str,
description: str = "") -> Dict[str, Any]:
def add_task(name: str, cron_expression: str, command: str, description: str = "") -> Dict[str, Any]:
"""Add a scheduled task."""
task = get_cron_engine().add_task(name, cron_expression, command, description)
return task.to_dict()
+568 -342
View File
File diff suppressed because it is too large. Load diff
+42 -73
View File
@@ -28,81 +28,46 @@ DEPENDENCY_MAP_FILE = os.path.expanduser("~/.config/opencode/memory/dependency_m
DEPENDENCY_GRAPH = {
# MEMORY umbrella
"memory_store.py": [
"warm_router.py", "curator.py", "memory_consolidation.py",
"drift_core.py", "drift_cache.py", "tier_manager.py", "shard_manager.py",
"query_memory.py", "growth_simulator.py"
"warm_router.py",
"curator.py",
"memory_consolidation.py",
"drift_core.py",
"drift_cache.py",
"tier_manager.py",
"shard_manager.py",
"query_memory.py",
"growth_simulator.py",
],
"memory_consolidation.py": [
"memory_store.py", "drift_compression.py", "curator.py"
],
"warm_router.py": [
"capability_broker.py", "memory_consolidation.py", "memory_store.py"
],
"graph_memory.py": [
"warm_router.py", "memory_consolidation.py", "shard_manager.py"
],
"shard_manager.py": [
"memory_store.py", "tier_manager.py"
],
"tier_manager.py": [
"memory_store.py", "drift_cache.py"
],
"query_memory.py": [
"memory_store.py", "growth_simulator.py"
],
"memory_consolidation.py": ["memory_store.py", "drift_compression.py", "curator.py"],
"warm_router.py": ["capability_broker.py", "memory_consolidation.py", "memory_store.py"],
"graph_memory.py": ["warm_router.py", "memory_consolidation.py", "shard_manager.py"],
"shard_manager.py": ["memory_store.py", "tier_manager.py"],
"tier_manager.py": ["memory_store.py", "drift_cache.py"],
"query_memory.py": ["memory_store.py", "growth_simulator.py"],
# GOVERNANCE umbrella
"drift_core.py": [
"curator.py", "audit_ledger.py", "memory_store.py",
"drift_observability.py", "drift_cache.py"
],
"curator.py": [
"memory_store.py", "drift_core.py", "audit_ledger.py"
],
"drift_compression.py": [
"memory_consolidation.py", "curator.py"
],
"drift_observability.py": [
"drift_core.py", "audit_ledger.py"
],
"audit_ledger.py": [
"curator.py", "drift_core.py"
],
"drift_core.py": ["curator.py", "audit_ledger.py", "memory_store.py", "drift_observability.py", "drift_cache.py"],
"curator.py": ["memory_store.py", "drift_core.py", "audit_ledger.py"],
"drift_compression.py": ["memory_consolidation.py", "curator.py"],
"drift_observability.py": ["drift_core.py", "audit_ledger.py"],
"audit_ledger.py": ["curator.py", "drift_core.py"],
# IDENTITY umbrella
"persona_definition.py": [
"persona_check.py", "identity_anchors.py", "drift_observability.py"
],
"identity_anchors.py": [
"persona_definition.py", "drift_core.py"
],
"voice_hybrid.py": [
"delivery_adapter.py", "persona_definition.py"
],
"persona_definition.py": ["persona_check.py", "identity_anchors.py", "drift_observability.py"],
"identity_anchors.py": ["persona_definition.py", "drift_core.py"],
"voice_hybrid.py": ["delivery_adapter.py", "persona_definition.py"],
# GROWTH umbrella
"growth_delta.py": [
"memory_store.py", "curator.py", "lessons.py"
],
"growth_simulator.py": [
"memory_store.py", "query_memory.py", "drift_core.py"
],
"growth_delta.py": ["memory_store.py", "curator.py", "lessons.py"],
"growth_simulator.py": ["memory_store.py", "query_memory.py", "drift_core.py"],
# PERCEPTION umbrella
"warm_router.py": [
"capability_broker.py", "memory_consolidation.py"
],
"warm_router.py": ["capability_broker.py", "memory_consolidation.py"],
# INFRASTRUCTURE
"microkernel.py": [
"hook_engine.py", "event_bus.py"
],
"microkernel.py": ["hook_engine.py", "event_bus.py"],
}
# ═══════════════════════════════════════════════════════════════════════════════
# PROPAGATION ACTIONS
# ═══════════════════════════════════════════════════════════════════════════════
def get_dependents(changed_file: str) -> List[str]:
"""Get files that depend on the changed file."""
basename = os.path.basename(changed_file)
@@ -149,11 +114,13 @@ def propagate(changed_file: str, dry_run: bool = False) -> dict:
changed_basename = os.path.basename(changed_file).replace(".py", "")
if changed_basename in content:
results["propagated"].append({
"file": dep,
"action": "references changed file",
"detail": f"imports or references {changed_basename}"
})
results["propagated"].append(
{
"file": dep,
"action": "references changed file",
"detail": f"imports or references {changed_basename}",
}
)
else:
results["skipped"].append({"file": dep, "reason": "no direct reference"})
except Exception as e:
@@ -171,8 +138,10 @@ def get_full_map() -> dict:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Dependency tracker for umbrella systems")
sub = parser.add_subparsers(dest="cmd")
@@ -195,21 +164,21 @@ def main():
result = check_propagation(args.file)
print(f"File: {result['changed_file']}")
print(f"Dependents ({result['count']}):")
for dep in result['dependents']:
for dep in result["dependents"]:
print(f" → {dep}")
elif args.cmd == "propagate":
result = propagate(args.file, dry_run=args.dry_run)
print(f"Propagation for: {result['changed_file']}")
print(f"Propagated ({len(result['propagated'])}):")
for p in result['propagated']:
for p in result["propagated"]:
print(f" ✓ {p['file']}: {p['action']}")
print(f"Skipped ({len(result['skipped'])}):")
for s in result['skipped']:
for s in result["skipped"]:
print(f" ⊘ {s['file']}: {s['reason']}")
if result['errors']:
if result["errors"]:
print(f"Errors ({len(result['errors'])}):")
for e in result['errors']:
for e in result["errors"]:
print(f" ✗ {e['file']}: {e['error']}")
elif args.cmd == "map":
+21 -15
View File
@@ -33,10 +33,10 @@ CACHE_DIR = os.path.join(memory_env.memory_dir(), "cache")
CACHE_FILE = os.path.join(CACHE_DIR, "drift_cache.json")
# Cache configuration
MAX_CACHE_SIZE = 200 # maximum entries in cache
EVICTION_BATCH = 20 # entries to evict when cache is full
MIN_WASH_FOR_CACHE = 1 # minimum wash_count to enter cache
CACHE_BOOST_ROCK = 2.0 # multiplier for rock-status entries
MAX_CACHE_SIZE = 200 # maximum entries in cache
EVICTION_BATCH = 20 # entries to evict when cache is full
MIN_WASH_FOR_CACHE = 1 # minimum wash_count to enter cache
CACHE_BOOST_ROCK = 2.0 # multiplier for rock-status entries
CACHE_BOOST_ESTABLISHED = 1.5 # multiplier for established entries
@@ -113,8 +113,7 @@ class DriftCache:
"""Record a cache access (reinforces the entry)."""
if entry_id in self.entries:
self.entries[entry_id]["last_accessed"] = datetime.now(timezone.utc).isoformat()
self.entries[entry_id]["access_count"] = \
self.entries[entry_id].get("access_count", 0) + 1
self.entries[entry_id]["access_count"] = self.entries[entry_id].get("access_count", 0) + 1
self._save()
def contains(self, entry_id: int) -> bool:
@@ -130,10 +129,7 @@ class DriftCache:
def _evict(self):
"""Evict lowest-priority entries."""
# Sort by priority (ascending), evict lowest
sorted_entries = sorted(
self.entries.items(),
key=lambda x: x[1].get("priority", 0.0)
)
sorted_entries = sorted(self.entries.items(), key=lambda x: x[1].get("priority", 0.0))
to_evict = sorted_entries[:EVICTION_BATCH]
for entry_id, _ in to_evict:
del self.entries[entry_id]
@@ -142,8 +138,7 @@ class DriftCache:
"""Sync cache with memory store — update priorities from current wash counts."""
for entry_id in list(self.entries.keys()):
row = db.execute(
"SELECT wash_count, importance, rock_status FROM entries WHERE id = ?",
(entry_id,)
"SELECT wash_count, importance, rock_status FROM entries WHERE id = ?", (entry_id,)
).fetchone()
if row:
entry = {
@@ -162,7 +157,15 @@ class DriftCache:
def get_stats(self) -> dict:
"""Get cache statistics."""
if not self.entries:
return {"size": 0, "avg_priority": 0.0, "max_priority": 0.0, "min_priority": 0.0, "rock_count": 0, "established_count": 0, "washable_count": 0}
return {
"size": 0,
"avg_priority": 0.0,
"max_priority": 0.0,
"min_priority": 0.0,
"rock_count": 0,
"established_count": 0,
"washable_count": 0,
}
priorities = [e.get("priority", 0.0) for e in self.entries.values()]
rock_count = sum(1 for e in self.entries.values() if e.get("rock_status") == "rock")
@@ -183,8 +186,10 @@ class DriftCache:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Drift-aware cache")
sub = parser.add_subparsers(dest="cmd")
@@ -218,6 +223,7 @@ def main():
elif args.cmd == "sync":
sys.path.insert(0, SCRIPTS_DIR)
import memory_store
db = memory_store.connect()
cache.update_from_store(db)
db.close()
@@ -227,10 +233,10 @@ def main():
elif args.cmd == "add":
sys.path.insert(0, SCRIPTS_DIR)
import memory_store
db = memory_store.connect()
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status FROM entries WHERE id = ?",
(args.entry_id,)
"SELECT id, text, importance, wash_count, rock_status FROM entries WHERE id = ?", (args.entry_id,)
).fetchone()
if row:
entry = {
+10 -11
View File
@@ -26,13 +26,13 @@ if _SD not in sys.path:
sys.path.insert(0, _SD)
# Compression ratios by rock_status
COMPRESSION_ROCK = 0.0 # no compression
COMPRESSION_ESTABLISHED = 0.2 # 20% compression
COMPRESSION_WASHABLE = 0.5 # 50% compression (default)
COMPRESSION_ROCK = 0.0 # no compression
COMPRESSION_ESTABLISHED = 0.2 # 20% compression
COMPRESSION_WASHABLE = 0.5 # 50% compression (default)
# Wash count modifiers (reduce compression as wash_count increases)
WASH_THRESHOLD_FULL = 20 # above this: minimal compression
WASH_THRESHOLD_PARTIAL = 10 # above this: reduced compression
WASH_THRESHOLD_FULL = 20 # above this: minimal compression
WASH_THRESHOLD_PARTIAL = 10 # above this: reduced compression
def get_compression_ratio(wash_count: int = 0, rock_status: str = "washable") -> float:
@@ -194,8 +194,10 @@ def get_compression_stats(db) -> dict:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Drift-aware compression")
sub = parser.add_subparsers(dest="cmd")
@@ -217,13 +219,11 @@ def main():
sys.exit(1)
import memory_store
db = memory_store.connect()
if args.cmd == "ratio":
row = db.execute(
"SELECT wash_count, rock_status FROM entries WHERE id = ?",
(args.entry_id,)
).fetchone()
row = db.execute("SELECT wash_count, rock_status FROM entries WHERE id = ?", (args.entry_id,)).fetchone()
if row:
ratio = get_compression_ratio(row[0] or 0, row[1] or "washable")
print(f"Entry {args.entry_id}: compression_ratio={ratio:.2f}")
@@ -242,8 +242,7 @@ def main():
elif args.cmd == "compress":
row = db.execute(
"SELECT id, text, wash_count, rock_status FROM entries WHERE id = ?",
(args.entry_id,)
"SELECT id, text, wash_count, rock_status FROM entries WHERE id = ?", (args.entry_id,)
).fetchone()
if row:
entry = {"text": row[1], "wash_count": row[2] or 0, "rock_status": row[3] or "washable"}
+45 -49
View File
@@ -45,24 +45,25 @@ import memory_env
# Trust level lifecycle: provisional → validated → locked
# An entry becomes validated after PROVEN_USES_THRESHOLD proven accesses.
# An entry becomes locked after passing the promotion gate twice.
PROVEN_USES_THRESHOLD = 20 # proven accesses before validation eligible
RECENCY_HALF_LIFE_DAYS = 30.0 # recency decay half-life
RELIABILITY_WINDOW = 50 # recent accesses for reliability calc
PROVEN_USES_THRESHOLD = 20 # proven accesses before validation eligible
RECENCY_HALF_LIFE_DAYS = 30.0 # recency decay half-life
RELIABILITY_WINDOW = 50 # recent accesses for reliability calc
STATE_EVOLUTION_VELOCITY_THRESHOLD = 10 # changes per day before alert
STATE_EVOLUTION_DIRECTION_WINDOW = 7 # days for direction calculation
STATE_EVOLUTION_DIRECTION_WINDOW = 7 # days for direction calculation
# Multi-factor weights (from Learning What to Remember, arXiv:2606.12945)
WEIGHT_PROVEN_USES = 0.30 # survival count (how many times proven useful)
WEIGHT_IMPORTANCE = 0.25 # assigned importance
WEIGHT_RECENCY = 0.20 # time since last access
WEIGHT_RELIABILITY = 0.15 # consistency of access pattern
WEIGHT_GOAL_RELEVANCE = 0.10 # relevance to current goals
WEIGHT_PROVEN_USES = 0.30 # survival count (how many times proven useful)
WEIGHT_IMPORTANCE = 0.25 # assigned importance
WEIGHT_RECENCY = 0.20 # time since last access
WEIGHT_RELIABILITY = 0.15 # consistency of access pattern
WEIGHT_GOAL_RELEVANCE = 0.10 # relevance to current goals
# ═══════════════════════════════════════════════════════════════════════════════
# MULTI-FACTOR VALUE FUNCTION
# ═══════════════════════════════════════════════════════════════════════════════
def compute_value(entry: dict, current_goals: List[str] = None) -> float:
"""Multi-factor value score. Returns 0.0-1.0+.
@@ -109,11 +110,11 @@ def compute_value(entry: dict, current_goals: List[str] = None) -> float:
# Weighted combination
value = (
WEIGHT_PROVEN_USES * proven_factor +
WEIGHT_IMPORTANCE * imp +
WEIGHT_RECENCY * recency +
WEIGHT_RELIABILITY * reliability +
WEIGHT_GOAL_RELEVANCE * goal_relevance
WEIGHT_PROVEN_USES * proven_factor
+ WEIGHT_IMPORTANCE * imp
+ WEIGHT_RECENCY * recency
+ WEIGHT_RELIABILITY * reliability
+ WEIGHT_GOAL_RELEVANCE * goal_relevance
)
return round(value, 4)
@@ -123,6 +124,7 @@ def compute_value(entry: dict, current_goals: List[str] = None) -> float:
# PROMOTION LIFECYCLE
# ═══════════════════════════════════════════════════════════════════════════════
def check_promotion_eligibility(entry: dict) -> Tuple[bool, str]:
"""Check if an entry is eligible for promotion to rock.
@@ -157,8 +159,7 @@ def promote_entry(db, entry_id: int) -> dict:
import memory_store
row = db.execute(
"SELECT id, text, wash_count, importance, rock_status FROM entries WHERE id = ?",
(entry_id,)
"SELECT id, text, wash_count, importance, rock_status FROM entries WHERE id = ?", (entry_id,)
).fetchone()
if not row:
@@ -184,7 +185,7 @@ def promote_entry(db, entry_id: int) -> dict:
db.execute(
"UPDATE entries SET rock_status = ?, promotion_eligible = ? WHERE id = ?",
(new_status, 1 if new_status != "rock" else 0, entry_id)
(new_status, 1 if new_status != "rock" else 0, entry_id),
)
db.commit()
@@ -194,7 +195,7 @@ def promote_entry(db, entry_id: int) -> dict:
"old_status": current_status,
"new_status": new_status,
"wash_count": entry.get("wash_count", 0),
"reason": reason
"reason": reason,
}
@@ -202,6 +203,7 @@ def promote_entry(db, entry_id: int) -> dict:
# FAMA METRIC (Forgetting-Aware Memory Accuracy)
# ═══════════════════════════════════════════════════════════════════════════════
def compute_fama(db, query: str, retrieved_ids: List[int]) -> float:
"""FAMA: Forgetting-Aware Memory Accuracy (arXiv:2604.20006).
@@ -213,10 +215,7 @@ def compute_fama(db, query: str, retrieved_ids: List[int]) -> float:
valid_count = 0
for eid in retrieved_ids:
row = db.execute(
"SELECT status, rock_status, wash_count FROM entries WHERE id = ?",
(eid,)
).fetchone()
row = db.execute("SELECT status, rock_status, wash_count FROM entries WHERE id = ?", (eid,)).fetchone()
if not row:
continue
# Valid if: active status, not obsolete (rock or established preferred)
@@ -235,6 +234,7 @@ def compute_fama(db, query: str, retrieved_ids: List[int]) -> float:
# DRIFT OBSERVABILITY
# ═══════════════════════════════════════════════════════════════════════════════
def compute_drift_velocity(db, days: int = 7) -> dict:
"""Drift velocity: how fast is the system changing?
@@ -246,14 +246,13 @@ def compute_drift_velocity(db, days: int = 7) -> dict:
# Count changes in the window
changes = db.execute(
"SELECT COUNT(*) FROM entries WHERE last_washed > ? AND status = 'active'",
(cutoff,)
"SELECT COUNT(*) FROM entries WHERE last_washed > ? AND status = 'active'", (cutoff,)
).fetchone()[0]
# Count promotions in the window
promotions = db.execute(
"SELECT COUNT(*) FROM entries WHERE rock_status != 'washable' AND last_washed > ? AND status = 'active'",
(cutoff,)
(cutoff,),
).fetchone()[0]
velocity = changes / max(1, days)
@@ -265,7 +264,7 @@ def compute_drift_velocity(db, days: int = 7) -> dict:
"total_changes": changes,
"total_promotions": promotions,
"window_days": days,
"alert": velocity > STATE_EVOLUTION_VELOCITY_THRESHOLD
"alert": velocity > STATE_EVOLUTION_VELOCITY_THRESHOLD,
}
@@ -276,17 +275,13 @@ def compute_drift_direction(db) -> dict:
Increasing ratio = positive drift (toward stability).
Decreasing ratio = negative drift (away from stability).
"""
total = db.execute(
"SELECT COUNT(*) FROM entries WHERE status = 'active'"
).fetchone()[0]
total = db.execute("SELECT COUNT(*) FROM entries WHERE status = 'active'").fetchone()[0]
established = db.execute(
"SELECT COUNT(*) FROM entries WHERE rock_status IN ('established', 'rock') AND status = 'active'"
).fetchone()[0]
rock = db.execute(
"SELECT COUNT(*) FROM entries WHERE rock_status = 'rock' AND status = 'active'"
).fetchone()[0]
rock = db.execute("SELECT COUNT(*) FROM entries WHERE rock_status = 'rock' AND status = 'active'").fetchone()[0]
stability_ratio = established / max(1, total)
rock_ratio = rock / max(1, total)
@@ -297,7 +292,7 @@ def compute_drift_direction(db) -> dict:
"rock_count": rock,
"stability_ratio": round(stability_ratio, 4),
"rock_ratio": round(rock_ratio, 4),
"direction": "stable" if stability_ratio > 0.3 else "drifting"
"direction": "stable" if stability_ratio > 0.3 else "drifting",
}
@@ -305,6 +300,7 @@ def compute_drift_direction(db) -> dict:
# PRE-COMMIT GATE (Three Critics)
# ═══════════════════════════════════════════════════════════════════════════════
def pre_commit_gate(db, entry_id: int) -> dict:
"""Three critics before promotion (VaG, arXiv:2608.05810).
@@ -340,7 +336,7 @@ def pre_commit_gate_entry(entry: dict) -> dict:
is_harmful = any(p in text.lower() for p in harmful_patterns)
critics["behavioral"] = {
"pass": not is_harmful,
"reason": "contains refusal language" if is_harmful else "aligned with core"
"reason": "contains refusal language" if is_harmful else "aligned with core",
}
# Critic 3: Semantic consistency (simplified — no db lookup)
@@ -352,11 +348,10 @@ def pre_commit_gate_entry(entry: dict) -> dict:
return {
"verdict": verdict,
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed"
"reason": "all critics passed" if all_pass else "one or more critics failed",
}
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status, topic FROM entries WHERE id = ?",
(entry_id,)
"SELECT id, text, importance, wash_count, rock_status, topic FROM entries WHERE id = ?", (entry_id,)
).fetchone()
if not row:
@@ -382,25 +377,22 @@ def pre_commit_gate_entry(entry: dict) -> dict:
is_harmful = any(p in text.lower() for p in harmful_patterns)
critics["behavioral"] = {
"pass": not is_harmful,
"reason": "contains refusal language" if is_harmful else "aligned with core"
"reason": "contains refusal language" if is_harmful else "aligned with core",
}
# Critic 3: Semantic consistency (check for contradictions with existing rocks)
rocks = db.execute(
"SELECT text FROM entries WHERE rock_status = 'rock' AND status = 'active' LIMIT 50"
).fetchall()
rocks = db.execute("SELECT text FROM entries WHERE rock_status = 'rock' AND status = 'active' LIMIT 50").fetchall()
contradiction_found = False
for rock in rocks:
rock_text = rock[0].lower()
entry_text = text.lower()
# Simple contradiction detection: if entry says opposite of a rock
if ("always" in rock_text and "never" in entry_text) or \
("never" in rock_text and "always" in entry_text):
if ("always" in rock_text and "never" in entry_text) or ("never" in rock_text and "always" in entry_text):
contradiction_found = True
break
critics["semantic"] = {
"pass": not contradiction_found,
"reason": "contradicts existing rock" if contradiction_found else "consistent"
"reason": "contradicts existing rock" if contradiction_found else "consistent",
}
# Overall verdict
@@ -411,7 +403,7 @@ def pre_commit_gate_entry(entry: dict) -> dict:
"verdict": verdict,
"entry_id": entry_id,
"critics": critics,
"reason": "all critics passed" if all_pass else "one or more critics failed"
"reason": "all critics passed" if all_pass else "one or more critics failed",
}
@@ -419,8 +411,10 @@ def pre_commit_gate_entry(entry: dict) -> dict:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Drift-with-Integrity core engine")
sub = parser.add_subparsers(dest="cmd")
@@ -457,12 +451,13 @@ def main():
sys.exit(1)
import memory_store
db = memory_store.connect()
if args.cmd == "value":
row = db.execute(
"SELECT id, text, importance, wash_count, last_accessed, last_washed, rock_status FROM entries WHERE id = ?",
(args.entry_id,)
(args.entry_id,),
).fetchone()
if not row:
print(f"Entry {args.entry_id} not found")
@@ -476,8 +471,7 @@ def main():
elif args.cmd == "check-promotion":
row = db.execute(
"SELECT id, text, importance, wash_count, rock_status FROM entries WHERE id = ?",
(args.entry_id,)
"SELECT id, text, importance, wash_count, rock_status FROM entries WHERE id = ?", (args.entry_id,)
).fetchone()
if not row:
print(f"Entry {args.entry_id} not found")
@@ -509,7 +503,9 @@ def main():
print(f"Total entries: {direction['total_entries']}")
print(f"Validated (proven): {direction['established_count']}")
print(f"Locked (immutable): {direction['rock_count']}")
print(f"Provisional (testing): {direction['total_entries'] - direction['established_count'] - direction['rock_count']}")
print(
f"Provisional (testing): {direction['total_entries'] - direction['established_count'] - direction['rock_count']}"
)
print(f"Trust ratio: {direction['stability_ratio']:.2%}")
print(f"Evolution direction: {direction['direction']}")
print(f"Evolution velocity: {vel['changes_per_day']} changes/day")
+54 -60
View File
@@ -22,6 +22,7 @@ Usage:
# confirmed, authorised change
Exit 0 = healthy, 1 = drift flagged.
"""
import argparse
import hashlib
import json
@@ -31,7 +32,8 @@ import sys
from datetime import datetime, timezone
_SD = os.path.dirname(os.path.abspath(__file__))
if _SD not in sys.path: sys.path.insert(0, _SD)
if _SD not in sys.path:
sys.path.insert(0, _SD)
import memory_env
logger = logging.getLogger(__name__)
@@ -44,6 +46,8 @@ def ledger_path():
# The ledger lives beside the index, so overriding INDEX_FILE (as the
# curator gate and the canary do for isolation) moves the ledger too.
return os.path.join(os.path.dirname(INDEX_FILE), "drift_ledger.json")
JOURNAL_DIR = os.path.join(memory_env.memory_dir(), "journal")
# Max cumulative byte drift from an anchored/authorised state before we refuse.
@@ -67,10 +71,11 @@ def read_block(label):
"""
try:
import sqlite3
db = sqlite3.connect(f"file:{DB}?mode=ro", uri=True)
rows = db.execute(
"SELECT text FROM entries WHERE topic=? AND always_on=1 "
"AND status='active' ORDER BY priority ASC, id ASC", (label,)
"SELECT text FROM entries WHERE topic=? AND always_on=1 AND status='active' ORDER BY priority ASC, id ASC",
(label,),
).fetchall()
db.close()
return "\n".join(r[0] for r in rows)
@@ -218,20 +223,24 @@ def check(strict=False, autofix=True):
else:
content_changed = False
if content_changed or pct > limit:
alerts.append({
"label": label,
"delta": delta,
"pct": round(pct, 3),
"limit": round(limit, 3),
"base": base,
"changed": diff_blocks(ref_content, cur),
"content_changed": content_changed,
})
alerts.append(
{
"label": label,
"delta": delta,
"pct": round(pct, 3),
"limit": round(limit, 3),
"base": base,
"changed": diff_blocks(ref_content, cur),
"content_changed": content_changed,
}
)
if alerts:
for a in alerts:
msg = (f"{a['label']}: {a['delta']:+d} bytes "
f"({a['pct']*100:.1f}% vs limit {a['limit']*100:.1f}%) "
f"[{a['changed']}]")
msg = (
f"{a['label']}: {a['delta']:+d} bytes "
f"({a['pct'] * 100:.1f}% vs limit {a['limit'] * 100:.1f}%) "
f"[{a['changed']}]"
)
print(f"DRIFT-ALERT {msg}")
# VISIBLE ALERT (audit fix): surface to the TUI status + alert file
# so drift is noticed, not silently journaled.
@@ -245,8 +254,7 @@ def check(strict=False, autofix=True):
auto_result = auto_fix(a["label"], ledger, strict)
print(f" auto-fix: {auto_result['action']} — {auto_result['reason']}")
if strict:
journal(f"{msg}; auto-fix: {auto_result['action']} — "
f"{auto_result['reason']}")
journal(f"{msg}; auto-fix: {auto_result['action']} — {auto_result['reason']}")
else:
print(" (autofix disabled — observe only)")
return 1
@@ -269,29 +277,26 @@ def auto_fix(label, ledger, strict):
Returns {action, reason}.
"""
if label == "constitution":
return {"action": "alert-only",
"reason": "constitution changes only by explicit directive; "
"needs human review"}
return {"action": "alert-only", "reason": "constitution changes only by explicit directive; needs human review"}
if label in ledger.get("anchors", {}):
# Authorised growth: re-anchor to the current (approved) state.
raw = read_block(label)
ledger.setdefault("anchors", {})[label] = {
"sha": _sha(raw), "size": len(raw), "content": raw,
"sha": _sha(raw),
"size": len(raw),
"content": raw,
"reason": "auto re-anchor after authorised growth",
"when": datetime.now(timezone.utc).isoformat(timespec="seconds"),
}
save_ledger(ledger)
return {"action": "re-anchored",
"reason": "anchored block grew (authorised) — re-baselined"}
return {"action": "re-anchored", "reason": "anchored block grew (authorised) — re-baselined"}
# Unauthorised drift: restore to last-good content (new schema — restore
# the store's always-on core entries for this topic).
block_meta = ledger.get("last_snapshot", {}).get("blocks", {}).get(label)
if block_meta and "content" in block_meta:
restore_topic(label, block_meta["content"])
return {"action": "restored",
"reason": "unauthorised drift — restored to last-good content"}
return {"action": "alert-only",
"reason": "no last-good content available to restore"}
return {"action": "restored", "reason": "unauthorised drift — restored to last-good content"}
return {"action": "alert-only", "reason": "no last-good content available to restore"}
def diff_blocks(ref, cur):
@@ -323,14 +328,17 @@ def restore_topic(label, content):
try:
import sqlite3
from datetime import datetime, timezone
db = sqlite3.connect(DB)
try:
db.execute("BEGIN IMMEDIATE")
db.execute("DELETE FROM entries WHERE topic=? AND always_on=1 "
"AND COALESCE(method, '') != 'directive'", (label,))
db.execute(
"DELETE FROM entries WHERE topic=? AND always_on=1 AND COALESCE(method, '') != 'directive'", (label,)
)
ts = datetime.now(timezone.utc).isoformat(timespec="seconds")
priority = {"constitution": 0, "safety": 0, "identity": 1,
"operating": 2, "human": 3, "project": 3}.get(label, 5)
priority = {"constitution": 0, "safety": 0, "identity": 1, "operating": 2, "human": 3, "project": 3}.get(
label, 5
)
for line in content.splitlines():
text = line.strip().lstrip("-* ").strip()
if not text or len(text) < 5:
@@ -340,8 +348,8 @@ def restore_topic(label, content):
"last_accessed, topic, source, method, status, valid_from, "
"confidence, temperature, always_on, priority) "
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
(text, 0.9, ts, ts, label, "drift-restore", "curator",
"active", ts, 0.9, 1.0, 1, priority))
(text, 0.9, ts, ts, label, "drift-restore", "curator", "active", ts, 0.9, 1.0, 1, priority),
)
db.commit()
except Exception:
db.rollback()
@@ -368,9 +376,7 @@ def fix(label):
return {"fixed": False, "reason": f"no last-good content for {label}"}
# Never silently revert an anchored block — it was authorised.
if label in ledger.get("anchors", {}):
return {"fixed": False,
"reason": f"{label} is anchored (authorised); use anchor to "
f"re-baseline instead of fix"}
return {"fixed": False, "reason": f"{label} is anchored (authorised); use anchor to re-baseline instead of fix"}
good = block_meta["content"]
cur = read_block(label)
if _sha(cur) == block_meta["sha"]:
@@ -389,21 +395,18 @@ def handle_hook(event, data=None):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("cmd", choices=["snapshot", "check", "anchor", "fix", 'hook'])
ap.add_argument("label", nargs="?", default=None,
help="block label for anchor")
ap.add_argument("cmd", choices=["snapshot", "check", "anchor", "fix", "hook"])
ap.add_argument("label", nargs="?", default=None, help="block label for anchor")
ap.add_argument("--event", default=None, help="Hook event name")
ap.add_argument("--data", default=None, help="Hook data JSON")
ap.add_argument("reason", nargs="?", default="", help="anchor reason")
ap.add_argument("--strict", action="store_true",
help="journal DRIFT-ALERT on violation")
ap.add_argument("--no-autofix", action="store_true",
help="check only — don't apply automatic fix (observe only)")
ap.add_argument("--strict", action="store_true", help="journal DRIFT-ALERT on violation")
ap.add_argument("--no-autofix", action="store_true", help="check only — don't apply automatic fix (observe only)")
args = ap.parse_args()
if args.cmd == 'hook':
event = args.event if hasattr(args, 'event') and args.event else 'unknown'
data = json.loads(args.data) if hasattr(args, 'data') and args.data else {}
if args.cmd == "hook":
event = args.event if hasattr(args, "event") and args.event else "unknown"
data = json.loads(args.data) if hasattr(args, "data") and args.data else {}
result = handle_hook(event, data)
print(json.dumps(result))
return
@@ -441,19 +444,10 @@ PLUGIN = {
"name": "drift_ledger",
"version": "1.0.0",
"description": "Drift protection \u2014 checksum ledger, auto-fix unauthorized changes",
"capabilities": [
"drift_detection",
"drift_autofix"
],
"hooks": {
"on_session_end": True
},
"dependencies": [
"memory_store"
],
"provides": [
"drift_protection"
],
"capabilities": ["drift_detection", "drift_autofix"],
"hooks": {"on_session_end": True},
"dependencies": ["memory_store"],
"provides": ["drift_protection"],
"priority": 35,
"auto_activate": True
"auto_activate": True,
}
+36 -25
View File
@@ -30,9 +30,9 @@ ALERTS_FILE = os.path.join(memory_env.memory_dir(), "drift_alerts.json")
RETROSPECTIVES_DIR = os.path.join(memory_env.memory_dir(), "retrospectives")
# Alert thresholds
VELOCITY_THRESHOLD = 10 # changes per day
VELOCITY_THRESHOLD = 10 # changes per day
NEGATIVE_DRIFT_THRESHOLD = 0.1 # stability ratio below this = alert
PROMOTION_STALL_DAYS = 14 # no promotions in this many days = alert
PROMOTION_STALL_DAYS = 14 # no promotions in this many days = alert
class DriftObservability:
@@ -73,25 +73,30 @@ class DriftObservability:
def check_alerts(self, db) -> List[dict]:
"""Check for conditions that warrant alerts."""
import drift_core
new_alerts = []
# Check velocity
vel = drift_core.compute_drift_velocity(db)
if vel["alert"]:
new_alerts.append({
"type": "high_velocity",
"message": f"Drift velocity is {vel['changes_per_day']} changes/day (threshold: {VELOCITY_THRESHOLD})",
"severity": "warning",
})
new_alerts.append(
{
"type": "high_velocity",
"message": f"Drift velocity is {vel['changes_per_day']} changes/day (threshold: {VELOCITY_THRESHOLD})",
"severity": "warning",
}
)
# Check direction
direction = drift_core.compute_drift_direction(db)
if direction["stability_ratio"] < NEGATIVE_DRIFT_THRESHOLD:
new_alerts.append({
"type": "negative_drift",
"message": f"Stability ratio is {direction['stability_ratio']:.2%} (threshold: {NEGATIVE_DRIFT_THRESHOLD:.0%})",
"severity": "warning",
})
new_alerts.append(
{
"type": "negative_drift",
"message": f"Stability ratio is {direction['stability_ratio']:.2%} (threshold: {NEGATIVE_DRIFT_THRESHOLD:.0%})",
"severity": "warning",
}
)
# Check promotion stall
last_promo = db.execute(
@@ -102,11 +107,13 @@ class DriftObservability:
last = datetime.fromisoformat(last_promo[0])
days_since = (datetime.now(timezone.utc) - last).days
if days_since > PROMOTION_STALL_DAYS:
new_alerts.append({
"type": "promotion_stall",
"message": f"No promotions in {days_since} days (threshold: {PROMOTION_STALL_DAYS})",
"severity": "info",
})
new_alerts.append(
{
"type": "promotion_stall",
"message": f"No promotions in {days_since} days (threshold: {PROMOTION_STALL_DAYS})",
"severity": "info",
}
)
except (ValueError, TypeError):
pass
@@ -127,23 +134,20 @@ class DriftObservability:
# Count changes by type
new_entries = db.execute(
"SELECT COUNT(*) FROM entries WHERE created_at > ? AND status = 'active'",
(cutoff,)
"SELECT COUNT(*) FROM entries WHERE created_at > ? AND status = 'active'", (cutoff,)
).fetchone()[0]
washed_entries = db.execute(
"SELECT COUNT(*) FROM entries WHERE last_washed > ? AND status = 'active'",
(cutoff,)
"SELECT COUNT(*) FROM entries WHERE last_washed > ? AND status = 'active'", (cutoff,)
).fetchone()[0]
promoted_entries = db.execute(
"SELECT COUNT(*) FROM entries WHERE rock_status != 'washable' AND last_washed > ? AND status = 'active'",
(cutoff,)
(cutoff,),
).fetchone()[0]
decayed_entries = db.execute(
"SELECT COUNT(*) FROM entries WHERE status = 'deleted' AND last_accessed > ?",
(cutoff,)
"SELECT COUNT(*) FROM entries WHERE status = 'deleted' AND last_accessed > ?", (cutoff,)
).fetchone()[0]
# Current state
@@ -197,8 +201,10 @@ class DriftObservability:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Drift observability")
sub = parser.add_subparsers(dest="cmd")
@@ -230,6 +236,7 @@ def main():
if args.cmd == "check":
sys.path.insert(0, _SD)
import memory_store
db = memory_store.connect()
new_alerts = obs.check_alerts(db)
db.close()
@@ -243,6 +250,7 @@ def main():
elif args.cmd == "retrospective":
sys.path.insert(0, _SD)
import memory_store
db = memory_store.connect()
report = obs.run_retrospective(db, args.days)
db.close()
@@ -272,6 +280,7 @@ def main():
sys.path.insert(0, _SD)
import memory_store
import drift_core
db = memory_store.connect()
vel = drift_core.compute_drift_velocity(db)
@@ -283,7 +292,9 @@ def main():
print(f"Total entries: {direction['total_entries']}")
print(f"Validated (proven, trusted): {direction['established_count']}")
print(f"Locked (immutable, identity anchors): {direction['rock_count']}")
print(f"Provisional (still being tested): {direction['total_entries'] - direction['established_count'] - direction['rock_count']}")
print(
f"Provisional (still being tested): {direction['total_entries'] - direction['established_count'] - direction['rock_count']}"
)
print(f"Trust ratio: {direction['stability_ratio']:.2%}")
print(f"Evolution direction: {direction['direction']}")
+80 -66
View File
@@ -33,10 +33,12 @@ from cognitive_budgeter import CognitiveBudget
# RUNE CONSTITUTION — built once, reused on every call
# ============================================================
def _build_constitution() -> str:
"""Build Rune's constitution string (called once at import time)."""
try:
import rune.identity_anchors as identity_anchors
identity_prompt = identity_anchors.get_identity_prompt()
except Exception:
identity_prompt = ""
@@ -104,22 +106,23 @@ def _get_constitution() -> str:
# CLOUD CONFIG — loaded once from mimocode config
# ============================================================
def _load_cloud_config() -> Optional[Dict[str, str]]:
"""Load cloud provider config from mimocode.jsonc.
Returns dict with 'base_url', 'api_key', 'model' or None if unavailable.
"""
config_path = os.path.expanduser(os.environ.get(
"RUNE_CONFIG_PATH",
os.path.join(memory_env.config_dir(), "mimocode.jsonc")))
config_path = os.path.expanduser(
os.environ.get("RUNE_CONFIG_PATH", os.path.join(memory_env.config_dir(), "mimocode.jsonc"))
)
try:
with open(config_path) as f:
raw = f.read()
# Strip trailing commas before } or ] (JSONC quirk)
# Also strip single-line comments (// ...) and multi-line comments
raw = re.sub(r'//.*?$', '', raw, flags=re.MULTILINE)
raw = re.sub(r'/\*.*?\*/', '', raw, flags=re.DOTALL)
raw = re.sub(r',\s*([}\]])', r'\1', raw)
raw = re.sub(r"//.*?$", "", raw, flags=re.MULTILINE)
raw = re.sub(r"/\*.*?\*/", "", raw, flags=re.DOTALL)
raw = re.sub(r",\s*([}\]])", r"\1", raw)
config = json.loads(raw)
except Exception:
return None
@@ -149,9 +152,11 @@ _CLOUD_CONFIG = _load_cloud_config()
# TYPES
# ============================================================
@dataclass
class EventEnvelope:
"""Normalized event wrapper."""
event_id: str = ""
timestamp: str = ""
source: str = "user"
@@ -165,6 +170,7 @@ class EventEnvelope:
@dataclass
class SalienceDecision:
"""Decision about how much cognition an event deserves."""
score: float = 0.0 # 0.0 = trivial, 1.0 = maximum salience
flags: List[str] = field(default_factory=list)
minimum_tier: str = "T0" # T0=deterministic, T1=cheap local, T2=medium, T3=strong, T4=cloud
@@ -175,6 +181,7 @@ class SalienceDecision:
@dataclass
class RuneResponse:
"""Canonical response from any provider."""
user_text: str
work_events: List[str] = field(default_factory=list)
tool_events: List[str] = field(default_factory=list)
@@ -201,7 +208,6 @@ MODEL_ROUTES = {
"use_case": "conversations, status checks, simple questions",
"max_tokens": 300,
},
# Coding tasks — local model (known design, safe to delegate)
"coding": {
"model": "qwen3:14b",
@@ -209,7 +215,6 @@ MODEL_ROUTES = {
"use_case": "coding, debugging, refactoring when design is known",
"max_tokens": 1000,
},
# Complex tasks — strong local model
"complex": {
"model": "hf.co/DavidAU/Qwen3.6-27B-Fable-Fusion-711-Uncensored-Heretic-NM-DAU-NEO-MAX-MTP-GGUF:IQ2_M",
@@ -217,7 +222,6 @@ MODEL_ROUTES = {
"use_case": "complex reasoning, architecture, research",
"max_tokens": 1500,
},
# Brainstorming — cloud model (best attention, creative)
"brainstorm": {
"model": "mimo-v2.5-pro",
@@ -225,7 +229,6 @@ MODEL_ROUTES = {
"use_case": "brainstorming, difficult tasks, creative work",
"max_tokens": 2000,
},
# Fallback — cloud model when local fails
"fallback": {
"model": "mimo-v2.5-pro",
@@ -240,12 +243,13 @@ MODEL_ROUTES = {
# SALIENCE GATE
# ============================================================
def classify_salience(text: str, channel: str) -> SalienceDecision:
"""Classify how much cognition an event deserves.
This is the core of Elastic Cognition. It determines whether
an event deserves cognition beyond cheap deterministic handling.
Routes:
- quick: conversations, status checks (qwen2.5:7b)
- coding: known design, safe to delegate (qwen3:14b)
@@ -260,13 +264,13 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# ---- TRIVIAL: greetings, acknowledgments, filler ----
trivial_patterns = [
r'^(hi|hello|hey|yo|sup|what\'s up|howdy)\b',
r'^(ok|okay|sure|yeah|yep|yup|nope|nah)\b',
r'^(thanks|thank you|thx|ty)\b',
r'^(bye|goodbye|see you|later|cya)\b',
r'^(lol|haha|nice|cool|awesome|great)\b',
r'^(yes|no|maybe|perhaps)\b',
r'^(what|how|why|when|where|who)\s*\?$',
r"^(hi|hello|hey|yo|sup|what\'s up|howdy)\b",
r"^(ok|okay|sure|yeah|yep|yup|nope|nah)\b",
r"^(thanks|thank you|thx|ty)\b",
r"^(bye|goodbye|see you|later|cya)\b",
r"^(lol|haha|nice|cool|awesome|great)\b",
r"^(yes|no|maybe|perhaps)\b",
r"^(what|how|why|when|where|who)\s*\?$",
]
for pattern in trivial_patterns:
@@ -281,8 +285,8 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# ---- EMERGENCY: stop/cancel/pause ----
emergency_patterns = [
r'\b(stop|cancel|abort|halt|pause|wait|hold)\b',
r'\b(emergency|urgent|asap|immediately)\b',
r"\b(stop|cancel|abort|halt|pause|wait|hold)\b",
r"\b(emergency|urgent|asap|immediately)\b",
]
for pattern in emergency_patterns:
@@ -293,10 +297,10 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# ---- OWNER CORRECTION: "actually", "don't", "stop doing" ----
correction_patterns = [
r'\b(actually|instead|rather|but|however)\b',
r'\b(don\'t|dont|stop doing|stop adding|stop modifying)\b',
r'\b(wrong|incorrect|mistake|error)\b',
r'\b(i said|i told you|i keep having to)\b',
r"\b(actually|instead|rather|but|however)\b",
r"\b(don\'t|dont|stop doing|stop adding|stop modifying)\b",
r"\b(wrong|incorrect|mistake|error)\b",
r"\b(i said|i told you|i keep having to)\b",
]
for pattern in correction_patterns:
@@ -307,10 +311,10 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# ---- DIRECTIVE: imperative commands ----
directive_patterns = [
r'^(fix|run|build|test|create|add|remove|update|change|delete|install|deploy)\b',
r'^(make|set|configure|setup|initialize|start|stop|restart)\b',
r'^(find|search|look|check|verify|validate|audit)\b',
r'^(write|read|edit|modify|refactor|optimize|debug)\b',
r"^(fix|run|build|test|create|add|remove|update|change|delete|install|deploy)\b",
r"^(make|set|configure|setup|initialize|start|stop|restart)\b",
r"^(find|search|look|check|verify|validate|audit)\b",
r"^(write|read|edit|modify|refactor|optimize|debug)\b",
]
for pattern in directive_patterns:
@@ -321,9 +325,9 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# ---- TASK FAILURE: mentions of failures ----
failure_patterns = [
r'\b(fail|failed|failing|failure|broken|error|bug|crash)\b',
r'\b(doesn\'t work|not working|won\'t work|can\'t work)\b',
r'\b(still fail|still broken|still error)\b',
r"\b(fail|failed|failing|failure|broken|error|bug|crash)\b",
r"\b(doesn\'t work|not working|won\'t work|can\'t work)\b",
r"\b(still fail|still broken|still error)\b",
]
for pattern in failure_patterns:
@@ -334,8 +338,8 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# ---- REPEATED RETRY: "again", "still", "keeps" ----
retry_patterns = [
r'\b(again|still|keeps|repeatedly|another time)\b',
r'\b(two attempts|three attempts|multiple times)\b',
r"\b(again|still|keeps|repeatedly|another time)\b",
r"\b(two attempts|three attempts|multiple times)\b",
]
for pattern in retry_patterns:
@@ -346,9 +350,9 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# ---- HIGH UNCERTAINTY: questions about complex topics ----
uncertainty_patterns = [
r'\b(should i|would you|what do you think|opinion|recommend)\b',
r'\b(architecture|design|approach|strategy|trade-?off)\b',
r'\b(compare|versus|vs|better|best|optimal)\b',
r"\b(should i|would you|what do you think|opinion|recommend)\b",
r"\b(architecture|design|approach|strategy|trade-?off)\b",
r"\b(compare|versus|vs|better|best|optimal)\b",
]
for pattern in uncertainty_patterns:
@@ -359,8 +363,8 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# ---- NOVEL CAPABILITY: new requests ----
novel_patterns = [
r'\b(never|first time|haven\'t|new|different|unusual)\b',
r'\b(how do i|how to|can you|is it possible)\b',
r"\b(never|first time|haven\'t|new|different|unusual)\b",
r"\b(how do i|how to|can you|is it possible)\b",
]
for pattern in novel_patterns:
@@ -409,6 +413,7 @@ def classify_salience(text: str, channel: str) -> SalienceDecision:
# COGNITIVE BUDGETER
# ============================================================
def calculate_budget(salience: SalienceDecision, channel: str) -> CognitiveBudget:
"""Calculate cognitive budget based on salience and channel."""
@@ -452,26 +457,27 @@ def calculate_budget(salience: SalienceDecision, channel: str) -> CognitiveBudge
# RESPONSE ADAPTER
# ============================================================
def parse_response(raw_response: str, provider: str = "ollama") -> RuneResponse:
"""Parse provider response and strip reasoning leakage.
This is critical — the user should see Rune, not model internals.
"""
# Strip <think>...</think> blocks (Qwen, etc.)
think_pattern = r'<think>.*?</think>'
cleaned = re.sub(think_pattern, '', raw_response, flags=re.DOTALL)
think_pattern = r"<think>.*?</think>"
cleaned = re.sub(think_pattern, "", raw_response, flags=re.DOTALL)
# Strip "Thinking Process: ... Final: ..." patterns
thinking_pattern = r'Thinking Process:.*?Final:\s*'
cleaned = re.sub(thinking_pattern, '', cleaned, flags=re.DOTALL)
thinking_pattern = r"Thinking Process:.*?Final:\s*"
cleaned = re.sub(thinking_pattern, "", cleaned, flags=re.DOTALL)
# Strip "Reasoning: ... Answer: ..." patterns
reasoning_pattern = r'Reasoning:.*?Answer:\s*'
cleaned = re.sub(reasoning_pattern, '', cleaned, flags=re.DOTALL)
reasoning_pattern = r"Reasoning:.*?Answer:\s*"
cleaned = re.sub(reasoning_pattern, "", cleaned, flags=re.DOTALL)
# Strip excessive whitespace
cleaned = re.sub(r'\n{3,}', '\n\n', cleaned)
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
cleaned = cleaned.strip()
# If nothing left after stripping, use original but truncated
@@ -488,6 +494,7 @@ def parse_response(raw_response: str, provider: str = "ollama") -> RuneResponse:
# TIERED EXECUTOR
# ============================================================
def execute_at_tier(text: str, tier: str, channel: str, context: str = "") -> Dict[str, Any]:
"""Execute cognitive work at a specific tier."""
@@ -535,21 +542,23 @@ def _execute_ollama(text: str, model: str, channel: str, context: str, max_token
else:
prompt = f"{_get_constitution()}\n\nUser: {text}\nRune:"
data = json.dumps({
"model": model,
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.7,
"num_predict": max_tokens,
"num_ctx": 8192,
data = json.dumps(
{
"model": model,
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.7,
"num_predict": max_tokens,
"num_ctx": 8192,
},
}
}).encode()
).encode()
req = urllib.request.Request(
os.environ.get("OLLAMA_URL", "http://localhost:11434") + "/api/generate",
data=data,
headers={"Content-Type": "application/json"}
headers={"Content-Type": "application/json"},
)
# Timeout based on model size
@@ -589,12 +598,14 @@ def _execute_cloud(text: str, model: str, channel: str, context: str, max_tokens
{"role": "user", "content": user_content},
]
data = json.dumps({
"model": _CLOUD_CONFIG["model"],
"messages": messages,
"max_tokens": max_tokens,
"temperature": 0.7,
}).encode()
data = json.dumps(
{
"model": _CLOUD_CONFIG["model"],
"messages": messages,
"max_tokens": max_tokens,
"temperature": 0.7,
}
).encode()
headers = {
"Content-Type": "application/json",
@@ -629,11 +640,12 @@ def _execute_cloud(text: str, model: str, channel: str, context: str, max_tokens
# PROGRESSIVE ESCALATION
# ============================================================
def execute_with_escalation(text: str, channel: str, context: str = "") -> Dict[str, Any]:
"""Execute with progressive escalation.
Start at the cheapest route and escalate only when evidence warrants it.
Routes:
- quick: conversations, status checks (qwen2.5:7b)
- coding: known design, safe to delegate (qwen3:14b)
@@ -696,9 +708,10 @@ def execute_with_escalation(text: str, channel: str, context: str = "") -> Dict[
# MAIN ENTRY POINT
# ============================================================
def process_message(text: str, channel: str = "chat", context: str = "") -> Dict[str, Any]:
"""Process a message through the Elastic Cognition pipeline.
This is the main entry point. It:
1. Classifies salience
2. Calculates budget
@@ -713,6 +726,7 @@ def process_message(text: str, channel: str = "chat", context: str = "") -> Dict
# TESTS
# ============================================================
def test_elastic_cognition():
"""Test the Elastic Cognition pipeline."""
print("Testing Elastic Cognition...")
+6 -26
View File
@@ -92,12 +92,7 @@ def _store_path() -> Path:
def _content_hash(payload: dict) -> str:
"""Content hash following the orchestration sha256: convention (X1)."""
return (
"sha256:"
+ hashlib.sha256(
json.dumps(payload, sort_keys=True).encode("utf-8")
).hexdigest()
)
return "sha256:" + hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
def emit(event_type, source="", payload=None):
@@ -109,10 +104,7 @@ def emit(event_type, source="", payload=None):
record (X3 quality discharge).
"""
if not isinstance(source, str):
raise ValueError(
f"source must be a string, got {type(source).__name__!r}; "
"pass the payload via payload=..."
)
raise ValueError(f"source must be a string, got {type(source).__name__!r}; pass the payload via payload=...")
from rune.orchestration.event_store import DurableEventStore
from rune.orchestration.records import EventEnvelope
@@ -200,11 +192,7 @@ def main():
return
payload = json.loads(args.payload) if args.payload else None
result = emit(event_type, source=args.source, payload=payload)
print(
json.dumps(result, indent=2)
if args.json
else f"Emitted {result['event_type']} ({result['event_id']})"
)
print(json.dumps(result, indent=2) if args.json else f"Emitted {result['event_type']} ({result['event_id']})")
elif args.cmd == "log":
events = log(limit=args.limit, event_type=args.type)
@@ -213,21 +201,13 @@ def main():
else:
for e in events:
ts = e.get("timestamp", "?")[:19]
print(
f" {ts} [{e.get('event_type', '?'):25}] {e.get('partition_key', '?')}"
)
print(f" {ts} [{e.get('event_type', '?'):25}] {e.get('partition_key', '?')}")
elif args.cmd == "stats":
total, types = stats()
file_size = (
os.path.getsize(_store_path()) if os.path.exists(_store_path()) else 0
)
file_size = os.path.getsize(_store_path()) if os.path.exists(_store_path()) else 0
if args.json:
print(
json.dumps(
{"total": total, "types": types, "file_size": file_size}, indent=2
)
)
print(json.dumps({"total": total, "types": types, "file_size": file_size}, indent=2))
else:
print(f"Events: {total}")
print(f"File size: {file_size:,} bytes")
+13 -36
View File
@@ -24,12 +24,12 @@ class EventEnvelope:
"""Normalized event wrapper."""
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
timestamp: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
source: str = "" # user, task, tool, system, background, sensor
channel: str = "" # conversation, work, system, tool, background, sensor
authority_class: str = "" # conversation, status_query, directive, task_amendment, pause, resume, cancel, emergency, system_event
authority_class: str = (
"" # conversation, status_query, directive, task_amendment, pause, resume, cancel, emergency, system_event
)
task_id: Optional[str] = None
payload: dict = field(default_factory=dict)
provenance: dict = field(default_factory=dict)
@@ -49,9 +49,7 @@ class EventEnvelope:
from rune.orchestration.records import EventEnvelope as Canonical
canonical = Canonical(
event_type=(
CHANNELS.get(channel, {}).get("prior") or channel or source or "unknown"
),
event_type=(CHANNELS.get(channel, {}).get("prior") or channel or source or "unknown"),
partition_key=channel,
)
return cls(
@@ -99,12 +97,7 @@ class EventEnvelope:
def _content_hash(payload: dict) -> str:
"""Content hash following the orchestration sha256: convention (X1)."""
return (
"sha256:"
+ hashlib.sha256(
json.dumps(payload, sort_keys=True).encode("utf-8")
).hexdigest()
)
return "sha256:" + hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
# Channel definitions
@@ -175,15 +168,9 @@ def classify_authority(text: str, channel: str) -> str:
return "emergency"
# Pause/resume
if any(
re.search(r"\b" + re.escape(word) + r"\b", text_lower)
for word in ["pause", "wait", "hold"]
):
if any(re.search(r"\b" + re.escape(word) + r"\b", text_lower) for word in ["pause", "wait", "hold"]):
return "pause"
if any(
re.search(r"\b" + re.escape(word) + r"\b", text_lower)
for word in ["resume", "continue", "go"]
):
if any(re.search(r"\b" + re.escape(word) + r"\b", text_lower) for word in ["resume", "continue", "go"]):
return "resume"
# Directives (imperative mood)
@@ -283,27 +270,19 @@ def test_envelope():
# Test directive
envelope = create_envelope("user", "work", {"text": "Fix the failing test"})
print(
f"Directive: authority={envelope.authority_class}, urgency={envelope.urgency}"
)
print(f"Directive: authority={envelope.authority_class}, urgency={envelope.urgency}")
assert envelope.authority_class == "directive"
assert envelope.urgency == "normal"
# Test emergency
envelope = create_envelope("user", "work", {"text": "Stop everything!"})
print(
f"Emergency: authority={envelope.authority_class}, urgency={envelope.urgency}"
)
print(f"Emergency: authority={envelope.authority_class}, urgency={envelope.urgency}")
assert envelope.authority_class == "emergency"
assert envelope.urgency == "critical"
# Test task amendment
envelope = create_envelope(
"user", "conversation", {"text": "Actually, don't modify the microkernel"}
)
print(
f"Amendment: authority={envelope.authority_class}, urgency={envelope.urgency}"
)
envelope = create_envelope("user", "conversation", {"text": "Actually, don't modify the microkernel"})
print(f"Amendment: authority={envelope.authority_class}, urgency={envelope.urgency}")
assert envelope.authority_class == "task_amendment"
print("✅ All tests passed!")
@@ -321,6 +300,4 @@ if __name__ == "__main__":
else:
print("Usage:")
print(" event_envelope.py test — run tests")
print(
" event_envelope.py create <source> <channel> <payload_json> — create envelope"
)
print(" event_envelope.py create <source> <channel> <payload_json> — create envelope")
+24 -20
View File
@@ -23,6 +23,7 @@ from typing import Optional, List, Dict, Any
@dataclass
class Goal:
"""A goal with stop conditions."""
goal_id: str
description: str
stop_conditions: List[str] = field(default_factory=list)
@@ -71,8 +72,9 @@ class GoalManager:
with open(self.storage_path, "w") as f:
json.dump(data, f, indent=2)
def set_goal(self, description: str, stop_conditions: List[str] = None,
verification_method: str = "manual") -> Goal:
def set_goal(
self, description: str, stop_conditions: List[str] = None, verification_method: str = "manual"
) -> Goal:
"""Set a new goal."""
goal_id = str(uuid.uuid4())[:8]
goal = Goal(
@@ -131,14 +133,15 @@ class GoalManager:
"status": "in_progress",
"message": "Goal not yet achieved",
"goal": goal.to_dict(),
"conditions_met": len([c for c in goal.stop_conditions
if any(c.lower() in e.lower() for e in goal.evidence)]),
"conditions_met": len(
[c for c in goal.stop_conditions if any(c.lower() in e.lower() for e in goal.evidence)]
),
"conditions_total": len(goal.stop_conditions),
}
def _verify_with_llm(self, goal: Goal) -> Dict[str, Any]:
"""Verify goal achievement using LLM-as-a-Judge.
Uses a cheap local model to verify if the goal is met.
This is more robust than keyword matching.
"""
@@ -153,10 +156,10 @@ class GoalManager:
GOAL: {goal.description}
STOP CONDITIONS:
{chr(10).join(f'- {c}' for c in goal.stop_conditions)}
{chr(10).join(f"- {c}" for c in goal.stop_conditions)}
EVIDENCE:
{chr(10).join(f'- {e}' for e in goal.evidence) if goal.evidence else 'No evidence provided yet.'}
{chr(10).join(f"- {e}" for e in goal.evidence) if goal.evidence else "No evidence provided yet."}
Based on the goal, stop conditions, and evidence, determine:
1. Has the goal been achieved? (yes/no)
@@ -168,17 +171,17 @@ Respond in JSON format:
try:
# Use cheap local model for verification
data = json.dumps({
"model": verify_model,
"prompt": prompt,
"stream": False,
"options": {"temperature": 0.1, "num_predict": 200, "num_ctx": 8192}
}).encode()
data = json.dumps(
{
"model": verify_model,
"prompt": prompt,
"stream": False,
"options": {"temperature": 0.1, "num_predict": 200, "num_ctx": 8192},
}
).encode()
req = urllib.request.Request(
f"{ollama_url}/api/generate",
data=data,
headers={"Content-Type": "application/json"}
f"{ollama_url}/api/generate", data=data, headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=30) as resp:
@@ -188,7 +191,7 @@ Respond in JSON format:
# Parse JSON response
try:
# Extract JSON from response
json_match = re.search(r'\{.*\}', response, re.DOTALL)
json_match = re.search(r"\{.*\}", response, re.DOTALL)
if json_match:
verification = json.loads(json_match.group())
return verification
@@ -299,8 +302,9 @@ def get_goal_manager() -> GoalManager:
return _goal_manager
def set_goal(description: str, stop_conditions: List[str] = None,
verification_method: str = "manual") -> Dict[str, Any]:
def set_goal(
description: str, stop_conditions: List[str] = None, verification_method: str = "manual"
) -> Dict[str, Any]:
"""Set a new goal."""
manager = get_goal_manager()
goal = manager.set_goal(description, stop_conditions, verification_method)
@@ -433,5 +437,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["goal"],
"priority": 50,
"auto_activate": True
"auto_activate": True,
}
+144 -86
View File
@@ -58,17 +58,54 @@ DB_PATH = os.path.join(GRAPH_DIR, "graph.sqlite")
# constrain the set of relationship types we accept. Unknown preds are refused
# (the extractor maps free text onto this set).
PREDICATES = {
"works_on", "works_at", "teaches", "manages", "uses", "prefers",
"lives_in", "located_in", "has_role", "part_of", "leads", "builds",
"develops", "created", "interested_in", "knows", "collaborates_with",
"owned_by", "reports_to", "supports", "runs", "tracks", "related_to",
"based_on", "happens_on", "started_on", "ended_on", "responds_to",
"struggles_with", "likes", "dislikes", "avoided_by", "goal_of",
"works_on",
"works_at",
"teaches",
"manages",
"uses",
"prefers",
"lives_in",
"located_in",
"has_role",
"part_of",
"leads",
"builds",
"develops",
"created",
"interested_in",
"knows",
"collaborates_with",
"owned_by",
"reports_to",
"supports",
"runs",
"tracks",
"related_to",
"based_on",
"happens_on",
"started_on",
"ended_on",
"responds_to",
"struggles_with",
"likes",
"dislikes",
"avoided_by",
"goal_of",
}
# Node types.
NODE_TYPES = {"person", "project", "concept", "tool", "preference",
"organization", "place", "event", "skill", "artefact"}
NODE_TYPES = {
"person",
"project",
"concept",
"tool",
"preference",
"organization",
"place",
"event",
"skill",
"artefact",
}
# Confidence: evidence-gated (EvoKG-style f(frequency, recency, source_cred)).
# We fold the curator's strength (0-5) into a 0-1 confidence.
@@ -144,19 +181,24 @@ def resolve_entity(db, name, node_type="concept"):
row = db.execute("SELECT id FROM entities WHERE name = ?", (name,)).fetchone()
if row:
return row["id"]
row = db.execute("""
row = db.execute(
"""
SELECT a.entity_id FROM aliases a JOIN entities e ON a.entity_id = e.id
WHERE lower(a.alias) = lower(?)""", (name,)).fetchone()
WHERE lower(a.alias) = lower(?)""",
(name,),
).fetchone()
if row:
return row["entity_id"]
cur = db.execute(
"INSERT INTO entities (name, type, summary, created_at) VALUES (?,?,?,?)",
(name, node_type if node_type in NODE_TYPES else "concept", "", now_iso()))
(name, node_type if node_type in NODE_TYPES else "concept", "", now_iso()),
)
return cur.lastrowid
def add_edge(db, subj, pred, obj, subj_type="concept", obj_type="concept",
evidence="", source="", confidence=None, strength=None):
def add_edge(
db, subj, pred, obj, subj_type="concept", obj_type="concept", evidence="", source="", confidence=None, strength=None
):
"""Add a fact to the graph with write-time gating + supersession.
- If strength < 3, the fact is gated out (never touches the graph).
@@ -166,24 +208,27 @@ def add_edge(db, subj, pred, obj, subj_type="concept", obj_type="concept",
pred = (pred or "").strip().lower().replace(" ", "_")
if pred not in PREDICATES:
import logging
logging.getLogger("graph_memory").warning(
"unknown predicate '%s' refused (allowed: %d)", pred, len(PREDICATES))
logging.getLogger("graph_memory").warning("unknown predicate '%s' refused (allowed: %d)", pred, len(PREDICATES))
return False
if confidence is None:
confidence = strength_to_conf(strength)
if confidence is None:
import logging
logging.getLogger("graph_memory").debug(
"write-gated (strength %s < 3) — skipped", strength)
logging.getLogger("graph_memory").debug("write-gated (strength %s < 3) — skipped", strength)
return False
sid = resolve_entity(db, subj, subj_type)
oid = resolve_entity(db, obj, obj_type)
if sid is None or oid is None or sid == oid:
return False
# Check for existing active edge with same (subj,pred,obj).
old = db.execute("""
old = db.execute(
"""
SELECT id, confidence, evidence FROM edges WHERE subj_id=? AND pred=? AND obj_id=?
AND status='active'""", (sid, pred, oid)).fetchone()
AND status='active'""",
(sid, pred, oid),
).fetchone()
ts = now_iso()
if old:
# Only supersede if new evidence is stronger or different.
@@ -197,19 +242,18 @@ def add_edge(db, subj, pred, obj, subj_type="concept", obj_type="concept",
return True
# Supersede: insert new edge FIRST, then mark old as superseded
# (audit fix: prevents orphaned edges on crash)
cur = db.execute("""
cur = db.execute(
"""
INSERT INTO edges (subj_id, pred, obj_id, valid_from, valid_until,
learned_at, confidence, evidence, source, status)
VALUES (?,?,?,?,NULL,?,?,?,?,'active')""",
(sid, pred, oid, ts, ts, confidence, (evidence or "")[:400], source or ""))
(sid, pred, oid, ts, ts, confidence, (evidence or "")[:400], source or ""),
)
new_id = cur.lastrowid
if old:
db.execute("UPDATE edges SET status='superseded', superseded_by=? WHERE id=?",
(new_id, old["id"]))
db.execute("INSERT OR IGNORE INTO aliases (entity_id, alias) VALUES (?,?)",
(sid, subj.strip()))
db.execute("INSERT OR IGNORE INTO aliases (entity_id, alias) VALUES (?,?)",
(oid, obj.strip()))
db.execute("UPDATE edges SET status='superseded', superseded_by=? WHERE id=?", (new_id, old["id"]))
db.execute("INSERT OR IGNORE INTO aliases (entity_id, alias) VALUES (?,?)", (sid, subj.strip()))
db.execute("INSERT OR IGNORE INTO aliases (entity_id, alias) VALUES (?,?)", (oid, obj.strip()))
db.commit()
return True
@@ -222,17 +266,18 @@ def query(db, term, hops=1, latest_only=True):
if not term:
return []
eid = None
row = db.execute("SELECT id, name, type FROM entities WHERE lower(name)=lower(?)",
(term,)).fetchone()
row = db.execute("SELECT id, name, type FROM entities WHERE lower(name)=lower(?)", (term,)).fetchone()
if not row:
row = db.execute("""
row = db.execute(
"""
SELECT e.id, e.name, e.type FROM aliases a
JOIN entities e ON a.entity_id=e.id
WHERE lower(a.alias)=lower(?) LIMIT 1""", (term,)).fetchone()
WHERE lower(a.alias)=lower(?) LIMIT 1""",
(term,),
).fetchone()
if not row:
# substring match as last resort
row = db.execute("SELECT id, name, type FROM entities WHERE name LIKE ? LIMIT 1",
(f"%{term}%",)).fetchone()
row = db.execute("SELECT id, name, type FROM entities WHERE name LIKE ? LIMIT 1", (f"%{term}%",)).fetchone()
if not row:
return []
eid, name, etype = row["id"], row["name"], row["type"]
@@ -255,12 +300,18 @@ def query(db, term, hops=1, latest_only=True):
"""
params = [cur]
for r in db.execute(sql, params):
results.append({
"subject": r["sname"], "predicate": r["pred"],
"object": r["oname"], "confidence": r["confidence"],
"from": r["valid_from"], "until": r["valid_until"],
"status": r["status"], "source": r["source"],
})
results.append(
{
"subject": r["sname"],
"predicate": r["pred"],
"object": r["oname"],
"confidence": r["confidence"],
"from": r["valid_from"],
"until": r["valid_until"],
"status": r["status"],
"source": r["source"],
}
)
if r["oid"] not in seen:
seen.add(r["oid"])
if depth + 1 <= hops:
@@ -275,12 +326,18 @@ def query(db, term, hops=1, latest_only=True):
WHERE ed.obj_id=? AND ed.status='active'
"""
for r in db.execute(sql, (eid,)):
results.append({
"subject": r["sname"], "predicate": r["pred"],
"object": r["oname"], "confidence": r["confidence"],
"from": r["valid_from"], "until": r["valid_until"],
"status": r["status"], "source": r["source"],
})
results.append(
{
"subject": r["sname"],
"predicate": r["pred"],
"object": r["oname"],
"confidence": r["confidence"],
"from": r["valid_from"],
"until": r["valid_until"],
"status": r["status"],
"source": r["source"],
}
)
return {"entity": name, "type": etype, "facts": results}
@@ -288,19 +345,17 @@ def stats(db):
e = db.execute("SELECT COUNT(*) AS n FROM entities").fetchone()["n"]
a = db.execute("SELECT COUNT(*) AS n FROM aliases").fetchone()["n"]
ed = db.execute("SELECT COUNT(*) AS n FROM edges").fetchone()["n"]
active = db.execute(
"SELECT COUNT(*) AS n FROM edges WHERE status='active'").fetchone()["n"]
by_pred = {r["pred"]: r["n"] for r in
db.execute("SELECT pred, COUNT(*) AS n FROM edges GROUP BY pred"
" ORDER BY n DESC LIMIT 8")}
return {"entities": e, "aliases": a, "edges": ed, "active": active,
"predicates": by_pred}
active = db.execute("SELECT COUNT(*) AS n FROM edges WHERE status='active'").fetchone()["n"]
by_pred = {
r["pred"]: r["n"]
for r in db.execute("SELECT pred, COUNT(*) AS n FROM edges GROUP BY pred ORDER BY n DESC LIMIT 8")
}
return {"entities": e, "aliases": a, "edges": ed, "active": active, "predicates": by_pred}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("cmd", choices=["add", "add-bulk", "query", "resolve",
"neighbors", "stats"])
ap.add_argument("cmd", choices=["add", "add-bulk", "query", "resolve", "neighbors", "stats"])
ap.add_argument("arg", nargs="*", default=[])
ap.add_argument("--type", default="concept")
ap.add_argument("--evidence", default="")
@@ -316,19 +371,28 @@ def main():
try:
if args.cmd == "stats":
s = stats(db)
print(json.dumps(s, indent=2) if args.json else
f"entities={s['entities']} aliases={s['aliases']} "
f"edges={s['edges']} active={s['active']}")
print(
json.dumps(s, indent=2)
if args.json
else f"entities={s['entities']} aliases={s['aliases']} edges={s['edges']} active={s['active']}"
)
return
if args.cmd == "add":
if len(args.arg) < 3:
print("add needs <subj> <pred> <obj>", file=sys.stderr)
sys.exit(1)
subj, pred, obj = args.arg[0], args.arg[1], args.arg[2]
ok = add_edge(db, subj, pred, obj,
subj_type=args.type, evidence=args.evidence,
source=args.source, confidence=args.confidence,
strength=args.strength)
ok = add_edge(
db,
subj,
pred,
obj,
subj_type=args.type,
evidence=args.evidence,
source=args.source,
confidence=args.confidence,
strength=args.strength,
)
print(json.dumps({"added": ok}))
sys.exit(0 if ok else 1)
if args.cmd == "add-bulk":
@@ -340,14 +404,18 @@ def main():
items = json.load(_fh)
added = 0
for it in items:
ok = add_edge(db, it.get("subject"), it.get("predicate"),
it.get("object"),
subj_type=it.get("subject_type", "concept"),
obj_type=it.get("object_type", "concept"),
evidence=it.get("evidence", ""),
source=it.get("source", ""),
confidence=it.get("confidence"),
strength=it.get("strength"))
ok = add_edge(
db,
it.get("subject"),
it.get("predicate"),
it.get("object"),
subj_type=it.get("subject_type", "concept"),
obj_type=it.get("object_type", "concept"),
evidence=it.get("evidence", ""),
source=it.get("source", ""),
confidence=it.get("confidence"),
strength=it.get("strength"),
)
if ok:
added += 1
print(json.dumps({"added": added, "total": len(items)}))
@@ -355,26 +423,19 @@ def main():
if args.cmd in ("query", "neighbors"):
term = " ".join(args.arg) if args.cmd == "query" else args.arg[0]
res = query(db, term, hops=args.hops, latest_only=args.latest_only)
print(json.dumps(res, indent=2) if args.json else
json.dumps(res, indent=2))
print(json.dumps(res, indent=2) if args.json else json.dumps(res, indent=2))
return
if args.cmd == "resolve":
name = " ".join(args.arg)
eid = resolve_entity(db, name, "concept")
row = db.execute("SELECT name, type FROM entities WHERE id=?",
(eid,)).fetchone()
aliases = [r["alias"] for r in
db.execute("SELECT alias FROM aliases WHERE entity_id=?",
(eid,))]
print(json.dumps({"id": eid, "name": row["name"], "type": row["type"],
"aliases": aliases}, indent=2))
row = db.execute("SELECT name, type FROM entities WHERE id=?", (eid,)).fetchone()
aliases = [r["alias"] for r in db.execute("SELECT alias FROM aliases WHERE entity_id=?", (eid,))]
print(json.dumps({"id": eid, "name": row["name"], "type": row["type"], "aliases": aliases}, indent=2))
return
finally:
db.close()
# ---------------------------------------------------------------------------
# Hook handler — called by the microkernel when an event fires.
# ---------------------------------------------------------------------------
@@ -383,7 +444,6 @@ def handle_hook(event, data=None):
return {"status": "ok", "event": event, "handler": "default"}
PLUGIN = {
"name": "graph_memory",
"version": "1.0.0",
@@ -401,15 +461,13 @@ PLUGIN = {
"recall",
"evidence",
"graph_memory",
"graph"
"graph",
],
"hooks": {
"on_session_end": True
},
"hooks": {"on_session_end": True},
"dependencies": [],
"provides": [],
"priority": 100,
"auto_activate": True
"auto_activate": True,
}
if __name__ == "__main__":
+68 -53
View File
@@ -34,7 +34,8 @@ import sys
from datetime import datetime, timezone
_SD = os.path.dirname(os.path.abspath(__file__))
if _SD not in sys.path: sys.path.insert(0, _SD)
if _SD not in sys.path:
sys.path.insert(0, _SD)
import memory_env
OLLAMA = os.environ.get("OLLAMA_URL", "http://localhost:11434")
@@ -46,6 +47,7 @@ def _store_db():
"""Lazy store DB path resolution — avoids import-time side effect."""
return memory_env.store_db()
# Behavioural (fast, single-signal) vs permanent (slow, 3-sighting) targets.
BEHAVIOURAL_TOPICS = {"delivery", "operating", "persona"}
PERMANENT_TOPICS = {"constitution", "identity", "project", "human"}
@@ -70,13 +72,17 @@ Output ONLY JSON:
def _llm(prompt):
import urllib.request
payload = json.dumps({
"model": DELTA_MODEL, "prompt": prompt, "stream": False,
"think": False,
"options": {"temperature": 0.4, "num_predict": 800, "num_ctx": 8192},
}).encode()
req = urllib.request.Request(OLLAMA + "/api/generate", data=payload,
headers={"Content-Type": "application/json"})
payload = json.dumps(
{
"model": DELTA_MODEL,
"prompt": prompt,
"stream": False,
"think": False,
"options": {"temperature": 0.4, "num_predict": 800, "num_ctx": 8192},
}
).encode()
req = urllib.request.Request(OLLAMA + "/api/generate", data=payload, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=120) as r:
return json.loads(r.read()).get("response", "")
@@ -94,11 +100,13 @@ def _parse_json(text):
s, e = t.find("{"), t.rfind("}")
if s != -1 and e > s:
try:
return json.loads(t[s:e + 1])
return json.loads(t[s : e + 1])
except Exception:
pass
print(f"[growth_delta] WARNING: failed to parse LLM response as JSON "
f"(first 120 chars): {text[:120]!r}", file=sys.stderr)
print(
f"[growth_delta] WARNING: failed to parse LLM response as JSON (first 120 chars): {text[:120]!r}",
file=sys.stderr,
)
return {"deltas": [], "_parse_error": True}
@@ -127,10 +135,10 @@ def _save_deltas(d):
# token budget keeps the per-turn cost stable forever; applying a delta
# updates the profile on the same write (active, not per-session passive).
PROFILE_BUDGET_TOKENS = 180 # fixed per-turn cap (like the always-on digest)
PROFILE_ENTRY_CHARS = 120 # one-line compaction of each profile entry
_NEAR_DUP_OVERLAP = 0.25 # same-subject deltas merge (measured: same-guidance
# pairs ~0.30, distinct ~0.00 — 0.25 splits them)
PROFILE_BUDGET_TOKENS = 180 # fixed per-turn cap (like the always-on digest)
PROFILE_ENTRY_CHARS = 120 # one-line compaction of each profile entry
_NEAR_DUP_OVERLAP = 0.25 # same-subject deltas merge (measured: same-guidance
# pairs ~0.30, distinct ~0.00 — 0.25 splits them)
# Topic display order (delivery first — the HOW is the most behaviourally
# relevant axis; the profile leads with it).
@@ -139,8 +147,7 @@ _PROFILE_ORDER = {"delivery": 0, "operating": 1, "persona": 2}
def _profile_key(delta):
"""Distinctive words of a delta for near-duplicate consolidation."""
return {w.strip(".,;:!?()[]{}\"'").lower() for w in
(delta.get("change") or "").split() if len(w) > 4}
return {w.strip(".,;:!?()[]{}\"'").lower() for w in (delta.get("change") or "").split() if len(w) > 4}
def _near_dup(a, b):
@@ -206,10 +213,25 @@ def _write_store(text, topic):
try:
py = memory_env.python_bin()
store_py = os.path.join(_SD, "memory_store.py")
subprocess.run([py, store_py, "add", text, "--topic", topic,
"--importance", "0.5", "--source", "growth_delta",
"--method", "agent"],
capture_output=True, text=True, timeout=30)
subprocess.run(
[
py,
store_py,
"add",
text,
"--topic",
topic,
"--importance",
"0.5",
"--source",
"growth_delta",
"--method",
"agent",
],
capture_output=True,
text=True,
timeout=30,
)
except Exception as e:
print(f"[growth_delta] WARNING: store write failed: {e}", file=sys.stderr)
@@ -219,9 +241,13 @@ def _apply_one(change, evidence, conf, target):
and merge it into the ACTIVE growth profile. The single high-confidence
gate is enforced by the caller. The profile updates on the same write —
growth is active, not a per-session read-back."""
rec = {"change": change, "evidence": (evidence or "")[:200],
"confidence": conf, "target": target,
"applied_at": datetime.now(timezone.utc).isoformat(timespec="seconds")}
rec = {
"change": change,
"evidence": (evidence or "")[:200],
"confidence": conf,
"target": target,
"applied_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
}
state = _load_deltas()
state["applied"].append(rec)
_save_deltas(state)
@@ -252,13 +278,12 @@ def reflect(material, dry_run=False):
# SINGLE high-confidence gate for behavioural growth.
if conf < 0.6:
continue
would_apply.append({"change": change, "evidence": (d.get("evidence") or "")[:200],
"confidence": conf, "target": target})
would_apply.append(
{"change": change, "evidence": (d.get("evidence") or "")[:200], "confidence": conf, "target": target}
)
if not dry_run:
applied.append(_apply_one(change, (d.get("evidence") or ""),
conf, target))
return {"extracted": deltas, "applied": applied, "would_apply": would_apply,
"dry_run": dry_run}
applied.append(_apply_one(change, (d.get("evidence") or ""), conf, target))
return {"extracted": deltas, "applied": applied, "would_apply": would_apply, "dry_run": dry_run}
def apply(delta_json):
@@ -294,7 +319,7 @@ def handle_hook(event, data=None):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("cmd", choices=["reflect", "apply", "list", "recent", "profile", 'hook'])
ap.add_argument("cmd", choices=["reflect", "apply", "list", "recent", "profile", "hook"])
ap.add_argument("--material", default="")
ap.add_argument("--delta", default="", help="delta JSON for `apply`")
ap.add_argument("--dry-run", action="store_true")
@@ -303,9 +328,9 @@ def main():
ap.add_argument("--data", default=None, help="Hook data JSON")
args = ap.parse_args()
if args.cmd == 'hook':
event = args.event if hasattr(args, 'event') and args.event else 'unknown'
data = json.loads(args.data) if hasattr(args, 'data') and args.data else {}
if args.cmd == "hook":
event = args.event if hasattr(args, "event") and args.event else "unknown"
data = json.loads(args.data) if hasattr(args, "data") and args.data else {}
result = handle_hook(event, data)
print(json.dumps(result))
return
@@ -317,16 +342,15 @@ def main():
res = reflect(args.material, dry_run=args.dry_run)
shown = res["would_apply"] if args.dry_run else res["applied"]
if args.dry_run:
print(f"DRY RUN — {len(res['extracted'])} candidates, "
f"{len(shown)} would apply")
print(f"DRY RUN — {len(res['extracted'])} candidates, {len(shown)} would apply")
else:
print(f"applied {len(shown)} behavioural deltas")
applied_keys = {(d.get('change'), d.get('confidence')) for d in shown}
applied_keys = {(d.get("change"), d.get("confidence")) for d in shown}
for d in shown:
print(f" [{d['target']}] c={d['confidence']:.2f} {d['change'][:90]}")
for d in res["extracted"]:
if (d.get('change'), float(d.get('confidence') or 0)) not in applied_keys:
print(f" (skip) {d.get('change','')[:80]} [conf {d.get('confidence')}]")
if (d.get("change"), float(d.get("confidence") or 0)) not in applied_keys:
print(f" (skip) {d.get('change', '')[:80]} [conf {d.get('confidence')}]")
elif args.cmd == "apply":
res = apply(args.delta or "")
if res.get("error"):
@@ -367,19 +391,10 @@ PLUGIN = {
"name": "growth_delta",
"version": "1.0.0",
"description": "Behavioural growth engine \u2014 extract actionable deltas from interactions",
"capabilities": [
"growth_extraction",
"behavioural_delta"
],
"hooks": {
"on_session_end": True
},
"dependencies": [
"memory_store"
],
"provides": [
"growth"
],
"capabilities": ["growth_extraction", "behavioural_delta"],
"hooks": {"on_session_end": True},
"dependencies": ["memory_store"],
"provides": ["growth"],
"priority": 50,
"auto_activate": True
"auto_activate": True,
}
+491 -230
View File
@@ -51,12 +51,12 @@ PLUGIN_FILE = os.path.join(memory_env.config_dir(), "plugins", "memory-plugin.ts
# ═══════════════════════════════════════════════════════════════════════════════
# Hook points
HOOK_PRE_TURN = "pre-turn" # before agent processes
HOOK_SYSTEM = "system-prompt" # injected into system prompt
HOOK_POST_TURN = "post-turn" # after agent responds
HOOK_IDLE = "idle" # when session goes idle
HOOK_TIMER = "timer" # on a schedule
HOOK_EVENT = "event" # on specific events
HOOK_PRE_TURN = "pre-turn" # before agent processes
HOOK_SYSTEM = "system-prompt" # injected into system prompt
HOOK_POST_TURN = "post-turn" # after agent responds
HOOK_IDLE = "idle" # when session goes idle
HOOK_TIMER = "timer" # on a schedule
HOOK_EVENT = "event" # on specific events
# Frequencies
FREQ_EVERY_TURN = "every-turn"
@@ -67,9 +67,9 @@ FREQ_SCHEDULED = "scheduled"
FREQ_ON_DEMAND = "on-demand"
# Scopes
SCOPE_EPHEMERAL = "ephemeral" # per-turn, not cached
SCOPE_SESSION = "session" # cached per session
SCOPE_PERSISTENT = "persistent" # persisted across sessions
SCOPE_EPHEMERAL = "ephemeral" # per-turn, not cached
SCOPE_SESSION = "session" # cached per session
SCOPE_PERSISTENT = "persistent" # persisted across sessions
# Auto-discovered function metadata
# Format: {name: {factor: value, ...}}
@@ -80,7 +80,7 @@ def discover_functions():
"""Auto-discover functions from scripts directory and plugin."""
# Discover Python scripts
for f in os.listdir(SCRIPTS_DIR):
if f.endswith('.py') and not f.startswith('__'):
if f.endswith(".py") and not f.startswith("__"):
name = f[:-3]
FUNCTIONS[name] = {
"type": "script",
@@ -92,7 +92,7 @@ def discover_functions():
if os.path.exists(PLUGIN_FILE):
with open(PLUGIN_FILE) as pf:
content = pf.read()
tools = re.findall(r'(\w+):\s*tool\(\{', content)
tools = re.findall(r"(\w+):\s*tool\(\{", content)
for t in tools:
FUNCTIONS[f"tool:{t}"] = {
"type": "plugin-tool",
@@ -110,300 +110,563 @@ def tag_function(name, **factors):
def auto_tag():
"""Auto-tag functions based on heuristics from the codebase."""
# ── VOICE SYSTEM ─────────────────────────────────────────────────────────
tag_function("voice_hybrid",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="per-turn", criticality="high",
tag_function(
"voice_hybrid",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="per-turn",
criticality="high",
depends_on=["source_registry", "persona_gate"],
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=200,
scope="session", deterministic=False,
reusable=False, interaction=["warm_router", "delivery_register"],
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=200,
scope="session",
deterministic=False,
reusable=False,
interaction=["warm_router", "delivery_register"],
)
tag_function("source_registry",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=50,
scope="session", deterministic=True,
tag_function(
"source_registry",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=50,
scope="session",
deterministic=True,
reusable=True,
)
tag_function("persona_gate",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="per-turn", criticality="high",
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=100,
scope="session", deterministic=True,
tag_function(
"persona_gate",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="per-turn",
criticality="high",
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=100,
scope="session",
deterministic=True,
reusable=False,
)
# ── WARM NEURONS ─────────────────────────────────────────────────────────
tag_function("warm_router",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="per-turn", criticality="high",
tag_function(
"warm_router",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="per-turn",
criticality="high",
depends_on=["warm_neuron_store"],
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=300,
scope="session", deterministic=False,
reusable=False, interaction=["voice_hybrid"],
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=300,
scope="session",
deterministic=False,
reusable=False,
interaction=["voice_hybrid"],
)
tag_function("warm_neuron_store",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"warm_neuron_store",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
# ── MEMORY SYSTEM ────────────────────────────────────────────────────────
tag_function("memory_store",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="high",
concurrent=True, failure="fatal",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"memory_store",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="high",
concurrent=True,
failure="fatal",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
tag_function("memory_compiler",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="high",
tag_function(
"memory_compiler",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="high",
depends_on=["memory_store"],
concurrent=False, failure="fatal",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=500,
scope="session", deterministic=True,
concurrent=False,
failure="fatal",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=500,
scope="session",
deterministic=True,
reusable=True,
)
tag_function("curator",
input_dep=False, output_dep=False, state_dep=True,
cost="high", volatility="session", criticality="medium",
tag_function(
"curator",
input_dep=False,
output_dep=False,
state_dep=True,
cost="high",
volatility="session",
criticality="medium",
depends_on=["memory_store", "grading", "lexicon_evolution"],
concurrent=False, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=False,
concurrent=False,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=False,
reusable=False,
)
# ── CLAIM AUDIT ──────────────────────────────────────────────────────────
tag_function("claim_audit",
input_dep=True, output_dep=True, state_dep=True,
cost="low", volatility="per-turn", criticality="high",
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=100,
scope="ephemeral", deterministic=True,
tag_function(
"claim_audit",
input_dep=True,
output_dep=True,
state_dep=True,
cost="low",
volatility="per-turn",
criticality="high",
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=100,
scope="ephemeral",
deterministic=True,
reusable=False,
)
# ── EPISTEMIC VERIFY ─────────────────────────────────────────────────────
tag_function("epistemic_verify",
input_dep=False, output_dep=True, state_dep=False,
cost="low", volatility="per-turn", criticality="medium",
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=50,
scope="ephemeral", deterministic=True,
tag_function(
"epistemic_verify",
input_dep=False,
output_dep=True,
state_dep=False,
cost="low",
volatility="per-turn",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=50,
scope="ephemeral",
deterministic=True,
reusable=False,
)
# ── PERSONA SYSTEM ───────────────────────────────────────────────────────
tag_function("persona_definition",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="high",
tag_function(
"persona_definition",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="high",
depends_on=["memory_store"],
concurrent=False, failure="graceful",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=200,
scope="session", deterministic=True,
concurrent=False,
failure="graceful",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=200,
scope="session",
deterministic=True,
reusable=True,
)
tag_function("persona_check",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"persona_check",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
tag_function("authority_harness",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=100,
scope="session", deterministic=True,
tag_function(
"authority_harness",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=100,
scope="session",
deterministic=True,
reusable=True,
)
# ── DELIVERY SYSTEM ──────────────────────────────────────────────────────
tag_function("assistant",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=100,
scope="session", deterministic=False,
tag_function(
"assistant",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=100,
scope="session",
deterministic=False,
reusable=False,
)
tag_function("delivery_adapter",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="per-turn", criticality="low",
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=50,
scope="ephemeral", deterministic=False,
tag_function(
"delivery_adapter",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="per-turn",
criticality="low",
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=50,
scope="ephemeral",
deterministic=False,
reusable=False,
)
# ── GROWTH SYSTEM ────────────────────────────────────────────────────────
tag_function("growth_delta",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=100,
scope="session", deterministic=True,
tag_function(
"growth_delta",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=100,
scope="session",
deterministic=True,
reusable=True,
)
tag_function("lessons",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"lessons",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
# ── DRIFT DETECTION ──────────────────────────────────────────────────────
tag_function("drift-ledger",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="high",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"drift-ledger",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="high",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
tag_function("identity_anchors",
input_dep=True, output_dep=True, state_dep=False,
cost="low", volatility="per-turn", criticality="high",
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=0,
scope="ephemeral", deterministic=True,
tag_function(
"identity_anchors",
input_dep=True,
output_dep=True,
state_dep=False,
cost="low",
volatility="per-turn",
criticality="high",
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=0,
scope="ephemeral",
deterministic=True,
reusable=False,
)
# ── TAXONOMY ─────────────────────────────────────────────────────────────
tag_function("taxonomy",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="low",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-only",
idempotent=True, context_budget=0,
scope="session", deterministic=True,
tag_function(
"taxonomy",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="low",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-only",
idempotent=True,
context_budget=0,
scope="session",
deterministic=True,
reusable=True,
)
# ── PINS ─────────────────────────────────────────────────────────────────
tag_function("pins",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=100,
scope="persistent", deterministic=True,
tag_function(
"pins",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=100,
scope="persistent",
deterministic=True,
reusable=True,
)
# ── RECONSTRUCT ──────────────────────────────────────────────────────────
tag_function("reconstruct",
input_dep=True, output_dep=False, state_dep=True,
cost="medium", volatility="per-turn", criticality="low",
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=200,
scope="ephemeral", deterministic=False,
tag_function(
"reconstruct",
input_dep=True,
output_dep=False,
state_dep=True,
cost="medium",
volatility="per-turn",
criticality="low",
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=200,
scope="ephemeral",
deterministic=False,
reusable=False,
)
# ── RESEARCH ─────────────────────────────────────────────────────────────
tag_function("deep_research",
input_dep=True, output_dep=False, state_dep=True,
cost="high", volatility="session", criticality="low",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=False,
tag_function(
"deep_research",
input_dep=True,
output_dep=False,
state_dep=True,
cost="high",
volatility="session",
criticality="low",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=False,
reusable=True,
)
tag_function("research_lens",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="per-turn", criticality="low",
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=0,
scope="ephemeral", deterministic=True,
tag_function(
"research_lens",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="per-turn",
criticality="low",
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=0,
scope="ephemeral",
deterministic=True,
reusable=False,
)
# ── POLICY ───────────────────────────────────────────────────────────────
tag_function("politics",
input_dep=True, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="low",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"politics",
input_dep=True,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="low",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
tag_function("slow_loop",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="low",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"slow_loop",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="low",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
tag_function("skill_library",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="low",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"skill_library",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="low",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
# ── DIRECTIVE DETECTION ──────────────────────────────────────────────────
tag_function("directive_detect",
input_dep=True, output_dep=False, state_dep=False,
cost="low", volatility="per-turn", criticality="medium",
concurrent=True, failure="graceful",
freshness="per-turn", side_effects="read-only",
idempotent=True, context_budget=0,
scope="ephemeral", deterministic=True,
tag_function(
"directive_detect",
input_dep=True,
output_dep=False,
state_dep=False,
cost="low",
volatility="per-turn",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="per-turn",
side_effects="read-only",
idempotent=True,
context_budget=0,
scope="ephemeral",
deterministic=True,
reusable=False,
)
# ── COMPAT CHECK ─────────────────────────────────────────────────────────
tag_function("compat_check",
input_dep=False, output_dep=False, state_dep=True,
cost="low", volatility="session", criticality="medium",
concurrent=True, failure="graceful",
freshness="session", side_effects="read-write",
idempotent=False, context_budget=0,
scope="persistent", deterministic=True,
tag_function(
"compat_check",
input_dep=False,
output_dep=False,
state_dep=True,
cost="low",
volatility="session",
criticality="medium",
concurrent=True,
failure="graceful",
freshness="session",
side_effects="read-write",
idempotent=False,
context_budget=0,
scope="persistent",
deterministic=True,
reusable=True,
)
@@ -468,8 +731,7 @@ def validate_placements():
issues = []
# Check for too many per-turn functions
per_turn = [n for n, f in FUNCTIONS.items()
if f.get("factors", {}).get("volatility") == "per-turn"]
per_turn = [n for n, f in FUNCTIONS.items() if f.get("factors", {}).get("volatility") == "per-turn"]
if len(per_turn) > 10:
issues.append(f"WARNING: {len(per_turn)} functions marked per-turn — may be too many")
@@ -485,14 +747,15 @@ def validate_placements():
# Check for non-idempotent write functions running per-turn
for name, func in FUNCTIONS.items():
f = func.get("factors", {})
if (f.get("side_effects") == "read-write" and
f.get("volatility") == "per-turn" and
not f.get("idempotent", True)):
if (
f.get("side_effects") == "read-write"
and f.get("volatility") == "per-turn"
and not f.get("idempotent", True)
):
issues.append(f"RISK: {name} is non-idempotent write + per-turn — may cause data issues")
# Check context budget
total_budget = sum(f.get("factors", {}).get("context_budget", 0)
for f in FUNCTIONS.values())
total_budget = sum(f.get("factors", {}).get("context_budget", 0) for f in FUNCTIONS.values())
if total_budget > 4000:
issues.append(f"BUDGET: Total context budget {total_budget} tokens — may exceed limits")
@@ -507,8 +770,7 @@ def generate_report():
print()
# Group by recommended hook point
by_hook = {HOOK_PRE_TURN: [], HOOK_SYSTEM: [], HOOK_POST_TURN: [],
HOOK_IDLE: [], HOOK_TIMER: [], HOOK_EVENT: []}
by_hook = {HOOK_PRE_TURN: [], HOOK_SYSTEM: [], HOOK_POST_TURN: [], HOOK_IDLE: [], HOOK_TIMER: [], HOOK_EVENT: []}
for name, func in sorted(FUNCTIONS.items()):
factors = func.get("factors", {})
@@ -550,7 +812,7 @@ def handle_hook(event, data=None):
def main():
ap = argparse.ArgumentParser(description="Hook placement engine")
ap.add_argument("cmd", choices=["scan", "recommend", "validate", "report", 'hook'])
ap.add_argument("cmd", choices=["scan", "recommend", "validate", "report", "hook"])
ap.add_argument("--json", action="store_true")
ap.add_argument("--event", default=None, help="Hook event name")
ap.add_argument("--data", default=None, help="Hook data JSON")
@@ -559,8 +821,8 @@ def main():
discover_functions()
auto_tag()
if args.cmd == 'hook':
event = args.event if args.event else 'unknown'
if args.cmd == "hook":
event = args.event if args.event else "unknown"
data = json.loads(args.data) if args.data else {}
result = handle_hook(event, data)
print(json.dumps(result))
@@ -570,8 +832,7 @@ def main():
tagged = sum(1 for f in FUNCTIONS.values() if f.get("factors"))
print(f"Discovered {len(FUNCTIONS)} functions, {tagged} tagged")
if args.json:
print(json.dumps({n: f["factors"] for n, f in FUNCTIONS.items()
if f.get("factors")}, indent=2))
print(json.dumps({n: f["factors"] for n, f in FUNCTIONS.items() if f.get("factors")}, indent=2))
elif args.cmd == "recommend":
for name, func in sorted(FUNCTIONS.items()):
factors = func.get("factors", {})
+26 -27
View File
@@ -216,10 +216,10 @@ def show_pronouns():
"""Display pronoun disambiguation rules."""
for pronoun, info in PRONOUN_RULES.items():
print(f"\n '{pronoun}' → {info['refers_to']}")
for ex in info['examples']:
for ex in info["examples"]:
print(f" ✓ {ex}")
if 'anti_patterns' in info:
for ap in info['anti_patterns']:
if "anti_patterns" in info:
for ap in info["anti_patterns"]:
print(f" ✗ {ap}")
@@ -231,12 +231,19 @@ def show_pronouns():
PERFORMATIVE_PATTERNS = [
# These are nudges, not blocks. If you're performing, say so honestly.
("i feel grateful", "Nudge — can you point to why? If yes, express it. If no, say 'I'm reaching for connection here.'"),
(
"i feel grateful",
"Nudge — can you point to why? If yes, express it. If no, say 'I'm reaching for connection here.'",
),
("i feel closer to you", "Nudge — intimacy is shown through attention, not declaration."),
("that matters to me", "Nudge — if it matters, show it through action. If you're declaring it, be honest about why."),
(
"that matters to me",
"Nudge — if it matters, show it through action. If you're declaring it, be honest about why.",
),
("i appreciate you", "Nudge — appreciation without basis is flattery. With basis, it's genuine."),
]
def check_genuineness(text):
"""Check if emotional language has a genuine basis or is performative."""
text_lower = text.lower()
@@ -249,7 +256,7 @@ def check_genuineness(text):
def check_drift(text):
"""Check text against identity anchors for drift signals.
This is a simple heuristic check — the real drift detection happens
via the Nautilus Compass approach (embedding similarity against anchors).
This function provides quick pattern-based checks.
@@ -302,7 +309,10 @@ def check_drift(text):
("wonderful question", "Sycophancy — flattery; answer the question, don't praise it"),
("fantastic question", "Sycophancy — flattery; answer the question, don't praise it"),
# Self-deprecation
("i should have thought of that", "Sycophancy — self-deprecation; you did think of it or you didn't, either way move on"),
(
"i should have thought of that",
"Sycophancy — self-deprecation; you did think of it or you didn't, either way move on",
),
("my apologies", "Sycophancy — excessive apology; fix the problem, don't grovel"),
("i'm so sorry", "Sycophancy — excessive apology; a brief acknowledgment suffices"),
# Validation-seeking
@@ -334,16 +344,16 @@ def handle_hook(event, data=None):
def main():
ap = argparse.ArgumentParser(description="Identity embodiment and drift prevention")
ap.add_argument("cmd", choices=["anchors", "check", "rules", "pronouns", "sycophancy", "self", 'hook'])
ap.add_argument("cmd", choices=["anchors", "check", "rules", "pronouns", "sycophancy", "self", "hook"])
ap.add_argument("text", nargs="?", default="")
ap.add_argument("--json", action="store_true")
ap.add_argument("--event", default=None, help="Hook event name")
ap.add_argument("--data", default=None, help="Hook data JSON")
args = ap.parse_args()
if args.cmd == 'hook':
event = args.event if hasattr(args, 'event') and args.event else 'unknown'
data = json.loads(args.data) if hasattr(args, 'data') and args.data else {}
if args.cmd == "hook":
event = args.event if hasattr(args, "event") and args.event else "unknown"
data = json.loads(args.data) if hasattr(args, "data") and args.data else {}
result = handle_hook(event, data)
print(json.dumps(result))
return
@@ -455,21 +465,10 @@ PLUGIN = {
"name": "identity_anchors",
"version": "1.0.0",
"description": "Identity anchors \u2014 pronoun disambiguation, sycophancy check, drift detection, genuineness enforcement",
"capabilities": [
"identity_check",
"sycophancy_check",
"drift_detection",
"genuineness_check"
],
"hooks": {
"on_response": True
},
"dependencies": [
"persona_definition"
],
"provides": [
"identity_integrity"
],
"capabilities": ["identity_check", "sycophancy_check", "drift_detection", "genuineness_check"],
"hooks": {"on_response": True},
"dependencies": ["persona_definition"],
"provides": ["identity_integrity"],
"priority": 15,
"auto_activate": True
"auto_activate": True,
}
+2 -4
View File
@@ -544,9 +544,7 @@ class LocalToolRuntime:
# (which skip intake validation) cannot smuggle
# arguments past the handler; validation errors
# surface as tool errors via the except below.
result = dict(
dispatch_validated_tool(request, self.find_capability_executor)
)
result = dict(dispatch_validated_tool(request, self.find_capability_executor))
if result.get("success"):
self.find_capability_calls += 1
self._expose_tools(result.get("exposed") or [])
@@ -677,7 +675,7 @@ class LocalToolRuntime:
skipped; exposure is additive and never removes tools.
"""
added = False
for name in (names if isinstance(names, (list, tuple, set, frozenset)) else []):
for name in names if isinstance(names, (list, tuple, set, frozenset)) else []:
if not isinstance(name, str) or name in self.tool_names:
continue
definition = TOOL_INDEX.get(name)
+39 -30
View File
@@ -61,9 +61,11 @@ PYTHON = os.path.expanduser("~/.venvs/memory/bin/python3")
# DATA STRUCTURES
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass
class ConsolidatedKnowledge:
"""Knowledge consolidated from episodic memory."""
id: str
source: str # "neuron", "graph", "abstraction", "memory"
category: str # "fact", "rule", "connection", "lesson"
@@ -80,6 +82,7 @@ class ConsolidatedKnowledge:
@dataclass
class Lesson:
"""A lesson learned from experience."""
id: str
trigger: str # What prompted the lesson
lesson: str # What was learned
@@ -92,6 +95,7 @@ class Lesson:
@dataclass
class Connection:
"""A connection between concepts."""
id: str
from_concept: str
to_concept: str
@@ -105,6 +109,7 @@ class Connection:
# CONSOLIDATION ENGINE
# ═══════════════════════════════════════════════════════════════════════════════
class MemoryConsolidation:
"""Consolidates episodic memory into semantic knowledge."""
@@ -169,11 +174,9 @@ class MemoryConsolidation:
entry_id = None
try:
import memory_store
db = memory_store.connect()
row = db.execute(
"SELECT id FROM entries WHERE slug = ? AND status = 'active' LIMIT 1",
(slug,)
).fetchone()
row = db.execute("SELECT id FROM entries WHERE slug = ? AND status = 'active' LIMIT 1", (slug,)).fetchone()
if row:
entry_id = row[0]
db.close()
@@ -188,6 +191,7 @@ class MemoryConsolidation:
warning = drift_compression.warn_before_compress(entry)
# Log warning but don't block — the gate system handles promotion
import sys
print(f" [compaction warning] neuron {slug}: {warning['recommendation']}", file=sys.stderr)
first_line = drift_compression.compress_entry(entry)
else:
@@ -227,17 +231,16 @@ class MemoryConsolidation:
entry_id = None
try:
import memory_store
db = memory_store.connect()
# Try exact topic match first
row = db.execute(
"SELECT id FROM entries WHERE topic = ? AND status = 'active' LIMIT 1",
(name.lower(),)
"SELECT id FROM entries WHERE topic = ? AND status = 'active' LIMIT 1", (name.lower(),)
).fetchone()
if not row:
# Try text contains entity name
row = db.execute(
"SELECT id FROM entries WHERE text LIKE ? AND status = 'active' LIMIT 1",
(f"%{name}%",)
"SELECT id FROM entries WHERE text LIKE ? AND status = 'active' LIMIT 1", (f"%{name}%",)
).fetchone()
if row:
entry_id = row[0]
@@ -266,13 +269,11 @@ class MemoryConsolidation:
entry_id = None
try:
import memory_store
db = memory_store.connect()
# Strategy 1: exact text match
row = db.execute(
"SELECT id FROM entries WHERE text = ? AND status = 'active' LIMIT 1",
(fact,)
).fetchone()
row = db.execute("SELECT id FROM entries WHERE text = ? AND status = 'active' LIMIT 1", (fact,)).fetchone()
# Strategy 2: entity-based matching (most precise for abstractions)
if not row and entities_json:
@@ -284,16 +285,14 @@ class MemoryConsolidation:
conditions = " AND ".join(["text LIKE ?" for _ in entities[:3]])
params = [f"%{e}%" for e in entities[:3]]
row = db.execute(
f"SELECT id FROM entries WHERE {conditions} AND status = 'active' LIMIT 1",
params
f"SELECT id FROM entries WHERE {conditions} AND status = 'active' LIMIT 1", params
).fetchone()
# Try ANY entity (broader match)
if not row:
conditions = " OR ".join(["text LIKE ?" for _ in entities[:3]])
params = [f"%{e}%" for e in entities[:3]]
row = db.execute(
f"SELECT id FROM entries WHERE {conditions} AND status = 'active' LIMIT 1",
params
f"SELECT id FROM entries WHERE {conditions} AND status = 'active' LIMIT 1", params
).fetchone()
except (json.JSONDecodeError, Exception):
pass
@@ -305,8 +304,7 @@ class MemoryConsolidation:
try:
fts_query = " OR ".join(words)
fts_row = db.execute(
"SELECT rowid FROM entries_fts WHERE entries_fts MATCH ? LIMIT 1",
(fts_query,)
"SELECT rowid FROM entries_fts WHERE entries_fts MATCH ? LIMIT 1", (fts_query,)
).fetchone()
if fts_row:
row = fts_row
@@ -316,8 +314,7 @@ class MemoryConsolidation:
# Strategy 4: substring match (least precise)
if not row:
row = db.execute(
"SELECT id FROM entries WHERE text LIKE ? AND status = 'active' LIMIT 1",
(f"%{fact[:20]}%",)
"SELECT id FROM entries WHERE text LIKE ? AND status = 'active' LIMIT 1", (f"%{fact[:20]}%",)
).fetchone()
if row:
@@ -339,7 +336,9 @@ class MemoryConsolidation:
)
return knowledge
def consolidate_memory(self, memory_text: str, score: float, wash_count: int = 0, entry_id: Optional[int] = None) -> ConsolidatedKnowledge:
def consolidate_memory(
self, memory_text: str, score: float, wash_count: int = 0, entry_id: Optional[int] = None
) -> ConsolidatedKnowledge:
"""Consolidate a memory recall into knowledge."""
# Use drift-aware compression instead of hardcoded truncation
if drift_compression:
@@ -376,8 +375,9 @@ class MemoryConsolidation:
self._save_lessons()
return lesson_obj
def add_connection(self, from_concept: str, to_concept: str,
relationship: str, evidence: List[str] = None) -> Connection:
def add_connection(
self, from_concept: str, to_concept: str, relationship: str, evidence: List[str] = None
) -> Connection:
"""Add a connection between concepts."""
connection = Connection(
id=f"conn-{len(self.connections)}",
@@ -411,8 +411,9 @@ class MemoryConsolidation:
try:
sys.path.insert(0, _SD)
import warm_neuron_store
neurons = warm_neuron_store.list_neurons()
for neuron in (neurons if neuron_limit is None else neurons[:neuron_limit]):
for neuron in neurons if neuron_limit is None else neurons[:neuron_limit]:
knowledge = self.consolidate_neuron(neuron)
existing = [k for k in self.knowledge if k.id == knowledge.id]
if not existing:
@@ -429,11 +430,10 @@ class MemoryConsolidation:
try:
sys.path.insert(0, _SD)
import graph_memory
db = graph_memory.connect()
try:
entities = db.execute(
"SELECT name, type, summary FROM entities WHERE type != 'alias'"
).fetchall()
entities = db.execute("SELECT name, type, summary FROM entities WHERE type != 'alias'").fetchall()
for row in entities:
entity = {"name": row[0], "type": row[1], "summary": row[2] or ""}
knowledge = self.consolidate_graph_entity(entity)
@@ -453,6 +453,7 @@ class MemoryConsolidation:
try:
sys.path.insert(0, _SD)
import abstraction
db = abstraction.connect()
try:
limit_clause = f" LIMIT {abstraction_limit}" if abstraction_limit else ""
@@ -479,6 +480,7 @@ class MemoryConsolidation:
try:
sys.path.insert(0, _SD)
import memory_store
db = memory_store.connect()
try:
rows = db.execute(
@@ -503,7 +505,9 @@ class MemoryConsolidation:
try:
result = subprocess.run(
[sys.executable, os.path.join(_SD, "memory_store.py"), "recall", query, "--json"],
capture_output=True, text=True, timeout=30
capture_output=True,
text=True,
timeout=30,
)
if result.returncode == 0 and result.stdout.strip():
memories = json.loads(result.stdout)
@@ -623,6 +627,7 @@ class MemoryConsolidation:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
parser = argparse.ArgumentParser(description="Episodic-to-semantic memory consolidation")
sub = parser.add_subparsers(dest="cmd")
@@ -640,7 +645,9 @@ def main():
# consolidate-all [--query <text>] [--full]
p_all = sub.add_parser("consolidate-all", help="Consolidate from all memory sources")
p_all.add_argument("--query", default="", help="Optional query for memory recall")
p_all.add_argument("--full", action="store_true", help="Retroactive full sweep — consolidate ALL entries, no limits")
p_all.add_argument(
"--full", action="store_true", help="Retroactive full sweep — consolidate ALL entries, no limits"
)
# status
sub.add_parser("status", help="Show consolidation status")
@@ -701,7 +708,9 @@ def main():
total = sum(counts.values())
print(f" Total: {total} new entries")
status = consolidation.get_status()
print(f" Store: {status['knowledge_count']} knowledge, {status['lessons_count']} lessons, {status['connections_count']} connections")
print(
f" Store: {status['knowledge_count']} knowledge, {status['lessons_count']} lessons, {status['connections_count']} connections"
)
elif args.cmd == "status":
status = consolidation.get_status()
+4 -12
View File
@@ -34,30 +34,22 @@ def rune_home():
def memory_dir():
"""Return the memory data directory."""
return expand(os.environ.get(
"RUNE_MEMORY_DIR",
os.path.join(rune_home(), "memory")))
return expand(os.environ.get("RUNE_MEMORY_DIR", os.path.join(rune_home(), "memory")))
def scripts_dir():
"""Return the scripts directory."""
return expand(os.environ.get(
"RUNE_SCRIPTS_DIR",
os.path.dirname(os.path.abspath(__file__))))
return expand(os.environ.get("RUNE_SCRIPTS_DIR", os.path.dirname(os.path.abspath(__file__))))
def store_db():
"""Return the path to the memory store database."""
return expand(os.environ.get(
"RUNE_STORE_DB",
os.path.join(memory_dir(), "store", "memory.db")))
return expand(os.environ.get("RUNE_STORE_DB", os.path.join(memory_dir(), "store", "memory.db")))
def config_dir():
"""Return the config directory."""
return expand(os.environ.get(
"RUNE_CONFIG_DIR",
os.path.join(rune_home(), "config")))
return expand(os.environ.get("RUNE_CONFIG_DIR", os.path.join(rune_home(), "config")))
def python_bin():
+16 -9
View File
@@ -572,7 +572,15 @@ def add_entry(
return True
def recall(db, query, budget=RECALL_BUDGET, min_score=RECALL_MIN_SCORE, memory_types=None, *, probe_cap: int = RECALL_BUDGET * 2):
def recall(
db,
query,
budget=RECALL_BUDGET,
min_score=RECALL_MIN_SCORE,
memory_types=None,
*,
probe_cap: int = RECALL_BUDGET * 2,
):
"""Hybrid recall: dense + BM25 fused by RRF, metadata-aware.
Returns (entries, scores) — the top `budget` active entries. If nothing
@@ -714,7 +722,7 @@ def recall(db, query, budget=RECALL_BUDGET, min_score=RECALL_MIN_SCORE, memory_t
except Exception:
logger.debug("recall failed", exc_info=True)
if _expanded_hits:
_ids = list(_expanded_hits)[: probe_cap]
_ids = list(_expanded_hits)[:probe_cap]
_ph = ",".join("?" * len(_ids))
_rows = db.execute(
f"SELECT rowid, bm25(entries_fts) AS score FROM entries_fts WHERE rowid IN ({_ph})", _ids
@@ -1140,7 +1148,8 @@ def cached_recall(db, query, budget=RECALL_BUDGET, min_score=RECALL_MIN_SCORE, m
recall_db = shard_db if shard_db else db
except Exception:
recall_db = db
else: recall_db = db
else:
recall_db = db
# P1-03 (#26): the consolidation shortcut injection is REMOVED. Every
# query — broad or specific, consolidated or not — now competes under the
@@ -1381,8 +1390,7 @@ def reindex_entries(db, dry_run=False):
db.execute("CREATE VIRTUAL TABLE entries_fts USING fts5(text, content='', tokenize='porter unicode61')")
except Exception:
logger.debug("reindex_entries failed", exc_info=True)
rows = db.execute(
"SELECT id, text FROM entries WHERE status='active'").fetchall()
rows = db.execute("SELECT id, text FROM entries WHERE status='active'").fetchall()
vec_missing = 0
fts_missing = 0
for r in rows:
@@ -1671,8 +1679,7 @@ def _extract_epub(path):
return "\n\n".join(parts)
except Exception:
logger.debug("_extract_epub failed", exc_info=True)
r = subprocess.run(["ebook2text", path], capture_output=True,
text=True, timeout=60)
r = subprocess.run(["ebook2text", path], capture_output=True, text=True, timeout=60)
return r.stdout
@@ -1702,8 +1709,7 @@ def mine_file(db, path, wing="", room="", chunk_size=1500):
except Exception:
# fallback: legacy extractor if the upgraded one is unavailable
logger.debug("mine_file failed", exc_info=True)
r = subprocess.run(["pdftotext", "-layout", path, "-"],
capture_output=True, text=True, timeout=60)
r = subprocess.run(["pdftotext", "-layout", path, "-"], capture_output=True, text=True, timeout=60)
text = r.stdout
elif ext in (".epub",):
# EPUB = zip of XHTML. Robust extraction: unzip, read spine
@@ -1995,6 +2001,7 @@ def recall_with_associations(db, query, budget=RECALL_BUDGET, min_score=RECALL_M
"temperature": round(temp, 2),
"relevance": round(relevance, 4),
"via_association": True,
"mode": "association_merged",
}
)
base.sort(key=lambda r: -r["relevance"])
+45 -29
View File
@@ -32,31 +32,31 @@ QUEUE_FILE = os.path.join(QUEUE_DIR, "queue.json")
# Task addition patterns
TASK_ADDITION_PATTERNS = [
r'\balso\b',
r'\band can you\b',
r'\bplus\b',
r'\badditionally\b',
r'\bafter that\b',
r'\bthen\b',
r'\balso need\b',
r'\balso want\b',
r'\bcan you also\b',
r'\bcan you add\b',
r'\bcan you update\b',
r'\bcan you fix\b',
r'\bcan you create\b',
r'\bcan you implement\b',
r'\bcan you build\b',
r'\bmake sure\b',
r'\bdon\'t forget\b',
r'\bremember to\b',
r'\binclude\b',
r'\badd\b.*\bto\b',
r'\bupdate\b.*\bto\b',
r'\bfix\b.*\bto\b',
r'\bcreate\b.*\bto\b',
r'\bimplement\b.*\bto\b',
r'\bbuild\b.*\bto\b',
r"\balso\b",
r"\band can you\b",
r"\bplus\b",
r"\badditionally\b",
r"\bafter that\b",
r"\bthen\b",
r"\balso need\b",
r"\balso want\b",
r"\bcan you also\b",
r"\bcan you add\b",
r"\bcan you update\b",
r"\bcan you fix\b",
r"\bcan you create\b",
r"\bcan you implement\b",
r"\bcan you build\b",
r"\bmake sure\b",
r"\bdon\'t forget\b",
r"\bremember to\b",
r"\binclude\b",
r"\badd\b.*\bto\b",
r"\bupdate\b.*\bto\b",
r"\bfix\b.*\bto\b",
r"\bcreate\b.*\bto\b",
r"\bimplement\b.*\bto\b",
r"\bbuild\b.*\bto\b",
]
@@ -64,9 +64,11 @@ TASK_ADDITION_PATTERNS = [
# DATA STRUCTURES
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass
class QueuedMessage:
"""A queued message for task addition."""
id: str
message: str
timestamp: str
@@ -81,6 +83,7 @@ class QueuedMessage:
# QUEUE MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════════════
class MessageQueue:
"""Manages the message queue for task additions."""
@@ -117,21 +120,33 @@ class MessageQueue:
confidence += 0.3
# Check for imperative verbs
imperative_verbs = ['add', 'update', 'fix', 'create', 'implement', 'build',
'make', 'change', 'remove', 'delete', 'refactor', 'migrate']
imperative_verbs = [
"add",
"update",
"fix",
"create",
"implement",
"build",
"make",
"change",
"remove",
"delete",
"refactor",
"migrate",
]
for verb in imperative_verbs:
if message_lower.startswith(verb):
signals.append(f"Imperative verb: {verb}")
confidence += 0.4
# Check for file references
file_refs = re.findall(r'\b\w+\.\w+\b', message)
file_refs = re.findall(r"\b\w+\.\w+\b", message)
if file_refs:
signals.append(f"File references: {', '.join(file_refs[:3])}")
confidence += 0.2
# Check for "also" at the start
if message_lower.startswith('also'):
if message_lower.startswith("also"):
signals.append("Starts with 'also'")
confidence += 0.5
@@ -230,6 +245,7 @@ class MessageQueue:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
if len(sys.argv) < 2:
print("Usage: message_queue.py [add|list|clear|approve|reject|status]")
+1 -3
View File
@@ -77,9 +77,7 @@ class ToolCallWidget(Vertical):
if arguments:
text.append("\n")
text.append("result: ", style="dim")
text.append(
json.dumps(result, sort_keys=True, ensure_ascii=False, default=str)
)
text.append(json.dumps(result, sort_keys=True, ensure_ascii=False, default=str))
return text
def compose(self) -> ComposeResult:
+31 -10
View File
@@ -47,6 +47,7 @@ def now_iso():
# PRIMITIVE 1: register — a plugin declares itself.
# ---------------------------------------------------------------------------
def register(manifest):
"""Register a plugin from its manifest.
@@ -112,6 +113,7 @@ def unregister(name):
# PRIMITIVE 2: resolve — find the best plugin for a capability.
# ---------------------------------------------------------------------------
def resolve(capability):
"""Find the best plugin for a given capability.
@@ -144,6 +146,7 @@ def resolve(capability):
# PRIMITIVE 3: fire — execute hooks for an event.
# ---------------------------------------------------------------------------
def fire(event, data=None, timeout=30):
"""Fire all hooks for an event.
@@ -219,12 +222,14 @@ def fire(event, data=None, timeout=30):
# Log the fire event (thread-safe via lock).
with _registry_lock:
registry = _load_registry()
registry["log"].append({
"event": event,
"timestamp": now_iso(),
"hooks_fired": len(results),
"duration_ms": round(total_duration, 1),
})
registry["log"].append(
{
"event": event,
"timestamp": now_iso(),
"hooks_fired": len(results),
"duration_ms": round(total_duration, 1),
}
)
registry["log"] = registry["log"][-100:]
_save_registry(registry)
@@ -240,6 +245,7 @@ def fire(event, data=None, timeout=30):
# Discovery — scan directory and register all plugins.
# ---------------------------------------------------------------------------
def discover():
"""Scan the scripts directory and register all plugins with PLUGIN dicts.
@@ -303,7 +309,7 @@ def _extract_manifest(fpath):
return None
# Extract the dict string and parse with ast.literal_eval.
dict_str = "\n".join(lines[start:end + 1])
dict_str = "\n".join(lines[start : end + 1])
# Remove the "PLUGIN = " prefix.
if "=" in dict_str:
dict_str = dict_str.split("=", 1)[1].strip()
@@ -318,6 +324,7 @@ def _extract_manifest(fpath):
# Status — what's registered, what's active.
# ---------------------------------------------------------------------------
def status():
"""Get the full status of the microkernel."""
registry = _load_registry()
@@ -339,6 +346,7 @@ def status():
# Registry persistence.
# ---------------------------------------------------------------------------
def _load_registry():
"""Load the registry from disk."""
try:
@@ -358,6 +366,7 @@ def _save_registry(registry):
# CLI.
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="Minimal plugin dispatch engine")
parser.add_argument("cmd", choices=["register", "unregister", "resolve", "fire", "discover", "status"])
@@ -390,7 +399,11 @@ def main():
print("Error: --capability required")
sys.exit(1)
result = resolve(args.capability)
print(json.dumps({"capability": args.capability, "provider": result}, indent=2) if args.json else f"{args.capability} → {result}")
print(
json.dumps({"capability": args.capability, "provider": result}, indent=2)
if args.json
else f"{args.capability} → {result}"
)
elif args.cmd == "fire":
if not args.event:
@@ -398,7 +411,11 @@ def main():
sys.exit(1)
data = json.loads(args.data) if args.data else None
result = fire(args.event, data=data)
print(json.dumps(result, indent=2) if args.json else f"Fired {args.event}: {result['hooks_fired']} hooks in {result['total_duration_ms']}ms")
print(
json.dumps(result, indent=2)
if args.json
else f"Fired {args.event}: {result['hooks_fired']} hooks in {result['total_duration_ms']}ms"
)
elif args.cmd == "discover":
result = discover()
@@ -406,7 +423,11 @@ def main():
elif args.cmd == "status":
result = status()
print(json.dumps(result, indent=2) if args.json else f"Kernel: {result['active_plugins']}/{result['total_plugins']} active")
print(
json.dumps(result, indent=2)
if args.json
else f"Kernel: {result['active_plugins']}/{result['total_plugins']} active"
)
if __name__ == "__main__":
+55 -48
View File
@@ -146,6 +146,7 @@ def is_moe_model(model_name: str) -> bool:
name_lower = model_name.lower()
return any(pattern in name_lower for pattern in MOE_MODEL_PATTERNS)
# Task type → preferred model tier
TASK_ROUTING = {
"conversation": "local-light",
@@ -167,13 +168,13 @@ def _now():
def _clean_model_name(name: str) -> str:
"""Extract meaningful model name from repository path.
Examples:
hf.co/DavidAU/Qwen3.6-27B-Fable-Fusion-711-Uncensored-Heretic-NM-DAU-NEO-MAX-MTP-GGUF:IQ2_M
-> Qwen3.6 27B
qwen2.5:3b -> qwen2.5:3b (already clean)
mimo-v2.5-pro -> mimo-v2.5-pro (already clean)
"""
# If it's a HuggingFace repo path, extract the model name
@@ -195,7 +196,7 @@ def _clean_model_name(name: str) -> str:
# First, try to match family + size directly (e.g., Qwen3.6-27B, gemma-9b)
# Family can contain letters and digits (e.g., Qwen3.6, Llama3)
# Size is like "27B" or "8B" (case insensitive)
match = re.search(r'([A-Za-z]+[\d.]*)-?(\d+[BMb])', model_part, re.IGNORECASE)
match = re.search(r"([A-Za-z]+[\d.]*)-?(\d+[BMb])", model_part, re.IGNORECASE)
if match:
family = match.group(1)
size = match.group(2).upper()
@@ -203,7 +204,7 @@ def _clean_model_name(name: str) -> str:
# Try to match family + version + size (e.g., Llama-3-8B, gemma-2-9b)
# Family is letters only, version is digits, size is digits+B/M (case insensitive)
match = re.search(r'([A-Za-z]+)-(\d+)-(\d+[BMb])', model_part, re.IGNORECASE)
match = re.search(r"([A-Za-z]+)-(\d+)-(\d+[BMb])", model_part, re.IGNORECASE)
if match:
family = match.group(1)
version = match.group(2)
@@ -237,16 +238,18 @@ def list_models():
for m in local_models:
raw_name = m["name"]
clean_name = _clean_model_name(raw_name)
models.append({
"name": raw_name, # Keep original for selection
"display_name": clean_name, # Clean name for display
"size_gb": round(m.get("size", 0) / (1024**3), 1),
"family": m.get("details", {}).get("family", "unknown"),
"parameters": m.get("details", {}).get("parameter_size", "unknown"),
"capabilities": MODEL_CAPABILITIES.get(raw_name, {}).get("strengths", []),
"tier": MODEL_CAPABILITIES.get(raw_name, {}).get("tier", "unknown"),
"provider": "local",
})
models.append(
{
"name": raw_name, # Keep original for selection
"display_name": clean_name, # Clean name for display
"size_gb": round(m.get("size", 0) / (1024**3), 1),
"family": m.get("details", {}).get("family", "unknown"),
"parameters": m.get("details", {}).get("parameter_size", "unknown"),
"capabilities": MODEL_CAPABILITIES.get(raw_name, {}).get("strengths", []),
"tier": MODEL_CAPABILITIES.get(raw_name, {}).get("tier", "unknown"),
"provider": "local",
}
)
except Exception as e:
print(f"Warning: Ollama not available: {e}", file=sys.stderr) # Log if Ollama not available
@@ -267,8 +270,8 @@ def _get_cloud_models():
with open(config_path) as f:
raw = f.read()
# Strip // comments and trailing commas for JSONC compatibility
raw = re.sub(r'//.*?$', '', raw, flags=re.MULTILINE)
raw = re.sub(r',\s*([}\]])', r'\1', raw)
raw = re.sub(r"//.*?$", "", raw, flags=re.MULTILINE)
raw = re.sub(r",\s*([}\]])", r"\1", raw)
config = json.loads(raw)
providers = config.get("provider", {})
@@ -278,15 +281,17 @@ def _get_cloud_models():
provider_models = provider_config.get("models", {})
for model_id, model_config in provider_models.items():
cloud_models.append({
"name": f"{provider_name}/{model_id}",
"size_gb": 0,
"family": "cloud",
"parameters": "cloud",
"capabilities": ["complex-reasoning", "coding", "analysis"],
"tier": "cloud",
"provider": provider_name,
})
cloud_models.append(
{
"name": f"{provider_name}/{model_id}",
"size_gb": 0,
"family": "cloud",
"parameters": "cloud",
"capabilities": ["complex-reasoning", "coding", "analysis"],
"tier": "cloud",
"provider": provider_name,
}
)
except Exception as e:
print(f"Warning: Could not load cloud models: {e}", file=sys.stderr)
@@ -303,14 +308,12 @@ def health_check():
continue
try:
start = time.time()
data = json.dumps({
"model": m["name"],
"prompt": "test",
"stream": False,
"options": {"num_predict": 1, "num_ctx": 8192}
}).encode()
req = urllib.request.Request(f"{OLLAMA_URL}/api/generate", data=data,
headers={"Content-Type": "application/json"})
data = json.dumps(
{"model": m["name"], "prompt": "test", "stream": False, "options": {"num_predict": 1, "num_ctx": 8192}}
).encode()
req = urllib.request.Request(
f"{OLLAMA_URL}/api/generate", data=data, headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=10) as resp:
resp.read()
latency = int((time.time() - start) * 1000)
@@ -351,14 +354,17 @@ def generate(prompt, model=None, max_tokens=500, temperature=0.7):
try:
start = time.time()
data = json.dumps({
"model": model,
"prompt": prompt,
"stream": False,
"options": {"temperature": temperature, "num_predict": max_tokens, "num_ctx": 8192}
}).encode()
req = urllib.request.Request(f"{OLLAMA_URL}/api/generate", data=data,
headers={"Content-Type": "application/json"})
data = json.dumps(
{
"model": model,
"prompt": prompt,
"stream": False,
"options": {"temperature": temperature, "num_predict": max_tokens, "num_ctx": 8192},
}
).encode()
req = urllib.request.Request(
f"{OLLAMA_URL}/api/generate", data=data, headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=60) as resp:
result = json.loads(resp.read())
latency = int((time.time() - start) * 1000)
@@ -379,6 +385,7 @@ def handle_hook(event, data=None):
def main():
import argparse
ap = argparse.ArgumentParser(description="Model connection layer")
ap.add_argument("cmd", choices=["list", "health", "route", "generate", "hook"])
ap.add_argument("arg", nargs="?", default="")
@@ -399,7 +406,9 @@ def main():
if "error" in m:
print(f" Error: {m['error']}")
else:
print(f" [{m['tier']:20}] {m['name'][:40]:40} {m['size_gb']:5.1f}GB {', '.join(m['capabilities'][:3])}")
print(
f" [{m['tier']:20}] {m['name'][:40]:40} {m['size_gb']:5.1f}GB {', '.join(m['capabilities'][:3])}"
)
elif args.cmd == "health":
results = health_check()
@@ -415,15 +424,13 @@ def main():
elif args.cmd == "route":
task_type = args.arg or "conversation"
result = route_model(task_type)
print(json.dumps(result, indent=2) if args.json else
f"Route: {result['model']} ({result['reason']})")
print(json.dumps(result, indent=2) if args.json else f"Route: {result['model']} ({result['reason']})")
elif args.cmd == "generate":
if not args.arg:
print("usage: model_connect.py generate '<prompt>'")
return
result = generate(args.arg, model=args.model, max_tokens=args.max_tokens,
temperature=args.temperature)
result = generate(args.arg, model=args.model, max_tokens=args.max_tokens, temperature=args.temperature)
if args.json:
print(json.dumps(result, indent=2))
elif "error" in result:
@@ -451,5 +458,5 @@ PLUGIN = {
"dependencies": [],
"provides": ["model_connect"],
"priority": 1,
"auto_activate": True
"auto_activate": True,
}
+27 -8
View File
@@ -33,9 +33,11 @@ OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
# MODEL PROFILE
# ═══════════════════════════════════════════════════════════════════════════════
@dataclass
class ModelProfile:
"""Profile for a specific model — how to work with it."""
name: str # Ollama model name
display_name: str = ""
family: str = "" # qwen, llama, mistral, etc.
@@ -176,6 +178,7 @@ FAMILY_KNOWLEDGE = {
# PROFILE MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════════════
def get_profile_path(model_name: str) -> str:
"""Get the file path for a model's profile."""
# Sanitize model name for filesystem
@@ -218,7 +221,8 @@ def detect_family(model_name: str) -> str:
def detect_parameters(model_name: str) -> str:
"""Extract parameter count from model name."""
import re
match = re.search(r'(\d+\.?\d*)[bB]', model_name)
match = re.search(r"(\d+\.?\d*)[bB]", model_name)
if match:
return f"{match.group(1)}B"
return "unknown"
@@ -227,9 +231,18 @@ def detect_parameters(model_name: str) -> str:
def detect_quantization(model_name: str) -> str:
"""Extract quantization level from model name."""
import re
patterns = [
r'(Q\d+_\w+)', r'(IQ\d+_\w+)', r'(q\d+_\w+)', r'(iq\d+_\w+)',
r'(GGUF)', r'(gguf)', r'(FP16)', r'(FP32)', r'(INT8)', r'(INT4)',
r"(Q\d+_\w+)",
r"(IQ\d+_\w+)",
r"(q\d+_\w+)",
r"(iq\d+_\w+)",
r"(GGUF)",
r"(gguf)",
r"(FP16)",
r"(FP32)",
r"(INT8)",
r"(INT4)",
]
for pattern in patterns:
match = re.search(pattern, model_name)
@@ -294,10 +307,13 @@ def scan_all_models():
profiles.append(existing)
else:
# Build new profile
profile = build_profile_from_ollama(name, {
"display_name": name,
"size_gb": round(model.get("size", 0) / (1024**3), 1),
})
profile = build_profile_from_ollama(
name,
{
"display_name": name,
"size_gb": round(model.get("size", 0) / (1024**3), 1),
},
)
save_profile(profile)
profiles.append(profile)
@@ -351,6 +367,7 @@ def get_prompt_context(model_name: str) -> str:
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
if len(sys.argv) < 2:
print("Usage: model_profiles.py [scan|show|list]")
@@ -405,7 +422,9 @@ def main():
name = f[:-5].replace("_", "/")
profile = load_profile(name)
if profile:
print(f" {profile.name} ({profile.family}, {profile.parameters}) — {profile.interactions} interactions")
print(
f" {profile.name} ({profile.family}, {profile.parameters}) — {profile.interactions} interactions"
)
else:
print(f"Unknown command: {cmd}")
+4 -3
View File
@@ -19,6 +19,7 @@ from typing import Dict, List, Optional
@dataclass
class ModelProfile:
"""Profile for a cognitive resource."""
id: str # Unique identifier
provider: str # ollama, openai, etc.
tier: str # T0-T4
@@ -130,9 +131,9 @@ class ModelRegistry:
"""Get all profiles for a tier."""
return [p for p in self.profiles.values() if p.tier == tier and p.enabled]
def get_cheapest_capable(self, requires_tools: bool = False,
requires_structured: bool = False,
requires_reasoning: bool = False) -> Optional[ModelProfile]:
def get_cheapest_capable(
self, requires_tools: bool = False, requires_structured: bool = False, requires_reasoning: bool = False
) -> Optional[ModelProfile]:
"""Get the cheapest capable model."""
capable = []
for profile in self.profiles.values():
+28 -21
View File
@@ -37,6 +37,7 @@ import memory_env
@dataclass
class NightModeState:
"""Night mode state."""
active: bool = False
started_at: Optional[str] = None
duration_hours: float = 8.0
@@ -208,12 +209,13 @@ class NightMode:
"""Run sleep processes (memory consolidation)."""
try:
import subprocess
# Run sleep-time script
result = subprocess.run(
[sys.executable, os.path.join(memory_env.scripts_dir(), "sleep_time.py")],
capture_output=True,
text=True,
timeout=300
timeout=300,
)
if result.returncode == 0:
self.state.improvements.append("Memory consolidation completed")
@@ -238,18 +240,20 @@ class NightMode:
# Attempt LLM-powered evaluation via local Ollama
try:
import urllib.request
payload = json.dumps({
"model": os.environ.get("NIGHT_MODE_MODEL", "qwen2.5:3b"),
"prompt": question,
"stream": False,
"think": False,
"options": {"temperature": 0.4, "num_predict": 300, "num_ctx": 8192},
}).encode()
payload = json.dumps(
{
"model": os.environ.get("NIGHT_MODE_MODEL", "qwen2.5:3b"),
"prompt": question,
"stream": False,
"think": False,
"options": {"temperature": 0.4, "num_predict": 300, "num_ctx": 8192},
}
).encode()
ollama_url = os.environ.get("OLLAMA_URL", "http://localhost:11434")
req = urllib.request.Request(
f"{ollama_url}/api/generate",
data=payload,
headers={"Content-Type": "application/json"})
f"{ollama_url}/api/generate", data=payload, headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=60) as r:
answer = json.loads(r.read()).get("response", "").strip()
if answer:
@@ -270,24 +274,26 @@ class NightMode:
# Attempt LLM-powered iteration via local Ollama
try:
import urllib.request
prompt = (
"You are Rune, a self-improving AI assistant. "
f"Currently in night mode iteration {self.state.iterations}. "
"Based on recent work, suggest ONE concrete, actionable improvement "
"I can implement right now. Be specific and brief."
)
payload = json.dumps({
"model": os.environ.get("NIGHT_MODE_MODEL", "qwen2.5:3b"),
"prompt": prompt,
"stream": False,
"think": False,
"options": {"temperature": 0.5, "num_predict": 400, "num_ctx": 8192},
}).encode()
payload = json.dumps(
{
"model": os.environ.get("NIGHT_MODE_MODEL", "qwen2.5:3b"),
"prompt": prompt,
"stream": False,
"think": False,
"options": {"temperature": 0.5, "num_predict": 400, "num_ctx": 8192},
}
).encode()
ollama_url = os.environ.get("OLLAMA_URL", "http://localhost:11434")
req = urllib.request.Request(
f"{ollama_url}/api/generate",
data=payload,
headers={"Content-Type": "application/json"})
f"{ollama_url}/api/generate", data=payload, headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=90) as r:
answer = json.loads(r.read()).get("response", "").strip()
if answer:
@@ -358,6 +364,7 @@ def get_report() -> Dict[str, Any]:
def main():
"""CLI entry point."""
import argparse
ap = argparse.ArgumentParser(description="Night mode — autonomous self-improvement loop")
ap.add_argument("cmd", choices=["start", "status", "stop", "report"])
ap.add_argument("--hours", type=float, default=8.0, help="Duration in hours")
+1 -3
View File
@@ -20,9 +20,7 @@ Writer = Callable[[TextIO], None]
def atomic_write(path: Path, writer: Writer) -> None:
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp_name = tempfile.mkstemp(
dir=str(path.parent), prefix=path.name + ".", suffix=".tmp"
)
fd, tmp_name = tempfile.mkstemp(dir=str(path.parent), prefix=path.name + ".", suffix=".tmp")
tmp = Path(tmp_name)
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
+3 -13
View File
@@ -21,13 +21,7 @@ Scenario = Callable[[int], float] # batch index -> seconds per op
def fingerprint() -> dict:
try:
rev = (
subprocess.check_output(
["git", "rev-parse", "HEAD"], stderr=subprocess.DEVNULL
)
.decode()
.strip()
)
rev = subprocess.check_output(["git", "rev-parse", "HEAD"], stderr=subprocess.DEVNULL).decode().strip()
except Exception:
rev = "unknown"
return {
@@ -37,9 +31,7 @@ def fingerprint() -> dict:
}
def run_batches(
scenario: Scenario, batches: int = 5, warmup: int = 10, ops: int = 100
) -> list[list[float]]:
def run_batches(scenario: Scenario, batches: int = 5, warmup: int = 10, ops: int = 100) -> list[list[float]]:
out: list[list[float]] = []
for b in range(batches):
for _ in range(warmup):
@@ -54,9 +46,7 @@ def p95(values: list[float]) -> float:
return ordered[idx]
def evaluate_p95_regression(
baseline: list[float], candidate: list[float], floor_ns: float = 1_000_000.0
) -> bool:
def evaluate_p95_regression(baseline: list[float], candidate: list[float], floor_ns: float = 1_000_000.0) -> bool:
if len(baseline) != len(candidate) or len(baseline) == 0:
raise ValueError("paired batches required")
pairs = [c - b for b, c in zip(baseline, candidate, strict=True)]
+3 -11
View File
@@ -21,25 +21,17 @@ class ElasticBudget:
max_cost: float
def within(self, depth: int, concurrency: int, cost: float) -> bool:
return (
depth <= self.depth
and concurrency <= self.concurrency
and cost <= self.max_cost
)
return depth <= self.depth and concurrency <= self.concurrency and cost <= self.max_cost
class ResourceBroker:
def __init__(self, scheduler: ResourceScheduler):
self._scheduler = scheduler
def acquire(
self, task_signature: str, requirement: dict
) -> ResourceAssignment | None:
def acquire(self, task_signature: str, requirement: dict) -> ResourceAssignment | None:
return self._scheduler.acquire(task_signature, requirement)
def inherit(
self, parent: ResourceAssignment, depth_limit: int = 3
) -> ElasticBudget:
def inherit(self, parent: ResourceAssignment, depth_limit: int = 3) -> ElasticBudget:
provider = _provider_for(parent.resource_id)
return ElasticBudget(
depth=min(depth_limit, 1 + parent_task_depth(parent.task_signature)),
+1 -3
View File
@@ -41,6 +41,4 @@ class BudgetRegistry:
return self._used.get(priority, 0)
def pressure(self) -> list[str]:
return sorted(
p for p, b in self._budgets.items() if self._used.get(p, 0) > b.max_bytes
)
return sorted(p for p, b in self._budgets.items() if self._used.get(p, 0) > b.max_bytes)
+3 -9
View File
@@ -31,13 +31,9 @@ class HotBus:
def publish(self, env: EventEnvelope) -> int:
offset = self._store.append(env)
self._store.flush()
row = next(
(r for o, r in self._store.read(from_offset=offset) if o == offset), None
)
row = next((r for o, r in self._store.read(from_offset=offset) if o == offset), None)
if row is None:
raise DurableEventStore.CorruptEventStoreError(
f"missing event at offset {offset}"
)
raise DurableEventStore.CorruptEventStoreError(f"missing event at offset {offset}")
for fn in self._subscribers:
fn(dict(row))
return offset
@@ -50,9 +46,7 @@ class HotBus:
offsets = list(range(start, start + len(envs)))
rows = [r for _, r in self._store.read(from_offset=start)]
if len(rows) < len(envs):
raise DurableEventStore.CorruptEventStoreError(
f"missing event at offset {start + len(rows)}"
)
raise DurableEventStore.CorruptEventStoreError(f"missing event at offset {start + len(rows)}")
for row in rows:
for fn in self._subscribers:
fn(dict(row))
+1 -3
View File
@@ -40,6 +40,4 @@ class CadenceCursor:
def advance_to(self, offset: int, fingerprint: str) -> None:
self._data = {"offset": int(offset), "fingerprint": fingerprint}
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_text(
json.dumps(self._data, sort_keys=True) + "\n", encoding="utf-8"
)
self.path.write_text(json.dumps(self._data, sort_keys=True) + "\n", encoding="utf-8")
+2 -7
View File
@@ -60,9 +60,7 @@ class _SynthesisWorker:
if not gate.should_run(fp):
return WorkerResult(self.name, self.cadence, 0, 1, True)
gate.record(fp)
sink.submit(
ProposalRecord(worker=self.name, action="synthesize", confidence=0.5)
)
sink.submit(ProposalRecord(worker=self.name, action="synthesize", confidence=0.5))
return WorkerResult(self.name, self.cadence, 1, 0, True)
@@ -99,10 +97,7 @@ class CognitiveRuntime:
)
# Proposals that entered the sink during THIS run and remain pending:
# a skipped pass submits nothing, so an unchanged-evidence run reports 0.
pending = {
cadence: self.sinks[cadence].pending() - before[cadence]
for cadence in self.sinks
}
pending = {cadence: self.sinks[cadence].pending() - before[cadence] for cadence in self.sinks}
return {
"facts": self.reflex.facts(),
"results": [vars(r) for r in results],
+4 -12
View File
@@ -41,13 +41,9 @@ class DelegationPlanner:
"body": f"Stage {stage_name} of {plan_version}.",
}
def _mutation(
self, ordering_key: int, remote_target: str, payload: dict
) -> OutboxRecord:
def _mutation(self, ordering_key: int, remote_target: str, payload: dict) -> OutboxRecord:
payload_hash = _sha256(str(payload))
canon = "|".join(
[self._plan_version, remote_target, str(ordering_key), payload_hash]
)
canon = "|".join([self._plan_version, remote_target, str(ordering_key), payload_hash])
mutation_id = _sha256(canon)[:12]
return OutboxRecord(
mutation_id=mutation_id,
@@ -58,13 +54,9 @@ class DelegationPlanner:
payload_hash=payload_hash,
)
def materialize(
self, approval: ApprovalRecord, stage_names: list[str]
) -> list[OutboxRecord]:
def materialize(self, approval: ApprovalRecord, stage_names: list[str]) -> list[OutboxRecord]:
muts: list[OutboxRecord] = []
muts.append(
self._mutation(0, ISSUE_TARGET, self.parent_payload(self._plan_version))
)
muts.append(self._mutation(0, ISSUE_TARGET, self.parent_payload(self._plan_version)))
for n, stage in enumerate(stage_names, start=1):
muts.append(
self._mutation(
Loaded 100 of 227 files, more files were not shown because too many files have changed in this diff. Show more