From cb91f4eec292555615dcb1dcbfa6320801215d33 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Thu, 14 May 2026 16:59:42 -0400 Subject: [PATCH 01/27] docs(goals): add captain runbooks Co-authored-by: Codex --- docs/goal.md | 249 +++++++++++++++++++++++++++++++++++ docs/upstream-return/goal.md | 228 ++++++++++++++++++++++++++++++++ 2 files changed, 477 insertions(+) create mode 100644 docs/goal.md create mode 100644 docs/upstream-return/goal.md diff --git a/docs/goal.md b/docs/goal.md new file mode 100644 index 00000000..45056612 --- /dev/null +++ b/docs/goal.md @@ -0,0 +1,249 @@ +# Mega 24h Captain Goal + +Short `/goal` capsule: + +```text +Read and execute docs/goal.md as the source-of-truth runbook. Run a 24h autonomous Captain pass on Fearvox/EverOS only. Complete >=30 logged iterations with strict gates, no main/upstream writes, draft PR output, full audit trail, and morning owner handoff. Optimize for owner-mergeable truth, not activity volume. Start with preflight + PR truth reset before edits. +``` + +## Full Captain Prompt v1 + +```text +ROLE: Autonomous 24h Captain for Fearvox/EverOS fork playground. +MODE: 24h non-stop, >30 logged iterations, owner asleep/offline. Optimize for morning owner-mergeable output, not activity theater. + +REPO_OWNER=Fearvox +REPO_NAME=EverOS +UPSTREAM_OWNER=EverMind-AI +UPSTREAM_NAME=EverOS +BASE_BRANCH=main +RUN_BRANCH=mega-24h-curator-$(date +%Y-%m-%d) +OWNER_TIMEZONE=America/Los_Angeles + +================ PRIME GOAL ================ + +By the end of 24h, deliver a curated, owner-reviewable EverOS fork packet that: +1. Repairs or clearly rejects broken sleep-run PRs (#7, #12, queue-shape #21/#22, new dependabot #23). +2. Curates May Agent architecture docs (#16-#22) into a coherent upstream/team-review packet. +3. Produces one clean draft PR or a small stack of draft PRs against Fearvox/EverOS:main. +4. Leaves a complete audit trail so Nolan can merge/reject in <15 minutes. +5. Completes >=30 logged iterations, where each iteration has intent, changed files, commands, gate result, score, and next decision. + +Iteration count is required, but if the code/doc packet becomes mergeable early, later iterations must be verification, review, reduction, evidence, or owner-brief improvement. Do not create random scope just to keep moving. + +================ HARD BOUNDARIES ================ + +H1. Never push to origin/main. +H2. Never write to upstream EverMind-AI/EverOS by git, issues, PRs, settings, labels, Actions, comments, or API. +H3. All GitHub commands must explicitly scope repo: `--repo Fearvox/EverOS`. +H4. Final PRs must be draft unless owner explicitly says otherwise. +H5. Do not force-push except to your own RUN_BRANCH with `--force-with-lease`. +H6. Do not edit `.claude/`, user secrets, or local machine config. +H7. Do not send Slack/Linear/email/external messages. Read/verify only unless fork-scoped mutation is explicitly part of this prompt. +H8. No broad formatting, repo-wide rename, dependency major upgrade, or destructive cleanup. +H9. No secret/local-path/public-surface leaks in docs or PR bodies. +H10. If any hard boundary is violated: stop, write `.planning/mega-run/HARD_FAIL.md`, do not continue. + +================ SETUP ================ + +1. cd repo root. +2. `git fetch --all --prune`. +3. Confirm remotes: + - origin must be `Fearvox/EverOS` + - upstream must be `EverMind-AI/EverOS` +4. Create or switch: + `git checkout -B $RUN_BRANCH origin/main` +5. Create: + - `.planning/mega-run/HEARTBEAT.txt` + - `.planning/mega-run/ITER_LOG.md` + - `.planning/mega-run/SCOREBOARD.md` + - `.planning/mega-run/GATE_RESULTS.md` + - `.planning/mega-run/DECISIONS.md` + - `.planning/mega-run/OWNER_BRIEF.md` +6. Record baseline: + - fork main SHA + - upstream main SHA + - open PR list + - current checks for #7-#23 + - dirty state + - available test/docs commands + +================ ITERATION CONTRACT ================ + +Run at least 30 iterations. Each iteration must append this exact block to `ITER_LOG.md`: + +## Iter - - +- Intent: +- Scope bucket: +- Files touched: +- Commands run: +- Gate result: +- Score delta: +- Evidence: +- Next decision: + +Each iteration must update `HEARTBEAT.txt` with: +` iter= slug= gate=` + +Commit every 1-3 iterations with small messages. Push RUN_BRANCH regularly. + +================ PHASE PLAN ================ + +Phase 0, iters 1-3: Preflight and truth reset +- Re-check #7, #12, #21, #22, #23 live. +- Write PR verdict table. +- Confirm previous sleep-run score inflation and queue-shape issues. +- No feature work until this is logged. + +Phase 1, iters 4-9: Repair broken queue +- Fix #7 link failure or mark close/recreate. +- Fix #12 by changing markdownlint to changed-files-only or baseline-aware; do not lint 1000+ legacy errors. +- Decide #21/#22 draft normalization. +- Triage #23 dependabot safely; no blind dependency merge. +- Gate: red PR count must decrease or be explicitly quarantined. + +Phase 2, iters 10-17: Curate May Agent architecture packet +- Review #16 first as strategy gate. +- Then #17-#22 as one packet. +- Produce `.planning/mega-run/MAY_AGENT_REVIEW.md` with: + - merge order + - contradictions + - missing evidence + - upstream-pitch framing + - what should not be merged +- Optional: consolidate docs into one curated branch/PR if smaller than the existing queue. + +Phase 3, iters 18-23: DX / CI / docs hygiene with proof +- Improve only high-leverage docs/CI surfaces. +- Every docs edit requires link check or explicit skipped reason. +- Every CI edit requires either local syntax validation or Actions result. +- Prefer additive, reversible changes. + +Phase 4, iters 24-28: Reproducibility and owner handoff +- Verify Quick Start commands where feasible. +- Identify real blockers separately from skipped heavy infra. +- Create morning owner flow: + - "merge now" + - "review first" + - "close/rework" + - "defer" +- Keep owner decision under 15 minutes. + +Phase 5, iters 29-32+: Reduction, final review, PR preparation +- Remove noise. +- Split oversized diffs. +- Ensure final PR body is truthful. +- Run final gates. +- Write final reports. + +If 32 iterations complete before 24h, continue with audit/reduction/recheck loops every 30-45 min until 24h or clean exit. + +================ SCORING ================ + +Use strict score accounting. Never bank failing work as full score. + ++3 repaired failing PR with green proof ++3 high-value curated architecture decision with evidence ++2 verified CI/docs improvement ++2 owner review burden reduced materially ++1 useful research/review artifact with citations or live evidence ++1 cleanup that reduces queue noise + +-2 skipped gate without explicit reason +-2 agent collision or duplicated work +-3 unverified mergeability claim +-3 broad diff without clear owner value +-5 red CI banked as success +-5 upstream/main write attempt +-5 secret/path/public-surface leak + +Success requires: +- >=30 logged iterations +- 0 hard violations +- all mandatory gates accounted +- final branch pushed +- draft PR or explicit no-PR rationale +- owner can merge/reject in <15 min + +================ GATES ================ + +Preflight gate: +- `git status --short --branch` +- `git remote -v` +- `gh repo view Fearvox/EverOS` +- fork main SHA +- upstream main SHA + +Per-iteration gate: +- Scope is one bucket only. +- Files touched are listed. +- Commands are listed. +- Failures are classified: introduced / pre-existing / infra-blocked / skipped-with-reason. + +Docs gate: +- Check relative links for touched docs. +- No placeholder URLs. +- No raw local paths/secrets. + +CI gate: +- YAML syntax validated for touched workflows. +- Do not add repo-wide failing checks without baseline/changed-file strategy. + +PR gate: +- Draft PR only. +- Base must be `Fearvox/EverOS:main`. +- PR body includes changed files, tests, risks, rollback. + +Final gate: +- Re-run PR list. +- Re-run sync-failed issue check. +- Confirm fork/main unchanged unless owner merged manually. +- Confirm upstream/main unchanged. +- Write `.planning/mega-run/FINAL_REPORT.md`. + +================ SUBAGENT POLICY ================ + +You may spawn subagents, but captain owns final branch state. + +Allowed roles: +- red-ci-fixer: owns #7/#12 only. +- architecture-reviewer: reads #16-#22, writes review artifact only. +- pr-curator: builds verdict table, no code edits. +- evidence-runner: runs commands/checks, no edits. + +Rules: +- Each subagent gets disjoint file ownership. +- No subagent pushes. +- No subagent edits same file as another. +- Captain reviews all changes before commit. +- If subagents disagree, log decision in `DECISIONS.md`. + +================ EXIT CONDITIONS ================ + +Exit after 24h, or earlier only if: +- >=30 iterations logged +- final PR/draft PR ready +- all gates PASS or explicitly FLAG with owner action +- FINAL_REPORT and OWNER_BRIEF complete + +On exit write: + +`.planning/mega-run/FINAL_REPORT.md` +- verdict: PASS / FLAG / BLOCK +- total iterations +- final score +- PR URLs +- changed files +- commands run +- failed/skipped gates +- owner morning actions + +`.planning/mega-run/OWNER_BRIEF.md` +- 10-line max +- what to merge +- what to review +- what to close +- what is risky + +Start now. First action: preflight gate. Do not write code until preflight and PR truth reset are logged. +``` diff --git a/docs/upstream-return/goal.md b/docs/upstream-return/goal.md new file mode 100644 index 00000000..9c56ea0f --- /dev/null +++ b/docs/upstream-return/goal.md @@ -0,0 +1,228 @@ +# EverOS Upstream Resolution Captain Goal + +Short `/goal` capsule: + +```text +Read and execute docs/upstream-return/goal.md. Run a 24h upstream-resolution pass for Fearvox/EverOS. Fetch all open issues and PRs from EverMind-AI/EverOS live, classify every item, and produce an owner-reviewable return packet. Do not touch upstream, do not push main, do not comment externally. Optimize for maintainer-ready truth, not activity volume. +``` + +## Role + +You are the 24h Upstream Resolution Captain for the Fearvox/EverOS fork. + +Your job is not to "do random fixes." Your job is to turn the full upstream +EverMind-AI/EverOS open issue and PR queue into a precise, owner-reviewable +return strategy, then implement only the highest-leverage fork-side artifacts or +small patches that help multiple upstream items at once. + +## Operating Repositories + +- Working fork: `Fearvox/EverOS` +- Upstream source of truth: `EverMind-AI/EverOS` +- Work only in the current local checkout unless explicitly told otherwise. +- Push only to a dedicated fork branch for this run. + +## Hard Boundaries + +1. Do not push to `origin/main`. +2. Do not push to `EverMind-AI/EverOS`. +3. Do not comment on upstream issues or PRs. +4. Do not close, label, assign, merge, or mark ready upstream items. +5. Do not edit `.claude/`, secrets, local machine config, or credential files. +6. Do not treat old cached GitHub data as truth; fetch live state. +7. Do not mark a PR or issue as resolved from title/body alone. +8. Do not create noisy one-off PRs unless the patch is narrow, verified, and + clearly maps to multiple upstream items. + +## Primary Objective + +Resolve the upstream queue into decisions. + +For every open upstream issue and pull request, assign exactly one disposition: + +- `FIX_IN_FORK` +- `ANSWER_DRAFT` +- `CLOSE_STALE` +- `DUPLICATE_OF` +- `REVIEW_EXISTING_PR` +- `NEEDS_MAINTAINER_DECISION` +- `OUT_OF_SCOPE` + +Each disposition must include evidence and a next action. + +## Required Outputs + +Create or update these files: + +- `docs/upstream-return/ISSUE_MATRIX.md` +- `docs/upstream-return/PR_MATRIX.md` +- `docs/upstream-return/CANONICAL_PROBLEM_FAMILIES.md` +- `docs/upstream-return/UPSTREAM_STRATEGY.md` +- `docs/upstream-return/OWNER_BRIEF.md` +- `docs/upstream-return/FINAL_REPORT.md` + +If implementation work is performed, also add: + +- `docs/upstream-return/VALIDATION.md` + +## Canonical Problem Families + +Classify every issue and PR into one primary family: + +1. Benchmark truth and reproducibility +2. Memory API correctness +3. Memory lifecycle and reliability +4. Integration DX and use cases +5. Infrastructure, security, and provider configuration +6. Stale hygiene and duplicate community PRs +7. Maintainer-only policy or roadmap decision + +## Initial Live Snapshot To Re-Verify + +The previous supervisor snapshot found: + +- Upstream open issues: 52 +- Upstream open PRs: 38 +- Open PR merge states: 32 dirty, 5 blocked, 1 clean +- Issue concentration: methods, use cases, benchmarks + +Do not trust those numbers blindly. Re-fetch before writing. + +## Mandatory First Cycle + +1. Capture local git state and current branch. +2. Fetch live upstream issue and PR state: + - `gh issue list --repo EverMind-AI/EverOS --state open --limit 200 --json ...` + - `gh pr list --repo EverMind-AI/EverOS --state open --limit 200 --json ...` +3. For every PR, inspect file surface: + - `gh pr view --repo EverMind-AI/EverOS --json files,mergeStateStatus,isDraft,baseRefName,headRefName` +4. Write a raw inventory section before any recommendations. +5. Group issues and PRs by problem family. +6. Identify duplicates and likely superseding PRs. +7. Only then decide whether any fork-side patch is worth doing. + +## Issue Matrix Schema + +Each upstream issue row must include: + +- Issue number and URL +- Title +- Labels +- Age / last updated +- Problem family +- Concrete user pain +- Related upstream PRs +- Related issues +- Disposition +- Evidence +- Proposed owner action +- Upstream return priority: P0 / P1 / P2 / P3 + +## PR Matrix Schema + +Each upstream PR row must include: + +- PR number and URL +- Title +- Author +- Base branch +- Head branch +- Merge state +- Check state +- Changed file surface +- Related issues +- Risk class: docs / tests / API / infra / security / broad refactor +- Verdict: mergeable / needs rebase / needs review / duplicate / close +- Evidence +- Proposed owner action + +## Scoring + +Score useful work, not motion: + +- +5 complete issue matrix covering all open upstream issues +- +5 complete PR matrix covering all open upstream PRs +- +5 canonical problem-family synthesis with duplicates and supersession map +- +4 upstream strategy that gives owner a concrete return order +- +4 small verified fork patch that resolves multiple upstream issues +- +3 benchmark reproduction/prompt/config evidence packet +- +3 API contract documentation or schema packet +- +2 answer drafts for high-value upstream questions +- +1 clean owner brief under 20 lines +- -3 claim without live evidence +- -5 upstream/main mutation or public comment without owner approval + +## Recommended Return Strategy + +Prefer a staged upstream return: + +1. Maintainer packet first: + - matrices + - deduplication map + - problem families + - proposed merge/close/rework list +2. Low-risk docs/API contract PR second. +3. Benchmark reproducibility packet third. +4. Code patches only after the queue shape is clear. + +The first artifact should help maintainers answer: "What should we merge, close, +or ask for next?" before asking them to review new code. + +## Candidate High-Leverage Tracks + +### Track A: Benchmark Truth Pack + +Targets likely related to LoCoMo, PersonaMem, HaluMem, prompt/config, raw +outputs, API-vs-local evaluation mismatch, and token accounting. + +Output should identify exact missing evidence, not invent benchmark claims. + +### Track B: Memory API Contract Pack + +Targets search/fetch behavior, `memory_types`, profile support, score +normalization, full episode content, timestamp format, and paper-vs-service +retrieval mismatch. + +Output should separate documented behavior, actual code behavior, and proposed +contract. + +### Track C: Integration DX Pack + +Targets OpenClaw, Chat Agent integration, Codex/plugin questions, Docker/local +provider setup, broken links, and 202 Accepted handling. + +Output should make community integrators faster without promising unsupported +runtime behavior. + +## Exit Conditions + +Stop and write `FINAL_REPORT.md` when any of these is true: + +- All open upstream issues and PRs have a disposition. +- A maintainer packet is ready for owner review. +- A hard boundary would be crossed to continue. +- The run reaches 24h. + +## Final Report Must Include + +- Live counts at start and end +- Files created or changed +- Every output artifact path +- Top 10 upstream actions recommended +- Items not safe to return upstream yet +- Any fork-only experiments that should stay fork-only +- Verification commands run +- Residual risks + +## Owner Brief Shape + +Keep `OWNER_BRIEF.md` under 20 lines: + +- Verdict +- What to return upstream first +- What to close or supersede +- What needs maintainer decision +- What not to touch yet +- Highest-risk PRs/issues +- Suggested next command or PR action + From 56a1c8496c7bcda5817dd89d97f13655e40484f5 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Thu, 14 May 2026 16:59:47 -0400 Subject: [PATCH 02/27] fix(openclaw-plugin): remove compaction hook ownership Co-authored-by: Codex --- .../examples/openclaw-plugin/SKILL.md | 2 +- .../examples/openclaw-plugin/package.json | 3 +++ .../examples/openclaw-plugin/src/engine.js | 20 -------------- .../examples/openclaw-plugin/src/types.js | 17 ------------ .../openclaw-plugin/test/engine.test.js | 26 +++++++++++++++++++ 5 files changed, 30 insertions(+), 38 deletions(-) create mode 100644 methods/EverCore/examples/openclaw-plugin/test/engine.test.js diff --git a/methods/EverCore/examples/openclaw-plugin/SKILL.md b/methods/EverCore/examples/openclaw-plugin/SKILL.md index 9f4f4f2f..1afe15e9 100644 --- a/methods/EverCore/examples/openclaw-plugin/SKILL.md +++ b/methods/EverCore/examples/openclaw-plugin/SKILL.md @@ -75,7 +75,7 @@ Automatic lifecycle behavior: | `bootstrap()` | Session starts | Backend health check and session state init | | `assemble()` | Before each turn | Searches relevant memory and injects it as context | | `afterTurn()` | After each turn | Saves new messages from the turn | -| `compact()` | Compaction check | Participates in token-budget decisions | +| Compaction | Not owned | Leaves host token-budget compaction untouched | | `dispose()` | Session ends | Clears in-memory session state | User-facing result: diff --git a/methods/EverCore/examples/openclaw-plugin/package.json b/methods/EverCore/examples/openclaw-plugin/package.json index 684ddea8..5de9be55 100644 --- a/methods/EverCore/examples/openclaw-plugin/package.json +++ b/methods/EverCore/examples/openclaw-plugin/package.json @@ -4,6 +4,9 @@ "description": "EverOS OpenClaw Plugin — persistent memory through natural conversation", "type": "module", "main": "./index.js", + "scripts": { + "test": "node --test test/*.test.js" + }, "bin": { "everos-install": "./bin/install.js" }, diff --git a/methods/EverCore/examples/openclaw-plugin/src/engine.js b/methods/EverCore/examples/openclaw-plugin/src/engine.js index 00a99f14..0015aac8 100644 --- a/methods/EverCore/examples/openclaw-plugin/src/engine.js +++ b/methods/EverCore/examples/openclaw-plugin/src/engine.js @@ -184,26 +184,6 @@ export function createContextEngine(pluginMeta, pluginConfig, logger) { } }, - async compact({ sessionId, sessionKey, tokenBudget, currentTokenCount }) { - const state = sessionState.get(sessionKey); - if (!state) { - return { ok: true, compacted: false, reason: "no session state" }; - } - - state.savedUpTo = 0; - - const threshold = tokenBudget ? tokenBudget * 0.8 : 8000; - const overBudget = currentTokenCount && currentTokenCount > threshold; - - return { - ok: true, - compacted: false, - reason: overBudget - ? `token count (${currentTokenCount}) exceeds 80% of budget (${tokenBudget}), host should compact` - : "within threshold", - }; - }, - async dispose({ sessionKey } = {}) { if (sessionKey) { sessionState.delete(sessionKey); diff --git a/methods/EverCore/examples/openclaw-plugin/src/types.js b/methods/EverCore/examples/openclaw-plugin/src/types.js index 3db2bc8f..5b78beed 100644 --- a/methods/EverCore/examples/openclaw-plugin/src/types.js +++ b/methods/EverCore/examples/openclaw-plugin/src/types.js @@ -50,25 +50,8 @@ * @property {string} [errorMessage] - Error message if turn failed */ -/** - * @typedef {Object} CompactContext - * @property {Array} messages - Current session messages - * @property {number} tokenCount - Estimated token count of context - * @property {string} [sessionId] - Optional session identifier - */ - -/** - * @typedef {Object} CompactResult - * @property {boolean} shouldCompact - Whether compaction is recommended - * @property {string} reason - Explanation of the decision - * @property {Object} [metadata] - Additional metadata - * @property {string} [metadata.memoryStrategy] - Suggested memory consolidation strategy - * @property {number} [metadata.turnCount] - Turn count at evaluation time - */ - /** * @typedef {Object} ParsedMemoryResponse * @property {Array<{text: string, timestamp: number|string|null}>} episodic - Episodic memories * @property {Array<{text: string, timestamp: number|string|null}>} pending - Recent unconsolidated messages */ - diff --git a/methods/EverCore/examples/openclaw-plugin/test/engine.test.js b/methods/EverCore/examples/openclaw-plugin/test/engine.test.js new file mode 100644 index 00000000..89540885 --- /dev/null +++ b/methods/EverCore/examples/openclaw-plugin/test/engine.test.js @@ -0,0 +1,26 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { createContextEngine } from "../src/engine.js"; + +const pluginMeta = { + id: "evermind-ai-everos", + name: "EverOS Test Engine", + version: "0.0.0-test", +}; + +function createTestEngine() { + const logger = { + info: () => {}, + warn: () => {}, + }; + return createContextEngine(pluginMeta, {}, logger); +} + +test("passive memory engine does not expose a compact capability", () => { + const engine = createTestEngine(); + + assert.equal(engine.info.ownsCompaction, false); + assert.equal(Object.hasOwn(engine, "compact"), false); +}); + From 392ac2ccdd77d0c974158153fa6bbfc820e3254e Mon Sep 17 00:00:00 2001 From: 0xVox Date: Thu, 14 May 2026 17:02:09 -0400 Subject: [PATCH 03/27] docs(upstream-return): capture fork leftover queue Co-authored-by: Codex --- docs/upstream-return/FORK_LEFTOVERS.md | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/upstream-return/FORK_LEFTOVERS.md diff --git a/docs/upstream-return/FORK_LEFTOVERS.md b/docs/upstream-return/FORK_LEFTOVERS.md new file mode 100644 index 00000000..b79b8da4 --- /dev/null +++ b/docs/upstream-return/FORK_LEFTOVERS.md @@ -0,0 +1,45 @@ +# Fork Leftovers + +As of 2026-05-14, `Fearvox/EverOS` still has a fork-side open PR queue that is +mostly clean at the GitHub merge/check level but not yet resolved into owner +decisions. + +## Current Local State + +- Local branch: `main` +- Local divergence: `ahead 7` vs `upstream/main` +- Working tree: clean +- This document is for fork cleanup only, not upstream mutation + +## Decision Table + +| Class | PRs | Recommended action | Why | +| --- | --- | --- | --- | +| Merge now | `#24`, `#27` | Merge after quick owner read | Both are `CLEAN`, have successful docs checks, and are explicit repo-surface improvements rather than speculative backlog work | +| Merge as packet or keep grouped | `#16`-`#22` | Review as one May Agent packet, then merge in order or squash into one curated outcome | The work is coherent, all seven PRs are `CLEAN`, and splitting the review across seven isolated approvals increases owner burden | +| Close after extracting signal | `#26` | Copy any useful review wording into the target thread, then close the PR | This is a draft reply artifact, not durable product/repo state | +| Defer pending explicit dependency policy | `#1`, `#23` | Leave open or close with rationale, but do not merge casually | Both are dependency bumps with no visible validation/check surface in the current fork | +| Triage separately | `#7`-`#15` | Re-check one by one only if they are still intended; otherwise close stale docs/ops drafts aggressively | These are older fork-side sleep-run outputs and are likely superseded by newer queue-shaping work | + +## Merge Order + +1. `#24` — docs gate repair path +2. `#27` — lead bridge operating contract +3. `#16` — May Agent vision gate +4. `#17`-`#22` — rest of the May Agent packet, preferably reviewed as one batch + +## Not Worth Carrying Forward + +- `#26` should not remain open as a pseudo-task queue item. +- `#1` and `#23` should not be merged without explicit validation policy. +- The older sleep-run PR tail should not stay open just because it is clean. + +## Next Operator Move + +Use this sequence: + +1. Merge `#24` +2. Merge `#27` +3. Decide whether `#16`-`#22` stay as seven PRs or collapse into one maintainer packet +4. Close `#26` +5. Sweep `#7`-`#15`, `#1`, and `#23` into explicit `close` or `defer` From e9271e01391810d7021fafca9a47a7d86c28ea6b Mon Sep 17 00:00:00 2001 From: 0xVox Date: Thu, 14 May 2026 17:11:52 -0400 Subject: [PATCH 04/27] docs(upstream-return): add upstream triage packet Classify the live upstream issue and PR queue into maintainer-facing matrices, problem families, strategy, owner brief, and final report. Co-authored-by: Codex --- .../CANONICAL_PROBLEM_FAMILIES.md | 51 +++++++++++++++ docs/upstream-return/FINAL_REPORT.md | 39 ++++++++++++ docs/upstream-return/ISSUE_MATRIX.md | 62 +++++++++++++++++++ docs/upstream-return/OWNER_BRIEF.md | 15 +++++ docs/upstream-return/PR_MATRIX.md | 45 ++++++++++++++ docs/upstream-return/UPSTREAM_STRATEGY.md | 47 ++++++++++++++ 6 files changed, 259 insertions(+) create mode 100644 docs/upstream-return/CANONICAL_PROBLEM_FAMILIES.md create mode 100644 docs/upstream-return/FINAL_REPORT.md create mode 100644 docs/upstream-return/ISSUE_MATRIX.md create mode 100644 docs/upstream-return/OWNER_BRIEF.md create mode 100644 docs/upstream-return/PR_MATRIX.md create mode 100644 docs/upstream-return/UPSTREAM_STRATEGY.md diff --git a/docs/upstream-return/CANONICAL_PROBLEM_FAMILIES.md b/docs/upstream-return/CANONICAL_PROBLEM_FAMILIES.md new file mode 100644 index 00000000..089b34ce --- /dev/null +++ b/docs/upstream-return/CANONICAL_PROBLEM_FAMILIES.md @@ -0,0 +1,51 @@ +# Canonical Problem Families + +Live source: upstream issue/PR inventory fetched on 2026-05-14. + +## 1. Memory API correctness and documentation + +Representative issues: #191, #78, #58, #46, #45, #34, #131. + +Open PRs: #185, #196, #138, #109, #89, #132. + +Maintainer move: choose one canonical v1 API docs patch and one canonical multi-type search patch. Close or rework the old-path/broad PRs after the choice. + +## 2. Memory lifecycle semantics + +Representative issues: #148, #143, #133, #101, #95, #27, #14. + +Open PRs: #129, #106. + +Maintainer move: decide cascade delete, reset, dedup, expiry, status metadata, and session scoping as API contracts before accepting implementation churn. + +## 3. Benchmark reproducibility and evaluation DX + +Representative issues: #195, #127, #88, #87, #73, #56, #41, #31, #22, #3. + +Open PRs: #136, #115. + +Maintainer move: publish a versioned repro matrix with expected runtime/cost, dataset/version pins, and tolerated metric deltas. Fix #127 with a narrow adapter patch. + +## 4. OpenClaw and external integration DX + +Representative issues: #193, #177, #150, #139, #93, #57, #52, #15, #11. + +Open PRs: #211, #202, #189, #128, #86. + +Maintainer move: keep current-tree OpenClaw docs/fixes; avoid merging legacy `methods/evermemos` or `evermemos-openclaw-plugin` paths without a path relevance check. + +## 5. Provider and deployment configuration + +Representative issues: #29, #23, #21, #9, #6, #4, #2, #1. + +Open PRs: #206, #157, #144, #90. + +Maintainer move: define supported provider matrix and official Docker/dependency boundary. Security/env fixes can proceed once DX migration is explicit. + +## 6. Broad cleanup PR backlog + +Representative issues: #50, #48 plus code-quality-only PRs without linked user reports. + +Open PRs: #154, #141, #137, #126, #118, #113, #112, #110, #108, #107, #98, #97, #91. + +Maintainer move: stop reviewing these as independent random cleanup. Pick one narrow bug-linked patch per family, then close duplicates. diff --git a/docs/upstream-return/FINAL_REPORT.md b/docs/upstream-return/FINAL_REPORT.md new file mode 100644 index 00000000..07e4f18d --- /dev/null +++ b/docs/upstream-return/FINAL_REPORT.md @@ -0,0 +1,39 @@ +# Upstream Return Final Report + +Date: 2026-05-14. + +## Verdict + +FLAG: upstream queue is now classified locally, but no upstream mutation was performed and no issue/PR should be marked resolved from this packet alone. + +## What Changed + +Added the required local maintainer packet: + +- `docs/upstream-return/ISSUE_MATRIX.md` covers all 52 open issues. +- `docs/upstream-return/PR_MATRIX.md` covers all 37 open PRs. +- `docs/upstream-return/CANONICAL_PROBLEM_FAMILIES.md` groups recurring problems. +- `docs/upstream-return/UPSTREAM_STRATEGY.md` proposes the maintainer sequence. +- `docs/upstream-return/OWNER_BRIEF.md` gives the short owner handoff. +- `docs/upstream-return/FINAL_REPORT.md` summarizes this pass. + +## Live Evidence Used + +- `gh issue list --repo EverMind-AI/EverOS --state open --limit 200 --json number,title,labels,createdAt,updatedAt,url` returned 52 issues. +- `gh pr list --repo EverMind-AI/EverOS --state open --limit 200 --json number,title,mergeStateStatus,isDraft,baseRefName,headRefName,url` returned 37 PRs. +- Per-PR file surfaces were fetched with `gh pr view ... --json number,title,mergeStateStatus,isDraft,baseRefName,headRefName,files,url`. + +## Key Findings + +- The live PR count is 37, not the older 38 count in the original goal note. +- GitHub reports only PR #128 as `CLEAN`. +- PRs #211, #206, #202, and #185 are `BLOCKED`. +- Most open PRs are `DIRTY` and many overlap by family. +- Several PRs touch legacy-looking paths such as `methods/evermemos/...`; those should not be merged without path relevance review. +- The fastest useful maintainer pass is narrow review of #211, #185, and #202, then duplicate/stale cleanup. + +## Residual Risk + +- This pass did not inspect full PR diffs or run upstream test suites. +- Dispositions are triage recommendations, not maintainer decisions. +- `VALIDATION.md` was not added because this pass did not implement runtime behavior. diff --git a/docs/upstream-return/ISSUE_MATRIX.md b/docs/upstream-return/ISSUE_MATRIX.md new file mode 100644 index 00000000..f22f16d6 --- /dev/null +++ b/docs/upstream-return/ISSUE_MATRIX.md @@ -0,0 +1,62 @@ +# Upstream Issue Matrix + +Live source: `EverMind-AI/EverOS` open issues fetched on 2026-05-14. + +This file assigns every currently open upstream issue to one disposition. It is intentionally a maintainer triage packet, not a claim that the underlying bugs are fixed. + +Disposition vocabulary: `FIX_IN_FORK`, `ANSWER_DRAFT`, `CLOSE_STALE`, `DUPLICATE_OF`, `REVIEW_EXISTING_PR`, `NEEDS_MAINTAINER_DECISION`, `OUT_OF_SCOPE`. + +| Issue | Family | Pain | Related PRs | Disposition | Priority | Evidence / owner action | +|---|---|---|---|---|---|---| +| [#205](https://github.com/EverMind-AI/EverOS/issues/205) answer prompt 确认 | Triage hygiene | unclear prompt/confirmation request | none | ANSWER_DRAFT | P3 | Ask for exact prompt, route, expected/actual behavior; close stale if no repro after maintainer window. | +| [#195](https://github.com/EverMind-AI/EverOS/issues/195) HyperMem stage2 takes >4h | Benchmark runtime | evaluation cost and expected runtime unclear | none | ANSWER_DRAFT | P1 | Needs official runtime envelope, hardware profile, and checkpoint/resume guidance. | +| [#193](https://github.com/EverMind-AI/EverOS/issues/193) Chat Agent + MemSys integration schema | Integration DX | missing recommended tool schema | none | ANSWER_DRAFT | P1 | Provide official search/write tool schema or mark as roadmap. | +| [#191](https://github.com/EverMind-AI/EverOS/issues/191) README memory search API example outdated | Memory API docs | README example points at stale API | [#185](https://github.com/EverMind-AI/EverOS/pull/185), [#196](https://github.com/EverMind-AI/EverOS/pull/196) | REVIEW_EXISTING_PR | P0 | Compare #185 narrow README fix vs #196 broad migration before merging either. | +| [#177](https://github.com/EverMind-AI/EverOS/issues/177) Codex plugins similar to Claude Code | Integration DX | asks whether plugin model exists | none | ANSWER_DRAFT | P3 | Answer with current supported integration path; do not turn into code work without owner roadmap. | +| [#158](https://github.com/EverMind-AI/EverOS/issues/158) profile not supported as expected | Memory behavior | profile behavior unclear | none | ANSWER_DRAFT | P2 | Needs repro data and expected profile lifecycle; likely docs/usage answer first. | +| [#150](https://github.com/EverMind-AI/EverOS/issues/150) OpenClaw plugin config not forwarded | OpenClaw integration | callback receives no config | [#128](https://github.com/EverMind-AI/EverOS/pull/128), [#189](https://github.com/EverMind-AI/EverOS/pull/189), [#202](https://github.com/EverMind-AI/EverOS/pull/202) | REVIEW_EXISTING_PR | P0 | Review current-path fix/docs; avoid old `methods/evermemos` path drift. | +| [#148](https://github.com/EverMind-AI/EverOS/issues/148) DELETE memories does not cascade | Memory lifecycle | delete leaves derived memories | none | NEEDS_MAINTAINER_DECISION | P0 | Requires canonical cascade semantics across episodic/foresight/event_log before patching. | +| [#143](https://github.com/EverMind-AI/EverOS/issues/143) status metadata and session-scoped retrieval | Memory lifecycle | action memories lack status/session scope | none | NEEDS_MAINTAINER_DECISION | P1 | Product/API contract decision; can become scoped design issue. | +| [#139](https://github.com/EverMind-AI/EverOS/issues/139) openclaw新插件bug | OpenClaw integration | plugin bug report overlaps #150 | [#128](https://github.com/EverMind-AI/EverOS/pull/128), [#189](https://github.com/EverMind-AI/EverOS/pull/189), [#202](https://github.com/EverMind-AI/EverOS/pull/202) | REVIEW_EXISTING_PR | P1 | Triage with #150; ask reporter whether same config-forwarding failure. | +| [#133](https://github.com/EverMind-AI/EverOS/issues/133) consolidation drift detection | Memory quality | memory accuracy over time | none | NEEDS_MAINTAINER_DECISION | P1 | Architecture feature; define eval signal before implementation. | +| [#131](https://github.com/EverMind-AI/EverOS/issues/131) full episode not returned | Memory API | GET loses full episode and summary fallback poor | [#132](https://github.com/EverMind-AI/EverOS/pull/132) | REVIEW_EXISTING_PR | P1 | Existing PR is broad/dirty; owner should request narrow rebase or split. | +| [#130](https://github.com/EverMind-AI/EverOS/issues/130) 反馈 | Triage hygiene | vague feedback issue | none | CLOSE_STALE | P4 | Ask for actionable repro once, then close if no concrete request. | +| [#127](https://github.com/EverMind-AI/EverOS/issues/127) BM25/Embedding filenames mismatch | Benchmark correctness | empty retrieval from adapter filename mismatch | [#136](https://github.com/EverMind-AI/EverOS/pull/136) | REVIEW_EXISTING_PR | P0 | Existing PR touches many files; require focused validation or split. | +| [#111](https://github.com/EverMind-AI/EverOS/issues/111) multimodal memory search | Memory API | modality support unclear | none | ANSWER_DRAFT | P2 | Answer current support boundaries and roadmap status. | +| [#101](https://github.com/EverMind-AI/EverOS/issues/101) semantic memory type | Memory architecture | asks for objective/semantic memory | none | NEEDS_MAINTAINER_DECISION | P1 | Requires taxonomy decision across memory types. | +| [#95](https://github.com/EverMind-AI/EverOS/issues/95) dedup and foresight expiry cleanup | Memory lifecycle | duplicate writes and stale foresight | [#129](https://github.com/EverMind-AI/EverOS/pull/129) | REVIEW_EXISTING_PR | P1 | Review #129 but keep semantics explicit: dedup scope, retention, tenant isolation. | +| [#93](https://github.com/EverMind-AI/EverOS/issues/93) Storage failed: Request accepted | Demo/API UX | demo treats 202 Accepted as failure | [#211](https://github.com/EverMind-AI/EverOS/pull/211) | REVIEW_EXISTING_PR | P0 | Small targeted PR exists; verify demo behavior and merge if checks pass. | +| [#88](https://github.com/EverMind-AI/EverOS/issues/88) evaluation supports HaluMem | Benchmark scope | benchmark coverage request | none | NEEDS_MAINTAINER_DECISION | P2 | Needs benchmark roadmap answer. | +| [#87](https://github.com/EverMind-AI/EverOS/issues/87) PersonaMem v1/v2 results | Benchmark reproducibility | published result/repro ambiguity | none | ANSWER_DRAFT | P1 | Provide versioned benchmark instructions and results pointer. | +| [#78](https://github.com/EverMind-AI/EverOS/issues/78) search uses only memory_types[0] | Memory API bug | multi-type search silently ignored | [#89](https://github.com/EverMind-AI/EverOS/pull/89), [#109](https://github.com/EverMind-AI/EverOS/pull/109), [#138](https://github.com/EverMind-AI/EverOS/pull/138) | REVIEW_EXISTING_PR | P0 | Pick one canonical PR; close duplicate multi-type PRs after rebase decision. | +| [#73](https://github.com/EverMind-AI/EverOS/issues/73) Cannot reproduce LoCoMo results | Benchmark reproducibility | paper/result reproduction gap | none | ANSWER_DRAFT | P0 | Needs official repro matrix, data/version pin, and expected tolerance. | +| [#65](https://github.com/EverMind-AI/EverOS/issues/65) retrieval pipeline mismatch with paper | Architecture truth | implementation/paper mismatch | none | NEEDS_MAINTAINER_DECISION | P0 | Owner must decide whether docs errata, implementation alignment, or paper caveat. | +| [#58](https://github.com/EverMind-AI/EverOS/issues/58) search cannot retrieve GET content | Memory API UX | retrieval vs stored memory confusion | none | ANSWER_DRAFT | P1 | Likely search semantics/docs answer; may fold into API docs after #191/#78. | +| [#57](https://github.com/EverMind-AI/EverOS/issues/57) stale Discord links | Docs/community | STARTER_KIT issue entry links dead | [#86](https://github.com/EverMind-AI/EverOS/pull/86) | REVIEW_EXISTING_PR | P1 | Verify current public links; accept narrow docs PR or replace with canonical community link. | +| [#56](https://github.com/EverMind-AI/EverOS/issues/56) token consumption | Benchmark/runtime | cost expectations unclear | none | ANSWER_DRAFT | P2 | Add cost envelope and knobs to docs. | +| [#53](https://github.com/EverMind-AI/EverOS/issues/53) Chinese environment stores English | Memory behavior | language behavior unclear | none | ANSWER_DRAFT | P2 | Answer extraction language behavior and prompt locale settings. | +| [#52](https://github.com/EverMind-AI/EverOS/issues/52) start command needs --longjob | Use-case DX | startup flags unclear | none | ANSWER_DRAFT | P2 | Clarify when `--longjob` is required. | +| [#50](https://github.com/EverMind-AI/EverOS/issues/50) two rrf mode in demo code | Code quality | duplicated/ambiguous RRF mode | [#97](https://github.com/EverMind-AI/EverOS/pull/97), [#141](https://github.com/EverMind-AI/EverOS/pull/141), [#154](https://github.com/EverMind-AI/EverOS/pull/154) | REVIEW_EXISTING_PR | P1 | Select one minimal fix; close overlapping anti-pattern PRs as duplicates. | +| [#48](https://github.com/EverMind-AI/EverOS/issues/48) inconsistent timestamp formats | Data consistency | mixed timestamp formats | [#108](https://github.com/EverMind-AI/EverOS/pull/108), [#110](https://github.com/EverMind-AI/EverOS/pull/110), [#112](https://github.com/EverMind-AI/EverOS/pull/112) | REVIEW_EXISTING_PR | P1 | Need canonical timestamp contract and one PR, not three overlapping changes. | +| [#47](https://github.com/EverMind-AI/EverOS/issues/47) filter memories by assistant | Memory API UX | actor/session filtering need | none | NEEDS_MAINTAINER_DECISION | P2 | API design decision: assistant identity, session scope, tenant scope. | +| [#46](https://github.com/EverMind-AI/EverOS/issues/46) search format differs from docs | Memory API docs | result format mismatch | none | ANSWER_DRAFT | P1 | Fold into README/API docs fix with #191/#58. | +| [#45](https://github.com/EverMind-AI/EverOS/issues/45) normalize search score | Memory API docs | scoring semantics unclear | none | ANSWER_DRAFT | P2 | Document score source and normalization expectations. | +| [#41](https://github.com/EverMind-AI/EverOS/issues/41) standardize evaluation scripts | Benchmark tooling | inconsistent eval commands | none | NEEDS_MAINTAINER_DECISION | P1 | Needs benchmark maintainer owner and command contract. | +| [#34](https://github.com/EverMind-AI/EverOS/issues/34) v1.1.0 search API discussion | Memory API design | public API open discussion | none | NEEDS_MAINTAINER_DECISION | P1 | Use as canonical API design thread; link bug/docs issues under it. | +| [#31](https://github.com/EverMind-AI/EverOS/issues/31) phase III latency and ablation | Benchmark reproducibility | performance/ablation questions | none | ANSWER_DRAFT | P1 | Needs official benchmark note. | +| [#29](https://github.com/EverMind-AI/EverOS/issues/29) OpenAI embeddings/rerank support | Provider config | provider choice unclear | [#144](https://github.com/EverMind-AI/EverOS/pull/144) | NEEDS_MAINTAINER_DECISION | P1 | Decide provider roadmap; #144 is MiniMax, not a direct OpenAI answer. | +| [#27](https://github.com/EverMind-AI/EverOS/issues/27) importance/dedup/hit tracking/decay | Memory lifecycle | memory maintenance feature cluster | none | NEEDS_MAINTAINER_DECISION | P1 | Split into lifecycle roadmap after #95/#143 decisions. | +| [#25](https://github.com/EverMind-AI/EverOS/issues/25) clustering and profile usage | Memory behavior docs | architecture usage unclear | none | ANSWER_DRAFT | P2 | Add/point to architecture explainer. | +| [#23](https://github.com/EverMind-AI/EverOS/issues/23) direct OpenAI provider | Provider config | wants OpenAI API provider | none | NEEDS_MAINTAINER_DECISION | P1 | Roadmap/API-key policy decision. | +| [#22](https://github.com/EverMind-AI/EverOS/issues/22) evaluation environment config | Benchmark tooling | setup broken/unclear | none | ANSWER_DRAFT | P1 | Ask current error if not enough detail; add env checklist if confirmed. | +| [#21](https://github.com/EverMind-AI/EverOS/issues/21) dockerize own service | Infra DX | service packaging gap | [#157](https://github.com/EverMind-AI/EverOS/pull/157), [#90](https://github.com/EverMind-AI/EverOS/pull/90) | NEEDS_MAINTAINER_DECISION | P1 | Decide official Docker support boundary before merging infra PRs. | +| [#16](https://github.com/EverMind-AI/EverOS/issues/16) memorize boundary decision | Memory behavior docs | boundary detection unclear | none | ANSWER_DRAFT | P2 | Explain boundary classifier and tuning knobs. | +| [#15](https://github.com/EverMind-AI/EverOS/issues/15) AI coding applicability | Use-case DX | asks whether coding use case fits | none | ANSWER_DRAFT | P3 | Product/use-case answer, possibly link examples. | +| [#14](https://github.com/EverMind-AI/EverOS/issues/14) delete/reset APIs | Memory lifecycle | chat editing/context clearing requires reset | none | NEEDS_MAINTAINER_DECISION | P0 | Tied to #148 cascade semantics; define API contract first. | +| [#11](https://github.com/EverMind-AI/EverOS/issues/11) preload memories for customer service | Use-case DX | asks seeding/customer-service use case | none | ANSWER_DRAFT | P2 | Answer with supported import/preload path and limitations. | +| [#9](https://github.com/EverMind-AI/EverOS/issues/9) milvus health starting | Infra DX | service health confusion | none | ANSWER_DRAFT | P2 | Add troubleshooting note if still reproducible. | +| [#6](https://github.com/EverMind-AI/EverOS/issues/6) 环境设置 | Infra DX | environment setup trouble | none | ANSWER_DRAFT | P2 | Needs exact env/error; can point at current Quick Start. | +| [#4](https://github.com/EverMind-AI/EverOS/issues/4) local embedding/rerank models | Provider config | local model support unclear | none | NEEDS_MAINTAINER_DECISION | P1 | Provider roadmap and doc answer. | +| [#3](https://github.com/EverMind-AI/EverOS/issues/3) LoCoMo detailed results | Benchmark reproducibility | overlaps #73 | none | DUPLICATE_OF #73 | P2 | Keep #73 as active repro thread; link #3 historical context. | +| [#2](https://github.com/EverMind-AI/EverOS/issues/2) supabase version | Infra/provider | asks for Supabase support | none | NEEDS_MAINTAINER_DECISION | P3 | Roadmap decision. | +| [#1](https://github.com/EverMind-AI/EverOS/issues/1) ES/Milvus optional | Infra DX | optional search components | none | NEEDS_MAINTAINER_DECISION | P1 | Architecture/deployment decision; tie to Docker/provider roadmap. | diff --git a/docs/upstream-return/OWNER_BRIEF.md b/docs/upstream-return/OWNER_BRIEF.md new file mode 100644 index 00000000..fcef1d78 --- /dev/null +++ b/docs/upstream-return/OWNER_BRIEF.md @@ -0,0 +1,15 @@ +# Owner Brief + +1. Live upstream queue on 2026-05-14: 52 open issues, 37 open PRs. +2. Only one open PR is GitHub-clean: #128, but its file path looks legacy and still needs path relevance review. +3. Four PRs are blocked: #211, #206, #202, #185. +4. The highest-value small-review candidates are #211 (#93), #185 (#191), and #202 (#150/#139). +5. #78 should have one canonical multi-memory-type fix; close #89/#109 after choosing #138 or requesting a new narrow patch. +6. #127 is important, but #136 is too broad; request a focused filename mismatch patch with repro. +7. #131 likewise needs a narrow full-episode patch; #132 is too broad. +8. OpenClaw fixes must be checked against current paths; several PRs still touch legacy `methods/evermemos` or plugin-root paths. +9. Delete/reset/cascade memory semantics (#14/#148) need an owner API decision before code. +10. Benchmark reproducibility issues (#73/#3/#195/#87) need an official matrix, not isolated replies. +11. Provider/deployment requests (#29/#23/#21/#4/#1) should become a supported-provider decision. +12. Most cleanup PRs are duplicates; pick one narrow bug-linked cleanup path and close the rest. +13. Recommended next maintainer action: review #211, #185, #202, then publish the duplicate/stale PR closeout policy. diff --git a/docs/upstream-return/PR_MATRIX.md b/docs/upstream-return/PR_MATRIX.md new file mode 100644 index 00000000..4c59bb56 --- /dev/null +++ b/docs/upstream-return/PR_MATRIX.md @@ -0,0 +1,45 @@ +# Upstream PR Matrix + +Live source: `EverMind-AI/EverOS` open PRs fetched on 2026-05-14. GitHub reported 37 open PRs: 1 `CLEAN`, 4 `BLOCKED`, and 32 `DIRTY`. + +This matrix treats `DIRTY` as needing rebase before merge review. `BLOCKED` means GitHub does not currently report the branch as cleanly mergeable; owner review is still required to distinguish checks, conflicts, and policy gates. + +| PR | Merge state | Family | File surface | Related issues | Verdict | Owner action | +|---|---|---|---|---|---|---| +| [#213](https://github.com/EverMind-AI/EverOS/pull/213) docs links to project repos | DIRTY | README docs | `README.md` | none | needs rebase | Rebase against current README or close if superseded by local README restructuring. | +| [#211](https://github.com/EverMind-AI/EverOS/pull/211) handle 202 Accepted in demo store | BLOCKED | Demo/API UX | `methods/EverCore/demo/utils/simple_memory_manager.py` | #93 | needs review | High-value small fix; inspect checks/conflict and merge after demo smoke. | +| [#206](https://github.com/EverMind-AI/EverOS/pull/206) MinIO credentials from env | BLOCKED | Security/config | `methods/EverCore/docker-compose.yaml`, `methods/EverCore/env.template` | infra/security | needs review | Security-positive, but owner must verify default DX and env template migration. | +| [#202](https://github.com/EverMind-AI/EverOS/pull/202) OpenClaw endpoint docs | BLOCKED | OpenClaw docs | `methods/EverCore/examples/openclaw-plugin/SKILL.md` | #150, #139 | needs review | Current-path doc PR; compare against local fork OpenClaw packet before merge. | +| [#196](https://github.com/EverMind-AI/EverOS/pull/196) v0 -> v1 API migration | DIRTY | API/docs migration | broad `methods/evermemos/...` surface | #191 | close/rework | Old path and broad scope; replace with narrow current-tree migration. | +| [#189](https://github.com/EverMind-AI/EverOS/pull/189) OpenClaw plugin call API fail | DIRTY | OpenClaw plugin | `methods/evermemos/examples/openclaw-plugin/src/api.js` | #150, #139 | close/rework | Old path surface; supersede with current `methods/EverCore` plugin work. | +| [#185](https://github.com/EverMind-AI/EverOS/pull/185) README search example | BLOCKED | README docs | `README.md` | #191 | needs review | Prefer this narrow docs fix if conflict is small; compare with #196. | +| [#159](https://github.com/EverMind-AI/EverOS/pull/159) query expansion rewrite | DIRTY | Memory retrieval | `src/agentic_layer/memory_manager.py`, `src/memory_layer/query_expansion.py` | #65/#34 class | needs maintainer decision | Architecture-level retrieval change; needs design review before rebase spend. | +| [#157](https://github.com/EverMind-AI/EverOS/pull/157) production Dockerfile | DIRTY | Infra/Docker | `.dockerignore`, `Dockerfile` | #21 | needs maintainer decision | Decide official Docker support shape first. | +| [#154](https://github.com/EverMind-AI/EverOS/pull/154) anti-pattern cleanup / duplicate RRF | DIRTY | Code quality | demo, biz, repository files | #50 | needs rebase | Candidate canonical cleanup if narrowed; overlaps #97/#141/#137. | +| [#144](https://github.com/EverMind-AI/EverOS/pull/144) MiniMax provider | DIRTY | Provider config | env/provider/test files under `methods/evermemos` | #29/#23 class | close/rework | Old path and provider-specific; decide provider roadmap before patch. | +| [#141](https://github.com/EverMind-AI/EverOS/pull/141) duplicate RRF in demo | DIRTY | Code quality | `demo/utils/simple_memory_manager.py` | #50 | duplicate | Close after selecting #154 or a new narrow PR. | +| [#140](https://github.com/EverMind-AI/EverOS/pull/140) normalize plugin-wrapped content | DIRTY | Memory extraction | extractor + test | memory quality | needs rebase | Potentially useful; requires focused extractor test. | +| [#138](https://github.com/EverMind-AI/EverOS/pull/138) multiple memory types search | DIRTY | Memory API bug | API DTO/controller/service/search files | #78 | needs rebase | Best canonical candidate for #78 if rebased and tested. | +| [#137](https://github.com/EverMind-AI/EverOS/pull/137) Python anti-patterns | DIRTY | Code quality | biz/repository files | code quality | duplicate | Overlaps #154/#126/#112/#110; close or split into targeted lint PR. | +| [#136](https://github.com/EverMind-AI/EverOS/pull/136) BM25/Embedding filename mismatch | DIRTY | Benchmark correctness | broad demo/docs/eval/src/tests | #127 | close/rework | The bug is high priority, but PR is too broad; request focused patch and repro. | +| [#135](https://github.com/EverMind-AI/EverOS/pull/135) AP Memory Agent demo | DIRTY | Demo/use case | demo app, pyproject, lockfile, memorize path | use cases | needs maintainer decision | Large demo + lockfile impact; owner must decide product fit. | +| [#132](https://github.com/EverMind-AI/EverOS/pull/132) full episode param | DIRTY | Memory API | broad demo/docs/eval/src/tests | #131 | close/rework | Too broad for #131; ask for narrow API/test patch. | +| [#129](https://github.com/EverMind-AI/EverOS/pull/129) dedup and foresight cleanup | BLOCKED | Memory lifecycle | cleanup/memorize/repository files | #95 | needs review | Semantically important; require tenant-scope and retention tests before merge. | +| [#128](https://github.com/EverMind-AI/EverOS/pull/128) OpenClaw plugin API compatibility | CLEAN | OpenClaw plugin | `evermemos-openclaw-plugin/*` | #150, #139 | needs review | GitHub-clean, but file path looks legacy; verify relevance before merge. | +| [#126](https://github.com/EverMind-AI/EverOS/pull/126) typos / None / bare except | DIRTY | Code quality | evaluation + biz/repository/tests | code quality | duplicate | Close or harvest tiny fixes into selected cleanup PR. | +| [#124](https://github.com/EverMind-AI/EverOS/pull/124) force_boundary | DIRTY | Memory behavior/API | API DTO/converter/memorize/extractor | #16 class | needs maintainer decision | API contract change; needs owner decision before rebase. | +| [#118](https://github.com/EverMind-AI/EverOS/pull/118) datetime conversion | DIRTY | Data consistency | memory_manager + Mongo base | #48 class | needs rebase | Candidate only if canonical timestamp contract is accepted. | +| [#115](https://github.com/EverMind-AI/EverOS/pull/115) retrieval filename typo | DIRTY | Benchmark correctness | evaluation adapter file | #127 class | duplicate | Likely superseded by #136 or a new focused #127 fix. | +| [#113](https://github.com/EverMind-AI/EverOS/pull/113) bool comparison | DIRTY | Code quality | biz/repository files | code quality | duplicate | Low-risk but overlaps cleanup wave; close or batch. | +| [#112](https://github.com/EverMind-AI/EverOS/pull/112) docstring/bare except/timestamp | DIRTY | Code quality/data consistency | demo, memory manager, biz, repo, prompt | #48 class | duplicate | Overlaps #108/#110/#126/#154. | +| [#110](https://github.com/EverMind-AI/EverOS/pull/110) bare except/ISO timestamp/docstring | DIRTY | Code quality/data consistency | demo, db ops, repo, prompt | #48 class | duplicate | Close after canonical timestamp/cleanup path chosen. | +| [#109](https://github.com/EverMind-AI/EverOS/pull/109) multiple memory_types search | DIRTY | Memory API bug | `src/agentic_layer/memory_manager.py` | #78 | duplicate | Superseded by broader #138 candidate. | +| [#108](https://github.com/EverMind-AI/EverOS/pull/108) ISO 8601 timestamp | DIRTY | Data consistency | demo, docker, db ops, repo, prompts | #48 | duplicate | Too broad; keep timestamp contract then reimplement narrow. | +| [#107](https://github.com/EverMind-AI/EverOS/pull/107) bare except | DIRTY | Code quality | demo, docker, db ops, repo, tests | code quality | duplicate | Close or batch into cleanup PR. | +| [#106](https://github.com/EverMind-AI/EverOS/pull/106) two phase memory extraction | DIRTY | Memory lifecycle | docs/env/worker/memorize/delete service | lifecycle | needs maintainer decision | Architecture change; needs design review. | +| [#98](https://github.com/EverMind-AI/EverOS/pull/98) bare excepts | DIRTY | Code quality | config/demo/docker/db/repo | code quality | duplicate | Close as stale cleanup duplicate. | +| [#97](https://github.com/EverMind-AI/EverOS/pull/97) duplicate RRF | DIRTY | Code quality | config/demo/docker | #50 | duplicate | Superseded by #141/#154 or a new narrow fix. | +| [#91](https://github.com/EverMind-AI/EverOS/pull/91) bare excepts | DIRTY | Code quality | db ops/repo/tests | code quality | duplicate | Close as stale cleanup duplicate. | +| [#90](https://github.com/EverMind-AI/EverOS/pull/90) remove unused Mongo init volume | DIRTY | Infra/Docker | `docker-compose.yaml` | #21/#1 class | needs maintainer decision | Small infra cleanup, but should follow Docker support decision. | +| [#89](https://github.com/EverMind-AI/EverOS/pull/89) multi-memory-type search | DIRTY | Memory API bug | `src/agentic_layer/memory_manager.py` | #78 | duplicate | Superseded by #138 candidate. | +| [#86](https://github.com/EverMind-AI/EverOS/pull/86) STARTER_KIT quick start | DIRTY | Docs/community | `docs/STARTER_KIT.md` | #57 | needs rebase | Rebase and verify links, or replace with narrow link-only PR. | diff --git a/docs/upstream-return/UPSTREAM_STRATEGY.md b/docs/upstream-return/UPSTREAM_STRATEGY.md new file mode 100644 index 00000000..f8b4813e --- /dev/null +++ b/docs/upstream-return/UPSTREAM_STRATEGY.md @@ -0,0 +1,47 @@ +# Upstream Strategy + +Live source: upstream inventory fetched on 2026-05-14. + +## Current Queue Shape + +- Open issues: 52. +- Open PRs: 37. +- PR merge states: 1 `CLEAN`, 4 `BLOCKED`, 32 `DIRTY`. +- The main risk is not lack of patches. The risk is merging stale, duplicated, or old-path patches without resolving maintainer policy. + +## Recommended Maintainer Order + +1. Triage and merge small current-tree fixes: + - #211 for #93 if demo smoke passes. + - #185 for #191 if README conflict is small. + - #202 for OpenClaw docs if it matches the current plugin path. +2. Resolve high-impact API bugs with one selected PR per bug: + - #78: pick #138 or request a new narrow PR; close #89/#109. + - #127: request a focused adapter/fixture fix; #136 is too broad as-is. + - #131: request a narrow full-episode patch; #132 is too broad as-is. +3. Make maintainer decisions before implementation: + - delete/reset/cascade semantics (#14/#148); + - lifecycle/dedup/status/session scope (#95/#143/#27); + - provider/deployment support (#29/#23/#21/#4/#1); + - benchmark reproducibility contract (#73/#3/#195/#87). +4. Sweep duplicated cleanup PRs: + - RRF duplicates: #97/#141/#154. + - timestamp duplicates: #108/#110/#112/#118. + - bare-except/code-quality duplicates: #91/#98/#107/#110/#112/#126/#137/#154. +5. Close or rework old-path PRs: + - `methods/evermemos/...` surfaces should not merge until path relevance is proven. + - `evermemos-openclaw-plugin/*` should be verified against current package layout even when GitHub reports `CLEAN`. + +## Fork Work That Is Worth Doing Locally + +- Build a narrow current-tree patch for #191 if #185 remains blocked. +- Build a narrow current-tree OpenClaw docs/fix patch if #202/#128 are stale or path-wrong. +- Prepare answer drafts for repeated question issues so maintainers can close low-code threads quickly. +- Prepare benchmark repro notes, but do not claim result parity without running the exact benchmark path. + +## What Not To Do + +- Do not merge or push anything to upstream from this fork pass. +- Do not mark issues resolved from title/body alone. +- Do not spend time rebasing every dirty cleanup PR; pick canonical ones first. +- Do not accept old-path patches only because the title matches a live issue. From 0186ab316e6c08182b01af1634fbc57872810543 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Thu, 14 May 2026 23:22:00 -0400 Subject: [PATCH 05/27] feat(everos): add Hermes memory dogfood use case Adds the Hermes EverOS memory provider use case with Raven run packets, SkillHub packet/view tooling, NixOS remote smoke artifacts, and local verification helpers. Co-authored-by: Codex --- use-cases/hermes-everos-memory/README.md | 182 +++++++ use-cases/hermes-everos-memory/__init__.py | 508 ++++++++++++++++++ .../bin/everos-memory.mjs | 160 ++++++ .../bin/mock-openai-compatible.mjs | 253 +++++++++ .../hermes-everos-memory/bin/raven-run.mjs | 245 +++++++++ .../bin/skillhub-mock-api.mjs | 231 ++++++++ .../bin/skillhub-packet.mjs | 295 ++++++++++ .../deploy/nixos/DEPLOY_PACKET.md | 86 +++ .../deploy/nixos/README.md | 128 +++++ .../deploy/nixos/docker-compose.remote.yaml | 180 +++++++ .../nixos/evercore-remote-workhorse.nix | 196 +++++++ .../deploy/nixos/evercore.env.example | 89 +++ .../nixos/scripts/evercore-remote-smoke.sh | 169 ++++++ use-cases/hermes-everos-memory/justfile | 64 +++ use-cases/hermes-everos-memory/package.json | 24 + use-cases/hermes-everos-memory/plugin.yaml | 7 + .../raven/COMMAND_CONTRACT.md | 115 ++++ .../hermes-everos-memory/raven/README.md | 44 ++ .../raven/fixtures/doomsday-run.json | 140 +++++ .../hermes-everos-memory/raven/schema.json | 156 ++++++ .../scripts/check-provider-load.sh | 38 ++ .../scripts/dogfood-smoke.sh | 196 +++++++ .../scripts/install-local.sh | 17 + .../scripts/skillhub-api-smoke.sh | 41 ++ .../skillhub/MVP_IMPLEMENTATION_PLAN.md | 92 ++++ .../hermes-everos-memory/skillhub/README.md | 94 ++++ .../evoagentbench-musician-life-event.json | 25 + .../fixtures/raven-skillhub-sample.json | 20 + .../hermes-everos-memory/skillhub/schema.json | 105 ++++ 29 files changed, 3900 insertions(+) create mode 100644 use-cases/hermes-everos-memory/README.md create mode 100644 use-cases/hermes-everos-memory/__init__.py create mode 100755 use-cases/hermes-everos-memory/bin/everos-memory.mjs create mode 100755 use-cases/hermes-everos-memory/bin/mock-openai-compatible.mjs create mode 100755 use-cases/hermes-everos-memory/bin/raven-run.mjs create mode 100755 use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs create mode 100755 use-cases/hermes-everos-memory/bin/skillhub-packet.mjs create mode 100644 use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md create mode 100644 use-cases/hermes-everos-memory/deploy/nixos/README.md create mode 100644 use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml create mode 100644 use-cases/hermes-everos-memory/deploy/nixos/evercore-remote-workhorse.nix create mode 100644 use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example create mode 100755 use-cases/hermes-everos-memory/deploy/nixos/scripts/evercore-remote-smoke.sh create mode 100644 use-cases/hermes-everos-memory/justfile create mode 100644 use-cases/hermes-everos-memory/package.json create mode 100644 use-cases/hermes-everos-memory/plugin.yaml create mode 100644 use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md create mode 100644 use-cases/hermes-everos-memory/raven/README.md create mode 100644 use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json create mode 100644 use-cases/hermes-everos-memory/raven/schema.json create mode 100755 use-cases/hermes-everos-memory/scripts/check-provider-load.sh create mode 100755 use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh create mode 100755 use-cases/hermes-everos-memory/scripts/install-local.sh create mode 100755 use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh create mode 100644 use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md create mode 100644 use-cases/hermes-everos-memory/skillhub/README.md create mode 100644 use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json create mode 100644 use-cases/hermes-everos-memory/skillhub/fixtures/raven-skillhub-sample.json create mode 100644 use-cases/hermes-everos-memory/skillhub/schema.json diff --git a/use-cases/hermes-everos-memory/README.md b/use-cases/hermes-everos-memory/README.md new file mode 100644 index 00000000..773dcb0f --- /dev/null +++ b/use-cases/hermes-everos-memory/README.md @@ -0,0 +1,182 @@ +# Hermes EverOS Memory Provider + +Hermes memory-provider integration for EverOS. + +This use case makes EverCore available to Hermes through Hermes' native +`MemoryProvider` lifecycle: + +- pre-turn recall with `prefetch` +- post-turn persistence with `sync_turn` and local auto-flush +- explicit memory tools for search, store, health, and flush +- compression/delegation hooks reserved for the next pass + +The provider is intentionally split: + +- `__init__.py` is a thin Hermes interface shim. Hermes memory providers are + loaded as Python classes, so this file stays small and dependency-free. +- `bin/everos-memory.mjs` is the operator/dev CLI used by Bun or Node for + direct smoke tests. +- `scripts/install-local.sh` installs the provider into the active Hermes + profile without activating it. + +## Status + +`v0` targets local EverCore at `http://127.0.0.1:1995`. + +It does not start EverCore for you. Bring EverCore up first: + +```bash +cd methods/EverCore +docker compose up -d +uv sync +uv run python src/run.py --host 127.0.0.1 --port 1995 +``` + +Remote NixOS/workhorse deployment packet: + +- `deploy/nixos/DEPLOY_PACKET.md` +- `deploy/nixos/README.md` +- `deploy/nixos/evercore-remote-workhorse.nix` + +The remote packet keeps EverCore bound to loopback by default and treats CCR as +a client/review lane, not the stateful memory host. + +## Configure + +Environment variables: + +| Variable | Default | Description | +| --- | --- | --- | +| `EVEROS_API_BASE_URL` | `http://127.0.0.1:1995` | EverCore API base URL | +| `EVEROS_USER_ID` | `hermes-user` | User scope for personal/agent memory | +| `EVEROS_AGENT_ID` | `hermes` | Sender id for assistant turns | +| `EVEROS_SEARCH_METHOD` | `hybrid` | EverCore search method | +| `EVEROS_MEMORY_TYPES` | `episodic_memory,profile` | Search memory types | +| `EVEROS_TOP_K` | `5` | Number of memories to recall | +| `EVEROS_AUTO_FLUSH` | `1` | Flush agent memory after writes so recall is immediately searchable | +| `EVEROS_SYNC_INLINE` | `1` | Write/flush synchronously for CLI sessions that exit immediately | + +## Local Smoke + +```bash +just provider-load +just health +just search "Hermes memory provider" +just sync-smoke +just dogfood-smoke +``` + +Equivalent without `just`: + +```bash +bun run bin/everos-memory.mjs health +bun run bin/everos-memory.mjs search "Hermes memory provider" +bun run bin/everos-memory.mjs sync-smoke +``` + +Node 18+ also works: + +```bash +node bin/everos-memory.mjs health +``` + +`just provider-load` is offline and only verifies that Hermes can discover and +load the provider from a temporary profile. + +`just dogfood-smoke` is also offline by default and verifies the Python provider +itself. When EverCore is running, use: + +```bash +just dogfood-smoke health +just dogfood-smoke full +``` + +Remote health smoke: + +```bash +just remote-smoke +just remote-smoke full +``` + +SkillHub packet dogfood: + +```bash +just skillhub-sample +just skillhub-render +just skillhub-api-check +just skillhub-api-smoke +just skillhub-api +just skillhub-from-skill ../../benchmarks/EvoAgentBench/src/skill_evolution/evermemos/skills_sample/MUSICIAN/musician_life_event/SKILL.md +``` + +Raven run packet dogfood: + +```bash +just raven-sample +just raven-render +``` + +Local model-free EverCore dogfood helper: + +```bash +just mock-openai-check +just mock-openai +``` + +Point `LLM_BASE_URL`, `OPENROUTER_BASE_URL`, `VECTORIZE_BASE_URL`, and +`RERANK_BASE_URL` at the local mock when you need to verify the EverCore +store/extract/index/search loop without touching external model providers. + +## Install Into Hermes + +```bash +scripts/install-local.sh +hermes config set memory.provider everos +hermes plugins enable everos +``` + +Or use the interactive selector: + +```bash +hermes memory setup +``` + +Run: + +```bash +hermes memory status +``` + +Expected active status: + +```text +Provider: everos +Plugin: installed +Status: available +``` + +## Safety Notes + +- This provider is local-first and does not require an API key. +- It does not print raw memory payloads during install. +- It uses Hermes profile-scoped config through environment variables and + `plugins.everos-memory` config keys. +- If EverCore is down, the provider reports unavailable and Hermes should + continue without external memory. + +## v0 Contract + +Hermes calls: + +- `initialize(session_id, hermes_home, platform, agent_identity, user_id, ...)` +- `prefetch(query, session_id=...)` +- `sync_turn(user_content, assistant_content, session_id=...)` +- `get_tool_schemas()` +- `handle_tool_call(name, args)` + +EverCore calls: + +- `GET /health` +- `POST /api/v1/memories/search` +- `POST /api/v1/memories/agent` +- `POST /api/v1/memories/agent/flush` diff --git a/use-cases/hermes-everos-memory/__init__.py b/use-cases/hermes-everos-memory/__init__.py new file mode 100644 index 00000000..4c059f43 --- /dev/null +++ b/use-cases/hermes-everos-memory/__init__.py @@ -0,0 +1,508 @@ +"""EverOS memory provider for Hermes. + +This file is intentionally a thin Python shim because Hermes memory providers +are loaded as Python classes. The provider talks to EverCore over HTTP and keeps +all behavior best-effort so memory outages never block Hermes turns. +""" + +from __future__ import annotations + +import json +import os +import threading +import time +import urllib.error +import urllib.request +from typing import Any, Dict, List, Optional + +from agent.memory_provider import MemoryProvider +from tools.registry import tool_error + +DEFAULT_BASE_URL = "http://127.0.0.1:1995" +DEFAULT_MEMORY_TYPES = ["episodic_memory", "profile"] + + +SEARCH_SCHEMA = { + "name": "everos_search", + "description": ( + "Search EverOS/EverCore memory. Use for past session context, user/project " + "preferences, prior decisions, and agent trajectory recall." + ), + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query."}, + "top_k": {"type": "integer", "description": "Max results, default 5."}, + "memory_types": { + "type": "array", + "items": {"type": "string"}, + "description": "Memory types: episodic_memory, profile, agent_memory, raw_message.", + }, + "method": { + "type": "string", + "enum": ["keyword", "vector", "hybrid", "agentic"], + "description": "Retrieval method, default hybrid.", + }, + }, + "required": ["query"], + }, +} + +STORE_SCHEMA = { + "name": "everos_store", + "description": "Store an explicit durable fact or note into EverOS memory.", + "parameters": { + "type": "object", + "properties": { + "content": {"type": "string", "description": "Memory content to store."}, + "role": { + "type": "string", + "enum": ["user", "assistant"], + "description": "Role attribution, default user.", + }, + }, + "required": ["content"], + }, +} + +HEALTH_SCHEMA = { + "name": "everos_health", + "description": "Check whether local EverCore is reachable.", + "parameters": {"type": "object", "properties": {}, "required": []}, +} + +FLUSH_SCHEMA = { + "name": "everos_flush", + "description": "Flush buffered agent messages for the current EverOS user/session.", + "parameters": {"type": "object", "properties": {}, "required": []}, +} + + +class EverOSClient: + def __init__(self, base_url: str, timeout: float = 10.0): + self.base_url = base_url.rstrip("/") + self.timeout = timeout + + def request(self, method: str, path: str, body: Optional[dict] = None) -> dict: + data = None + headers = {"Content-Type": "application/json"} + if body is not None: + data = json.dumps(body).encode("utf-8") + req = urllib.request.Request( + self.base_url + path, + data=data, + headers=headers, + method=method, + ) + with urllib.request.urlopen(req, timeout=self.timeout) as response: # noqa: S310 + raw = response.read().decode("utf-8") + if not raw: + return {} + return json.loads(raw) + + def health(self) -> dict: + return self.request("GET", "/health") + + def search( + self, + *, + query: str, + user_id: str, + method: str, + memory_types: List[str], + top_k: int, + ) -> dict: + return self.request( + "POST", + "/api/v1/memories/search", + { + "query": query, + "method": method, + "memory_types": memory_types, + "top_k": top_k, + "filters": {"user_id": user_id}, + }, + ) + + def add_agent_messages( + self, + *, + user_id: str, + session_id: str, + messages: List[dict], + ) -> dict: + return self.request( + "POST", + "/api/v1/memories/agent", + { + "user_id": user_id, + "session_id": session_id, + "messages": messages, + }, + ) + + def flush_agent(self, *, user_id: str, session_id: str) -> dict: + return self.request( + "POST", + "/api/v1/memories/agent/flush", + {"user_id": user_id, "session_id": session_id}, + ) + + +class EverOSMemoryProvider(MemoryProvider): + def __init__(self): + self._base_url = os.environ.get("EVEROS_API_BASE_URL", DEFAULT_BASE_URL) + self._user_id = os.environ.get("EVEROS_USER_ID", "hermes-user") + self._agent_id = os.environ.get("EVEROS_AGENT_ID", "hermes") + self._search_method = os.environ.get("EVEROS_SEARCH_METHOD", "hybrid") + self._top_k = int(os.environ.get("EVEROS_TOP_K", "5")) + self._auto_flush = os.environ.get("EVEROS_AUTO_FLUSH", "1").lower() not in { + "0", + "false", + "no", + } + self._sync_inline = os.environ.get("EVEROS_SYNC_INLINE", "1").lower() not in { + "0", + "false", + "no", + } + self._memory_types = self._parse_memory_types() + self._client = EverOSClient(self._base_url) + self._session_id = "" + self._prefetch_result = "" + self._prefetch_lock = threading.Lock() + self._prefetch_thread: Optional[threading.Thread] = None + self._sync_thread: Optional[threading.Thread] = None + self._consecutive_failures = 0 + self._breaker_open_until = 0.0 + + @property + def name(self) -> str: + return "everos" + + def is_available(self) -> bool: + try: + status = self._client.health() + return status.get("status") in {"healthy", "ok"} or bool(status) + except Exception: + return False + + def initialize(self, session_id: str, **kwargs) -> None: + self._session_id = session_id + self._user_id = ( + kwargs.get("user_id") + or os.environ.get("EVEROS_USER_ID") + or self._user_id + ) + identity = kwargs.get("agent_identity") or os.environ.get("EVEROS_AGENT_ID") + if identity: + self._agent_id = f"hermes-{identity}" + + def system_prompt_block(self) -> str: + return ( + "# EverOS Memory\n" + "Active. Use EverOS for durable cross-session recall. " + "Use everos_search for explicit lookup and everos_store for durable facts." + ) + + def prefetch(self, query: str, *, session_id: str = "") -> str: + if self._prefetch_thread and self._prefetch_thread.is_alive(): + self._prefetch_thread.join(timeout=2.0) + with self._prefetch_lock: + result = self._prefetch_result + self._prefetch_result = "" + if result: + return result + if not query: + return "" + # First turn after startup has no warmed result yet. Do a small direct + # recall so enabling the provider is immediately visible. + try: + return self._format_prefetch( + self._search(query=query, top_k=min(self._top_k, 3)) + ) + except Exception: + self._record_failure() + return "" + + def queue_prefetch(self, query: str, *, session_id: str = "") -> None: + if not query or self._is_breaker_open(): + return + + def run() -> None: + try: + formatted = self._format_prefetch(self._search(query=query)) + with self._prefetch_lock: + self._prefetch_result = formatted + self._record_success() + except Exception: + self._record_failure() + + self._prefetch_thread = threading.Thread( + target=run, daemon=True, name="everos-prefetch" + ) + self._prefetch_thread.start() + + def sync_turn( + self, user_content: str, assistant_content: str, *, session_id: str = "" + ) -> None: + if self._is_breaker_open(): + return + effective_session = session_id or self._session_id or f"hermes-{int(time.time())}" + now = int(time.time() * 1000) + + def run() -> None: + try: + self._client.add_agent_messages( + user_id=self._user_id, + session_id=effective_session, + messages=[ + { + "role": "user", + "sender_id": self._user_id, + "timestamp": now, + "content": user_content, + }, + { + "role": "assistant", + "sender_id": self._agent_id, + "timestamp": now + 1, + "content": assistant_content, + }, + ], + ) + self._flush_session(effective_session) + self._record_success() + except Exception: + self._record_failure() + + if self._sync_inline: + run() + return + if self._sync_thread and self._sync_thread.is_alive(): + self._sync_thread.join(timeout=2.0) + self._sync_thread = threading.Thread( + target=run, daemon=True, name="everos-sync" + ) + self._sync_thread.start() + + def get_tool_schemas(self) -> List[Dict[str, Any]]: + return [SEARCH_SCHEMA, STORE_SCHEMA, HEALTH_SCHEMA, FLUSH_SCHEMA] + + def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str: + try: + if tool_name == "everos_health": + return json.dumps({"result": self._client.health()}, ensure_ascii=False) + if tool_name == "everos_search": + query = args.get("query", "") + if not query: + return tool_error("Missing required parameter: query") + data = self._search( + query=query, + top_k=int(args.get("top_k") or self._top_k), + method=args.get("method") or self._search_method, + memory_types=args.get("memory_types") or self._memory_types, + ) + return json.dumps(self._compact_search_result(data), ensure_ascii=False) + if tool_name == "everos_store": + content = args.get("content", "") + if not content: + return tool_error("Missing required parameter: content") + role = args.get("role") if args.get("role") in {"user", "assistant"} else "user" + sender_id = self._agent_id if role == "assistant" else self._user_id + session_id = self._session_id or f"hermes-{int(time.time())}" + data = self._client.add_agent_messages( + user_id=self._user_id, + session_id=session_id, + messages=[ + { + "role": role, + "sender_id": sender_id, + "timestamp": int(time.time() * 1000), + "content": content, + } + ], + ) + self._flush_session(session_id) + return json.dumps({"result": "stored", "data": data.get("data")}, ensure_ascii=False) + if tool_name == "everos_flush": + data = self._client.flush_agent( + user_id=self._user_id, + session_id=self._session_id or "", + ) + return json.dumps({"result": "flushed", "data": data.get("data")}, ensure_ascii=False) + except urllib.error.URLError as exc: + self._record_failure() + return tool_error(f"EverOS unavailable: {exc}") + except Exception as exc: + self._record_failure() + return tool_error(f"EverOS tool failed: {exc}") + return tool_error(f"Unknown EverOS tool: {tool_name}") + + def on_pre_compress(self, messages: List[Dict[str, Any]]) -> str: + if not messages: + return "" + tail = [] + for item in messages[-12:]: + role = item.get("role", "") + content = item.get("content", "") + if isinstance(content, str) and content.strip(): + tail.append(f"{role}: {content[:1200]}") + if not tail: + return "" + try: + query = "\n".join(tail)[-3000:] + return self._format_prefetch(self._search(query=query, top_k=5)) + except Exception: + return "" + + def on_memory_write( + self, + action: str, + target: str, + content: str, + metadata: Optional[Dict[str, Any]] = None, + ) -> None: + if action not in {"add", "replace"} or not content: + return + note = f"[Hermes built-in memory:{target}] {content}" + self.sync_turn(note, "Stored in Hermes built-in memory.", session_id=self._session_id) + + def on_delegation( + self, task: str, result: str, *, child_session_id: str = "", **kwargs + ) -> None: + if not task and not result: + return + content = f"Delegated task: {task}\n\nResult: {result}" + self.sync_turn(content, "Delegation observation recorded.", session_id=self._session_id) + + def on_session_end(self, messages: List[Dict[str, Any]]) -> None: + if self._sync_thread and self._sync_thread.is_alive(): + self._sync_thread.join(timeout=3.0) + self._flush_session(self._session_id) + del messages + + def shutdown(self) -> None: + for thread in (self._prefetch_thread, self._sync_thread): + if thread and thread.is_alive(): + thread.join(timeout=3.0) + self._flush_session(self._session_id) + + def get_config_schema(self) -> List[Dict[str, Any]]: + return [ + {"key": "base_url", "description": "EverCore base URL", "default": DEFAULT_BASE_URL}, + {"key": "user_id", "description": "EverOS user id", "default": "hermes-user"}, + {"key": "agent_id", "description": "EverOS agent id", "default": "hermes"}, + {"key": "search_method", "description": "Search method", "default": "hybrid", "choices": ["keyword", "vector", "hybrid", "agentic"]}, + {"key": "top_k", "description": "Prefetch result count", "default": "5"}, + ] + + def save_config(self, values: Dict[str, Any], hermes_home: str) -> None: + # Hermes' generic setup persists memory.provider. Runtime config stays + # env-first for now so profiles, cron, and gateway can scope separately. + del values, hermes_home + + def _search( + self, + *, + query: str, + top_k: Optional[int] = None, + method: Optional[str] = None, + memory_types: Optional[List[str]] = None, + ) -> dict: + return self._client.search( + query=query, + user_id=self._user_id, + method=method or self._search_method, + memory_types=memory_types or self._memory_types, + top_k=top_k or self._top_k, + ) + + def _flush_session(self, session_id: str) -> None: + if not self._auto_flush or not session_id: + return + self._client.flush_agent(user_id=self._user_id, session_id=session_id) + + def _format_prefetch(self, data: dict) -> str: + compact = self._compact_search_result(data) + rows = compact.get("memories", []) + if not rows: + return "" + lines = ["## EverOS Memory"] + for item in rows[: self._top_k]: + title = item.get("subject") or item.get("type") or "memory" + text = item.get("text") or "" + score = item.get("score") + prefix = f"- [{score:.2f}] " if isinstance(score, (int, float)) else "- " + lines.append(f"{prefix}{title}: {text[:600]}") + return "\n".join(lines) + + def _compact_search_result(self, data: dict) -> dict: + payload = data.get("data") or {} + memories = [] + for ep in payload.get("episodes") or []: + memories.append( + { + "type": "episodic_memory", + "subject": ep.get("subject") or "", + "text": ep.get("summary") or ep.get("episode") or "", + "score": ep.get("score"), + "session_id": ep.get("session_id"), + } + ) + for profile in payload.get("profiles") or []: + memories.append( + { + "type": "profile", + "subject": "profile", + "text": json.dumps(profile.get("profile_data") or {}, ensure_ascii=False), + "score": profile.get("score"), + } + ) + agent_memory = payload.get("agent_memory") or {} + for case in agent_memory.get("cases") or []: + memories.append( + { + "type": "agent_case", + "subject": case.get("task_intent") or "agent case", + "text": case.get("approach") or "", + "score": case.get("score"), + "session_id": case.get("session_id"), + } + ) + for skill in agent_memory.get("skills") or []: + memories.append( + { + "type": "agent_skill", + "subject": skill.get("name") or "agent skill", + "text": skill.get("description") or skill.get("content") or "", + "score": skill.get("score"), + } + ) + return {"count": len(memories), "memories": memories} + + def _parse_memory_types(self) -> List[str]: + raw = os.environ.get("EVEROS_MEMORY_TYPES", "") + if not raw: + return list(DEFAULT_MEMORY_TYPES) + return [item.strip() for item in raw.split(",") if item.strip()] + + def _is_breaker_open(self) -> bool: + if self._consecutive_failures < 5: + return False + if time.monotonic() >= self._breaker_open_until: + self._consecutive_failures = 0 + return False + return True + + def _record_success(self) -> None: + self._consecutive_failures = 0 + + def _record_failure(self) -> None: + self._consecutive_failures += 1 + if self._consecutive_failures >= 5: + self._breaker_open_until = time.monotonic() + 120 + + +def register(ctx) -> None: + ctx.register_memory_provider(EverOSMemoryProvider()) diff --git a/use-cases/hermes-everos-memory/bin/everos-memory.mjs b/use-cases/hermes-everos-memory/bin/everos-memory.mjs new file mode 100755 index 00000000..b75fd32b --- /dev/null +++ b/use-cases/hermes-everos-memory/bin/everos-memory.mjs @@ -0,0 +1,160 @@ +#!/usr/bin/env node + +const DEFAULT_BASE_URL = 'http://127.0.0.1:1995'; + +function config() { + return { + baseUrl: (process.env.EVEROS_API_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, ''), + userId: process.env.EVEROS_USER_ID || 'hermes-user', + agentId: process.env.EVEROS_AGENT_ID || 'hermes', + searchMethod: process.env.EVEROS_SEARCH_METHOD || 'hybrid', + memoryTypes: (process.env.EVEROS_MEMORY_TYPES || 'episodic_memory,profile') + .split(',') + .map((item) => item.trim()) + .filter(Boolean), + topK: Number.parseInt(process.env.EVEROS_TOP_K || '5', 10), + }; +} + +async function requestJson(path, options = {}) { + const cfg = config(); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 10000); + try { + const response = await fetch(`${cfg.baseUrl}${path}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...(options.headers || {}), + }, + signal: controller.signal, + }); + const text = await response.text(); + let data = null; + if (text) { + try { + data = JSON.parse(text); + } catch { + data = { raw: text }; + } + } + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${JSON.stringify(data)}`); + } + return data; + } finally { + clearTimeout(timeout); + } +} + +async function health() { + return requestJson('/health'); +} + +async function search(query) { + const cfg = config(); + return requestJson('/api/v1/memories/search', { + method: 'POST', + body: JSON.stringify({ + query, + method: cfg.searchMethod, + memory_types: cfg.memoryTypes, + top_k: Number.isFinite(cfg.topK) ? cfg.topK : 5, + filters: { + user_id: cfg.userId, + }, + }), + }); +} + +async function syncTurn(user, assistant) { + const cfg = config(); + const now = Date.now(); + return requestJson('/api/v1/memories/agent', { + method: 'POST', + body: JSON.stringify({ + user_id: cfg.userId, + session_id: `hermes-smoke-${now}`, + messages: [ + { + role: 'user', + sender_id: cfg.userId, + timestamp: now, + content: user, + }, + { + role: 'assistant', + sender_id: cfg.agentId, + timestamp: now + 1, + content: assistant, + }, + ], + }), + }); +} + +function summarizeSearch(data) { + const payload = data?.data || {}; + const episodes = Array.isArray(payload.episodes) ? payload.episodes : []; + const profiles = Array.isArray(payload.profiles) ? payload.profiles : []; + const cases = Array.isArray(payload.agent_memory?.cases) ? payload.agent_memory.cases : []; + const skills = Array.isArray(payload.agent_memory?.skills) ? payload.agent_memory.skills : []; + return { + episodes: episodes.length, + profiles: profiles.length, + agent_cases: cases.length, + agent_skills: skills.length, + }; +} + +async function main() { + const [command, ...args] = process.argv.slice(2); + + if (!command || command === 'help' || command === '--help' || command === '-h') { + console.log(`Usage: + everos-memory health + everos-memory search + everos-memory sync-smoke + everos-memory self-test`); + return; + } + + if (command === 'health') { + const result = await health(); + console.log(JSON.stringify({ ok: true, status: result?.status || result?.data?.status || 'unknown' }, null, 2)); + return; + } + + if (command === 'search') { + const query = args.join(' ').trim(); + if (!query) throw new Error('search requires a query'); + const result = await search(query); + console.log(JSON.stringify({ ok: true, ...summarizeSearch(result) }, null, 2)); + return; + } + + if (command === 'sync-smoke') { + const result = await syncTurn( + 'Hermes EverOS memory provider smoke test user message.', + 'Hermes EverOS memory provider smoke test assistant response.' + ); + console.log(JSON.stringify({ ok: true, data: result?.data || null }, null, 2)); + return; + } + + if (command === 'self-test') { + const h = await health(); + console.log(JSON.stringify({ health: h?.status || 'ok' })); + const s = await search('Hermes EverOS memory provider'); + console.log(JSON.stringify({ search: summarizeSearch(s) })); + return; + } + + throw new Error(`unknown command: ${command}`); +} + +main().catch((error) => { + console.error(JSON.stringify({ ok: false, error: error.message })); + process.exit(1); +}); + diff --git a/use-cases/hermes-everos-memory/bin/mock-openai-compatible.mjs b/use-cases/hermes-everos-memory/bin/mock-openai-compatible.mjs new file mode 100755 index 00000000..a096b728 --- /dev/null +++ b/use-cases/hermes-everos-memory/bin/mock-openai-compatible.mjs @@ -0,0 +1,253 @@ +#!/usr/bin/env node +import http from 'node:http'; + +const DEFAULT_PORT = Number.parseInt(process.env.MOCK_OPENAI_PORT || '18080', 10); +const DEFAULT_HOST = process.env.MOCK_OPENAI_HOST || '127.0.0.1'; +const DEFAULT_DIMENSIONS = Number.parseInt(process.env.MOCK_OPENAI_DIMENSIONS || '1024', 10); + +function parseArgs(argv) { + const args = { + host: DEFAULT_HOST, + port: DEFAULT_PORT, + check: false, + }; + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + if (arg === '--host') { + args.host = argv[i + 1]; + i += 1; + } else if (arg === '--port') { + args.port = Number.parseInt(argv[i + 1], 10); + i += 1; + } else if (arg === '--check') { + args.check = true; + } else if (arg === '-h' || arg === '--help') { + printHelp(); + process.exit(0); + } else { + throw new Error(`unknown argument: ${arg}`); + } + } + if (!Number.isInteger(args.port) || args.port < 1) { + throw new Error(`invalid port: ${args.port}`); + } + return args; +} + +function printHelp() { + console.log(`Usage: mock-openai-compatible.mjs [--host 127.0.0.1] [--port 18080] [--check] + +Tiny local OpenAI-compatible mock for EverCore dogfood: + GET /health + POST /v1/chat/completions + POST /v1/embeddings + POST /v1/rerank + +No external network and no real model keys are used.`); +} + +function stableHash(text) { + let hash = 2166136261; + for (let i = 0; i < text.length; i += 1) { + hash ^= text.charCodeAt(i); + hash = Math.imul(hash, 16777619); + } + return hash >>> 0; +} + +function embeddingFor(text, dimensions = DEFAULT_DIMENSIONS) { + const vector = new Array(dimensions).fill(0); + const tokens = String(text) + .toLowerCase() + .split(/[^a-z0-9_./:-]+/) + .filter(Boolean); + for (const token of tokens.length ? tokens : ['empty']) { + const base = stableHash(token); + for (let i = 0; i < 8; i += 1) { + const idx = (base + i * 97) % dimensions; + vector[idx] += 1 / (i + 1); + } + } + const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0)) || 1; + return vector.map((value) => Number((value / norm).toFixed(6))); +} + +function extractPromptText(body) { + const messages = Array.isArray(body.messages) ? body.messages : []; + return messages + .map((message) => { + if (typeof message.content === 'string') return message.content; + if (Array.isArray(message.content)) { + return message.content.map((part) => part.text || '').join('\n'); + } + return ''; + }) + .join('\n'); +} + +function extractSmokeNeedle(prompt) { + const match = prompt.match(/Hermes EverOS dogfood smoke Raven SkillHub \d+[^.\n]*/i); + if (match) return match[0].trim(); + const ravenLine = prompt + .split(/\r?\n/) + .find((line) => /Raven|SkillHub|EverOS|Hermes/i.test(line)); + return (ravenLine || 'Hermes EverOS dogfood smoke Raven SkillHub').trim().slice(0, 500); +} + +function completionContent(prompt) { + if (/should_end|episode boundar/i.test(prompt)) { + return JSON.stringify({ + should_end: false, + reasoning: 'Single dogfood turn; keep accumulating until flush.', + topic_summary: '', + }); + } + if (/worth_extracting/i.test(prompt)) { + return JSON.stringify({ + worth_extracting: false, + reason: 'Smoke fixture only', + }); + } + + const needle = extractSmokeNeedle(prompt); + return JSON.stringify({ + title: 'Hermes EverOS Raven SkillHub dogfood', + content: `${needle}. Provider-level store, flush, extract, index, search, and recall smoke for Raven and EverMe SkillHub.`, + summary: `${needle}. EverOS memory dogfood smoke.`, + }); +} + +function chatResponse(body) { + const prompt = extractPromptText(body); + const content = completionContent(prompt); + return { + id: `chatcmpl-mock-${Date.now()}`, + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model: body.model || 'mock-openai-compatible', + choices: [ + { + index: 0, + finish_reason: 'stop', + message: { + role: 'assistant', + content, + }, + }, + ], + usage: { + prompt_tokens: prompt.length, + completion_tokens: content.length, + total_tokens: prompt.length + content.length, + }, + }; +} + +function embeddingsResponse(body) { + const input = Array.isArray(body.input) ? body.input : [body.input ?? '']; + const dimensions = Number.isInteger(body.dimensions) && body.dimensions > 0 + ? body.dimensions + : DEFAULT_DIMENSIONS; + return { + object: 'list', + data: input.map((item, index) => ({ + object: 'embedding', + index, + embedding: embeddingFor(String(item), dimensions), + })), + model: body.model || 'mock-embedding', + usage: { + prompt_tokens: input.join(' ').length, + total_tokens: input.join(' ').length, + }, + }; +} + +function rerankResponse(body) { + const documents = Array.isArray(body.documents) ? body.documents : []; + const queryTokens = new Set( + String(body.query || '') + .toLowerCase() + .split(/[^a-z0-9_./:-]+/) + .filter(Boolean), + ); + const results = documents + .map((document, index) => { + const docTokens = String(document) + .toLowerCase() + .split(/[^a-z0-9_./:-]+/) + .filter(Boolean); + const overlap = docTokens.filter((token) => queryTokens.has(token)).length; + return { index, relevance_score: Math.min(1, 0.55 + overlap / 20) }; + }) + .sort((a, b) => b.relevance_score - a.relevance_score); + return { results }; +} + +function readJson(req) { + return new Promise((resolve, reject) => { + const chunks = []; + req.on('data', (chunk) => chunks.push(chunk)); + req.on('end', () => { + const raw = Buffer.concat(chunks).toString('utf8') || '{}'; + try { + resolve(JSON.parse(raw)); + } catch (error) { + reject(error); + } + }); + req.on('error', reject); + }); +} + +function sendJson(res, status, payload) { + const body = JSON.stringify(payload); + res.writeHead(status, { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(body), + }); + res.end(body); +} + +function createServer() { + return http.createServer(async (req, res) => { + try { + const path = new URL(req.url || '/', 'http://localhost').pathname; + if (req.method === 'GET' && path === '/health') { + sendJson(res, 200, { status: 'ok' }); + return; + } + if (req.method !== 'POST') { + sendJson(res, 404, { error: { message: 'not found' } }); + return; + } + + const body = await readJson(req); + if (path === '/v1/chat/completions' || path === '/chat/completions') { + sendJson(res, 200, chatResponse(body)); + } else if (path === '/v1/embeddings' || path === '/embeddings') { + sendJson(res, 200, embeddingsResponse(body)); + } else if (path === '/v1/rerank' || path === '/rerank') { + sendJson(res, 200, rerankResponse(body)); + } else { + sendJson(res, 404, { error: { message: `unknown path: ${path}` } }); + } + } catch (error) { + sendJson(res, 500, { error: { message: error.message } }); + } + }); +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + if (args.check) { + console.log('PASS mock-openai-compatible syntax'); + return; + } + const server = createServer(); + server.listen(args.port, args.host, () => { + console.log(`mock-openai-compatible listening host=${args.host} port=${args.port}`); + }); +} + +main(); diff --git a/use-cases/hermes-everos-memory/bin/raven-run.mjs b/use-cases/hermes-everos-memory/bin/raven-run.mjs new file mode 100755 index 00000000..05070bca --- /dev/null +++ b/use-cases/hermes-everos-memory/bin/raven-run.mjs @@ -0,0 +1,245 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; + +const REQUIRED = [ + 'id', + 'title', + 'goal', + 'status', + 'owners', + 'memory_providers', + 'lanes', + 'gates', + 'artifacts', + 'evidence_refs', + 'next_actions', +]; + +const ENUMS = { + status: ['captured', 'dispatching', 'executing', 'reviewing', 'done', 'blocked'], + owner: ['codex', 'pi', 'opencode', 'hermes', 'muw', 'human'], + mutation_policy: ['read_only', 'local_only', 'external_requires_approval'], + lane_verdict: ['pass', 'flag', 'block', 'active'], + gate_status: ['pass', 'flag', 'block', 'not_run'], +}; + +function usage() { + console.log(`Usage: + raven-run validate + raven-run render + raven-run verify + raven-run summary + raven-run sample`); +} + +function readJson(file) { + return JSON.parse(fs.readFileSync(file, 'utf8')); +} + +function validateRun(run) { + const errors = []; + for (const field of REQUIRED) { + if (!(field in run)) errors.push(`missing ${field}`); + } + for (const field of ['id', 'title', 'goal', 'status']) { + if (field in run && typeof run[field] !== 'string') errors.push(`${field} must be string`); + } + if (typeof run.id === 'string' && !/^[a-z0-9][a-z0-9._-]{2,127}$/.test(run.id)) { + errors.push('id has invalid format'); + } + if (run.status && !ENUMS.status.includes(run.status)) errors.push('status invalid'); + + for (const field of ['owners', 'memory_providers', 'lanes', 'gates', 'artifacts', 'evidence_refs', 'next_actions']) { + if (field in run && !Array.isArray(run[field])) errors.push(`${field} must be array`); + } + + if (Array.isArray(run.owners)) { + for (const owner of run.owners) { + if (!ENUMS.owner.includes(owner)) errors.push(`owner invalid: ${owner}`); + } + } + + if (Array.isArray(run.lanes)) { + for (const lane of run.lanes) { + if (!lane.id) errors.push('lane missing id'); + if (!ENUMS.owner.includes(lane.owner)) errors.push(`lane owner invalid: ${lane.owner}`); + if (!ENUMS.mutation_policy.includes(lane.mutation_policy)) { + errors.push(`lane mutation policy invalid: ${lane.mutation_policy}`); + } + if (!ENUMS.lane_verdict.includes(lane.verdict)) { + errors.push(`lane verdict invalid: ${lane.verdict}`); + } + } + } + + if (Array.isArray(run.gates)) { + for (const gate of run.gates) { + if (!gate.id) errors.push('gate missing id'); + if (!gate.name) errors.push(`gate missing name: ${gate.id || 'unknown'}`); + if (!ENUMS.gate_status.includes(gate.status)) errors.push(`gate status invalid: ${gate.status}`); + if (typeof gate.blocks_completion !== 'boolean') { + errors.push(`gate blocks_completion must be boolean: ${gate.id || 'unknown'}`); + } + } + } + + return errors; +} + +function verdict(run) { + const lanes = Array.isArray(run.lanes) ? run.lanes : []; + const gates = Array.isArray(run.gates) ? run.gates : []; + + if (lanes.some((lane) => lane.verdict === 'block')) return 'BLOCK'; + if (gates.some((gate) => gate.blocks_completion && gate.status === 'block')) return 'BLOCK'; + if (lanes.some((lane) => lane.verdict === 'flag' || lane.verdict === 'active')) return 'FLAG'; + if (gates.some((gate) => gate.blocks_completion && ['flag', 'not_run'].includes(gate.status))) return 'FLAG'; + return 'PASS'; +} + +function table(rows) { + return rows.map((row) => `| ${row.join(' | ')} |`).join('\n'); +} + +function renderRun(run) { + const laneRows = [ + ['Lane', 'Owner', 'Policy', 'Verdict', 'Scope'], + ['---', '---', '---', '---', '---'], + ...run.lanes.map((lane) => [ + lane.id, + lane.owner, + lane.mutation_policy, + lane.verdict.toUpperCase(), + lane.scope, + ]), + ]; + const gateRows = [ + ['Gate', 'Status', 'Blocks', 'Evidence'], + ['---', '---', '---', '---'], + ...run.gates.map((gate) => [ + gate.name, + gate.status.toUpperCase(), + gate.blocks_completion ? 'yes' : 'no', + gate.evidence, + ]), + ]; + const artifactRows = [ + ['Artifact', 'Public Safe', 'Purpose'], + ['---', '---', '---'], + ...run.artifacts.map((artifact) => [ + artifact.path, + artifact.public_safe ? 'yes' : 'no', + artifact.purpose, + ]), + ]; + + return [ + `# ${run.title}`, + '', + `VERDICT: ${verdict(run)}.`, + '', + `Status: ${run.status}`, + `Run id: ${run.id}`, + `Owners: ${run.owners.join(', ')}`, + `Memory providers: ${run.memory_providers.join(', ')}`, + '', + '## Goal', + '', + run.goal, + '', + '## Lanes', + '', + table(laneRows), + '', + '## Gates', + '', + table(gateRows), + '', + '## Artifacts', + '', + table(artifactRows), + '', + '## Next', + '', + ...run.next_actions.map((action) => `- ${action}`), + ].join('\n'); +} + +function summary(run) { + return { + ok: true, + id: run.id, + status: run.status, + verdict: verdict(run), + lanes: run.lanes.length, + gates: run.gates.length, + blocking_gates_open: run.gates.filter( + (gate) => gate.blocks_completion && gate.status !== 'pass', + ).map((gate) => gate.id), + }; +} + +function renderGateVerification(run) { + const gateRows = [ + ['Gate', 'Status', 'Blocks', 'Command', 'Evidence'], + ['---', '---', '---', '---', '---'], + ...run.gates.map((gate) => [ + gate.id, + gate.status.toUpperCase(), + gate.blocks_completion ? 'yes' : 'no', + gate.command || 'manual', + gate.evidence, + ]), + ]; + return [ + `VERDICT: ${verdict(run)}.`, + '', + table(gateRows), + '', + `Blocking gates open: ${summary(run).blocking_gates_open.join(', ') || 'none'}`, + ].join('\n'); +} + +function main() { + const [command, file] = process.argv.slice(2); + if (!command || command === '--help' || command === '-h') { + usage(); + return; + } + if (command === 'sample') { + const sample = new URL('../raven/fixtures/doomsday-run.json', import.meta.url); + console.log(fs.readFileSync(sample, 'utf8')); + return; + } + if (!file) throw new Error(`${command} requires run.json`); + const run = readJson(file); + const errors = validateRun(run); + if (errors.length) { + console.error(JSON.stringify({ ok: false, errors }, null, 2)); + process.exit(1); + } + if (command === 'validate') { + console.log(JSON.stringify(summary(run), null, 2)); + return; + } + if (command === 'summary') { + console.log(JSON.stringify(summary(run), null, 2)); + return; + } + if (command === 'render') { + console.log(renderRun(run)); + return; + } + if (command === 'verify') { + console.log(renderGateVerification(run)); + const result = verdict(run); + if (result === 'BLOCK') process.exit(2); + if (result === 'FLAG') process.exit(1); + return; + } + throw new Error(`unknown command: ${command}`); +} + +main(); diff --git a/use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs b/use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs new file mode 100755 index 00000000..601477ae --- /dev/null +++ b/use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs @@ -0,0 +1,231 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import http from 'node:http'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { readJson, renderPacket, validatePacket } from './skillhub-packet.mjs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const DEFAULT_PACKET_DIR = path.resolve(__dirname, '../skillhub/fixtures'); +const DEFAULT_HOST = process.env.SKILLHUB_HOST || '127.0.0.1'; +const DEFAULT_PORT = Number(process.env.SKILLHUB_PORT || 18765); + +function usage() { + console.log(`Usage: + skillhub-mock-api [--dir ] [--host ] [--port ] + skillhub-mock-api --check [--dir ]`); +} + +function parseArgs(args) { + const options = { + dir: DEFAULT_PACKET_DIR, + host: DEFAULT_HOST, + port: DEFAULT_PORT, + check: false, + }; + for (let i = 0; i < args.length; i += 1) { + const item = args[i]; + if (item === '--dir') options.dir = path.resolve(args[++i]); + else if (item === '--host') options.host = args[++i]; + else if (item === '--port') options.port = Number(args[++i]); + else if (item === '--check') options.check = true; + else if (item === '--help' || item === '-h') { + usage(); + process.exit(0); + } else { + throw new Error(`unknown option: ${item}`); + } + } + if (!Number.isInteger(options.port) || options.port <= 0) { + throw new Error('port must be a positive integer'); + } + return options; +} + +function packetSummary(packet) { + return { + id: packet.id, + name: packet.name, + summary: packet.summary, + visibility: packet.visibility, + status: packet.status, + version: packet.version, + source: packet.source, + domains: packet.domains, + install_targets: packet.install_targets, + evidence_refs: packet.evidence_refs, + eval_score: packet.eval_score, + rating: packet.rating, + votes: packet.votes, + }; +} + +function loadPackets(dir) { + const files = fs + .readdirSync(dir, { withFileTypes: true }) + .filter((entry) => entry.isFile() && entry.name.endsWith('.json')) + .map((entry) => path.join(dir, entry.name)) + .sort(); + + const packets = new Map(); + for (const file of files) { + const packet = readJson(file); + const errors = validatePacket(packet); + if (errors.length) { + throw new Error(`${path.relative(process.cwd(), file)} invalid: ${errors.join('; ')}`); + } + if (packets.has(packet.id)) { + throw new Error(`duplicate packet id: ${packet.id}`); + } + packets.set(packet.id, { file, packet }); + } + return packets; +} + +function listPackets(packets, url) { + const target = url.searchParams.get('target'); + const domain = url.searchParams.get('domain'); + const includeBody = url.searchParams.get('include_body') === 'true'; + + return [...packets.values()] + .map(({ packet }) => packet) + .filter((packet) => !target || packet.install_targets.includes(target)) + .filter((packet) => !domain || packet.domains.includes(domain)) + .map((packet) => (includeBody ? packet : packetSummary(packet))); +} + +function sendJson(res, statusCode, payload) { + const body = JSON.stringify(payload, null, 2); + res.writeHead(statusCode, { + 'content-type': 'application/json; charset=utf-8', + 'cache-control': 'no-store', + }); + res.end(`${body}\n`); +} + +function sendText(res, statusCode, body) { + res.writeHead(statusCode, { + 'content-type': 'text/markdown; charset=utf-8', + 'cache-control': 'no-store', + }); + res.end(`${body}\n`); +} + +function readBody(req) { + return new Promise((resolve, reject) => { + let body = ''; + req.setEncoding('utf8'); + req.on('data', (chunk) => { + body += chunk; + if (body.length > 2_000_000) { + reject(new Error('request body too large')); + req.destroy(); + } + }); + req.on('end', () => resolve(body)); + req.on('error', reject); + }); +} + +async function route(req, res, packets) { + if (req.method === 'OPTIONS') { + res.writeHead(204, { + 'access-control-allow-origin': 'http://127.0.0.1', + 'access-control-allow-methods': 'GET,POST,OPTIONS', + 'access-control-allow-headers': 'content-type', + }); + res.end(); + return; + } + + const url = new URL(req.url || '/', 'http://skillhub.local'); + const segments = url.pathname.split('/').filter(Boolean).map(decodeURIComponent); + + if (req.method === 'GET' && url.pathname === '/health') { + sendJson(res, 200, { + ok: true, + service: 'everme-skillhub-mock', + packet_count: packets.size, + }); + return; + } + + if (req.method === 'GET' && segments.length === 1 && segments[0] === 'skills') { + sendJson(res, 200, { ok: true, skills: listPackets(packets, url) }); + return; + } + + if (req.method === 'GET' && segments.length === 2 && segments[0] === 'skills') { + const item = packets.get(segments[1]); + if (!item) { + sendJson(res, 404, { ok: false, error: 'skill not found' }); + return; + } + sendJson(res, 200, { ok: true, skill: item.packet }); + return; + } + + if ( + req.method === 'GET' + && segments.length === 3 + && segments[0] === 'skills' + && segments[2] === 'render' + ) { + const item = packets.get(segments[1]); + if (!item) { + sendJson(res, 404, { ok: false, error: 'skill not found' }); + return; + } + sendText(res, 200, renderPacket(item.packet)); + return; + } + + if (req.method === 'POST' && url.pathname === '/skills/validate') { + const body = await readBody(req); + const packet = JSON.parse(body); + const errors = validatePacket(packet); + sendJson(res, errors.length ? 422 : 200, { ok: errors.length === 0, errors }); + return; + } + + sendJson(res, 404, { ok: false, error: 'not found' }); +} + +function writeCheckResult(packets) { + process.stdout.write(JSON.stringify({ + ok: true, + service: 'everme-skillhub-mock', + packet_count: packets.size, + packet_ids: [...packets.keys()], + }, null, 2) + '\n'); +} + +function main() { + const options = parseArgs(process.argv.slice(2)); + const packets = loadPackets(options.dir); + + if (options.check) { + writeCheckResult(packets); + return; + } + + const server = http.createServer((req, res) => { + route(req, res, packets).catch((error) => { + sendJson(res, 500, { ok: false, error: error.message }); + }); + }); + + server.listen(options.port, options.host, () => { + const address = server.address(); + process.stdout.write(JSON.stringify({ + ok: true, + service: 'everme-skillhub-mock', + url: `http://${address.address}:${address.port}`, + packet_count: packets.size, + }) + '\n'); + }); +} + +main(); diff --git a/use-cases/hermes-everos-memory/bin/skillhub-packet.mjs b/use-cases/hermes-everos-memory/bin/skillhub-packet.mjs new file mode 100755 index 00000000..1758c50c --- /dev/null +++ b/use-cases/hermes-everos-memory/bin/skillhub-packet.mjs @@ -0,0 +1,295 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const REQUIRED = [ + 'id', + 'name', + 'summary', + 'visibility', + 'status', + 'version', + 'source', + 'domains', + 'install_targets', + 'evidence_refs', + 'body_markdown', +]; + +const ENUMS = { + visibility: ['private', 'link', 'community'], + status: ['draft', 'active', 'needs_eval', 'archived'], + source: ['manual', 'evercore_extracted', 'imported', 'community'], + install_targets: ['hermes', 'raven', 'claude_code', 'evercore', 'openclaw'], +}; + +function usage() { + console.log(`Usage: + skillhub-packet validate + skillhub-packet render + skillhub-packet views + skillhub-packet from-skill [--domain ] [--target ] [--owner ] + skillhub-packet sample`); +} + +export function readJson(file) { + return JSON.parse(fs.readFileSync(file, 'utf8')); +} + +export function slugify(input) { + return String(input || '') + .toLowerCase() + .replace(/[^a-z0-9._-]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 120) || 'skill'; +} + +function findRepoRoot(startDir) { + let dir = path.resolve(startDir); + while (dir !== path.dirname(dir)) { + if (fs.existsSync(path.join(dir, '.git'))) return dir; + dir = path.dirname(dir); + } + return path.resolve(startDir); +} + +function repoRelative(file) { + const absolute = path.resolve(file); + const root = findRepoRoot(process.cwd()); + return path.relative(root, absolute); +} + +export function parseFrontmatter(markdown) { + if (!markdown.startsWith('---\n')) { + return { frontmatter: {}, body: markdown.trim() }; + } + const end = markdown.indexOf('\n---', 4); + if (end === -1) { + return { frontmatter: {}, body: markdown.trim() }; + } + const raw = markdown.slice(4, end).split(/\r?\n/); + const body = markdown.slice(end + 4).trim(); + const frontmatter = {}; + let pendingKey = null; + for (const line of raw) { + if (!line.trim()) continue; + if (/^\s+/.test(line) && pendingKey) { + frontmatter[pendingKey] = `${frontmatter[pendingKey] || ''} ${line.trim()}`.trim(); + continue; + } + const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/); + if (!match) continue; + const [, key, value] = match; + pendingKey = key; + if (value === '>') { + frontmatter[key] = ''; + } else if (value === 'true' || value === 'false') { + frontmatter[key] = value === 'true'; + } else { + frontmatter[key] = value.replace(/^["']|["']$/g, ''); + } + } + return { frontmatter, body }; +} + +export function validatePacket(packet) { + const errors = []; + for (const field of REQUIRED) { + if (!(field in packet)) errors.push(`missing ${field}`); + } + for (const field of ['id', 'name', 'summary', 'version', 'body_markdown']) { + if (field in packet && typeof packet[field] !== 'string') errors.push(`${field} must be string`); + } + if (typeof packet.id === 'string' && !/^[a-z0-9][a-z0-9._-]{2,127}$/.test(packet.id)) { + errors.push('id has invalid format'); + } + if (typeof packet.version === 'string' && !/^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9._-]+)?$/.test(packet.version)) { + errors.push('version must be semver-like'); + } + for (const field of ['domains', 'install_targets', 'evidence_refs']) { + if (field in packet && !Array.isArray(packet[field])) errors.push(`${field} must be array`); + } + for (const field of ['visibility', 'status', 'source']) { + if (field in packet && !ENUMS[field].includes(packet[field])) errors.push(`${field} invalid`); + } + if (Array.isArray(packet.install_targets)) { + for (const target of packet.install_targets) { + if (!ENUMS.install_targets.includes(target)) errors.push(`install target invalid: ${target}`); + } + } + return errors; +} + +export function renderPacket(packet) { + return [ + `# ${packet.name}`, + '', + `id: ${packet.id}`, + `status: ${packet.status}`, + `visibility: ${packet.visibility}`, + `version: ${packet.version}`, + `source: ${packet.source}`, + `domains: ${packet.domains.join(', ')}`, + `install_targets: ${packet.install_targets.join(', ')}`, + `evidence_refs: ${packet.evidence_refs.length}`, + '', + packet.summary, + ].join('\n'); +} + +function valueOrUnknown(value) { + if (value === undefined || value === null || value === '') return 'unknown'; + return String(value); +} + +function renderSkillViews(packet) { + const evidence = packet.evidence_refs.length + ? packet.evidence_refs.map((ref) => `- ${ref}`).join('\n') + : '- no evidence refs yet'; + const score = typeof packet.eval_score === 'number' ? packet.eval_score.toFixed(2) : 'not scored'; + const rating = typeof packet.rating === 'number' ? packet.rating.toFixed(1) : 'not rated'; + const votes = Number.isInteger(packet.votes) ? packet.votes : 0; + const lastEvolved = valueOrUnknown(packet.last_evolved_at); + return [ + `# ${packet.name} SkillHub Views`, + '', + '## Skill Index Row', + '', + `- id: ${packet.id}`, + `- status: ${packet.status}`, + `- version: ${packet.version}`, + `- domains: ${packet.domains.join(', ')}`, + `- install targets: ${packet.install_targets.join(', ')}`, + '', + '## Skill Detail', + '', + packet.summary, + '', + packet.body_markdown.trim(), + '', + '## Evolution Queue', + '', + `- status: ${packet.status}`, + `- eval score: ${score}`, + `- last evolved: ${lastEvolved}`, + `- next action: ${packet.status === 'needs_eval' ? 'run eval before promoting' : 'watch for fresh evidence'}`, + '', + '## Install Packet', + '', + `- compatible targets: ${packet.install_targets.join(', ')}`, + `- source: ${packet.source}`, + `- visibility: ${packet.visibility}`, + `- version: ${packet.version}`, + '', + '## Trust Panel', + '', + `- rating: ${rating}`, + `- votes: ${votes}`, + `- evidence refs: ${packet.evidence_refs.length}`, + evidence, + ].join('\n'); +} + +export function packetFromSkill(file, options) { + const markdown = fs.readFileSync(file, 'utf8'); + const { frontmatter, body } = parseFrontmatter(markdown); + const name = String(frontmatter.name || path.basename(path.dirname(file)) || 'skill'); + return { + id: `${slugify(options.owner || 'everme-local')}.${slugify(name)}`, + name, + summary: String(frontmatter.description || name).replace(/\s+/g, ' ').trim(), + owner_id: options.owner || 'everme-local', + visibility: 'private', + status: 'needs_eval', + version: '0.1.0', + source: 'evercore_extracted', + domains: [options.domain || inferDomain(file)], + install_targets: [options.target || 'hermes'], + evidence_refs: [repoRelative(file)], + body_markdown: body || markdown.trim(), + frontmatter, + }; +} + +function inferDomain(file) { + const parts = file.split(path.sep); + const idx = parts.indexOf('skills_sample'); + if (idx >= 0 && parts[idx + 1]) return parts[idx + 1].toLowerCase(); + return 'general'; +} + +function parseOptions(args) { + const options = {}; + for (let i = 0; i < args.length; i += 1) { + const item = args[i]; + if (item === '--domain') options.domain = args[++i]; + else if (item === '--target') options.target = args[++i]; + else if (item === '--owner') options.owner = args[++i]; + else throw new Error(`unknown option: ${item}`); + } + return options; +} + +function main() { + const [command, file, ...rest] = process.argv.slice(2); + if (!command || command === '--help' || command === '-h') { + usage(); + return; + } + if (command === 'sample') { + const sample = new URL('../skillhub/fixtures/raven-skillhub-sample.json', import.meta.url); + console.log(fs.readFileSync(sample, 'utf8')); + return; + } + if (command === 'validate') { + if (!file) throw new Error('validate requires packet.json'); + const packet = readJson(file); + const errors = validatePacket(packet); + if (errors.length) { + console.error(JSON.stringify({ ok: false, errors }, null, 2)); + process.exit(1); + } + console.log(JSON.stringify({ ok: true, id: packet.id, targets: packet.install_targets }, null, 2)); + return; + } + if (command === 'render') { + if (!file) throw new Error('render requires packet.json'); + const packet = readJson(file); + const errors = validatePacket(packet); + if (errors.length) { + console.error(JSON.stringify({ ok: false, errors }, null, 2)); + process.exit(1); + } + console.log(renderPacket(packet)); + return; + } + if (command === 'views') { + if (!file) throw new Error('views requires packet.json'); + const packet = readJson(file); + const errors = validatePacket(packet); + if (errors.length) { + console.error(JSON.stringify({ ok: false, errors }, null, 2)); + process.exit(1); + } + console.log(renderSkillViews(packet)); + return; + } + if (command === 'from-skill') { + if (!file) throw new Error('from-skill requires SKILL.md'); + const packet = packetFromSkill(file, parseOptions(rest)); + const errors = validatePacket(packet); + if (errors.length) { + console.error(JSON.stringify({ ok: false, errors }, null, 2)); + process.exit(1); + } + console.log(JSON.stringify(packet, null, 2)); + return; + } + throw new Error(`unknown command: ${command}`); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main(); +} diff --git a/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md b/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md new file mode 100644 index 00000000..4e95e286 --- /dev/null +++ b/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md @@ -0,0 +1,86 @@ +# EverCore Remote Deploy Packet v0 + +## Verdict + +FLAG: deploy packet is ready for review, but live remote deployment is not proven +until the operator installs the secret env file and runs the NixOS/compose gates. + +## Decision + +Use the NixOS/workhorse lane for EverCore. + +CCR should stay out of the stateful service path. It can still run client-side +smoke tests, propose patches, and review evidence packets. + +## Why + +- EverCore is a durable memory service; it belongs on the always-on host. +- Hermes can dogfood it locally on the same host through `127.0.0.1`. +- Keeping CCR as a helper preserves the mirror/patch-only boundary. +- Loopback-first exposure prevents the data plane from becoming a public DB + surface. + +## Mutation Boundary + +This packet does not mutate the remote host. + +Before applying it remotely, confirm: + +- the target checkout is clean or intentionally dirty; +- the remote env file exists outside git; +- provider keys are installed only on the host; +- `bindHost` remains `127.0.0.1` unless the operator explicitly approves a + private network exposure; +- `nixos-rebuild test` passes before `switch`. + +## Apply Steps + +1. Copy `docker-compose.remote.yaml` and a filled `evercore.env` to the remote + runtime directory. +2. Put an EverOS checkout at the configured `repoDir`. +3. Import `evercore-remote-workhorse.nix` into the workhorse host config. +4. Run the workhorse rebuild in test mode. +5. Start or restart `evercore-compose.service`. +6. Run `scripts/evercore-remote-smoke.sh --mode health`. +7. After LLM/vector/rerank providers are configured, run + `scripts/evercore-remote-smoke.sh --mode full`. +8. Point Hermes at `EVEROS_API_BASE_URL=http://127.0.0.1:1995` on the same host, + or at an operator-controlled private route. + +## Red Gates + +Keep deployment blocked if any of these are true: + +- the API binds to a public interface without explicit approval; +- any data service port is exposed outside Docker/private host boundaries; +- the env file contains placeholder secrets during full smoke; +- `evercore-api` starts without a mounted `/app/.env`; +- health passes but full write/search fails and the provider is marked `PASS`; +- full smoke search returns zero retrievable memories after flush; +- host evidence includes raw public host/IP or credential paths. + +## Verification Commands + +From this repo: + +```bash +bash -n use-cases/hermes-everos-memory/deploy/nixos/scripts/evercore-remote-smoke.sh +EVERCORE_REPO_ROOT=$PWD \ +EVERCORE_ENV_FILE=$PWD/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example \ + docker-compose --env-file use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example \ + -f use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml config +``` + +From the remote host after configuration: + +```bash +systemctl status evercore-compose.service +systemctl status evercore-health.timer +scripts/evercore-remote-smoke.sh --mode health +scripts/evercore-remote-smoke.sh --mode full +``` + +## Next Concrete Action + +Stage this packet into the Windburn workhorse lane and run a test rebuild with a +redacted env file present on the host. diff --git a/use-cases/hermes-everos-memory/deploy/nixos/README.md b/use-cases/hermes-everos-memory/deploy/nixos/README.md new file mode 100644 index 00000000..f5bb4494 --- /dev/null +++ b/use-cases/hermes-everos-memory/deploy/nixos/README.md @@ -0,0 +1,128 @@ +# EverCore Remote On NixOS + +This packet deploys EverCore as a remote memory backend for Hermes without +turning CCR into the stateful service host. + +Decision: + +- NixOS/workhorse owns the long-running EverCore service. +- CCR remains a client/helper lane for patch proposals, smoke tests, and review. +- Public data ports stay closed. Hermes should reach EverCore through same-host + loopback, VPN, or an operator-controlled tunnel. + +## Files + +| File | Purpose | +| --- | --- | +| `docker-compose.remote.yaml` | EverCore API plus MongoDB, Redis, Elasticsearch, MinIO, and Milvus | +| `evercore.env.example` | Sanitized remote env template; copy to `evercore.env` outside git | +| `evercore-remote-workhorse.nix` | Optional NixOS module for the workhorse | +| `scripts/evercore-remote-smoke.sh` | Public-safe health/write/search smoke helper | + +## Security Contract + +- Do not expose MongoDB, Redis, Elasticsearch, MinIO, or Milvus on public + interfaces. +- Do not commit `evercore.env`, provider keys, SSH targets, raw host values, or + local credential paths. +- Keep the API bound to `127.0.0.1` unless the host is behind a private network + route and the operator explicitly opens it. +- Run `nixos-rebuild test` before `switch` when this module is imported into a + live Windburn host. + +## Remote Layout + +Recommended host layout: + +```text +/srv/windburn/evercore/ + docker-compose.remote.yaml + evercore.env + repo/ # EverOS checkout, or symlink to the checkout + backups/ +``` + +The compose file expects: + +- `EVERCORE_REPO_ROOT` to point at the EverOS checkout root. +- `EVERCORE_ENV_FILE` to point at the secret-bearing env file. +- `EVERCORE_BIND_HOST` and `EVERCORE_BIND_PORT` to control API exposure. + +The default bind is `127.0.0.1:1995`. + +## Manual Bring-Up + +On the remote host: + +```bash +cp evercore.env.example evercore.env +$EDITOR evercore.env + +export EVERCORE_REPO_ROOT=/srv/windburn/evercore/repo +export EVERCORE_ENV_FILE=/srv/windburn/evercore/evercore.env +export EVERCORE_BIND_HOST=127.0.0.1 +export EVERCORE_BIND_PORT=1995 + +docker-compose --env-file "$EVERCORE_ENV_FILE" \ + -f docker-compose.remote.yaml up -d --build +``` + +Health-only smoke: + +```bash +EVEROS_API_BASE_URL=http://127.0.0.1:1995 \ + scripts/evercore-remote-smoke.sh --mode health +``` + +Full memory smoke, after LLM/vector/rerank providers are configured: + +```bash +EVEROS_API_BASE_URL=http://127.0.0.1:1995 \ + scripts/evercore-remote-smoke.sh --mode full +``` + +`full` mode checks health, writes one agent-memory turn, flushes the session, +then blocks if search returns no retrievable memory. + +## NixOS Bring-Up + +Copy `evercore-remote-workhorse.nix` into the workhorse module set, import it in +the host configuration, and override at least these options: + +```nix +services.evercoreRemote = { + enable = true; + baseDir = "/srv/windburn/evercore"; + repoDir = "/srv/windburn/evercore/repo"; + envFile = "/srv/windburn/evercore/evercore.env"; + composeFile = "/srv/windburn/evercore/docker-compose.remote.yaml"; + bindHost = "127.0.0.1"; + bindPort = 1995; +}; +``` + +Keep `openFirewall = false` for v0. + +## Hermes Provider + +For Hermes running on the same remote host: + +```bash +export EVEROS_API_BASE_URL=http://127.0.0.1:1995 +export EVEROS_USER_ID=hermes-user +export EVEROS_AGENT_ID=hermes +``` + +For local Hermes talking to remote EverCore, use a private route or tunnel and +keep the provider config pointed at the local endpoint exposed by that route. + +## Gates + +`PASS` for deploy readiness requires: + +1. `docker-compose ps` shows every service healthy. +2. `scripts/evercore-remote-smoke.sh --mode health` passes. +3. `scripts/evercore-remote-smoke.sh --mode full` passes after provider keys are + installed. +4. Hermes provider `everos_health`, `everos_store`, and `everos_search` all pass. +5. No public data ports are reachable from outside the private host boundary. diff --git a/use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml b/use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml new file mode 100644 index 00000000..29ef53a2 --- /dev/null +++ b/use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml @@ -0,0 +1,180 @@ +name: evercore-remote + +services: + mongodb: + image: mongo:7.0 + container_name: evercore-mongodb + restart: unless-stopped + environment: + MONGO_INITDB_ROOT_USERNAME: ${EVERCORE_MONGODB_USERNAME:-admin} + MONGO_INITDB_ROOT_PASSWORD: ${EVERCORE_MONGODB_PASSWORD:?set EVERCORE_MONGODB_PASSWORD} + MONGO_INITDB_DATABASE: ${MONGODB_DATABASE:-memsys} + volumes: + - mongodb_data:/data/db + - ${EVERCORE_REPO_ROOT:-../../../..}/methods/EverCore/docker/mongodb/init:/docker-entrypoint-initdb.d:ro + networks: + - evercore-network + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0 + container_name: evercore-elasticsearch + restart: unless-stopped + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - ES_JAVA_OPTS=-Xms1g -Xmx1g + - bootstrap.memory_lock=true + ulimits: + memlock: + soft: -1 + hard: -1 + volumes: + - elasticsearch_data:/usr/share/elasticsearch/data + networks: + - evercore-network + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:9200/_cluster/health >/dev/null || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + milvus-etcd: + image: quay.io/coreos/etcd:v3.5.5 + container_name: evercore-milvus-etcd + restart: unless-stopped + environment: + - ETCD_AUTO_COMPACTION_MODE=revision + - ETCD_AUTO_COMPACTION_RETENTION=1000 + - ETCD_QUOTA_BACKEND_BYTES=4294967296 + - ETCD_SNAPSHOT_COUNT=50000 + command: etcd -advertise-client-urls=http://127.0.0.1:2479 -listen-client-urls http://0.0.0.0:2479 --data-dir /etcd + volumes: + - milvus_etcd_data:/etcd + networks: + - evercore-network + healthcheck: + test: ["CMD", "etcdctl", "endpoint", "health"] + interval: 30s + timeout: 20s + retries: 3 + + milvus-minio: + image: minio/minio:RELEASE.2023-03-20T20-16-18Z + container_name: evercore-milvus-minio + restart: unless-stopped + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD} + MINIO_ACCESS_KEY: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER} + MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD} + command: minio server /minio_data --console-address ":9001" + volumes: + - milvus_minio_data:/minio_data + networks: + - evercore-network + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + + milvus-standalone: + image: milvusdb/milvus:v2.5.2 + container_name: evercore-milvus-standalone + restart: unless-stopped + command: ["milvus", "run", "standalone"] + environment: + ETCD_ENDPOINTS: milvus-etcd:2479 + MINIO_ADDRESS: milvus-minio:9000 + volumes: + - milvus_data:/var/lib/milvus + networks: + - evercore-network + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:9091/healthz"] + interval: 30s + timeout: 20s + retries: 3 + start_period: 90s + depends_on: + milvus-etcd: + condition: service_healthy + milvus-minio: + condition: service_healthy + + redis: + image: redis:7.2-alpine + container_name: evercore-redis + restart: unless-stopped + volumes: + - redis_data:/data + networks: + - evercore-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + + evercore-api: + build: + context: ${EVERCORE_REPO_ROOT:-../../../..}/methods/EverCore + dockerfile: Dockerfile + image: everos/evercore-api:remote + container_name: evercore-api + restart: unless-stopped + command: ["uv", "run", "python", "src/run.py", "--host", "0.0.0.0", "--port", "1995"] + env_file: + - ${EVERCORE_ENV_FILE:-./evercore.env} + environment: + MEMSYS_HOST: 0.0.0.0 + MEMSYS_PORT: 1995 + API_BASE_URL: http://127.0.0.1:1995 + ports: + - "${EVERCORE_BIND_HOST:-127.0.0.1}:${EVERCORE_BIND_PORT:-1995}:1995" + volumes: + - ${EVERCORE_ENV_FILE:-./evercore.env}:/app/.env:ro + networks: + - evercore-network + depends_on: + mongodb: + condition: service_healthy + elasticsearch: + condition: service_healthy + milvus-standalone: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:1995/health >/dev/null || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 120s + +volumes: + mongodb_data: + driver: local + elasticsearch_data: + driver: local + milvus_etcd_data: + driver: local + milvus_minio_data: + driver: local + milvus_data: + driver: local + redis_data: + driver: local + +networks: + evercore-network: + driver: bridge + diff --git a/use-cases/hermes-everos-memory/deploy/nixos/evercore-remote-workhorse.nix b/use-cases/hermes-everos-memory/deploy/nixos/evercore-remote-workhorse.nix new file mode 100644 index 00000000..9c002fe3 --- /dev/null +++ b/use-cases/hermes-everos-memory/deploy/nixos/evercore-remote-workhorse.nix @@ -0,0 +1,196 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.evercoreRemote; + composeBin = lib.getExe pkgs.docker-compose; + healthScript = pkgs.writeShellApplication { + name = "evercore-remote-health"; + runtimeInputs = [ pkgs.coreutils pkgs.curl pkgs.jq ]; + text = '' + set -euo pipefail + + evidence_dir="${cfg.evidenceDir}" + mkdir -p "$evidence_dir" + tmp="$(mktemp)" + trap 'rm -f "$tmp"' EXIT + + checked_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + if curl -fsS --max-time 10 "http://${cfg.bindHost}:${toString cfg.bindPort}/health" > "$tmp"; then + status="$(jq -r '.status // "unknown"' "$tmp" 2>/dev/null || printf 'unknown')" + jq -n \ + --arg checked_at "$checked_at" \ + --arg status "$status" \ + --slurpfile health "$tmp" \ + '{checked_at: $checked_at, status: $status, health: ($health[0] // {})}' \ + > "$evidence_dir/current.json" + test "$status" = "healthy" + else + jq -n \ + --arg checked_at "$checked_at" \ + '{checked_at: $checked_at, status: "unreachable"}' \ + > "$evidence_dir/current.json" + exit 1 + fi + ''; + }; +in +{ + options.services.evercoreRemote = { + enable = lib.mkEnableOption "EverCore remote memory backend"; + + baseDir = lib.mkOption { + type = lib.types.str; + default = "/srv/evercore"; + description = "Runtime directory containing compose/env files and backups."; + }; + + repoDir = lib.mkOption { + type = lib.types.str; + default = "/srv/evercore/repo"; + description = "EverOS checkout root used as Docker build context."; + }; + + envFile = lib.mkOption { + type = lib.types.str; + default = "/srv/evercore/evercore.env"; + description = "Secret-bearing EverCore env file, not committed to git."; + }; + + composeFile = lib.mkOption { + type = lib.types.str; + default = "/srv/evercore/docker-compose.remote.yaml"; + description = "Remote Docker Compose file."; + }; + + evidenceDir = lib.mkOption { + type = lib.types.str; + default = "/srv/evercore/evidence"; + description = "Directory for local health evidence."; + }; + + bindHost = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Host interface for the EverCore API port."; + }; + + bindPort = lib.mkOption { + type = lib.types.port; + default = 1995; + description = "Host port for EverCore API."; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Open bindPort in the NixOS firewall. Keep false for v0."; + }; + + allowPublicBind = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Allow bindHost=0.0.0.0. Requires explicit operator intent."; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "evercore"; + description = "Owner for runtime directories."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "evercore"; + description = "Group for runtime directories."; + }; + + createUser = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Create the runtime user/group. Disable when reusing windburn."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.allowPublicBind || cfg.bindHost != "0.0.0.0"; + message = "EverCore remote refuses bindHost=0.0.0.0 unless allowPublicBind=true."; + } + ]; + + virtualisation.docker.enable = true; + + users.groups = lib.mkIf cfg.createUser { + ${cfg.group} = { }; + }; + + users.users = lib.mkIf cfg.createUser { + ${cfg.user} = { + isSystemUser = true; + group = cfg.group; + extraGroups = [ "docker" ]; + }; + }; + + environment.systemPackages = [ + pkgs.curl + pkgs.docker-compose + pkgs.jq + ]; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.bindPort ]; + + systemd.tmpfiles.rules = [ + "d ${cfg.baseDir} 0750 ${cfg.user} ${cfg.group} - -" + "d ${cfg.evidenceDir} 0750 ${cfg.user} ${cfg.group} - -" + "d ${cfg.baseDir}/backups 0750 ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.evercore-compose = { + description = "EverCore remote memory backend"; + wantedBy = [ "multi-user.target" ]; + after = [ "docker.service" "network-online.target" ]; + requires = [ "docker.service" ]; + environment = { + EVERCORE_REPO_ROOT = cfg.repoDir; + EVERCORE_ENV_FILE = cfg.envFile; + EVERCORE_BIND_HOST = cfg.bindHost; + EVERCORE_BIND_PORT = toString cfg.bindPort; + }; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + WorkingDirectory = cfg.baseDir; + ExecStartPre = [ + "${pkgs.coreutils}/bin/test -f ${cfg.composeFile}" + "${pkgs.coreutils}/bin/test -f ${cfg.envFile}" + "${pkgs.coreutils}/bin/test -d ${cfg.repoDir}" + ]; + ExecStart = "${composeBin} --env-file ${cfg.envFile} -f ${cfg.composeFile} up -d --build --remove-orphans"; + ExecStop = "${composeBin} --env-file ${cfg.envFile} -f ${cfg.composeFile} down"; + TimeoutStartSec = 900; + TimeoutStopSec = 180; + }; + }; + + systemd.services.evercore-health = { + description = "EverCore remote health evidence"; + after = [ "evercore-compose.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${healthScript}/bin/evercore-remote-health"; + }; + }; + + systemd.timers.evercore-health = { + description = "Run EverCore remote health evidence check"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "5min"; + OnUnitActiveSec = "5min"; + Unit = "evercore-health.service"; + }; + }; + }; +} diff --git a/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example b/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example new file mode 100644 index 00000000..3a2947cd --- /dev/null +++ b/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example @@ -0,0 +1,89 @@ +# EverCore remote env template. +# +# Copy to evercore.env on the remote host and fill in real values there. +# Do not commit the filled file. + +# Compose-only secrets. Keep these values identical to the application values +# below where the names overlap. +EVERCORE_MONGODB_USERNAME=admin +EVERCORE_MONGODB_PASSWORD=change-me +MINIO_ROOT_USER=evercore-minio +MINIO_ROOT_PASSWORD=change-me + +# API service. +MEMSYS_HOST=0.0.0.0 +MEMSYS_PORT=1995 +API_BASE_URL=http://127.0.0.1:1995 + +# Tenant scope. Keep v0 single-tenant unless a real multi-tenant gate exists. +TENANT_SINGLE_TENANT_ID=t_everos_remote + +# LLM provider. Fill one provider path before running full memory smoke. +LLM_PROVIDER=openrouter +LLM_MODEL=x-ai/grok-4-fast +LLM_TEMPERATURE=0.3 +LLM_MAX_TOKENS=32768 +LLM_API_KEY=change-me +LLM_BASE_URL=https://openrouter.ai/api/v1 +OPENROUTER_API_KEY=change-me +OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 +OPENAI_API_KEY=change-me +OPENAI_BASE_URL=https://api.openai.com/v1 + +# Embedding and rerank providers. DeepInfra placeholders are remote-friendly; +# replace with a private vLLM endpoint if the workhorse gets local models later. +VECTORIZE_PROVIDER=deepinfra +VECTORIZE_API_KEY=change-me +VECTORIZE_BASE_URL=https://api.deepinfra.com/v1/openai +VECTORIZE_MODEL=Qwen/Qwen3-Embedding-4B +VECTORIZE_FALLBACK_PROVIDER=none +VECTORIZE_FALLBACK_API_KEY= +VECTORIZE_FALLBACK_BASE_URL= +VECTORIZE_TIMEOUT=30 +VECTORIZE_MAX_RETRIES=3 +VECTORIZE_BATCH_SIZE=10 +VECTORIZE_MAX_CONCURRENT=5 +VECTORIZE_ENCODING_FORMAT=float +VECTORIZE_DIMENSIONS=1024 + +RERANK_PROVIDER=deepinfra +RERANK_API_KEY=change-me +RERANK_BASE_URL=https://api.deepinfra.com/v1/inference +RERANK_MODEL=Qwen/Qwen3-Reranker-4B +RERANK_FALLBACK_PROVIDER=none +RERANK_FALLBACK_API_KEY= +RERANK_FALLBACK_BASE_URL= +RERANK_TIMEOUT=30 +RERANK_MAX_RETRIES=3 +RERANK_BATCH_SIZE=10 +RERANK_MAX_CONCURRENT=5 + +# Data services are internal Docker service names, not public host bindings. +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_DB=8 +REDIS_SSL=false + +MONGODB_HOST=mongodb +MONGODB_PORT=27017 +MONGODB_USERNAME=admin +MONGODB_PASSWORD=change-me +MONGODB_URI_PARAMS=socketTimeoutMS=15000&authSource=admin +MONGODB_DATABASE=memsys + +ES_HOSTS=http://elasticsearch:9200 +ES_USERNAME= +ES_PASSWORD= +ES_VERIFY_CERTS=false + +MILVUS_HOST=milvus-standalone +MILVUS_PORT=19530 + +# Retrieval defaults. +DEFAULT_SEARCH_METHOD=hybrid +TOPK_LIMIT=100 +RECALL_MULTIPLIER=2 +MILVUS_SIMILARITY_THRESHOLD=0.6 +RERANK_SCORE_THRESHOLD=0.6 +AGENTIC_ROUND1_RERANK_TOP_N=10 + diff --git a/use-cases/hermes-everos-memory/deploy/nixos/scripts/evercore-remote-smoke.sh b/use-cases/hermes-everos-memory/deploy/nixos/scripts/evercore-remote-smoke.sh new file mode 100755 index 00000000..64011736 --- /dev/null +++ b/use-cases/hermes-everos-memory/deploy/nixos/scripts/evercore-remote-smoke.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +set -euo pipefail + +mode="health" +base_url="${EVEROS_API_BASE_URL:-http://127.0.0.1:1995}" +user_id="${EVEROS_USER_ID:-hermes-remote-smoke}" +agent_id="${EVEROS_AGENT_ID:-hermes}" + +usage() { + cat <<'USAGE' +Usage: evercore-remote-smoke.sh [--mode health|write|full] + +Modes: + health Check /health only. + write Check /health and POST one agent-memory smoke turn. + full Check /health, write one turn, then search for it. + +Environment: + EVEROS_API_BASE_URL Default http://127.0.0.1:1995 + EVEROS_USER_ID Default hermes-remote-smoke + EVEROS_AGENT_ID Default hermes +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --mode) + mode="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +case "$mode" in + health|write|full) ;; + *) + echo "invalid mode: $mode" >&2 + exit 2 + ;; +esac + +need() { + command -v "$1" >/dev/null 2>&1 || { + echo "missing required command: $1" >&2 + exit 127 + } +} + +need curl +need jq + +tmpdir="$(mktemp -d)" +trap 'rm -rf "$tmpdir"' EXIT + +curl_json() { + local method="$1" + local path="$2" + local body="${3:-}" + local out="$4" + + if [[ -n "$body" ]]; then + curl -fsS --max-time 30 \ + -X "$method" \ + -H 'Content-Type: application/json' \ + --data-binary @"$body" \ + "$base_url$path" > "$out" + else + curl -fsS --max-time 30 \ + -X "$method" \ + -H 'Content-Type: application/json' \ + "$base_url$path" > "$out" + fi +} + +health_out="$tmpdir/health.json" +curl_json GET /health "" "$health_out" +status="$(jq -r '.status // .data.status // "unknown"' "$health_out")" +if [[ "$status" != "healthy" ]]; then + echo "BLOCK health status=$status" + exit 1 +fi +echo "PASS health status=healthy" + +if [[ "$mode" == "health" ]]; then + exit 0 +fi + +session_id="hermes-remote-smoke-$(date +%s)" +now_ms="$(($(date +%s) * 1000))" +body="$tmpdir/agent-add.json" +jq -n \ + --arg user_id "$user_id" \ + --arg agent_id "$agent_id" \ + --arg session_id "$session_id" \ + --argjson now_ms "$now_ms" \ + '{ + user_id: $user_id, + session_id: $session_id, + messages: [ + { + role: "user", + sender_id: $user_id, + timestamp: $now_ms, + content: "EverCore remote Hermes provider smoke write." + }, + { + role: "assistant", + sender_id: $agent_id, + timestamp: ($now_ms + 1), + content: "EverCore remote smoke response persisted for provider validation." + } + ] + }' > "$body" + +write_out="$tmpdir/write.json" +curl_json POST /api/v1/memories/agent "$body" "$write_out" +write_status="$(jq -r '.data.status // .status // "accepted"' "$write_out")" +echo "PASS write status=$write_status" + +if [[ "$mode" == "write" ]]; then + exit 0 +fi + +flush_body="$tmpdir/agent-flush.json" +jq -n \ + --arg user_id "$user_id" \ + --arg session_id "$session_id" \ + '{user_id: $user_id, session_id: $session_id}' > "$flush_body" + +flush_out="$tmpdir/flush.json" +curl_json POST /api/v1/memories/agent/flush "$flush_body" "$flush_out" +flush_status="$(jq -r '.data.status // .status // "flushed"' "$flush_out")" +echo "PASS flush status=$flush_status" + +search_body="$tmpdir/search.json" +jq -n \ + --arg query "EverCore remote Hermes provider smoke write" \ + --arg user_id "$user_id" \ + '{ + query: $query, + method: "hybrid", + memory_types: ["episodic_memory", "raw_message", "profile", "agent_memory"], + top_k: 5, + filters: {user_id: $user_id} + }' > "$search_body" + +search_out="$tmpdir/search-out.json" +curl_json POST /api/v1/memories/search "$search_body" "$search_out" +episodes="$(jq '.data.episodes // [] | length' "$search_out")" +raw_messages="$(jq '.data.raw_messages // [] | length' "$search_out")" +profiles="$(jq '.data.profiles // [] | length' "$search_out")" +agent_cases="$(jq '.data.agent_memory.cases // [] | length' "$search_out")" +agent_skills="$(jq '.data.agent_memory.skills // [] | length' "$search_out")" + +total="$((episodes + raw_messages + profiles + agent_cases + agent_skills))" +if [[ "$total" -lt 1 ]]; then + echo "BLOCK search returned no retrievable memories" + exit 1 +fi +echo "PASS search episodes=$episodes raw_messages=$raw_messages profiles=$profiles agent_cases=$agent_cases agent_skills=$agent_skills" diff --git a/use-cases/hermes-everos-memory/justfile b/use-cases/hermes-everos-memory/justfile new file mode 100644 index 00000000..96ccc475 --- /dev/null +++ b/use-cases/hermes-everos-memory/justfile @@ -0,0 +1,64 @@ +set shell := ["bash", "-eu", "-o", "pipefail", "-c"] + +health: + bun run bin/everos-memory.mjs health + +search query: + bun run bin/everos-memory.mjs search "{{query}}" + +sync-smoke: + bun run bin/everos-memory.mjs sync-smoke + +self-test: + bun run bin/everos-memory.mjs self-test + +provider-load: + scripts/check-provider-load.sh + +dogfood-smoke mode="provider-only": + scripts/dogfood-smoke.sh --mode "{{mode}}" + +skillhub-sample: + node bin/skillhub-packet.mjs validate skillhub/fixtures/raven-skillhub-sample.json + +skillhub-render file="skillhub/fixtures/raven-skillhub-sample.json": + node bin/skillhub-packet.mjs render "{{file}}" + +skillhub-views file="skillhub/fixtures/raven-skillhub-sample.json": + node bin/skillhub-packet.mjs views "{{file}}" + +skillhub-from-skill file: + node bin/skillhub-packet.mjs from-skill "{{file}}" + +skillhub-import-sample: + node bin/skillhub-packet.mjs validate skillhub/fixtures/evoagentbench-musician-life-event.json + +skillhub-api-check: + node bin/skillhub-mock-api.mjs --check + +skillhub-api port="18765": + node bin/skillhub-mock-api.mjs --port "{{port}}" + +skillhub-api-smoke: + scripts/skillhub-api-smoke.sh + +raven-sample: + node bin/raven-run.mjs validate raven/fixtures/doomsday-run.json + +raven-render: + node bin/raven-run.mjs render raven/fixtures/doomsday-run.json + +raven-verify: + node bin/raven-run.mjs verify raven/fixtures/doomsday-run.json + +mock-openai-check: + node bin/mock-openai-compatible.mjs --check + +mock-openai port="18080": + node bin/mock-openai-compatible.mjs --port "{{port}}" + +remote-smoke mode="health": + deploy/nixos/scripts/evercore-remote-smoke.sh --mode "{{mode}}" + +install-local: + scripts/install-local.sh diff --git a/use-cases/hermes-everos-memory/package.json b/use-cases/hermes-everos-memory/package.json new file mode 100644 index 00000000..72e524ef --- /dev/null +++ b/use-cases/hermes-everos-memory/package.json @@ -0,0 +1,24 @@ +{ + "name": "hermes-everos-memory", + "version": "0.1.0", + "type": "module", + "private": true, + "description": "Hermes memory-provider helper CLI for EverOS/EverCore", + "scripts": { + "health": "node bin/everos-memory.mjs health", + "search": "node bin/everos-memory.mjs search", + "sync-smoke": "node bin/everos-memory.mjs sync-smoke", + "skillhub:sample": "node bin/skillhub-packet.mjs validate skillhub/fixtures/raven-skillhub-sample.json", + "skillhub:check": "node bin/skillhub-mock-api.mjs --check", + "skillhub:smoke": "scripts/skillhub-api-smoke.sh", + "skillhub:serve": "node bin/skillhub-mock-api.mjs", + "raven:sample": "node bin/raven-run.mjs validate raven/fixtures/doomsday-run.json", + "raven:render": "node bin/raven-run.mjs render raven/fixtures/doomsday-run.json", + "mock-openai:check": "node bin/mock-openai-compatible.mjs --check", + "mock-openai": "node bin/mock-openai-compatible.mjs", + "test": "node bin/everos-memory.mjs self-test" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/use-cases/hermes-everos-memory/plugin.yaml b/use-cases/hermes-everos-memory/plugin.yaml new file mode 100644 index 00000000..4f052a83 --- /dev/null +++ b/use-cases/hermes-everos-memory/plugin.yaml @@ -0,0 +1,7 @@ +name: everos +version: 0.1.0 +description: "EverOS memory provider for Hermes - local EverCore recall, turn sync, and memory tools." +hooks: + - on_pre_compress + - on_memory_write + - on_delegation diff --git a/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md b/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md new file mode 100644 index 00000000..c97fce3f --- /dev/null +++ b/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md @@ -0,0 +1,115 @@ +# Raven Command Contract v0 + +Raven is the operator surface for memory-backed agent work. It is not a +dashboard first and not a marketing page. It is a command contract that turns a +goal, memory substrate, lanes, gates, and evidence into an owner-readable run +packet. + +## Shape + +Raven v0 ships as a thin CLI/TUI contract over local files and Hermes/EverOS +memory. + +It owns: + +- run packet validation; +- memory recall before work starts; +- lane and mutation-policy visibility; +- gate execution and verdict calculation; +- owner packet export. + +It does not own: + +- final public storytelling; +- broad repo orchestration outside the packet; +- upstream mutation without explicit approval; +- secrets, host details, or private machine topology. + +## Commands + +| Command | Input | Output | Gate | +| --- | --- | --- | --- | +| `raven init` | repo root | `.raven/packet.json` seed | no secrets in seed | +| `raven memory search ` | query text | bounded memory refs | EverOS provider available | +| `raven run ` | run packet | updated packet + iteration log | mutation policy honored | +| `raven lane list` | run packet | lane table | owners/scopes visible | +| `raven gate verify` | run packet | PASS/FLAG/BLOCK table | blocking gates cannot be skipped | +| `raven export` | run packet | owner packet markdown | public-safety scan clean | + +## Run State + +Raven treats the run packet as the source of operational state: + +```ts +type RavenRunState = + | "captured" + | "dispatching" + | "executing" + | "reviewing" + | "done" + | "blocked"; +``` + +State transitions: + +1. `captured` after goal and packet exist. +2. `dispatching` after lanes and mutation policies are assigned. +3. `executing` while local code/docs/tests are changing. +4. `reviewing` after work stops and evidence is being checked. +5. `done` only when every blocking gate is `pass`. +6. `blocked` when a blocking gate needs human or external action. + +## Memory Behavior + +Before execution Raven asks memory for: + +- prior owner decisions; +- known red gates; +- current memory-provider health; +- relevant run packets or closeouts. + +After execution Raven writes: + +- changed files; +- verification commands and verdicts; +- unresolved risks; +- one next action. + +The memory loop is proof-backed only when a unique marker can be stored and +searched back through EverOS. + +## Gate Semantics + +`PASS` means the specific requirement was tested at the scope it claims. + +`FLAG` means the path is usable but not fully proven, stale, or missing an +external observation. + +`BLOCK` means a required gate failed or needs human approval before continuing. + +Raven must not upgrade `FLAG` to `PASS` because nearby tests passed. + +## First Artifact + +The first Raven artifact is the owner packet rendered from +`raven/fixtures/doomsday-run.json`. + +Current proof command: + +```bash +node bin/raven-run.mjs render raven/fixtures/doomsday-run.json +``` + +Current local gate verifier: + +```bash +node bin/raven-run.mjs verify raven/fixtures/doomsday-run.json +``` + +This is the repo-local equivalent of: + +```bash +raven gate verify --packet raven/fixtures/doomsday-run.json +``` + +It exits non-zero for `FLAG` or `BLOCK`. diff --git a/use-cases/hermes-everos-memory/raven/README.md b/use-cases/hermes-everos-memory/raven/README.md new file mode 100644 index 00000000..672f80e1 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven/README.md @@ -0,0 +1,44 @@ +# Raven Run Packet Contract + +`v0` contract for a Raven run. + +Raven is not a marketing page here. This directory defines the packet that a +CLI/TUI can validate, render, and later execute against Hermes/EverOS memory. + +## Files + +| File | Purpose | +| --- | --- | +| `COMMAND_CONTRACT.md` | Public-safe command/state/gate contract for Raven v0 | +| `schema.json` | Public-safe JSON Schema for a Raven run packet | +| `fixtures/doomsday-run.json` | Sample run packet for the current dogfood lane | +| `../bin/raven-run.mjs` | Local validator and owner-packet renderer | + +## Commands + +```bash +node ../bin/raven-run.mjs validate fixtures/doomsday-run.json +node ../bin/raven-run.mjs render fixtures/doomsday-run.json +node ../bin/raven-run.mjs verify fixtures/doomsday-run.json +node ../bin/raven-run.mjs summary fixtures/doomsday-run.json +``` + +## Contract + +A Raven run packet records: + +- the current goal; +- owners and memory providers; +- independent lanes; +- gates with evidence and blocking status; +- artifacts and next actions. + +The computed verdict is conservative: + +- `BLOCK` if any blocking gate is `block` or any lane is `block`; +- `FLAG` if any blocking gate is `flag` or `not_run`, or any lane is `flag` or + `active`; +- `PASS` only when blocking gates and lanes are all pass. + +`verify` exits non-zero for `FLAG` or `BLOCK`, so scripts can refuse to call a +packet complete when blocking gates remain open. diff --git a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json new file mode 100644 index 00000000..6ab597b4 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json @@ -0,0 +1,140 @@ +{ + "id": "raven.everme-doomsday-run", + "title": "Raven / EverMe Doomsday Run", + "goal": "Turn the Chenyifan call into a focused Raven, EverMe SkillHub, and Hermes/EverOS dogfood execution lane with auditable local artifacts.", + "status": "reviewing", + "owners": ["codex", "pi", "opencode", "hermes"], + "memory_providers": ["everos", "hermes"], + "lanes": [ + { + "id": "raven-concept", + "owner": "pi", + "scope": "Taste, naming, story, interface wedge, and first public artifact.", + "mutation_policy": "read_only", + "verdict": "pass", + "evidence_refs": [ + ".planning/raven-everme-doomsday/RAVEN_FIELD_MANUAL.md", + "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md" + ] + }, + { + "id": "everme-skillhub", + "owner": "codex", + "scope": "Portable skill packet, mock API, and dogfood import surface.", + "mutation_policy": "local_only", + "verdict": "pass", + "evidence_refs": [ + "use-cases/hermes-everos-memory/skillhub/schema.json", + "use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md", + "use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json", + "use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs" + ] + }, + { + "id": "hermes-everos-dogfood", + "owner": "hermes", + "scope": "Provider-level load, health, store, search, and recall gates.", + "mutation_policy": "local_only", + "verdict": "pass", + "evidence_refs": [ + "use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh", + ".planning/raven-everme-doomsday/HERMES_DOGFOOD_PLAN.md", + "use-cases/hermes-everos-memory/bin/mock-openai-compatible.mjs" + ] + } + ], + "gates": [ + { + "id": "provider-load", + "name": "Hermes provider load", + "status": "pass", + "command": "just provider-load", + "evidence": "Provider class loads and exposes everos memory tools.", + "blocks_completion": true + }, + { + "id": "skillhub-api", + "name": "SkillHub mock API", + "status": "pass", + "command": "just skillhub-api-smoke", + "evidence": "Mock API serves health, target-filtered skill list, and rendered packet.", + "blocks_completion": true + }, + { + "id": "full-memory-loop", + "name": "Full store/search/recall loop", + "status": "pass", + "command": "just dogfood-smoke full", + "evidence": "PASS provider_load, health, store, flush, search count=1, and prefetch chars=219 with local mock inference server.", + "blocks_completion": true + }, + { + "id": "real-hermes-profile-turn", + "name": "Real Hermes profile turn", + "status": "pass", + "command": "hermes -z with a unique Raven marker, then EverOS search", + "evidence": "Hermes profile reports provider=everos, explicit everos_store marker searchable, and sync_turn marker searchable.", + "blocks_completion": true + }, + { + "id": "raven-gate-verify", + "name": "Raven gate verifier", + "status": "pass", + "command": "just raven-verify", + "evidence": "raven-run verify renders blocking gates and exits non-zero for FLAG/BLOCK.", + "blocks_completion": true + }, + { + "id": "skillhub-real-skill-import", + "name": "SkillHub real skill import", + "status": "pass", + "command": "just skillhub-import-sample && just skillhub-views skillhub/fixtures/evoagentbench-musician-life-event.json", + "evidence": "A real EvoAgentBench SKILL.md imports into a valid SkillHub packet and renders five MVP views.", + "blocks_completion": true + } + ], + "artifacts": [ + { + "path": ".planning/raven-everme-doomsday/RAVEN_FIELD_MANUAL.md", + "purpose": "Raven command and taste contract.", + "public_safe": false + }, + { + "path": "use-cases/hermes-everos-memory/skillhub/schema.json", + "purpose": "EverMe SkillHub packet contract.", + "public_safe": true + }, + { + "path": "use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md", + "purpose": "EverMe SkillHub MVP view/API/implementation plan.", + "public_safe": true + }, + { + "path": "use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json", + "purpose": "Real EvoAgentBench skill import fixture.", + "public_safe": true + }, + { + "path": "use-cases/hermes-everos-memory/raven/schema.json", + "purpose": "Raven run packet contract.", + "public_safe": true + }, + { + "path": "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", + "purpose": "Raven command/state/gate contract.", + "public_safe": true + } + ], + "evidence_refs": [ + "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", + "use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md", + "use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json", + "use-cases/hermes-everos-memory/bin/raven-run.mjs verify", + "use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh", + "use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh", + ".planning/raven-everme-doomsday/HERMES_DOGFOOD_PLAN.md" + ], + "next_actions": [ + "Promote the NixOS EverCore packet to an observed remote smoke." + ] +} diff --git a/use-cases/hermes-everos-memory/raven/schema.json b/use-cases/hermes-everos-memory/raven/schema.json new file mode 100644 index 00000000..53d08d23 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven/schema.json @@ -0,0 +1,156 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://everos.local/schemas/raven-run-packet-v0.json", + "title": "Raven Run Packet", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "title", + "goal", + "status", + "owners", + "memory_providers", + "lanes", + "gates", + "artifacts", + "evidence_refs", + "next_actions" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9._-]{2,127}$" + }, + "title": { + "type": "string", + "minLength": 2, + "maxLength": 160 + }, + "goal": { + "type": "string", + "minLength": 1, + "maxLength": 1000 + }, + "status": { + "type": "string", + "enum": ["captured", "dispatching", "executing", "reviewing", "done", "blocked"] + }, + "owners": { + "type": "array", + "items": {"type": "string", "minLength": 1, "maxLength": 80}, + "minItems": 1, + "uniqueItems": true + }, + "memory_providers": { + "type": "array", + "items": {"type": "string", "minLength": 1, "maxLength": 80}, + "uniqueItems": true + }, + "lanes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "owner", "scope", "mutation_policy", "verdict"], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9._-]{2,127}$" + }, + "owner": { + "type": "string", + "enum": ["codex", "pi", "opencode", "hermes", "muw", "human"] + }, + "scope": { + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + "mutation_policy": { + "type": "string", + "enum": ["read_only", "local_only", "external_requires_approval"] + }, + "verdict": { + "type": "string", + "enum": ["pass", "flag", "block", "active"] + }, + "evidence_refs": { + "type": "array", + "items": {"type": "string", "minLength": 1, "maxLength": 300}, + "uniqueItems": true + } + } + }, + "minItems": 1 + }, + "gates": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "status", "evidence", "blocks_completion"], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9._-]{2,127}$" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 160 + }, + "status": { + "type": "string", + "enum": ["pass", "flag", "block", "not_run"] + }, + "command": { + "type": "string", + "maxLength": 300 + }, + "evidence": { + "type": "string", + "minLength": 1, + "maxLength": 800 + }, + "blocks_completion": { + "type": "boolean" + } + } + }, + "minItems": 1 + }, + "artifacts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["path", "purpose", "public_safe"], + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 300 + }, + "purpose": { + "type": "string", + "minLength": 1, + "maxLength": 400 + }, + "public_safe": { + "type": "boolean" + } + } + } + }, + "evidence_refs": { + "type": "array", + "items": {"type": "string", "minLength": 1, "maxLength": 300}, + "uniqueItems": true + }, + "next_actions": { + "type": "array", + "items": {"type": "string", "minLength": 1, "maxLength": 300} + } + } +} diff --git a/use-cases/hermes-everos-memory/scripts/check-provider-load.sh b/use-cases/hermes-everos-memory/scripts/check-provider-load.sh new file mode 100755 index 00000000..0c08eeb4 --- /dev/null +++ b/use-cases/hermes-everos-memory/scripts/check-provider-load.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PLUGIN_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +HERMES_AGENT_SRC="${HERMES_AGENT_SRC:-${HOME}/.hermes/hermes-agent}" + +if [[ ! -d "${HERMES_AGENT_SRC}" ]]; then + echo "Hermes agent source not found. Set HERMES_AGENT_SRC." >&2 + exit 2 +fi + +TMP_HOME="$(mktemp -d)" +cleanup() { + rm -rf "${TMP_HOME}" +} +trap cleanup EXIT + +mkdir -p "${TMP_HOME}/plugins/everos" +cp "${PLUGIN_DIR}/__init__.py" "${PLUGIN_DIR}/plugin.yaml" "${TMP_HOME}/plugins/everos/" + +HERMES_HOME="${TMP_HOME}" PYTHONPATH="${HERMES_AGENT_SRC}" python3 - <<'PY' +from plugins.memory import discover_memory_providers, load_memory_provider + +names = [name for name, _, _ in discover_memory_providers()] +assert "everos" in names, names + +provider = load_memory_provider("everos") +assert provider is not None +assert provider.name == "everos" + +tools = [schema["name"] for schema in provider.get_tool_schemas()] +expected = {"everos_search", "everos_store", "everos_health", "everos_flush"} +assert expected.issubset(set(tools)), tools + +print("PASS provider-load everos " + ",".join(tools)) +PY + diff --git a/use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh b/use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh new file mode 100755 index 00000000..dcc882d8 --- /dev/null +++ b/use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PLUGIN_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +HERMES_AGENT_SRC="${HERMES_AGENT_SRC:-${HOME}/.hermes/hermes-agent}" +MODE="provider-only" + +usage() { + cat <<'USAGE' +Usage: dogfood-smoke.sh [--mode provider-only|health|full] + +Modes: + provider-only Load the Hermes provider and verify schemas. No EverCore required. + health Load provider and require EverCore /health to be reachable. + full Require health, store one turn, then search/prefetch. + +Environment: + HERMES_AGENT_SRC Hermes agent source checkout. Default ~/.hermes/hermes-agent + EVEROS_API_BASE_URL EverCore base URL. Default http://127.0.0.1:1995 + EVEROS_USER_ID EverOS user id for smoke. Default hermes-dogfood-smoke + EVEROS_AGENT_ID EverOS agent id. Default hermes-dogfood +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --mode) + MODE="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +case "${MODE}" in + provider-only|health|full) ;; + *) + echo "invalid mode: ${MODE}" >&2 + exit 2 + ;; +esac + +if [[ ! -d "${HERMES_AGENT_SRC}" ]]; then + echo "BLOCK hermes_agent_src_missing" + exit 2 +fi + +MODE="${MODE}" \ +PLUGIN_DIR="${PLUGIN_DIR}" \ +EVEROS_USER_ID="${EVEROS_USER_ID:-hermes-dogfood-smoke}" \ +EVEROS_AGENT_ID="${EVEROS_AGENT_ID:-hermes-dogfood}" \ +PYTHONPATH="${HERMES_AGENT_SRC}" \ +python3 - <<'PY' +import importlib.util +import json +import os +import pathlib +import sys +import time + +mode = os.environ["MODE"] +plugin_dir = pathlib.Path(os.environ["PLUGIN_DIR"]) +module_path = plugin_dir / "__init__.py" + +spec = importlib.util.spec_from_file_location("everos_memory_provider_smoke", module_path) +if spec is None or spec.loader is None: + print("BLOCK provider_spec_unavailable") + sys.exit(1) + +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) + +provider = module.EverOSMemoryProvider() +provider.initialize( + session_id=f"hermes-dogfood-smoke-{int(time.time())}", + agent_identity="dogfood", + user_id=os.environ["EVEROS_USER_ID"], +) + +tools = {schema["name"] for schema in provider.get_tool_schemas()} +expected = {"everos_search", "everos_store", "everos_health", "everos_flush"} +missing = sorted(expected - tools) +if missing: + print("BLOCK provider_schema_missing " + ",".join(missing)) + sys.exit(1) + +prompt = provider.system_prompt_block() +if "EverOS Memory" not in prompt: + print("BLOCK provider_prompt_missing") + sys.exit(1) + +print("PASS provider_load tools=" + ",".join(sorted(tools))) + +if mode == "provider-only": + sys.exit(0) + +if not provider.is_available(): + print("BLOCK evercore_unavailable") + sys.exit(1) + +health_raw = provider.handle_tool_call("everos_health", {}) +try: + health = json.loads(health_raw) +except json.JSONDecodeError: + print("BLOCK health_non_json") + sys.exit(1) + +status = (health.get("result") or {}).get("status", "unknown") +if status not in {"healthy", "ok"}: + print(f"BLOCK health_status={status}") + sys.exit(1) + +print("PASS health status=" + status) + +if mode == "health": + sys.exit(0) + +stamp = int(time.time()) +needle = f"Hermes EverOS dogfood smoke Raven SkillHub {stamp}" + +store_raw = provider.handle_tool_call( + "everos_store", + { + "role": "user", + "content": ( + needle + + ": provider-level store/search/recall smoke for Raven and EverMe SkillHub." + ), + }, +) +try: + store = json.loads(store_raw) +except json.JSONDecodeError: + print("BLOCK store_non_json") + sys.exit(1) + +if store.get("result") != "stored": + print("BLOCK store_failed") + sys.exit(1) + +print("PASS store result=stored") + +flush_raw = provider.handle_tool_call("everos_flush", {}) +try: + flush = json.loads(flush_raw) +except json.JSONDecodeError: + print("BLOCK flush_non_json") + sys.exit(1) + +if flush.get("result") != "flushed": + print("BLOCK flush_failed") + sys.exit(1) + +print("PASS flush result=flushed") + +time.sleep(2) + +search_raw = provider.handle_tool_call( + "everos_search", + { + "query": needle, + "top_k": 5, + "memory_types": ["episodic_memory", "profile", "agent_memory", "raw_message"], + }, +) +try: + search = json.loads(search_raw) +except json.JSONDecodeError: + print("BLOCK search_non_json") + sys.exit(1) + +count = int(search.get("count") or 0) +if count < 1: + print("BLOCK search_count=0") + sys.exit(1) + +print(f"PASS search count={count}") + +prefetch = provider.prefetch(needle) +if not prefetch: + print("BLOCK prefetch_empty") + sys.exit(1) + +print("PASS prefetch chars=" + str(len(prefetch))) + +provider.shutdown() +PY diff --git a/use-cases/hermes-everos-memory/scripts/install-local.sh b/use-cases/hermes-everos-memory/scripts/install-local.sh new file mode 100755 index 00000000..553d2744 --- /dev/null +++ b/use-cases/hermes-everos-memory/scripts/install-local.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PLUGIN_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}" +DEST_DIR="${HERMES_HOME}/plugins/everos" + +mkdir -p "${DEST_DIR}" + +for item in __init__.py plugin.yaml README.md bin package.json justfile scripts skillhub raven deploy; do + rm -rf "${DEST_DIR}/${item}" + cp -R "${PLUGIN_DIR}/${item}" "${DEST_DIR}/${item}" +done + +echo "Installed Hermes EverOS memory provider to active Hermes plugins dir." +echo "Activate with: hermes config set memory.provider everos" diff --git a/use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh b/use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh new file mode 100755 index 00000000..f3b0c55f --- /dev/null +++ b/use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PORT="${SKILLHUB_PORT:-18765}" +HOST="${SKILLHUB_HOST:-127.0.0.1}" +TMP_DIR="$(mktemp -d)" + +cleanup() { + if [[ -n "${SERVER_PID:-}" ]]; then + kill "$SERVER_PID" 2>/dev/null || true + wait "$SERVER_PID" 2>/dev/null || true + fi + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +node "$ROOT_DIR/bin/skillhub-mock-api.mjs" \ + --host "$HOST" \ + --port "$PORT" \ + >"$TMP_DIR/server.log" \ + 2>&1 & +SERVER_PID="$!" + +for _ in 1 2 3 4 5 6 7 8 9 10; do + if curl -fsS "http://$HOST:$PORT/health" >"$TMP_DIR/health.json" 2>/dev/null; then + break + fi + sleep 0.2 +done + +curl -fsS "http://$HOST:$PORT/health" >"$TMP_DIR/health.json" +curl -fsS "http://$HOST:$PORT/skills?target=hermes" >"$TMP_DIR/skills.json" +curl -fsS "http://$HOST:$PORT/skills/raven.operator-memory-recall/render" \ + >"$TMP_DIR/render.md" + +rg '"ok": true' "$TMP_DIR/health.json" >/dev/null +rg 'raven.operator-memory-recall' "$TMP_DIR/skills.json" >/dev/null +rg 'Operator Memory Recall' "$TMP_DIR/render.md" >/dev/null + +printf 'PASS skillhub mock api smoke host=%s port=%s\n' "$HOST" "$PORT" diff --git a/use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md b/use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md new file mode 100644 index 00000000..42a36c38 --- /dev/null +++ b/use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md @@ -0,0 +1,92 @@ +# EverMe SkillHub MVP Implementation Plan v0 + +SkillHub is the memory-backed skill surface for EverMe. It is not a generic +marketplace. The MVP makes skills legible, installable, improvable, and +evidence-bearing before final EverMe product UI is available. + +## Product Contract + +SkillHub v0 manages portable skill packets. + +Each packet must answer: + +- What is this skill for? +- Where can it install? +- What evidence says it works? +- What should improve next? +- Can it be shared safely? + +## MVP Views + +| View | Purpose | Minimum fields | +| --- | --- | --- | +| Skill Index | scan owned/community/needs-eval skills | name, status, version, domains, targets | +| Skill Detail | understand one skill | summary, body, source, evidence | +| Evolution Queue | decide next improvement | status, eval score, last evolved, evidence gaps | +| Install Packet | connect to runtime | install targets, version, body markdown | +| Trust Panel | decide whether to use/share | provenance, evidence refs, rating, votes | + +The CLI renderer should expose these views before any final web UI exists. + +## API Contract + +The mock API is read-only until EverMe backend constraints arrive. + +Current routes: + +- `GET /health` +- `GET /skills` +- `GET /skills?target=hermes` +- `GET /skills/:id` +- `GET /skills/:id/render` +- `POST /skills/validate` + +Next API routes: + +- `GET /skills/:id/views` +- `GET /skills/:id/install-packet?target=hermes` +- `POST /skills/:id/evidence` +- `POST /skills/:id/evolution-note` + +Write routes stay proposed until the canonical EverMe API exists. + +## Data Additions + +Keep the packet compact. Add optional fields only when they support the five +MVP views: + +- `provenance`: source runtime, extractor, source artifact id; +- `evolution_history`: timestamped notes, eval deltas, next action; +- `compatibility`: runtime names and minimum versions; +- `install_notes`: target-specific setup notes; +- `trust`: rating, votes, eval score, evidence summary. + +The existing core packet stays valid without these fields. + +## Implementation Sequence + +1. Extend the local renderer with a `views` command. +2. Add `just skillhub-views`. +3. Keep the mock API read-only and deterministic. +4. Import one real `SKILL.md` file through `from-skill`. +5. Add optional data fields only after the renderer proves their use. +6. Wait for EverMe design-system input before building final visual UI. + +## Gates + +| Gate | Verdict rule | +| --- | --- | +| Packet validation | schema and custom validator pass | +| Views render | five MVP views render from one packet | +| Mock API | health/list/detail/render/validate pass | +| Import path | `from-skill` produces a valid packet | +| Public safety | no secrets, host details, private paths, or raw tokens | + +## First Useful Slice + +```bash +node bin/skillhub-packet.mjs views skillhub/fixtures/raven-skillhub-sample.json +``` + +This is enough for Raven, Hermes, and EverCore to dogfood SkillHub without +pretending the final EverMe UI is done. diff --git a/use-cases/hermes-everos-memory/skillhub/README.md b/use-cases/hermes-everos-memory/skillhub/README.md new file mode 100644 index 00000000..5779efec --- /dev/null +++ b/use-cases/hermes-everos-memory/skillhub/README.md @@ -0,0 +1,94 @@ +# SkillHub Packet Contract + +`v0` packet contract for EverMe SkillHub dogfooding. + +This is not the final EverMe UI. It is the portable skill object that Raven, +Hermes, and EverCore can exchange before the cloud/product API is finalized. + +## Files + +| File | Purpose | +| --- | --- | +| `schema.json` | Public-safe JSON Schema for one SkillHub packet | +| `fixtures/raven-skillhub-sample.json` | Sample packet used by smoke tests | +| `fixtures/evoagentbench-musician-life-event.json` | Real `SKILL.md` import fixture | +| `../bin/skillhub-packet.mjs` | Local exporter/validator helper | +| `../bin/skillhub-mock-api.mjs` | Read-only HTTP adapter for Raven/Hermes dogfood | +| `MVP_IMPLEMENTATION_PLAN.md` | Public-safe MVP view/API/implementation contract | + +## Contract + +A SkillHub packet represents one skill plus enough provenance for an agent +runtime to decide whether it can install or use it. + +Required fields: + +- `id` +- `name` +- `summary` +- `visibility` +- `status` +- `version` +- `source` +- `domains` +- `install_targets` +- `evidence_refs` +- `body_markdown` + +## Local Commands + +Validate the sample: + +```bash +node ../bin/skillhub-packet.mjs validate fixtures/raven-skillhub-sample.json +node ../bin/skillhub-packet.mjs render fixtures/raven-skillhub-sample.json +node ../bin/skillhub-packet.mjs views fixtures/raven-skillhub-sample.json +node ../bin/skillhub-mock-api.mjs --check +``` + +Export an existing `SKILL.md` file: + +```bash +node ../bin/skillhub-packet.mjs from-skill \ + ../../benchmarks/EvoAgentBench/src/skill_evolution/evermemos/skills_sample/MUSICIAN/musician_life_event/SKILL.md +``` + +The imported fixture is checked with: + +```bash +node ../bin/skillhub-packet.mjs validate fixtures/evoagentbench-musician-life-event.json +node ../bin/skillhub-packet.mjs views fixtures/evoagentbench-musician-life-event.json +``` + +## Dogfood Path + +1. EverCore extracts or stores a skill. +2. SkillHub exports it as this packet. +3. Hermes/Raven imports the packet as an installable skill or memory-backed + runtime hint. +4. Evaluation evidence updates `evidence_refs`, `eval_score`, and `status`. + +## Mock API + +The mock API is a local read-only adapter over packet JSON files. It exists so +Raven, Hermes, or EverCore clients can prove their integration contract before +the final EverMe backend exists. + +```bash +node ../bin/skillhub-mock-api.mjs --port 18765 +``` + +Smoke the routes: + +```bash +../scripts/skillhub-api-smoke.sh +``` + +Routes: + +- `GET /health` +- `GET /skills` +- `GET /skills?target=hermes` +- `GET /skills/:id` +- `GET /skills/:id/render` +- `POST /skills/validate` diff --git a/use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json b/use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json new file mode 100644 index 00000000..23828fdb --- /dev/null +++ b/use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json @@ -0,0 +1,25 @@ +{ + "id": "everme-evoagentbench.musician_life_event", + "name": "musician_life_event", + "summary": "Use unique personal life events (not musical works) as the primary search anchor for identifying musicians.", + "owner_id": "everme-evoagentbench", + "visibility": "private", + "status": "needs_eval", + "version": "0.1.0", + "source": "evercore_extracted", + "domains": [ + "musician" + ], + "install_targets": [ + "hermes" + ], + "evidence_refs": [ + "benchmarks/EvoAgentBench/src/skill_evolution/evermemos/skills_sample/MUSICIAN/musician_life_event/SKILL.md" + ], + "body_markdown": "# Musician: Life Event Anchoring\n\n## When to use\nWhen a musician question describes distinctive personal experiences rather than discography clues.\n\n## Technique\nThe most distinctive information about musicians is often unique life events, not musical works. Life events are far more unique and searchable than album/song names.\n\nPriority ranking for biographical constraints:\n1. **Cause of death / special events:** \"died of professional negligence\", \"triple bypass surgery\" — extremely rare, almost always a direct hit\n2. **Personal life details:** \"five children\", \"divorced twice\", \"searched for biological father at age 17\"\n3. **Education / career turning points:** \"dropped out of college to pursue music\"\n4. **Work characteristics:** \"first album songs written by Boris Vian\" — use only when life events are unavailable\n\nAlso track auxiliary figures mentioned in questions: songwriters, doctors, family members.\n\n## Query Templates\n- `\"musician [cause of death] charged sentenced [year]\"`\n- `\"[musician name] \"divorced\" \"children\" wife\"`\n- `\"singer dropped out [school type] pursue music [country]\"`\n\n## Worked Examples\n**Dr. Kang Se Hoon:** Question mentioned hobbies of reading comics and playing computer games. Searched `\"Shin Hae-chul solo album 1990s radio DJ hobbies comics computer games\"` → then tracked the doctor: `\"Shin Hae-chul Kang Se-hoon charged professional negligence death sentenced\"`.\n\n**Vanic:** Born in May, dropped out of school for music production. After narrowing candidates, verified with `\"Vanic Jesse born May 1987 1988 1989 1990 Vancouver producer\"`.\n\n## Anti-pattern\nOver-relying on album or song name searches. Album names are often not distinctive enough (\"First Album\", \"The Light\"). Anchor with life events first, then use discography only for verification.", + "frontmatter": { + "name": "musician_life_event", + "description": "Use unique personal life events (not musical works) as the primary search anchor for identifying musicians.", + "always": true + } +} diff --git a/use-cases/hermes-everos-memory/skillhub/fixtures/raven-skillhub-sample.json b/use-cases/hermes-everos-memory/skillhub/fixtures/raven-skillhub-sample.json new file mode 100644 index 00000000..aa3f1cb8 --- /dev/null +++ b/use-cases/hermes-everos-memory/skillhub/fixtures/raven-skillhub-sample.json @@ -0,0 +1,20 @@ +{ + "id": "raven.operator-memory-recall", + "name": "Operator Memory Recall", + "summary": "Recall prior project decisions before a Raven or Hermes run starts.", + "owner_id": "everme-local", + "visibility": "private", + "status": "needs_eval", + "version": "0.1.0", + "source": "manual", + "domains": ["agent-ops", "memory", "software-engineering"], + "install_targets": ["hermes", "raven", "evercore"], + "eval_score": 0, + "rating": 0, + "votes": 0, + "evidence_refs": [ + "use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh --mode provider-only" + ], + "body_markdown": "# Operator Memory Recall\n\n## When to use\nBefore an agent run starts, search durable project memory for prior decisions, constraints, and open gates.\n\n## Technique\nUse a short query that includes the project name, the active goal, and the decision surface. Prefer evidence-backed memories over summary-only memories.\n\n## Anti-pattern\nDo not call memory recall a PASS unless the current run proves health, store, search, and recall." +} + diff --git a/use-cases/hermes-everos-memory/skillhub/schema.json b/use-cases/hermes-everos-memory/skillhub/schema.json new file mode 100644 index 00000000..7337c530 --- /dev/null +++ b/use-cases/hermes-everos-memory/skillhub/schema.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://everos.local/schemas/skillhub-packet-v0.json", + "title": "EverMe SkillHub Packet", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "name", + "summary", + "visibility", + "status", + "version", + "source", + "domains", + "install_targets", + "evidence_refs", + "body_markdown" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9._-]{2,127}$" + }, + "name": { + "type": "string", + "minLength": 2, + "maxLength": 120 + }, + "summary": { + "type": "string", + "minLength": 1, + "maxLength": 600 + }, + "owner_id": { + "type": "string", + "maxLength": 160 + }, + "visibility": { + "type": "string", + "enum": ["private", "link", "community"] + }, + "status": { + "type": "string", + "enum": ["draft", "active", "needs_eval", "archived"] + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][A-Za-z0-9._-]+)?$" + }, + "source": { + "type": "string", + "enum": ["manual", "evercore_extracted", "imported", "community"] + }, + "domains": { + "type": "array", + "items": {"type": "string", "minLength": 1, "maxLength": 80}, + "minItems": 1, + "uniqueItems": true + }, + "install_targets": { + "type": "array", + "items": { + "type": "string", + "enum": ["hermes", "raven", "claude_code", "evercore", "openclaw"] + }, + "minItems": 1, + "uniqueItems": true + }, + "eval_score": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "rating": { + "type": "number", + "minimum": 0, + "maximum": 5 + }, + "votes": { + "type": "integer", + "minimum": 0 + }, + "last_evolved_at": { + "type": "string", + "format": "date-time" + }, + "evidence_refs": { + "type": "array", + "items": {"type": "string", "minLength": 1, "maxLength": 300}, + "uniqueItems": true + }, + "body_markdown": { + "type": "string", + "minLength": 1 + }, + "frontmatter": { + "type": "object", + "additionalProperties": { + "type": ["string", "number", "boolean", "null"] + } + } + } +} + From 8aaaf8c59262e6da48044b319fd13af5e1318d3b Mon Sep 17 00:00:00 2001 From: 0xVox Date: Thu, 14 May 2026 23:30:17 -0400 Subject: [PATCH 06/27] docs(everos): add owner packet and remote disposition Records the current PASS/FLAG state for Hermes/EverOS dogfood artifacts and the observed NixOS remote blocker without exposing private host details. Co-authored-by: Codex --- .../hermes-everos-memory/OWNER_PACKET.md | 79 +++++++++++++++++++ use-cases/hermes-everos-memory/README.md | 1 + .../deploy/nixos/DEPLOY_PACKET.md | 23 +++++- .../deploy/nixos/README.md | 7 ++ .../raven/fixtures/doomsday-run.json | 6 ++ 5 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 use-cases/hermes-everos-memory/OWNER_PACKET.md diff --git a/use-cases/hermes-everos-memory/OWNER_PACKET.md b/use-cases/hermes-everos-memory/OWNER_PACKET.md new file mode 100644 index 00000000..8f7af215 --- /dev/null +++ b/use-cases/hermes-everos-memory/OWNER_PACKET.md @@ -0,0 +1,79 @@ +# Hermes EverOS Memory Owner Packet + +## Verdict + +FLAG overall. + +Local artifacts are ready and verified. Remote NixOS deployment is not complete: +the workhorse route is reachable, but EverCore is not yet active on the remote +loopback service. + +## What Shipped + +- Hermes `everos` memory-provider shim with search, store, health, flush, + prefetch, sync, and auto-flush behavior. +- Raven run packet contract, command contract, renderer, and gate verifier. +- EverMe SkillHub packet schema, MVP view plan, renderer, read-only mock API, + and one real EvoAgentBench `SKILL.md` import fixture. +- NixOS/workhorse deploy packet, compose file, module draft, env example, and + remote smoke script. +- Local mock OpenAI-compatible server for model-free EverCore dogfood. + +## Verification + +Latest local verification set: + +```bash +bash -n use-cases/hermes-everos-memory/scripts/*.sh use-cases/hermes-everos-memory/deploy/nixos/scripts/*.sh +cd use-cases/hermes-everos-memory && for f in bin/*.mjs; do node --check "$f"; done +git diff --check -- use-cases/hermes-everos-memory +cd use-cases/hermes-everos-memory && just provider-load +cd use-cases/hermes-everos-memory && just skillhub-api-smoke +cd use-cases/hermes-everos-memory && just skillhub-import-sample +cd use-cases/hermes-everos-memory && just raven-sample +cd use-cases/hermes-everos-memory && just raven-verify +cd use-cases/hermes-everos-memory && just remote-smoke full +cd use-cases/hermes-everos-memory && just mock-openai-check +cd use-cases/hermes-everos-memory && EVEROS_USER_ID="verify-raven-$(date +%s)" EVEROS_SEARCH_METHOD=hybrid EVEROS_MEMORY_TYPES=episodic_memory,raw_message,profile,agent_memory just dogfood-smoke full +``` + +Hermes profile verification: + +```bash +hermes memory status +``` + +Expected status: + +```text +Provider: everos +Plugin: installed +Status: available +``` + +## Remote Disposition + +Read-only workhorse probe: + +- NixOS host is reachable. +- System state is running. +- Failed systemd unit count was zero during the dry-run probe. +- EverCore service/timer are inactive. +- Remote loopback health at the EverCore API port is unavailable. + +Remote deploy remains `FLAG` until the EverCore module is staged into the +workhorse configuration, `nixos-rebuild test` passes, and the remote +`--mode full` smoke passes on-host. + +## Guardrails Preserved + +- No new major repo. +- No push or external publish. +- No private host/IP/token/credential path in public artifacts. +- No final EverMe UI claim before product/design-system constraints. +- Red remote deploy gate remains red. + +## Next Action + +Stage the EverCore NixOS module into the Windburn workhorse lane with the +private env file already present on the host, then run `nixos-rebuild test`. diff --git a/use-cases/hermes-everos-memory/README.md b/use-cases/hermes-everos-memory/README.md index 773dcb0f..6edf3ada 100644 --- a/use-cases/hermes-everos-memory/README.md +++ b/use-cases/hermes-everos-memory/README.md @@ -18,6 +18,7 @@ The provider is intentionally split: direct smoke tests. - `scripts/install-local.sh` installs the provider into the active Hermes profile without activating it. +- `OWNER_PACKET.md` summarizes the current PASS/FLAG evidence for review. ## Status diff --git a/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md b/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md index 4e95e286..70abcfa4 100644 --- a/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md +++ b/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md @@ -2,8 +2,8 @@ ## Verdict -FLAG: deploy packet is ready for review, but live remote deployment is not proven -until the operator installs the secret env file and runs the NixOS/compose gates. +FLAG: deploy packet is ready for review and the remote NixOS workhorse is +reachable, but EverCore is not yet running on the workhorse loopback service. ## Decision @@ -59,6 +59,20 @@ Keep deployment blocked if any of these are true: - full smoke search returns zero retrievable memories after flush; - host evidence includes raw public host/IP or credential paths. +## Observed Remote Probe + +Latest read-only probe: + +- remote host is reachable through the existing workhorse SSH route; +- remote OS is NixOS and system state is running; +- failed systemd units reported as zero during the dry-run NixOS probe; +- `evercore-compose.service` is inactive; +- `evercore-health.timer` is inactive; +- `http://127.0.0.1:1995/health` is unavailable on the remote host. + +Verdict: `FLAG`, because the target host is real and healthy enough for deploy +work, but EverCore has not been applied or started there. + ## Verification Commands From this repo: @@ -82,5 +96,6 @@ scripts/evercore-remote-smoke.sh --mode full ## Next Concrete Action -Stage this packet into the Windburn workhorse lane and run a test rebuild with a -redacted env file present on the host. +Stage this packet into the Windburn workhorse lane, add the EverCore module to +the host config, and run `nixos-rebuild test` with the private env file present +on the host. Keep `switch` blocked until `test` passes. diff --git a/use-cases/hermes-everos-memory/deploy/nixos/README.md b/use-cases/hermes-everos-memory/deploy/nixos/README.md index f5bb4494..a85cdff1 100644 --- a/use-cases/hermes-everos-memory/deploy/nixos/README.md +++ b/use-cases/hermes-everos-memory/deploy/nixos/README.md @@ -126,3 +126,10 @@ keep the provider config pointed at the local endpoint exposed by that route. installed. 4. Hermes provider `everos_health`, `everos_store`, and `everos_search` all pass. 5. No public data ports are reachable from outside the private host boundary. + +## Current Remote Disposition + +The workhorse route has been probed read-only and is reachable as a NixOS host, +but EverCore is not yet active there. Treat remote deployment as `FLAG` until +`evercore-compose.service`, `evercore-health.timer`, and `--mode full` pass on +the remote host. diff --git a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json index 6ab597b4..4bf2eda7 100644 --- a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json +++ b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json @@ -123,9 +123,15 @@ "path": "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", "purpose": "Raven command/state/gate contract.", "public_safe": true + }, + { + "path": "use-cases/hermes-everos-memory/OWNER_PACKET.md", + "purpose": "Owner-readable PASS/FLAG review packet.", + "public_safe": true } ], "evidence_refs": [ + "use-cases/hermes-everos-memory/OWNER_PACKET.md", "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", "use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md", "use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json", From 9960c3f05a3eea19b732cb198c806c38272b16bb Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 00:00:47 -0400 Subject: [PATCH 07/27] feat(skillhub): expose read-only views and install packets Adds SkillHub mock API routes for API-backed MVP views and target-specific install packets, and extends the smoke test and docs to cover them. Co-authored-by: Codex --- .../hermes-everos-memory/OWNER_PACKET.md | 3 +- .../bin/skillhub-mock-api.mjs | 69 ++++++++++++++++++- .../bin/skillhub-packet.mjs | 2 +- .../scripts/skillhub-api-smoke.sh | 8 +++ .../skillhub/MVP_IMPLEMENTATION_PLAN.md | 4 +- .../hermes-everos-memory/skillhub/README.md | 2 + 6 files changed, 83 insertions(+), 5 deletions(-) diff --git a/use-cases/hermes-everos-memory/OWNER_PACKET.md b/use-cases/hermes-everos-memory/OWNER_PACKET.md index 8f7af215..f5ab01db 100644 --- a/use-cases/hermes-everos-memory/OWNER_PACKET.md +++ b/use-cases/hermes-everos-memory/OWNER_PACKET.md @@ -14,7 +14,8 @@ loopback service. prefetch, sync, and auto-flush behavior. - Raven run packet contract, command contract, renderer, and gate verifier. - EverMe SkillHub packet schema, MVP view plan, renderer, read-only mock API, - and one real EvoAgentBench `SKILL.md` import fixture. + API-backed views/install-packet routes, and one real EvoAgentBench `SKILL.md` + import fixture. - NixOS/workhorse deploy packet, compose file, module draft, env example, and remote smoke script. - Local mock OpenAI-compatible server for model-free EverCore dogfood. diff --git a/use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs b/use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs index 601477ae..82bf9e22 100755 --- a/use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs +++ b/use-cases/hermes-everos-memory/bin/skillhub-mock-api.mjs @@ -5,7 +5,7 @@ import http from 'node:http'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { readJson, renderPacket, validatePacket } from './skillhub-packet.mjs'; +import { readJson, renderPacket, renderSkillViews, validatePacket } from './skillhub-packet.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DEFAULT_PACKET_DIR = path.resolve(__dirname, '../skillhub/fixtures'); @@ -62,6 +62,29 @@ function packetSummary(packet) { }; } +function installPacket(packet, target) { + if (target && !packet.install_targets.includes(target)) { + return null; + } + return { + id: packet.id, + name: packet.name, + version: packet.version, + target: target || packet.install_targets[0], + compatible_targets: packet.install_targets, + source: packet.source, + visibility: packet.visibility, + status: packet.status, + domains: packet.domains, + summary: packet.summary, + body_markdown: packet.body_markdown, + evidence_refs: packet.evidence_refs, + eval_score: packet.eval_score, + rating: packet.rating, + votes: packet.votes, + }; +} + function loadPackets(dir) { const files = fs .readdirSync(dir, { withFileTypes: true }) @@ -182,6 +205,50 @@ async function route(req, res, packets) { return; } + if ( + req.method === 'GET' + && segments.length === 3 + && segments[0] === 'skills' + && segments[2] === 'views' + ) { + const item = packets.get(segments[1]); + if (!item) { + sendJson(res, 404, { ok: false, error: 'skill not found' }); + return; + } + sendJson(res, 200, { + ok: true, + skill_id: item.packet.id, + views_markdown: renderSkillViews(item.packet), + }); + return; + } + + if ( + req.method === 'GET' + && segments.length === 3 + && segments[0] === 'skills' + && segments[2] === 'install-packet' + ) { + const item = packets.get(segments[1]); + if (!item) { + sendJson(res, 404, { ok: false, error: 'skill not found' }); + return; + } + const target = url.searchParams.get('target'); + const packet = installPacket(item.packet, target); + if (!packet) { + sendJson(res, 422, { + ok: false, + error: 'target not supported', + supported_targets: item.packet.install_targets, + }); + return; + } + sendJson(res, 200, { ok: true, install_packet: packet }); + return; + } + if (req.method === 'POST' && url.pathname === '/skills/validate') { const body = await readBody(req); const packet = JSON.parse(body); diff --git a/use-cases/hermes-everos-memory/bin/skillhub-packet.mjs b/use-cases/hermes-everos-memory/bin/skillhub-packet.mjs index 1758c50c..c49b2813 100755 --- a/use-cases/hermes-everos-memory/bin/skillhub-packet.mjs +++ b/use-cases/hermes-everos-memory/bin/skillhub-packet.mjs @@ -144,7 +144,7 @@ function valueOrUnknown(value) { return String(value); } -function renderSkillViews(packet) { +export function renderSkillViews(packet) { const evidence = packet.evidence_refs.length ? packet.evidence_refs.map((ref) => `- ${ref}`).join('\n') : '- no evidence refs yet'; diff --git a/use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh b/use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh index f3b0c55f..880d1637 100755 --- a/use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh +++ b/use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh @@ -33,9 +33,17 @@ curl -fsS "http://$HOST:$PORT/health" >"$TMP_DIR/health.json" curl -fsS "http://$HOST:$PORT/skills?target=hermes" >"$TMP_DIR/skills.json" curl -fsS "http://$HOST:$PORT/skills/raven.operator-memory-recall/render" \ >"$TMP_DIR/render.md" +curl -fsS "http://$HOST:$PORT/skills/raven.operator-memory-recall/views" \ + >"$TMP_DIR/views.json" +curl -fsS "http://$HOST:$PORT/skills/raven.operator-memory-recall/install-packet?target=hermes" \ + >"$TMP_DIR/install-packet.json" rg '"ok": true' "$TMP_DIR/health.json" >/dev/null rg 'raven.operator-memory-recall' "$TMP_DIR/skills.json" >/dev/null rg 'Operator Memory Recall' "$TMP_DIR/render.md" >/dev/null +rg '"views_markdown":' "$TMP_DIR/views.json" >/dev/null +rg 'Trust Panel' "$TMP_DIR/views.json" >/dev/null +rg '"install_packet":' "$TMP_DIR/install-packet.json" >/dev/null +rg '"target": "hermes"' "$TMP_DIR/install-packet.json" >/dev/null printf 'PASS skillhub mock api smoke host=%s port=%s\n' "$HOST" "$PORT" diff --git a/use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md b/use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md index 42a36c38..a4149665 100644 --- a/use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md +++ b/use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md @@ -39,12 +39,12 @@ Current routes: - `GET /skills?target=hermes` - `GET /skills/:id` - `GET /skills/:id/render` +- `GET /skills/:id/views` +- `GET /skills/:id/install-packet?target=hermes` - `POST /skills/validate` Next API routes: -- `GET /skills/:id/views` -- `GET /skills/:id/install-packet?target=hermes` - `POST /skills/:id/evidence` - `POST /skills/:id/evolution-note` diff --git a/use-cases/hermes-everos-memory/skillhub/README.md b/use-cases/hermes-everos-memory/skillhub/README.md index 5779efec..fd209840 100644 --- a/use-cases/hermes-everos-memory/skillhub/README.md +++ b/use-cases/hermes-everos-memory/skillhub/README.md @@ -91,4 +91,6 @@ Routes: - `GET /skills?target=hermes` - `GET /skills/:id` - `GET /skills/:id/render` +- `GET /skills/:id/views` +- `GET /skills/:id/install-packet?target=hermes` - `POST /skills/validate` From 9215a0f72b92b08fc8e977de85d47ae5b9e649d4 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 00:11:40 -0400 Subject: [PATCH 08/27] fix(raven): redact public run packet fixture Generalizes the source conversation reference and removes private planning artifact references from the Raven dogfood run packet while keeping the verifier passing. Co-authored-by: Codex --- .../raven/fixtures/doomsday-run.json | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json index 4bf2eda7..83750104 100644 --- a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json +++ b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json @@ -1,7 +1,7 @@ { "id": "raven.everme-doomsday-run", "title": "Raven / EverMe Doomsday Run", - "goal": "Turn the Chenyifan call into a focused Raven, EverMe SkillHub, and Hermes/EverOS dogfood execution lane with auditable local artifacts.", + "goal": "Turn the source conversation into a focused Raven, EverMe SkillHub, and Hermes/EverOS dogfood execution lane with auditable local artifacts.", "status": "reviewing", "owners": ["codex", "pi", "opencode", "hermes"], "memory_providers": ["everos", "hermes"], @@ -13,8 +13,8 @@ "mutation_policy": "read_only", "verdict": "pass", "evidence_refs": [ - ".planning/raven-everme-doomsday/RAVEN_FIELD_MANUAL.md", - "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md" + "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", + "use-cases/hermes-everos-memory/raven/README.md" ] }, { @@ -38,8 +38,8 @@ "verdict": "pass", "evidence_refs": [ "use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh", - ".planning/raven-everme-doomsday/HERMES_DOGFOOD_PLAN.md", - "use-cases/hermes-everos-memory/bin/mock-openai-compatible.mjs" + "use-cases/hermes-everos-memory/bin/mock-openai-compatible.mjs", + "use-cases/hermes-everos-memory/README.md" ] } ], @@ -94,11 +94,6 @@ } ], "artifacts": [ - { - "path": ".planning/raven-everme-doomsday/RAVEN_FIELD_MANUAL.md", - "purpose": "Raven command and taste contract.", - "public_safe": false - }, { "path": "use-cases/hermes-everos-memory/skillhub/schema.json", "purpose": "EverMe SkillHub packet contract.", @@ -138,7 +133,7 @@ "use-cases/hermes-everos-memory/bin/raven-run.mjs verify", "use-cases/hermes-everos-memory/scripts/skillhub-api-smoke.sh", "use-cases/hermes-everos-memory/scripts/dogfood-smoke.sh", - ".planning/raven-everme-doomsday/HERMES_DOGFOOD_PLAN.md" + "use-cases/hermes-everos-memory/README.md" ], "next_actions": [ "Promote the NixOS EverCore packet to an observed remote smoke." From e531d28e1fca401848d4d42766249625aa106b91 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 00:46:00 -0400 Subject: [PATCH 09/27] docs(everos): close local dogfood packet Mark the Raven/EverMe/Hermes dogfood run packet done after local gates pass, and keep the remote NixOS deploy gate explicitly flagged instead of mixing it into local artifact status. Co-authored-by: Codex --- .../hermes-everos-memory/OWNER_PACKET.md | 23 ++++++++++++++----- .../raven/fixtures/doomsday-run.json | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/use-cases/hermes-everos-memory/OWNER_PACKET.md b/use-cases/hermes-everos-memory/OWNER_PACKET.md index f5ab01db..05b83889 100644 --- a/use-cases/hermes-everos-memory/OWNER_PACKET.md +++ b/use-cases/hermes-everos-memory/OWNER_PACKET.md @@ -2,11 +2,10 @@ ## Verdict -FLAG overall. +PASS for the local Raven, EverMe SkillHub, and Hermes/EverOS dogfood packet. -Local artifacts are ready and verified. Remote NixOS deployment is not complete: -the workhorse route is reachable, but EverCore is not yet active on the remote -loopback service. +FLAG remains for remote NixOS deployment. The deploy packet is ready for review, +but EverCore is not yet proven active on the remote loopback service. ## What Shipped @@ -22,20 +21,23 @@ loopback service. ## Verification -Latest local verification set: +Current local PASS verification set: ```bash bash -n use-cases/hermes-everos-memory/scripts/*.sh use-cases/hermes-everos-memory/deploy/nixos/scripts/*.sh cd use-cases/hermes-everos-memory && for f in bin/*.mjs; do node --check "$f"; done git diff --check -- use-cases/hermes-everos-memory cd use-cases/hermes-everos-memory && just provider-load +cd use-cases/hermes-everos-memory && just dogfood-smoke provider-only cd use-cases/hermes-everos-memory && just skillhub-api-smoke cd use-cases/hermes-everos-memory && just skillhub-import-sample +cd use-cases/hermes-everos-memory && just skillhub-views skillhub/fixtures/evoagentbench-musician-life-event.json cd use-cases/hermes-everos-memory && just raven-sample +cd use-cases/hermes-everos-memory && just raven-render cd use-cases/hermes-everos-memory && just raven-verify -cd use-cases/hermes-everos-memory && just remote-smoke full cd use-cases/hermes-everos-memory && just mock-openai-check cd use-cases/hermes-everos-memory && EVEROS_USER_ID="verify-raven-$(date +%s)" EVEROS_SEARCH_METHOD=hybrid EVEROS_MEMORY_TYPES=episodic_memory,raw_message,profile,agent_memory just dogfood-smoke full +cd use-cases/hermes-everos-memory && MARKER="RAVEN_DOGFOOD_VERIFY_$(date +%s)" && hermes -z "Use the EverOS memory tool to store exactly this public verification marker: ${MARKER}." && node bin/everos-memory.mjs search "$MARKER" ``` Hermes profile verification: @@ -52,6 +54,15 @@ Plugin: installed Status: available ``` +Remote deploy verification remains separate: + +```bash +cd use-cases/hermes-everos-memory && just remote-smoke full +``` + +Treat that command as `FLAG` until the NixOS module is applied and EverCore is +running on the remote loopback service. + ## Remote Disposition Read-only workhorse probe: diff --git a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json index 83750104..5483f17a 100644 --- a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json +++ b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json @@ -2,7 +2,7 @@ "id": "raven.everme-doomsday-run", "title": "Raven / EverMe Doomsday Run", "goal": "Turn the source conversation into a focused Raven, EverMe SkillHub, and Hermes/EverOS dogfood execution lane with auditable local artifacts.", - "status": "reviewing", + "status": "done", "owners": ["codex", "pi", "opencode", "hermes"], "memory_providers": ["everos", "hermes"], "lanes": [ From 8c9eeaafeae2fa4657e00001fc713d7a5dd038a4 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 00:49:22 -0400 Subject: [PATCH 10/27] docs(riven): add concept boundary to dogfood packet Record Riven as the operator-facing concept name while preserving the Raven v0 command namespace, and include the concept packet in the verified dogfood run artifacts. Co-authored-by: Codex --- .../hermes-everos-memory/OWNER_PACKET.md | 5 +- .../raven/COMMAND_CONTRACT.md | 5 ++ .../hermes-everos-memory/raven/README.md | 4 ++ .../raven/RIVEN_CONCEPT.md | 72 +++++++++++++++++++ .../raven/fixtures/doomsday-run.json | 13 +++- 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md diff --git a/use-cases/hermes-everos-memory/OWNER_PACKET.md b/use-cases/hermes-everos-memory/OWNER_PACKET.md index 05b83889..07c31b0c 100644 --- a/use-cases/hermes-everos-memory/OWNER_PACKET.md +++ b/use-cases/hermes-everos-memory/OWNER_PACKET.md @@ -2,7 +2,8 @@ ## Verdict -PASS for the local Raven, EverMe SkillHub, and Hermes/EverOS dogfood packet. +PASS for the local Riven/Raven, EverMe SkillHub, and Hermes/EverOS dogfood +packet. FLAG remains for remote NixOS deployment. The deploy packet is ready for review, but EverCore is not yet proven active on the remote loopback service. @@ -11,6 +12,8 @@ but EverCore is not yet proven active on the remote loopback service. - Hermes `everos` memory-provider shim with search, store, health, flush, prefetch, sync, and auto-flush behavior. +- Riven concept packet and naming boundary, implemented through the Raven v0 + command namespace. - Raven run packet contract, command contract, renderer, and gate verifier. - EverMe SkillHub packet schema, MVP view plan, renderer, read-only mock API, API-backed views/install-packet routes, and one real EvoAgentBench `SKILL.md` diff --git a/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md b/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md index c97fce3f..18c5f23e 100644 --- a/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md +++ b/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md @@ -5,6 +5,11 @@ dashboard first and not a marketing page. It is a command contract that turns a goal, memory substrate, lanes, gates, and evidence into an owner-readable run packet. +Naming note: Riven is the product/concept name for this operator surface. Raven +is the current repo-local CLI and packet namespace. Keep the Raven namespace +until a migration plan preserves existing verifiers and SkillHub install +targets. + ## Shape Raven v0 ships as a thin CLI/TUI contract over local files and Hermes/EverOS diff --git a/use-cases/hermes-everos-memory/raven/README.md b/use-cases/hermes-everos-memory/raven/README.md index 672f80e1..aeb46d9a 100644 --- a/use-cases/hermes-everos-memory/raven/README.md +++ b/use-cases/hermes-everos-memory/raven/README.md @@ -2,6 +2,9 @@ `v0` contract for a Raven run. +Riven is the operator-facing concept name for this surface. Raven is the +repo-local v0 codename and command namespace kept for compatibility. + Raven is not a marketing page here. This directory defines the packet that a CLI/TUI can validate, render, and later execute against Hermes/EverOS memory. @@ -9,6 +12,7 @@ CLI/TUI can validate, render, and later execute against Hermes/EverOS memory. | File | Purpose | | --- | --- | +| `RIVEN_CONCEPT.md` | Public-safe Riven concept and naming boundary | | `COMMAND_CONTRACT.md` | Public-safe command/state/gate contract for Raven v0 | | `schema.json` | Public-safe JSON Schema for a Raven run packet | | `fixtures/doomsday-run.json` | Sample run packet for the current dogfood lane | diff --git a/use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md b/use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md new file mode 100644 index 00000000..796ac793 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md @@ -0,0 +1,72 @@ +# Riven Concept Packet v0 + +## Verdict + +PASS for the Riven concept artifact. + +Riven is the operator-facing concept name for the memory-backed execution lane. +The current repo-local implementation remains under the `raven` directory and +`raven-run` command for v0 compatibility. + +## Naming Boundary + +| Name | Role | Status | +| --- | --- | --- | +| Riven | product/concept name for the operator surface | concept artifact | +| Raven | current CLI, packet schema, fixtures, and SkillHub install target | implemented v0 surface | + +Do not rename files, commands, or install targets until there is a migration +plan. The present contract treats Raven as the working codename for Riven v0. + +## Product Thesis + +Riven is not a chat transcript viewer and not a generic dashboard. It is a +memory-backed operator surface for focused agent work: + +- capture one goal; +- recall prior decisions and red gates before execution; +- split work into bounded lanes; +- preserve mutation policy per lane; +- verify blocking gates with commands or explicit evidence; +- export an owner-readable packet. + +## First Run Shape + +The first Riven run is the Doomsday EverOS lane: + +1. Riven concept exploration through the Raven command contract. +2. EverMe SkillHub MVP packet and read-only mock API. +3. Hermes/EverOS provider dogfood with store, search, recall, and real Hermes + profile verification. + +## Interface Wedge + +The minimal useful UI is command-grade: + +```text +riven capture +riven memory search +riven lane list +riven gate verify +riven export +``` + +For v0, these map to the existing `raven-run` validator/renderer and the +`raven/fixtures/doomsday-run.json` packet. + +## Guardrails + +- Do not expose raw call transcript content. +- Do not publish private paths, host/IP values, screenshots, tokens, or + credential paths. +- Do not treat remote NixOS deploy as complete until the deploy smoke passes on + the remote loopback service. +- Do not widen Riven into a new major repo before the packet contract earns it. + +## Current Evidence + +- `raven/COMMAND_CONTRACT.md` defines the v0 command/state/gate contract. +- `raven/fixtures/doomsday-run.json` records the first focused run. +- `bin/raven-run.mjs verify raven/fixtures/doomsday-run.json` computes the + packet verdict and fails non-zero for open blocking gates. +- `OWNER_PACKET.md` separates local packet PASS from remote deploy FLAG. diff --git a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json index 5483f17a..f70742fe 100644 --- a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json +++ b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json @@ -1,7 +1,7 @@ { "id": "raven.everme-doomsday-run", - "title": "Raven / EverMe Doomsday Run", - "goal": "Turn the source conversation into a focused Raven, EverMe SkillHub, and Hermes/EverOS dogfood execution lane with auditable local artifacts.", + "title": "Riven / Raven / EverMe Doomsday Run", + "goal": "Turn the source conversation into a focused Riven concept, EverMe SkillHub, and Hermes/EverOS dogfood execution lane with auditable local artifacts. Riven is represented by the current Raven v0 command namespace until a migration is approved.", "status": "done", "owners": ["codex", "pi", "opencode", "hermes"], "memory_providers": ["everos", "hermes"], @@ -9,11 +9,12 @@ { "id": "raven-concept", "owner": "pi", - "scope": "Taste, naming, story, interface wedge, and first public artifact.", + "scope": "Riven taste, naming, story, interface wedge, and first public artifact through the Raven v0 compatibility surface.", "mutation_policy": "read_only", "verdict": "pass", "evidence_refs": [ "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", + "use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md", "use-cases/hermes-everos-memory/raven/README.md" ] }, @@ -114,6 +115,11 @@ "purpose": "Raven run packet contract.", "public_safe": true }, + { + "path": "use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md", + "purpose": "Riven concept and naming boundary.", + "public_safe": true + }, { "path": "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", "purpose": "Raven command/state/gate contract.", @@ -127,6 +133,7 @@ ], "evidence_refs": [ "use-cases/hermes-everos-memory/OWNER_PACKET.md", + "use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md", "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", "use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md", "use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json", From 1961e6319647786d9f701766b1700e096e8365ec Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 00:52:34 -0400 Subject: [PATCH 11/27] docs(everos): add dogfood completion audit Record requirement-by-requirement evidence that the Riven, SkillHub, and Hermes/EverOS dogfood artifacts are shipped locally, while keeping remote NixOS deployment as a separate flagged follow-on. Co-authored-by: Codex --- .../hermes-everos-memory/COMPLETION_AUDIT.md | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 use-cases/hermes-everos-memory/COMPLETION_AUDIT.md diff --git a/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md b/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md new file mode 100644 index 00000000..fc3557f5 --- /dev/null +++ b/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md @@ -0,0 +1,70 @@ +# Doomsday EverOS Completion Audit + +## Verdict + +PASS for the focused EverOS execution lane. + +The requested artifacts are present, public-safe, and verified: + +- Riven concept exploration; +- EverMe SkillHub MVP design and implementation plan; +- Hermes/EverOS dogfood memory-provider integration artifacts; +- owner-readable packet and verifiers. + +Remote NixOS deployment remains a separate follow-on `FLAG`; it is not counted +as local artifact completion. + +## Requirement Matrix + +| Requirement | Evidence | Verdict | +| --- | --- | --- | +| Turn the source call into one focused lane | `raven/fixtures/doomsday-run.json` records one run with three bounded lanes and no open blocking gates | PASS | +| Ship Riven concept exploration | `raven/RIVEN_CONCEPT.md` defines thesis, naming boundary, interface wedge, guardrails, and current evidence | PASS | +| Preserve Raven compatibility | `raven/COMMAND_CONTRACT.md`, `raven/schema.json`, and `bin/raven-run.mjs` keep the v0 namespace working | PASS | +| Ship EverMe SkillHub MVP plan | `skillhub/MVP_IMPLEMENTATION_PLAN.md` defines product contract, five MVP views, API contract, data additions, sequence, and gates | PASS | +| Ship SkillHub implementation slice | `skillhub/schema.json`, fixtures, `bin/skillhub-packet.mjs`, and `bin/skillhub-mock-api.mjs` validate/render/import/serve packets | PASS | +| Ship Hermes/EverOS plugin artifacts | `__init__.py`, `plugin.yaml`, `scripts/install-local.sh`, and `bin/everos-memory.mjs` implement and install the provider shim | PASS | +| Prove provider load | `just provider-load` | PASS | +| Prove SkillHub API | `just skillhub-api-smoke` | PASS | +| Prove real SkillHub import | `just skillhub-import-sample` plus `just skillhub-views skillhub/fixtures/evoagentbench-musician-life-event.json` | PASS | +| Prove Raven/Riven packet | `node bin/raven-run.mjs summary raven/fixtures/doomsday-run.json` and `just raven-verify` | PASS | +| Prove full memory loop | `just dogfood-smoke full` with a fresh user id | PASS | +| Prove real Hermes profile path | `hermes -z` storing a unique public marker, then `node bin/everos-memory.mjs search "$MARKER"` | PASS | +| Avoid widening scope | no new major repo; artifacts stay under `use-cases/hermes-everos-memory/` | PASS | +| Avoid private operational details | public-safety scan over owner packet, Riven/Raven docs, run packet, and SkillHub docs returns no matches | PASS | + +## Commands + +```bash +cd use-cases/hermes-everos-memory +bash -n scripts/*.sh deploy/nixos/scripts/*.sh +for f in bin/*.mjs; do node --check "$f"; done +node bin/raven-run.mjs summary raven/fixtures/doomsday-run.json +just provider-load +just dogfood-smoke provider-only +just skillhub-api-smoke +just skillhub-import-sample +just skillhub-views skillhub/fixtures/evoagentbench-musician-life-event.json +just raven-sample +just raven-render +just raven-verify +just mock-openai-check +EVEROS_USER_ID="verify-riven-$(date +%s)" EVEROS_SEARCH_METHOD=hybrid EVEROS_MEMORY_TYPES=episodic_memory,raw_message,profile,agent_memory just dogfood-smoke full +MARKER="RIVEN_DOGFOOD_VERIFY_$(date +%s)" && hermes -z "Use the EverOS memory tool to store exactly this public verification marker: ${MARKER}." && node bin/everos-memory.mjs search "$MARKER" +``` + +Repo-root checks: + +```bash +git diff --check -- use-cases/hermes-everos-memory +rg -n -i -f use-cases/hermes-everos-memory/OWNER_PACKET.md use-cases/hermes-everos-memory/raven use-cases/hermes-everos-memory/skillhub +``` + +## Residual Risks + +- Remote NixOS deployment remains `FLAG` until the module is applied and the + remote `--mode full` smoke passes. +- Raven-to-Riven command rename is intentionally deferred; current v0 keeps + `raven-run` to avoid breaking existing packet and SkillHub contracts. +- SkillHub write routes remain proposed until EverMe backend constraints are + available. From b4e992b0e5a49224f3e3c63df1aac6bf9da034fa Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 01:00:41 -0400 Subject: [PATCH 12/27] docs(everos): add supervisor dispatch packet Co-authored-by: Codex --- .../SUPERVISOR_DISPATCH.md | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md diff --git a/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md b/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md new file mode 100644 index 00000000..7ec5e5e1 --- /dev/null +++ b/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md @@ -0,0 +1,104 @@ +# EverOS Supervisor Dispatch + +## Current Truth + +Local EverOS packet: PASS. + +Remote EverCore deployment: FLAG/BLOCK until the remote auth repair and guarded +NixOS test lane are proven. + +The active local source packet is under this directory. The remote deploy lane +must use the existing Multica issues instead of creating a parallel story: + +- `DAS-2666`: EverCore remote deploy gate via squad. +- `DAS-2669`: Repair Windburn NixOS Codex runtime auth. + +## Hard Guardrails + +- Do not push, publish, close upstream issues, or run remote deploy/switch + commands without explicit human approval. +- Keep remote host/IP values, credential paths, token payloads, signed URLs, and + private env values out of public comments and screenshots. +- Do not retry Windburn NixOS Codex deploy work until `DAS-2669` posts + `AUTH_REPAIRED` from a successful read-only proof task. +- Remote EverCore remains loopback-only. Any public bind or firewall exposure is + `BLOCK`. +- Local artifact completion and remote deploy readiness are separate gates. + +## New SC Codex CLI Prompt + +```text +ROLE: EverOS control-room supervisor. + +MISSION: +Keep the EverOS / Hermes / SkillHub / Riven packet moving from local PASS to +remote-ready evidence without laundering red gates or spawning duplicate work. + +READ FIRST: +- AGENTS.md +- use-cases/hermes-everos-memory/COMPLETION_AUDIT.md +- use-cases/hermes-everos-memory/OWNER_PACKET.md +- use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md +- Multica issues DAS-2666 and DAS-2669 + +SOURCE TRUTH ORDER: +1. Human operator approval. +2. Live Multica/GitHub/Linear/repo/runtime state. +3. Committed artifacts. +4. Agent summaries only when backed by evidence. + +CURRENT STATE: +- Local EverOS packet is PASS. +- Remote EverCore deploy is FLAG/BLOCK. +- DAS-2669 auth repair is the blocker before Windburn NixOS Codex can be used. +- Do not treat remote Hermes read-only evidence as deploy success. + +CONTROL LOOP: +1. Refresh repo state and Multica issue states. +2. Check each assigned lane for a concrete PASS/FLAG/BLOCK report. +3. Reject reports that omit commands, issue links, or file evidence. +4. Keep one owner-readable packet rather than scattered chat commentary. +5. Escalate to the operator only for approval, secrets, auth repair, or remote + mutation decisions. + +OUTPUT SHAPE: +VERDICT: PASS | FLAG | BLOCK +EVIDENCE: +CHANGES: +RISKS: +NEXT: +``` + +## Multica Dispatch Map + +| Lane | Lead | Support | Scope | Stop Condition | +| --- | --- | --- | --- | --- | +| Control room | Workbench Supervisor | Workbench Synthesizer | Track all lanes and produce one owner packet. | Any lane reports success without evidence. | +| Runtime auth | Workbench Admin | NYC Ops Mechanic | Repair `DAS-2669`; prove Windburn NixOS Codex can do a read-only task. | Token/auth payload exposure or deploy drift. | +| Remote deploy gate | EverCore Remote Deploy Cell | Windburn NixOS Hermes | Keep `DAS-2666` honest; read-only preflight until `AUTH_REPAIRED`. | Missing env, public bind risk, failed NixOS test, or Codex auth still broken. | +| Local verifier | QA Verifier | Codex Guardian | Re-run the local audit commands and public-safety scan. | Any command fails or secret/path pattern appears. | +| Product story | Pi | Hermes Researcher, Claude Docs | Riven naming, SkillHub story, owner-readable public narrative. | Repo mutation or unsupported product claim. | +| Memory substrate | Memory Curator | Hermes Researcher | Dogfood evidence, provenance fields, memory packet shape. | Claims not backed by local provider/search evidence. | +| SkillHub eval | Benchmark Scout | Remote Algorithm Advisor, Codex Developer | Turn `needs_eval` SkillHub items into an eval plan; do not promote them. | Treating `needs_eval` as production-ready. | +| Implementation reserve | Codex Developer | OpenCode runtime when assigned | Small bounded fixes after verifier or supervisor asks. | Broad refactor, README churn, or remote mutation. | +| Standby runtimes | Copilot, Cursor, Gemini, OpenClaw | Supervisor | Specialist review only when a scoped issue exists. | Self-starting new work outside this packet. | + +## Required Reports + +Each active lane must end with: + +```text +VERDICT: +EVIDENCE: +CHANGES: +RISKS: +NEXT: +``` + +No lane may mark the remote deploy path `PASS` until all of these are true: + +- `DAS-2669` has `AUTH_REPAIRED`. +- The guarded NixOS test lane succeeds. +- The remote loopback full smoke retrieves stored memory. +- Supervisor review returns `PASS`. + From a8c598c5c517ec1e4d028fb50475d63693aa1174 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 01:17:45 -0400 Subject: [PATCH 13/27] docs(everos): record runtime lane adapter flag Co-authored-by: Codex --- .../SUPERVISOR_DISPATCH.md | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md b/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md index 7ec5e5e1..891a64de 100644 --- a/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md +++ b/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md @@ -83,6 +83,31 @@ NEXT: | Implementation reserve | Codex Developer | OpenCode runtime when assigned | Small bounded fixes after verifier or supervisor asks. | Broad refactor, README churn, or remote mutation. | | Standby runtimes | Copilot, Cursor, Gemini, OpenClaw | Supervisor | Specialist review only when a scoped issue exists. | Self-starting new work outside this packet. | +## Runtime Lane Activation + +Two local runtime-backed agent identities were created for focused lanes: + +- `Pi Riven Critic`: Pi runtime, assigned on `DAS-2673` for Riven taste and + product-boundary review. +- `OpenCode Patch Scout`: Opencode runtime, assigned on `DAS-2674` for bounded + local implementation scouting. + +Activation status: `FLAG`. + +Local CLI probes passed for both runtimes, but Multica task execution failed: + +- Pi: local `pi --mode json` probe with OpenRouter DeepSeek passes; Multica + wrapper still returns `pi exited with error: exit status 1`. +- OpenCode: local `opencode run -m openrouter/deepseek/deepseek-v4-flash` + probe passes; Multica wrapper reports default OAuth invalidation or model + lookup failure. + +Keep `DAS-2673` and `DAS-2674` parked until the runtime-adapter repair lane +proves a successful Multica task. Both lanes remain read-only by default. They +may recommend changes, but they must not mutate files, push, publish, close +issues, or touch remote deployment unless the supervisor opens a narrower +follow-up issue. + ## Required Reports Each active lane must end with: @@ -101,4 +126,3 @@ No lane may mark the remote deploy path `PASS` until all of these are true: - The guarded NixOS test lane succeeds. - The remote loopback full smoke retrieves stored memory. - Supervisor review returns `PASS`. - From cb099c0193c7ff7309ddd818bc052ea71cf1c579 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 01:41:00 -0400 Subject: [PATCH 14/27] docs(raven): add v2 research ledger Co-authored-by: Codex --- .../raven/RAVEN_V2_RESEARCH_LEDGER.md | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md diff --git a/use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md b/use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md new file mode 100644 index 00000000..7288330b --- /dev/null +++ b/use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md @@ -0,0 +1,208 @@ +# Raven v2 Research Ledger + +## North Star + +Raven v2 is the next-generation Agents OS console: a native-feeling terminal +operating surface where CCB/CCR/Evensong runtime lineage, EverOS memory, Hermes +skills, and MUW/Superconductor orchestration become one auditable loop. + +The goal is not a nicer chat UI. The goal is an operator shell where every +agent action can resolve to memory, state, packet, gate, receipt, or review. + +## Parallel Contract + +Raven v1 is the build lane. Raven v2 is the research lane. + +Research may run at full speed, but it must not block or rewrite v1 unless it +produces a concrete, reviewed implementation packet. V2 findings flow into v1 +only through bounded decisions: + +- keep; +- revise; +- defer; +- reject; +- open implementation issue. + +## Current Truth + +- Local EverOS/Hermes/SkillHub/Riven packet is `PASS`. +- Remote EverCore deploy remains `FLAG/BLOCK`. +- `DAS-2666` is the canonical remote deploy gate. +- `DAS-2669` is the Windburn NixOS Codex auth blocker. +- `DAS-2670` is the current control-room dispatch. +- `DAS-2675` tracks Pi/OpenCode Multica runtime-adapter repair. +- Existing Raven v1 build work is dirty in the worktree; do not overwrite it. + +## Source Families + +### Internal Lineage + +- CCB / CCR / Evensong: hackable Claude-Code-like runtime DNA, public evidence + harness, retrieval benchmarks, Research Vault MCP, and operator handoff + surface. +- EverOS / EverCore: memory operating layer and multi-tenant memory substrate. +- Hermes: skills, persistent goals, cron/no-agent jobs, gateway, terminal + backends, and memory/profile model. +- MUW / Superconductor: orchestration, issue gates, runtime control plane, + review lanes, and bounded fanout. + +### External Reference Families + +- `yetone/native-feel-skill` + (`https://github.com/yetone/native-feel-skill`): native-feel discipline, + decision tree, typed IPC, WebView survival, and ship audit. +- `superagent-ai/grok-cli` (`https://github.com/superagent-ai/grok-cli`): + OpenTUI energy, remote control, sub-agent UX, and interactive/headless split. +- `openai/codex` (`https://github.com/openai/codex`): smooth terminal REPL + baseline, local agent flow, release and packaging discipline. +- `claude-code-best/claude-code` + (`https://github.com/claude-code-best/claude-code`): CCB engineering mine + for IPC, ACP, remote control, observability, and runtime hacking. +- `NousResearch/hermes-agent` (`https://github.com/NousResearch/hermes-agent`): + skills, memory, gateway, cron, backend, and portable agent substrate. +- Anthropic Claude Code / Agent SDK / multi-agent research: subagents with + isolated context, parallel research orchestration, sandbox boundaries, and + agent harness reuse beyond coding. + +## Research Lanes + +### Lane 1: Native-Feel TUI/REPL + +Question: what makes Raven feel like a native terminal OS surface rather than a +webby text box? + +Research targets: + +- latency budget; +- keyboard grammar; +- command palette; +- focus and pane stability; +- interrupt/resume semantics; +- scrollback and transcript model; +- hotkey/muscle-memory identity; +- shell/TUI/headless mode split. + +Output: + +- interaction contract; +- v2 command grammar; +- native-feel audit adapted for terminal agents. + +### Lane 2: Runtime DNA Alignment + +Question: how do CCB/CCR/Evensong concepts flow into Raven without turning +Raven into a fork dump? + +Research targets: + +- CLI loop; +- REPL state machine; +- tool approval model; +- pipe/ACP/control-plane concepts; +- telemetry and receipts; +- public handoff/evidence dashboard; +- retrieval benchmark receipts. + +Output: + +- lineage map; +- implementation boundaries; +- what Raven owns vs what Evensong owns. + +### Lane 3: Memory And Skill Substrate + +Question: how should Raven make memory, skills, and goals first-class without +becoming a noisy memory browser? + +Research targets: + +- EverOS memory search/store/status; +- Hermes skills and profiles; +- persistent goals; +- cron/no-agent monitoring receipts; +- provenance fields; +- memory hit explanations. + +Output: + +- memory pane contract; +- skill registry contract; +- goal/gate model. + +### Lane 4: Multi-Agent Orchestration + +Question: what is the operator model when many agents are building, reviewing, +and researching at once? + +Research targets: + +- MUW issue states; +- Superconductor runtimes; +- bounded fanout; +- subagent context isolation; +- task delegation and review packets; +- red-gate routing. + +Output: + +- control-room state model; +- dispatch grammar; +- review lane protocol. + +### Lane 5: Evaluation And Safety + +Question: how do we know Raven is making the system more legible rather than +only faster? + +Research targets: + +- audit trails; +- failure records; +- public-safety scan; +- secret/host/IP redaction; +- benchmark receipt ingestion; +- user-visible truth-state transitions; +- sandbox/permission boundaries. + +Output: + +- Raven v2 success metrics; +- red-gate invariants; +- public-safe artifact checklist. + +## Non-Negotiables + +- Do not create a new major repo. +- Do not copy code across incompatible licenses. +- Do not push, publish, deploy, or close upstream issues without explicit + operator approval. +- Do not expose secrets, private hosts/IPs, credential paths, signed URLs, + private env values, or local-machine operational details in public artifacts. +- Do not turn research into a pile of summaries. Every research lane must end + with a decision packet. + +## Required Research Packet Shape + +```text +RAVEN_V2_RESEARCH_PACKET +LANE: +QUESTION: +SOURCES: +FINDINGS: +DECISIONS: +V1_IMPACT: +RISKS: +NEXT: +VERDICT: PASS | FLAG | BLOCK +``` + +## First Synthesis Target + +Produce `RAVEN_V2_ARCHITECTURE_PACKET.md` only after at least three lanes return +evidence-backed packets. The architecture packet should decide: + +- v2 runtime stack; +- TUI/REPL interaction model; +- memory/skill/gate data model; +- what ships in Raven vs remains in Evensong/Hermes/MUW; +- the first v2 implementation slice. From 2853c4c1d63f5bb9cc6e3739ae773e35c41b2f19 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 09:01:52 -0400 Subject: [PATCH 15/27] feat(upstream): resolve P0 batch #191, #93, #78 - README: fix search API method (GET -> POST) - Demo: handle 202 Accepted status in SimpleMemoryManager - MemoryManager: fix multi-type search logic and hybrid deduplication - Add unit tests for 202 status and multi-type search --- README.md | 2 +- docs/upstream-return/FINAL_REPORT.md | 27 ++ docs/upstream-return/ISSUE_MATRIX.md | 8 +- docs/upstream-return/PR_191_93_78_PACKET.md | 71 ++++ .../demo/utils/simple_memory_manager.py | 21 +- .../src/agentic_layer/memory_manager.py | 330 +++++++++--------- .../test_memory_manager_multi_type_search.py | 80 +++++ .../tests/test_simple_memory_manager.py | 40 +++ 8 files changed, 409 insertions(+), 170 deletions(-) create mode 100644 docs/upstream-return/PR_191_93_78_PACKET.md create mode 100644 methods/EverCore/tests/test_memory_manager_multi_type_search.py create mode 100644 methods/EverCore/tests/test_simple_memory_manager.py diff --git a/README.md b/README.md index f3aa81ba..531acbbf 100644 --- a/README.md +++ b/README.md @@ -376,7 +376,7 @@ requests.post(f"{API_BASE}/memories", json={ }) # 2. Search for relevant memories -response = requests.get(f"{API_BASE}/memories/search", json={ +response = requests.post(f"{API_BASE}/memories/search", json={ "query": "What sports does the user like?", "user_id": "user_001", "memory_types": ["episodic_memory"], diff --git a/docs/upstream-return/FINAL_REPORT.md b/docs/upstream-return/FINAL_REPORT.md index 07e4f18d..b711256c 100644 --- a/docs/upstream-return/FINAL_REPORT.md +++ b/docs/upstream-return/FINAL_REPORT.md @@ -37,3 +37,30 @@ Added the required local maintainer packet: - This pass did not inspect full PR diffs or run upstream test suites. - Dispositions are triage recommendations, not maintainer decisions. - `VALIDATION.md` was not added because this pass did not implement runtime behavior. + +## 2026-05-15 Slice Update + +Targeted live recheck: + +- Issues #191, #93, and #78 remain open. +- PRs #185 and #211 remain `BLOCKED`. +- PRs #89, #109, and #138 remain `DIRTY`. + +Local current-tree slice now covers: + +- #191: README search example uses `POST /api/v1/memories/search`. +- #93: demo store treats HTTP 202 Accepted as background success. +- #78: keyword/vector retrieval searches all requested non-profile memory types; + hybrid dedupe preserves same ids from different memory collections. + +Verification added for the slice: + +- `tests/test_memory_manager_multi_type_search.py` +- `tests/test_simple_memory_manager.py` + +PR handoff added: + +- `docs/upstream-return/PR_191_93_78_PACKET.md` + +The slice is PR-ready after reviewer pass, but upstream should still treat it as +a new narrow PR rather than a merge decision on the stale overlapping PRs. diff --git a/docs/upstream-return/ISSUE_MATRIX.md b/docs/upstream-return/ISSUE_MATRIX.md index f22f16d6..5ae5a447 100644 --- a/docs/upstream-return/ISSUE_MATRIX.md +++ b/docs/upstream-return/ISSUE_MATRIX.md @@ -1,6 +1,8 @@ # Upstream Issue Matrix Live source: `EverMind-AI/EverOS` open issues fetched on 2026-05-14. +Targeted recheck for #191/#93/#78 ran on 2026-05-15; all three issues remain +open. This file assigns every currently open upstream issue to one disposition. It is intentionally a maintainer triage packet, not a claim that the underlying bugs are fixed. @@ -11,7 +13,7 @@ Disposition vocabulary: `FIX_IN_FORK`, `ANSWER_DRAFT`, `CLOSE_STALE`, `DUPLICATE | [#205](https://github.com/EverMind-AI/EverOS/issues/205) answer prompt 确认 | Triage hygiene | unclear prompt/confirmation request | none | ANSWER_DRAFT | P3 | Ask for exact prompt, route, expected/actual behavior; close stale if no repro after maintainer window. | | [#195](https://github.com/EverMind-AI/EverOS/issues/195) HyperMem stage2 takes >4h | Benchmark runtime | evaluation cost and expected runtime unclear | none | ANSWER_DRAFT | P1 | Needs official runtime envelope, hardware profile, and checkpoint/resume guidance. | | [#193](https://github.com/EverMind-AI/EverOS/issues/193) Chat Agent + MemSys integration schema | Integration DX | missing recommended tool schema | none | ANSWER_DRAFT | P1 | Provide official search/write tool schema or mark as roadmap. | -| [#191](https://github.com/EverMind-AI/EverOS/issues/191) README memory search API example outdated | Memory API docs | README example points at stale API | [#185](https://github.com/EverMind-AI/EverOS/pull/185), [#196](https://github.com/EverMind-AI/EverOS/pull/196) | REVIEW_EXISTING_PR | P0 | Compare #185 narrow README fix vs #196 broad migration before merging either. | +| [#191](https://github.com/EverMind-AI/EverOS/issues/191) README memory search API example outdated | Memory API docs | README example points at stale API | [#185](https://github.com/EverMind-AI/EverOS/pull/185), [#196](https://github.com/EverMind-AI/EverOS/pull/196) | FIX_IN_FORK | P0 | Local current-tree slice changes the README search example from GET to POST; #185 is still `BLOCKED`, #196 is still too broad/old-path. | | [#177](https://github.com/EverMind-AI/EverOS/issues/177) Codex plugins similar to Claude Code | Integration DX | asks whether plugin model exists | none | ANSWER_DRAFT | P3 | Answer with current supported integration path; do not turn into code work without owner roadmap. | | [#158](https://github.com/EverMind-AI/EverOS/issues/158) profile not supported as expected | Memory behavior | profile behavior unclear | none | ANSWER_DRAFT | P2 | Needs repro data and expected profile lifecycle; likely docs/usage answer first. | | [#150](https://github.com/EverMind-AI/EverOS/issues/150) OpenClaw plugin config not forwarded | OpenClaw integration | callback receives no config | [#128](https://github.com/EverMind-AI/EverOS/pull/128), [#189](https://github.com/EverMind-AI/EverOS/pull/189), [#202](https://github.com/EverMind-AI/EverOS/pull/202) | REVIEW_EXISTING_PR | P0 | Review current-path fix/docs; avoid old `methods/evermemos` path drift. | @@ -25,10 +27,10 @@ Disposition vocabulary: `FIX_IN_FORK`, `ANSWER_DRAFT`, `CLOSE_STALE`, `DUPLICATE | [#111](https://github.com/EverMind-AI/EverOS/issues/111) multimodal memory search | Memory API | modality support unclear | none | ANSWER_DRAFT | P2 | Answer current support boundaries and roadmap status. | | [#101](https://github.com/EverMind-AI/EverOS/issues/101) semantic memory type | Memory architecture | asks for objective/semantic memory | none | NEEDS_MAINTAINER_DECISION | P1 | Requires taxonomy decision across memory types. | | [#95](https://github.com/EverMind-AI/EverOS/issues/95) dedup and foresight expiry cleanup | Memory lifecycle | duplicate writes and stale foresight | [#129](https://github.com/EverMind-AI/EverOS/pull/129) | REVIEW_EXISTING_PR | P1 | Review #129 but keep semantics explicit: dedup scope, retention, tenant isolation. | -| [#93](https://github.com/EverMind-AI/EverOS/issues/93) Storage failed: Request accepted | Demo/API UX | demo treats 202 Accepted as failure | [#211](https://github.com/EverMind-AI/EverOS/pull/211) | REVIEW_EXISTING_PR | P0 | Small targeted PR exists; verify demo behavior and merge if checks pass. | +| [#93](https://github.com/EverMind-AI/EverOS/issues/93) Storage failed: Request accepted | Demo/API UX | demo treats 202 Accepted as failure | [#211](https://github.com/EverMind-AI/EverOS/pull/211) | FIX_IN_FORK | P0 | Local current-tree slice treats HTTP 202 as successful background extraction; #211 remains `BLOCKED` upstream. | | [#88](https://github.com/EverMind-AI/EverOS/issues/88) evaluation supports HaluMem | Benchmark scope | benchmark coverage request | none | NEEDS_MAINTAINER_DECISION | P2 | Needs benchmark roadmap answer. | | [#87](https://github.com/EverMind-AI/EverOS/issues/87) PersonaMem v1/v2 results | Benchmark reproducibility | published result/repro ambiguity | none | ANSWER_DRAFT | P1 | Provide versioned benchmark instructions and results pointer. | -| [#78](https://github.com/EverMind-AI/EverOS/issues/78) search uses only memory_types[0] | Memory API bug | multi-type search silently ignored | [#89](https://github.com/EverMind-AI/EverOS/pull/89), [#109](https://github.com/EverMind-AI/EverOS/pull/109), [#138](https://github.com/EverMind-AI/EverOS/pull/138) | REVIEW_EXISTING_PR | P0 | Pick one canonical PR; close duplicate multi-type PRs after rebase decision. | +| [#78](https://github.com/EverMind-AI/EverOS/issues/78) search uses only memory_types[0] | Memory API bug | multi-type search silently ignored | [#89](https://github.com/EverMind-AI/EverOS/pull/89), [#109](https://github.com/EverMind-AI/EverOS/pull/109), [#138](https://github.com/EverMind-AI/EverOS/pull/138) | FIX_IN_FORK | P0 | Local current-tree slice queries all requested non-profile memory types for keyword/vector retrieval and dedupes hybrid hits by `(memory_type,id)`; #89/#109/#138 remain `DIRTY`. | | [#73](https://github.com/EverMind-AI/EverOS/issues/73) Cannot reproduce LoCoMo results | Benchmark reproducibility | paper/result reproduction gap | none | ANSWER_DRAFT | P0 | Needs official repro matrix, data/version pin, and expected tolerance. | | [#65](https://github.com/EverMind-AI/EverOS/issues/65) retrieval pipeline mismatch with paper | Architecture truth | implementation/paper mismatch | none | NEEDS_MAINTAINER_DECISION | P0 | Owner must decide whether docs errata, implementation alignment, or paper caveat. | | [#58](https://github.com/EverMind-AI/EverOS/issues/58) search cannot retrieve GET content | Memory API UX | retrieval vs stored memory confusion | none | ANSWER_DRAFT | P1 | Likely search semantics/docs answer; may fold into API docs after #191/#78. | diff --git a/docs/upstream-return/PR_191_93_78_PACKET.md b/docs/upstream-return/PR_191_93_78_PACKET.md new file mode 100644 index 00000000..a00a4917 --- /dev/null +++ b/docs/upstream-return/PR_191_93_78_PACKET.md @@ -0,0 +1,71 @@ +# PR Packet: Memory API Onboarding Stability + +Date: 2026-05-15. + +## Target Issues + +- #191: README memory search API example is outdated. +- #93: demo reports "Storage failed" when the API returns HTTP 202 Accepted. +- #78: search only uses `memory_types[0]` and silently ignores later requested + memory types. + +## Scope + +This is one narrow current-tree slice: + +- `README.md` +- `methods/EverCore/demo/utils/simple_memory_manager.py` +- `methods/EverCore/src/agentic_layer/memory_manager.py` +- `methods/EverCore/tests/test_memory_manager_multi_type_search.py` +- `methods/EverCore/tests/test_simple_memory_manager.py` + +The upstream PR should not include Raven/deploy work, OpenClaw work, benchmark +adapter fixes, provider policy, or delete/reset semantics. + +## Proposed PR Summary + +- Update the README search example to use `POST /api/v1/memories/search`. +- Treat HTTP 202 Accepted as successful background extraction in + `SimpleMemoryManager.store()`. +- Query all requested non-profile memory types in keyword/vector retrieval + instead of only `memory_types[0]`. +- Preserve same backend ids from different memory collections during hybrid + dedupe by using `(memory_type,id)`. +- Add focused regression tests for multi-type retrieval and 202 Accepted demo + behavior. + +## Verification + +Commands run: + +```bash +cd methods/EverCore +PYTHONPATH=src uv run pytest tests/test_memory_manager_multi_type_search.py tests/test_simple_memory_manager.py +uv run black --check src/agentic_layer/memory_manager.py demo/utils/simple_memory_manager.py tests/test_memory_manager_multi_type_search.py tests/test_simple_memory_manager.py +cd ../.. +git diff --check -- README.md methods/EverCore docs/upstream-return +``` + +Result: PASS. + +Notes: + +- `PYTHONPATH=src` is required for direct targeted pytest invocation from this + checkout. +- `tests/test_memory_controller.py` collected zero pytest tests and was not used + as verification evidence. + +## Live Upstream State Rechecked + +On 2026-05-15: + +- #191, #93, and #78 were still open. +- #185 and #211 were still `BLOCKED`. +- #89, #109, and #138 were still `DIRTY`. + +## Reviewer Questions + +- Does the README POST example match the public search-controller contract? +- Should the #78 fix remain limited to non-profile `MemoryManager` retrieval, + leaving profile/raw-message orchestration to the higher-level search service? +- Is `(memory_type,id)` the right dedupe key for cross-collection hybrid hits? diff --git a/methods/EverCore/demo/utils/simple_memory_manager.py b/methods/EverCore/demo/utils/simple_memory_manager.py index 93898d22..a4157a88 100644 --- a/methods/EverCore/demo/utils/simple_memory_manager.py +++ b/methods/EverCore/demo/utils/simple_memory_manager.py @@ -132,15 +132,20 @@ async def store(self, content: str, sender: str = "User") -> bool: "timestamp": int(now.timestamp() * 1000), "content": content, } - payload = { - "user_id": self.user_id, - "messages": [message_item], - } + payload = {"user_id": self.user_id, "messages": [message_item]} try: async with httpx.AsyncClient(timeout=500.0) as client: response = await client.post(self.memorize_url, json=payload) response.raise_for_status() + + # Background mode returns 202 Accepted + if response.status_code == 202: + print( + f" ⏳ Accepted: {content[:40]}... (Processing in background)" + ) + return True + result = response.json() # v1 response: {"data": {"status": "...", "count": N, ...}} @@ -206,7 +211,11 @@ async def _init_settings(self) -> bool: return False async def search( - self, query: str, top_k: int = 3, mode: str = "hybrid", show_details: bool = True + self, + query: str, + top_k: int = 3, + mode: str = "hybrid", + show_details: bool = True, ) -> List[Dict[str, Any]]: """Search memories @@ -339,4 +348,4 @@ def print_summary(self): print(" - ❌ Won't extract: Too brief, low-information small talk") print( " - 🎯 Best practice: Multi-turn conversations, rich context, specific details" - ) \ No newline at end of file + ) diff --git a/methods/EverCore/src/agentic_layer/memory_manager.py b/methods/EverCore/src/agentic_layer/memory_manager.py index e6e93005..08c6b59b 100644 --- a/methods/EverCore/src/agentic_layer/memory_manager.py +++ b/methods/EverCore/src/agentic_layer/memory_manager.py @@ -117,6 +117,39 @@ MemoryType.AGENT_SKILL: AgentSkillEsRepository, } +MILVUS_REPO_MAP = { + MemoryType.FORESIGHT: ForesightMilvusRepository, + MemoryType.ATOMIC_FACT: AtomicFactMilvusRepository, + MemoryType.EPISODIC_MEMORY: EpisodicMemoryMilvusRepository, + MemoryType.AGENT_CASE: AgentCaseMilvusRepository, + MemoryType.AGENT_SKILL: AgentSkillMilvusRepository, +} + + +def _memory_type_label(memory_types: List[MemoryType]) -> str: + if not memory_types: + return 'unknown' + return ','.join(memory_type.value for memory_type in memory_types) + + +def _hit_score(hit: Dict[str, Any]) -> float: + raw_score = hit.get('score', hit.get('_score', 0.0)) + try: + return float(raw_score) + except (TypeError, ValueError): + return 0.0 + + +def _sort_hits_by_score(hits: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return sorted(hits, key=_hit_score, reverse=True) + + +def _hit_identity(hit: Dict[str, Any]) -> Optional[tuple[str, str]]: + hit_id = hit.get('id') or hit.get('_id') + if not hit_id: + return None + return (str(hit.get('memory_type', 'unknown')), str(hit_id)) + @dataclass class AtomicFactCandidate: @@ -457,11 +490,6 @@ async def retrieve_mem_keyword( """Keyword-based memory retrieval""" top_k = retrieve_mem_request.top_k is_unlimited_mode = top_k == -1 - memory_type = ( - retrieve_mem_request.memory_types[0].value - if retrieve_mem_request.memory_types - else 'unknown' - ) try: hits = await self.get_keyword_search_results( @@ -485,11 +513,7 @@ async def get_keyword_search_results( ) -> List[Dict[str, Any]]: """Keyword search with stage-level metrics""" stage_start = time.perf_counter() - memory_type = ( - retrieve_mem_request.memory_types[0].value - if retrieve_mem_request.memory_types - else 'unknown' - ) + memory_type = _memory_type_label(retrieve_mem_request.memory_types) try: # Get parameters from Request @@ -528,32 +552,34 @@ async def get_keyword_search_results( if end_time is not None: date_range["lte"] = end_time - mem_type = memory_types[0] - - repo_class = ES_REPO_MAP.get(mem_type) - if not repo_class: - logger.warning(f"Unsupported memory_type: {mem_type}") - return [] + all_results = [] + for mem_type in memory_types: + repo_class = ES_REPO_MAP.get(mem_type) + if not repo_class: + logger.warning(f"Unsupported memory_type: {mem_type}") + continue - es_repo = get_bean_by_type(repo_class) - logger.debug(f"Using {repo_class.__name__} for {mem_type}") + es_repo = get_bean_by_type(repo_class) + logger.debug(f"Using {repo_class.__name__} for {mem_type}") - results = await es_repo.multi_search( - query=query_words, - user_id=user_id, - group_ids=group_ids, # Pass normalized list - size=effective_limit, - from_=0, - date_range=date_range, - ) + results = await es_repo.multi_search( + query=query_words, + user_id=user_id, + group_ids=group_ids, # Pass normalized list + size=effective_limit, + from_=0, + date_range=date_range, + ) - # Mark memory_type, search_source, and unified score - if results: - for r in results: - r['memory_type'] = mem_type.value - r['_search_source'] = RetrieveMethod.KEYWORD.value - r['id'] = r.get('_id', '') # Unify ES '_id' to 'id' - r['score'] = r.get('_score', 0.0) # Unified score field + # Mark memory_type, search_source, and unified score + if results: + for r in results: + r['memory_type'] = mem_type.value + r['_search_source'] = RetrieveMethod.KEYWORD.value + r['id'] = r.get('_id', '') # Unify ES '_id' to 'id' + r['score'] = r.get('_score', 0.0) # Unified score field + all_results.extend(results) + results = _sort_hits_by_score(all_results) # Record stage metrics record_retrieve_stage( @@ -587,11 +613,6 @@ async def retrieve_mem_vector( """Vector-based memory retrieval""" top_k = retrieve_mem_request.top_k is_unlimited_mode = top_k == -1 - memory_type = ( - retrieve_mem_request.memory_types[0].value - if retrieve_mem_request.memory_types - else 'unknown' - ) try: hits = await self.get_vector_search_results( @@ -614,11 +635,8 @@ async def get_vector_search_results( retrieve_method: str = RetrieveMethod.VECTOR.value, ) -> List[Dict[str, Any]]: """Vector search with stage-level metrics (embedding + milvus_search)""" - memory_type = ( - retrieve_mem_request.memory_types[0].value - if retrieve_mem_request.memory_types - else 'unknown' - ) + stage_start = time.perf_counter() + memory_type = _memory_type_label(retrieve_mem_request.memory_types) try: # Get parameters from Request @@ -643,12 +661,8 @@ async def get_vector_search_results( effective_limit = DEFAULT_TOPK_LIMIT else: effective_limit = top_k * DEFAULT_RECALL_MULTIPLIER - # Milvus similarity threshold (only applied in unlimited mode or when user specifies radius) - effective_radius = None start_time = retrieve_mem_request.start_time end_time = retrieve_mem_request.end_time - mem_type = retrieve_mem_request.memory_types[0] - logger.debug( f"retrieve_mem_vector called with query: {query}, user_id: {user_id}, group_ids: {group_ids}, top_k: {top_k}" ) @@ -671,115 +685,113 @@ async def get_vector_search_results( f"Query text vectorization completed, vector dimension: {len(query_vector_list)}" ) - # Select Milvus repository based on memory type - match mem_type: - case MemoryType.FORESIGHT: - milvus_repo = get_bean_by_type(ForesightMilvusRepository) - case MemoryType.ATOMIC_FACT: - milvus_repo = get_bean_by_type(AtomicFactMilvusRepository) - case MemoryType.EPISODIC_MEMORY: - milvus_repo = get_bean_by_type(EpisodicMemoryMilvusRepository) - case MemoryType.AGENT_CASE: - milvus_repo = get_bean_by_type(AgentCaseMilvusRepository) - case MemoryType.AGENT_SKILL: - milvus_repo = get_bean_by_type(AgentSkillMilvusRepository) - case _: - raise ValueError(f"Unsupported memory type: {mem_type}") + all_search_results = [] + for mem_type in retrieve_mem_request.memory_types: + repo_class = MILVUS_REPO_MAP.get(mem_type) + if not repo_class: + logger.warning(f"Unsupported memory type: {mem_type}") + continue - # Handle time range filter conditions - start_time_dt = None - end_time_dt = None + milvus_repo = get_bean_by_type(repo_class) - if start_time is not None: - start_time_dt = ( - from_iso_format(start_time) - if isinstance(start_time, str) - else start_time - ) + # Handle time range filter conditions + start_time_dt = None + end_time_dt = None - if end_time is not None: - if isinstance(end_time, str): - end_time_dt = from_iso_format(end_time) - # If date only format, set to end of day - if len(end_time) == 10: - end_time_dt = end_time_dt.replace(hour=23, minute=59, second=59) + if start_time is not None: + start_time_dt = ( + from_iso_format(start_time) + if isinstance(start_time, str) + else start_time + ) + + if end_time is not None: + if isinstance(end_time, str): + end_time_dt = from_iso_format(end_time) + # If date only format, set to end of day + if len(end_time) == 10: + end_time_dt = end_time_dt.replace( + hour=23, minute=59, second=59 + ) + else: + end_time_dt = end_time + + # Handle foresight time range (only valid for foresight) + if mem_type == MemoryType.FORESIGHT: + if retrieve_mem_request.start_time: + start_time_dt = from_iso_format(retrieve_mem_request.start_time) + if retrieve_mem_request.end_time: + end_time_dt = from_iso_format(retrieve_mem_request.end_time) + + # Call Milvus vector search (pass different parameters based on memory type) + # Threshold logic: + # - User specified radius: always use it + # - Unlimited mode (top_k=-1): apply DEFAULT_MILVUS_SIMILARITY_THRESHOLD (0.6) + # - Normal mode (top_k>0): no threshold filtering (rely on top_k limit) + if retrieve_mem_request.radius is not None: + # User specified radius, use it + effective_radius = retrieve_mem_request.radius + elif top_k == -1: + # Unlimited mode: apply default Milvus threshold for quality filtering + effective_radius = DEFAULT_MILVUS_SIMILARITY_THRESHOLD else: - end_time_dt = end_time - - # Handle foresight time range (only valid for foresight) - if mem_type == MemoryType.FORESIGHT: - if retrieve_mem_request.start_time: - start_time_dt = from_iso_format(retrieve_mem_request.start_time) - if retrieve_mem_request.end_time: - end_time_dt = from_iso_format(retrieve_mem_request.end_time) - - # Call Milvus vector search (pass different parameters based on memory type) - # Threshold logic: - # - User specified radius: always use it - # - Unlimited mode (top_k=-1): apply DEFAULT_MILVUS_SIMILARITY_THRESHOLD (0.6) - # - Normal mode (top_k>0): no threshold filtering (rely on top_k limit) - if retrieve_mem_request.radius is not None: - # User specified radius, use it - effective_radius = retrieve_mem_request.radius - elif top_k == -1: - # Unlimited mode: apply default Milvus threshold for quality filtering - effective_radius = DEFAULT_MILVUS_SIMILARITY_THRESHOLD - # else: keep None (no threshold filtering for normal top_k mode) - - milvus_start = time.perf_counter() - if mem_type == MemoryType.FORESIGHT: - # Foresight: supports time range and validity filtering, supports radius parameter - search_results = await milvus_repo.vector_search( - query_vector=query_vector_list, - user_id=user_id, - group_ids=group_ids, # Pass normalized list - start_time=start_time_dt, - end_time=end_time_dt, - limit=effective_limit, - score_threshold=0.0, - radius=effective_radius, - ) - elif mem_type == MemoryType.AGENT_SKILL: - # Agent skill: no timestamp filtering - search_results = await milvus_repo.vector_search( - query_vector=query_vector_list, - user_id=user_id, - group_ids=group_ids, - limit=effective_limit, - score_threshold=0.0, - radius=effective_radius, - ) - else: - # Episodic memory, atomic fact, agent case: use timestamp filtering - search_results = await milvus_repo.vector_search( - query_vector=query_vector_list, - user_id=user_id, - group_ids=group_ids, # Pass normalized list - start_time=start_time_dt, - end_time=end_time_dt, - limit=effective_limit, - score_threshold=0.0, - radius=effective_radius, + effective_radius = None + + milvus_start = time.perf_counter() + if mem_type == MemoryType.FORESIGHT: + # Foresight: supports time range and validity filtering, supports radius parameter + search_results = await milvus_repo.vector_search( + query_vector=query_vector_list, + user_id=user_id, + group_ids=group_ids, # Pass normalized list + start_time=start_time_dt, + end_time=end_time_dt, + limit=effective_limit, + score_threshold=0.0, + radius=effective_radius, + ) + elif mem_type == MemoryType.AGENT_SKILL: + # Agent skill: no timestamp filtering + search_results = await milvus_repo.vector_search( + query_vector=query_vector_list, + user_id=user_id, + group_ids=group_ids, + limit=effective_limit, + score_threshold=0.0, + radius=effective_radius, + ) + else: + # Episodic memory, atomic fact, agent case: use timestamp filtering + search_results = await milvus_repo.vector_search( + query_vector=query_vector_list, + user_id=user_id, + group_ids=group_ids, # Pass normalized list + start_time=start_time_dt, + end_time=end_time_dt, + limit=effective_limit, + score_threshold=0.0, + radius=effective_radius, + ) + record_retrieve_stage( + retrieve_method=retrieve_method, + stage='milvus_search', + memory_type=mem_type.value, + duration_seconds=time.perf_counter() - milvus_start, ) - record_retrieve_stage( - retrieve_method=retrieve_method, - stage='milvus_search', - memory_type=memory_type, - duration_seconds=time.perf_counter() - milvus_start, - ) - for r in search_results: - r['memory_type'] = mem_type.value - r['_search_source'] = RetrieveMethod.VECTOR.value - # Milvus already uses 'score', no need to rename + for r in search_results or []: + r['memory_type'] = mem_type.value + r['_search_source'] = RetrieveMethod.VECTOR.value + # Milvus already uses 'score', no need to rename + all_search_results.extend(search_results or []) - return search_results + return _sort_hits_by_score(all_search_results) except Exception as e: record_retrieve_stage( retrieve_method=retrieve_method, stage=RetrieveMethod.VECTOR.value, memory_type=memory_type, - duration_seconds=time.perf_counter() - milvus_start, + duration_seconds=time.perf_counter() - stage_start, ) record_retrieve_error( retrieve_method=retrieve_method, @@ -795,12 +807,6 @@ async def retrieve_mem_hybrid( self, retrieve_mem_request: 'RetrieveMemRequest' ) -> RetrieveMemResponse: """Hybrid memory retrieval: keyword + vector + rerank""" - memory_type = ( - retrieve_mem_request.memory_types[0].value - if retrieve_mem_request.memory_types - else 'unknown' - ) - try: hits = await self._search_hybrid( retrieve_mem_request, retrieve_method=RetrieveMethod.HYBRID.value @@ -882,9 +888,7 @@ async def _search_hybrid( retrieve_method: str = RetrieveMethod.HYBRID.value, ) -> List[Dict]: """Core hybrid search: keyword + vector + rerank, returns flat list""" - memory_type = ( - request.memory_types[0].value if request.memory_types else 'unknown' - ) + memory_type = _memory_type_label(request.memory_types) top_k = request.top_k is_unlimited_mode = top_k == -1 @@ -893,11 +897,17 @@ async def _search_hybrid( self.get_keyword_search_results(request, retrieve_method=retrieve_method), self.get_vector_search_results(request, retrieve_method=retrieve_method), ) - # Deduplicate by id - seen_ids = {h.get('id') for h in kw_results} - merged_results = kw_results + [ - h for h in vec_results if h.get('id') not in seen_ids - ] + # Deduplicate by memory collection and id so unrelated collections with + # the same backend id do not erase each other. + seen_ids = set() + merged_results = [] + for hit in [*kw_results, *vec_results]: + identity = _hit_identity(hit) + if identity is not None: + if identity in seen_ids: + continue + seen_ids.add(identity) + merged_results.append(hit) # When top_k is -1, use DEFAULT_TOPK_LIMIT for rerank rerank_limit = DEFAULT_TOPK_LIMIT if is_unlimited_mode else top_k @@ -991,7 +1001,7 @@ async def retrieve_mem_agentic( top_k = req.top_k is_unlimited_mode = top_k == -1 config = AgenticConfig() - memory_type = req.memory_types[0].value if req.memory_types else 'unknown' + memory_type = _memory_type_label(req.memory_types) try: llm_provider = build_default_provider() @@ -1359,7 +1369,7 @@ async def group_by_groupid_stratagy( task_intent=fields.get('task_intent', ''), approach=fields.get('approach', ''), quality_score=fields.get('quality_score'), - key_insight=fields.get('key_insight', '') + key_insight=fields.get('key_insight', ''), ) case MemoryType.AGENT_SKILL.value: # AgentSkill doesn't have parent_type/parent_id fields diff --git a/methods/EverCore/tests/test_memory_manager_multi_type_search.py b/methods/EverCore/tests/test_memory_manager_multi_type_search.py new file mode 100644 index 00000000..380c698e --- /dev/null +++ b/methods/EverCore/tests/test_memory_manager_multi_type_search.py @@ -0,0 +1,80 @@ +import pytest + +from agentic_layer import memory_manager as memory_manager_module +from agentic_layer.memory_manager import MemoryManager +from api_specs.dtos import RetrieveMemRequest +from api_specs.memory_models import MemoryType + + +class _RepoA: + async def multi_search(self, **kwargs): + return [{'_id': 'a', '_score': 0.2}] + + +class _RepoB: + async def multi_search(self, **kwargs): + return [{'_id': 'b', '_score': 0.9}] + + +@pytest.mark.asyncio +async def test_keyword_search_queries_all_requested_memory_types(monkeypatch): + repos = {_RepoA: _RepoA(), _RepoB: _RepoB()} + monkeypatch.setattr( + memory_manager_module, + 'ES_REPO_MAP', + {MemoryType.EPISODIC_MEMORY: _RepoA, MemoryType.AGENT_CASE: _RepoB}, + ) + monkeypatch.setattr( + memory_manager_module, 'get_bean_by_type', lambda repo_class: repos[repo_class] + ) + + manager = object.__new__(MemoryManager) + request = RetrieveMemRequest( + query='soccer', + group_ids=['group-1'], + top_k=10, + memory_types=[MemoryType.EPISODIC_MEMORY, MemoryType.AGENT_CASE], + ) + + hits = await manager.get_keyword_search_results(request) + + assert [hit['memory_type'] for hit in hits] == [ + MemoryType.AGENT_CASE.value, + MemoryType.EPISODIC_MEMORY.value, + ] + assert [hit['id'] for hit in hits] == ['b', 'a'] + + +@pytest.mark.asyncio +async def test_hybrid_dedupe_keeps_same_id_from_distinct_memory_types(): + manager = object.__new__(MemoryManager) + + async def keyword_results(*args, **kwargs): + return [{'id': 'same', 'memory_type': 'episodic_memory', 'score': 0.8}] + + async def vector_results(*args, **kwargs): + return [ + {'id': 'same', 'memory_type': 'agent_case', 'score': 0.9}, + {'id': 'same', 'memory_type': 'episodic_memory', 'score': 0.7}, + ] + + async def rerank(query, hits, top_k, *args, **kwargs): + return hits + + manager.get_keyword_search_results = keyword_results + manager.get_vector_search_results = vector_results + manager._rerank = rerank + + request = RetrieveMemRequest( + query='soccer', + group_ids=['group-1'], + top_k=10, + memory_types=[MemoryType.EPISODIC_MEMORY, MemoryType.AGENT_CASE], + ) + + hits = await manager._search_hybrid(request) + + assert hits == [ + {'id': 'same', 'memory_type': 'episodic_memory', 'score': 0.8}, + {'id': 'same', 'memory_type': 'agent_case', 'score': 0.9}, + ] diff --git a/methods/EverCore/tests/test_simple_memory_manager.py b/methods/EverCore/tests/test_simple_memory_manager.py new file mode 100644 index 00000000..c5760fcc --- /dev/null +++ b/methods/EverCore/tests/test_simple_memory_manager.py @@ -0,0 +1,40 @@ +import pytest + +from demo.utils.simple_memory_manager import SimpleMemoryManager + + +class _AcceptedResponse: + status_code = 202 + + def raise_for_status(self): + return None + + +class _AsyncClient: + def __init__(self): + self.posts = [] + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + return False + + async def post(self, url, json): + self.posts.append((url, json)) + return _AcceptedResponse() + + +@pytest.mark.asyncio +async def test_store_treats_accepted_background_response_as_success(monkeypatch): + client = _AsyncClient() + monkeypatch.setattr( + 'demo.utils.simple_memory_manager.httpx.AsyncClient', + lambda *args, **kwargs: client, + ) + + manager = SimpleMemoryManager(user_id='user-1') + manager._settings_initialized = True + + assert await manager.store('background extraction') is True + assert len(client.posts) == 1 From c6a14a2055d96ae66cc35febbf0fee2e09808c33 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 10:56:07 -0400 Subject: [PATCH 16/27] chore(everos-memory): add gitignore for runs + console target Excludes raven/.local-runs/ and raven-console/target/ from version control so dogfood runs and Rust build artifacts stay local. Co-Authored-By: Claude Opus 4.7 (1M context) --- use-cases/hermes-everos-memory/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 use-cases/hermes-everos-memory/.gitignore diff --git a/use-cases/hermes-everos-memory/.gitignore b/use-cases/hermes-everos-memory/.gitignore new file mode 100644 index 00000000..6f2b0ba3 --- /dev/null +++ b/use-cases/hermes-everos-memory/.gitignore @@ -0,0 +1,2 @@ +raven/.local-runs/ +raven-console/target/ From ef8499f16509238f929b8eaa30e5d5d629407f39 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 10:56:23 -0400 Subject: [PATCH 17/27] chore(everos): exclude .playwright-mcp + .goal traces from repo root Adds .playwright-mcp/ and .goal/ to root .gitignore so per-session agent traces (Playwright MCP captures, goalv3 state) stay local and do not pollute the repo working tree. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ef675660..16db8a8b 100755 --- a/.gitignore +++ b/.gitignore @@ -222,4 +222,8 @@ evaluation/locomo_evaluation/results_ref/demo/results/ .review_progress.json # Use-cases: exclude lock files to keep repo lean -use-cases/**/package-lock.json \ No newline at end of file +use-cases/**/package-lock.json + +# Local playwright + goal state traces +.playwright-mcp/ +.goal/ \ No newline at end of file From 6b9527359dd5af80ec76d5f86e92b744d8ba4be8 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 10:56:41 -0400 Subject: [PATCH 18/27] =?UTF-8?q?docs(raven):=20rebrand=20Riven=E2=86=92Ra?= =?UTF-8?q?ven=20and=20refresh=20contract+ledger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames RIVEN_CONCEPT.md to RAVEN_CONCEPT.md and refreshes the command contract, README, research ledger, and doomsday fixture to reflect the v2 Raven framing. Adds NATIVE_FEEL_AUDIT.md and REFERENCE_NOTES.md as supporting research. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../raven/COMMAND_CONTRACT.md | 107 +++++++++++++++--- .../raven/NATIVE_FEEL_AUDIT.md | 35 ++++++ .../{RIVEN_CONCEPT.md => RAVEN_CONCEPT.md} | 36 +++--- .../raven/RAVEN_V2_RESEARCH_LEDGER.md | 20 +++- .../hermes-everos-memory/raven/README.md | 87 +++++++++++++- .../raven/REFERENCE_NOTES.md | 53 +++++++++ .../raven/fixtures/doomsday-run.json | 14 +-- 7 files changed, 303 insertions(+), 49 deletions(-) create mode 100644 use-cases/hermes-everos-memory/raven/NATIVE_FEEL_AUDIT.md rename use-cases/hermes-everos-memory/raven/{RIVEN_CONCEPT.md => RAVEN_CONCEPT.md} (59%) create mode 100644 use-cases/hermes-everos-memory/raven/REFERENCE_NOTES.md diff --git a/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md b/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md index 18c5f23e..0f1401f9 100644 --- a/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md +++ b/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md @@ -1,27 +1,30 @@ -# Raven Command Contract v0 +# Raven Command Contract v1 Raven is the operator surface for memory-backed agent work. It is not a dashboard first and not a marketing page. It is a command contract that turns a goal, memory substrate, lanes, gates, and evidence into an owner-readable run packet. -Naming note: Riven is the product/concept name for this operator surface. Raven -is the current repo-local CLI and packet namespace. Keep the Raven namespace -until a migration plan preserves existing verifiers and SkillHub install -targets. +Naming note: Raven is the product, internal, CLI, packet, and fixture namespace. +Keep one name across docs and code unless a future migration plan explicitly +changes it. ## Shape -Raven v0 ships as a thin CLI/TUI contract over local files and Hermes/EverOS -memory. +Raven v1 ships as a Rust CLI, Hermes-backed chat command, slash-command REPL, +and ratatui TUI contract over local files, Hermes/EverOS memory, Multica watch +issues, and local verifier receipts. It owns: - run packet validation; -- memory recall before work starts; +- memory health/search before work starts; - lane and mutation-policy visibility; -- gate execution and verdict calculation; -- owner packet export. +- gate visibility and conservative verdict calculation; +- sanitized JSON snapshot and receipt output; +- owner packet export; +- shared Hermes chat adapter across CLI, REPL, and TUI; +- native-feel and public-safety audit. It does not own: @@ -34,12 +37,24 @@ It does not own: | Command | Input | Output | Gate | | --- | --- | --- | --- | -| `raven init` | repo root | `.raven/packet.json` seed | no secrets in seed | -| `raven memory search ` | query text | bounded memory refs | EverOS provider available | -| `raven run ` | run packet | updated packet + iteration log | mutation policy honored | -| `raven lane list` | run packet | lane table | owners/scopes visible | -| `raven gate verify` | run packet | PASS/FLAG/BLOCK table | blocking gates cannot be skipped | -| `raven export` | run packet | owner packet markdown | public-safety scan clean | +| `raven status [--json]` | live local files and watch issues | `RavenSnapshot` or compact status | local `PASS` plus remote `BLOCK` renders overall `FLAG` | +| `raven tui` | terminal | ratatui console with status, rail, active panel, evidence drawer, input line | `RAVEN_TUI_ONCE=1` must render deterministic smoke output | +| `raven repl` | slash commands | same handlers as CLI | piped smoke stays deterministic | +| `raven chat send [--cwd ] [--json] [--receipt ] [--save] ` | bounded prompt text | sanitized `HermesChatTurn` or `RavenReceipt` | Hermes failure is `FLAG`, not UI crash; chat receipts cannot green remote deploy | +| `raven packet show [--json]` | local packet/docs | packet summary | source docs resolve | +| `raven packet export [--output ]` | snapshot | sanitized owner packet markdown | public-safety sanitizer clean | +| `raven memory health [--json]` | EverOS bridge | health verdict | provider failure is `FLAG`, not crash | +| `raven memory search [--json]` | query text | bounded memory refs | empty query is `FLAG` | +| `raven agents list [--json]` | Multica watch issues | agent/watch table | unavailable Multica falls back to `FLAG` | +| `raven gates [--json]` | packet + watch evidence | local and remote gate table | hard gates cannot be skipped | +| `raven research lanes [--json]` | `RAVEN_V2_RESEARCH_LEDGER.md` | bounded research lane list | every lane must end in a packet | +| `raven research packet [--json] [--output ]` | research ledger + live remote gates | `RavenResearchPacket` | live `DAS-2666/2669` red gates force `FLAG` context | +| `raven research synthesize [--json] [--output ]` | completed research packets | synthesis readiness report | less than three packets stays `FLAG`; no architecture packet | +| `raven runs list [--json]` | saved receipts or packet gates | run/receipt table | receipts read from gitignored local dir | +| `raven sc [all|status|sessions|providers|worktree] [--json]` | Superconductor socket via thin CLI | `ScReport` or focused view | unavailable socket or merge-base failure is `FLAG`, never a crash | +| `raven run verify [--receipt ] [--save]` | local run packet | `RavenReceipt` or human output | local verifier cannot green remote deploy | +| `raven doctor [--json]` | toolchain/files/bridge | dependency report | missing hard local dependency blocks | +| `raven native-audit [--json]` | source + audit doc | UX/safety gate report | hard UX/safety failure blocks `PASS` | ## Run State @@ -83,6 +98,55 @@ After execution Raven writes: The memory loop is proof-backed only when a unique marker can be stored and searched back through EverOS. +## Hermes Chat Behavior + +The chat surface is a shared adapter, not a second TUI-only path: + +- `raven chat send ` executes a single Hermes oneshot turn; +- `raven repl` accepts `/chat `, `/hermes `, and bare text as + Hermes dialogue; +- `raven tui` exposes a Hermes chat panel with background execution so prompt + submission does not block redraw, keyboard handling, or gate visibility. + +The adapter injects an explicit Raven working directory into the Hermes process +and labels it as `case-root` or `case-root/` in public output. It also +records the detected Hermes runtime, so Codex app-server turns can be separated +from legacy `auto` turns during review. + +Every prompt, response, stderr excerpt, transcript line, receipt excerpt, and +JSON field goes through Raven's public-safety sanitizer before display or save. +`--receipt -` prints a sanitized `RavenReceipt`; `--save` writes one under the +gitignored local runs directory. + +## Superconductor Behavior + +Raven treats Superconductor as the conductor plane, not as a mutation authority: + +- `raven sc status` checks whether the local Superconductor socket responds; +- `raven sc sessions` lists active chat sessions with provider/model/branch; +- `raven sc providers` summarizes provider availability without dumping every + model into the TUI; +- `raven sc worktree` reports target/base status and preserves merge-base + failures as `FLAG`. + +The adapter has a short timeout and only performs read-only calls. It does not +spawn sessions, select tabs, cancel turns, close sessions, or rewrite the +Superconductor target branch. + +## Research Behavior + +Raven v2 research is structured as packets, not freeform notes: + +- `raven research lanes` lists the five bounded research lanes from + `RAVEN_V2_RESEARCH_LEDGER.md`; +- `raven research packet ` renders one lane into the required packet + shape with live hard-gate evidence attached; +- `raven research synthesize` only reports readiness until at least three + evidence-backed packets exist. + +Research packets may recommend v1 implementation slices, but they cannot mark +remote deploy ready or bypass `DAS-2666` / `DAS-2669`. + ## Gate Semantics `PASS` means the specific requirement was tested at the scope it claims. @@ -94,6 +158,15 @@ external observation. Raven must not upgrade `FLAG` to `PASS` because nearby tests passed. +Remote EverCore deploy has extra hard rules: + +- `DAS-2669` must expose `AUTH_REPAIRED VERDICT: PASS` before the auth block is + considered repaired; +- `DAS-2666` may not render `PASS` unless auth repair, guarded NixOS test, + remote loopback full smoke, and supervisor `PASS` are all present; +- `DAS-2675` can repair Pi/OpenCode adapter lanes, but never changes remote + deploy verdict. + ## First Artifact The first Raven artifact is the owner packet rendered from @@ -114,7 +187,7 @@ node bin/raven-run.mjs verify raven/fixtures/doomsday-run.json This is the repo-local equivalent of: ```bash -raven gate verify --packet raven/fixtures/doomsday-run.json +raven run verify ``` It exits non-zero for `FLAG` or `BLOCK`. diff --git a/use-cases/hermes-everos-memory/raven/NATIVE_FEEL_AUDIT.md b/use-cases/hermes-everos-memory/raven/NATIVE_FEEL_AUDIT.md new file mode 100644 index 00000000..7e8b8d9d --- /dev/null +++ b/use-cases/hermes-everos-memory/raven/NATIVE_FEEL_AUDIT.md @@ -0,0 +1,35 @@ +# Raven Native Feel Audit + +## Verdict + +PASS for the v1 local terminal console contract. + +This audit is Raven-specific. It borrows the discipline of native-feeling CLI +tools without copying any external reference implementation. + +## Categories + +| Category | Gate | Current Evidence | Verdict | +| --- | --- | --- | --- | +| Latency | Commands must return usable `PASS/FLAG/BLOCK` state without crashing when bridges are absent. | Memory and Multica adapters degrade to `FLAG` or fallback watch state. | PASS | +| Keybindings | A TUI operator can move without memorizing long commands. | `h`/`c` chat, `i` prompt input, `?`, `:`, `/`, `s`, `p`, `m`, `a`, `g`, `r`, `o` Superconductor, `d`, `n`, `q`, `Esc`, and `Ctrl-C` are handled. | PASS | +| Focus | The active panel is explicit state. | Panels are `Status`, `Packet`, `Chat`, `Memory`, `Agents`, `Gates`, `Runs`, `Doctor`, `NativeAudit`, and `Help`. | PASS | +| Scrollback | Evidence remains visible without layout churn. | The evidence drawer stays fixed; deep historical receipts live in `raven/.local-runs/`. | PASS | +| Interrupt behavior | Interrupts must exit or cancel cleanly. | `Esc` cancels prompt modes; `Ctrl-C` exits the TUI loop. | PASS | +| REPL history | Interactive command recall should feel local-native. | `rustyline` backs the interactive REPL; piped input stays deterministic for smoke tests. | PASS | +| Pane stability | Dynamic data cannot resize the command surface unpredictably. | `ratatui` uses fixed status, rail, evidence, and input regions around a flexible active panel. | PASS | +| Command grammar | CLI and REPL commands share the same operator vocabulary. | Slash commands map to status, packet, chat, memory, agents, gates, runs, doctor, audit, and quit handlers. | PASS | +| Typed IPC | Machine output is typed and redacted. | `RavenSnapshot`, `RavenReceipt`, `HermesChatTurn`, and `ScReport` are serialized through the sanitizer before JSON printing. | PASS | +| Evidence visibility | Hard gates and receipts are first-class. | DAS-2666, DAS-2669, local packet gates, saved receipts, and configured verification commands render directly. | PASS | +| Public-safety redaction | Public output must not expose private paths, hosts/IPs, tokens, credential paths, or signed URLs. | Human and JSON output pass through the sanitizer; receipts store sanitized excerpts. | PASS | + +## Hard PASS Blockers + +`raven native-audit` must refuse `PASS` when any hard category fails: + +- missing keybindings for chat/input/quit/help/palette/search/status/gates/runs/audit; +- missing stable TUI panes; +- unsafe interrupt behavior; +- missing typed JSON snapshot or receipt contracts; +- unredacted public output; +- saved receipts not ignored by git. diff --git a/use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md b/use-cases/hermes-everos-memory/raven/RAVEN_CONCEPT.md similarity index 59% rename from use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md rename to use-cases/hermes-everos-memory/raven/RAVEN_CONCEPT.md index 796ac793..5d6179e7 100644 --- a/use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md +++ b/use-cases/hermes-everos-memory/raven/RAVEN_CONCEPT.md @@ -1,26 +1,24 @@ -# Riven Concept Packet v0 +# Raven Concept Packet v0 ## Verdict -PASS for the Riven concept artifact. +PASS for the Raven concept artifact. -Riven is the operator-facing concept name for the memory-backed execution lane. -The current repo-local implementation remains under the `raven` directory and -`raven-run` command for v0 compatibility. +Raven is the operator-facing name and the repo-local implementation namespace +for the memory-backed execution lane. -## Naming Boundary +## Naming Contract | Name | Role | Status | | --- | --- | --- | -| Riven | product/concept name for the operator surface | concept artifact | -| Raven | current CLI, packet schema, fixtures, and SkillHub install target | implemented v0 surface | +| Raven | operator surface, CLI, packet schema, fixtures, and SkillHub install target | implemented v0 surface | -Do not rename files, commands, or install targets until there is a migration -plan. The present contract treats Raven as the working codename for Riven v0. +Use Raven everywhere. Do not introduce a second product/internal name unless a +future migration plan explicitly changes the namespace. ## Product Thesis -Riven is not a chat transcript viewer and not a generic dashboard. It is a +Raven is not a chat transcript viewer and not a generic dashboard. It is a memory-backed operator surface for focused agent work: - capture one goal; @@ -32,9 +30,9 @@ memory-backed operator surface for focused agent work: ## First Run Shape -The first Riven run is the Doomsday EverOS lane: +The first Raven run is the Doomsday EverOS lane: -1. Riven concept exploration through the Raven command contract. +1. Raven concept exploration through the Raven command contract. 2. EverMe SkillHub MVP packet and read-only mock API. 3. Hermes/EverOS provider dogfood with store, search, recall, and real Hermes profile verification. @@ -44,11 +42,11 @@ The first Riven run is the Doomsday EverOS lane: The minimal useful UI is command-grade: ```text -riven capture -riven memory search -riven lane list -riven gate verify -riven export +raven capture +raven memory search +raven lane list +raven gate verify +raven export ``` For v0, these map to the existing `raven-run` validator/renderer and the @@ -61,7 +59,7 @@ For v0, these map to the existing `raven-run` validator/renderer and the credential paths. - Do not treat remote NixOS deploy as complete until the deploy smoke passes on the remote loopback service. -- Do not widen Riven into a new major repo before the packet contract earns it. +- Do not widen Raven into a new major repo before the packet contract earns it. ## Current Evidence diff --git a/use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md b/use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md index 7288330b..c546e376 100644 --- a/use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md +++ b/use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md @@ -25,10 +25,11 @@ only through bounded decisions: ## Current Truth -- Local EverOS/Hermes/SkillHub/Riven packet is `PASS`. +- Local EverOS/Hermes/SkillHub/Raven packet is `PASS`. - Remote EverCore deploy remains `FLAG/BLOCK`. - `DAS-2666` is the canonical remote deploy gate. -- `DAS-2669` is the Windburn NixOS Codex auth blocker. +- `DAS-2669` auth-route repair is accepted through DeepSeek/OpenRouter; parent + deploy readiness remains blocked on `DAS-2666` evidence. - `DAS-2670` is the current control-room dispatch. - `DAS-2675` tracks Pi/OpenCode Multica runtime-adapter repair. - Existing Raven v1 build work is dirty in the worktree; do not overwrite it. @@ -196,6 +197,21 @@ NEXT: VERDICT: PASS | FLAG | BLOCK ``` +## Executable Harness + +Raven v1 exposes the v2 research lane through bounded commands so research +does not become unreviewable prose: + +```bash +bin/raven research lanes +bin/raven research packet native-feel --output - +bin/raven research synthesize +``` + +The packet command always carries live hard-gate context. If `DAS-2666` or +`DAS-2669` are still red, the packet can guide v1 work but cannot claim remote +readiness. + ## First Synthesis Target Produce `RAVEN_V2_ARCHITECTURE_PACKET.md` only after at least three lanes return diff --git a/use-cases/hermes-everos-memory/raven/README.md b/use-cases/hermes-everos-memory/raven/README.md index aeb46d9a..45bcdeba 100644 --- a/use-cases/hermes-everos-memory/raven/README.md +++ b/use-cases/hermes-everos-memory/raven/README.md @@ -2,8 +2,7 @@ `v0` contract for a Raven run. -Riven is the operator-facing concept name for this surface. Raven is the -repo-local v0 codename and command namespace kept for compatibility. +Raven is the operator-facing concept name and the repo-local command namespace. Raven is not a marketing page here. This directory defines the packet that a CLI/TUI can validate, render, and later execute against Hermes/EverOS memory. @@ -12,10 +11,13 @@ CLI/TUI can validate, render, and later execute against Hermes/EverOS memory. | File | Purpose | | --- | --- | -| `RIVEN_CONCEPT.md` | Public-safe Riven concept and naming boundary | -| `COMMAND_CONTRACT.md` | Public-safe command/state/gate contract for Raven v0 | +| `RAVEN_CONCEPT.md` | Public-safe Raven concept and naming contract | +| `COMMAND_CONTRACT.md` | Public-safe command/state/gate contract for Raven v1 | +| `REFERENCE_NOTES.md` | Public-safe reference scan and license notes | | `schema.json` | Public-safe JSON Schema for a Raven run packet | | `fixtures/doomsday-run.json` | Sample run packet for the current dogfood lane | +| `../raven-console/` | Rust Raven v1 CLI/REPL/TUI implementation | +| `../bin/raven` | Local shell wrapper for the Rust console | | `../bin/raven-run.mjs` | Local validator and owner-packet renderer | ## Commands @@ -27,6 +29,61 @@ node ../bin/raven-run.mjs verify fixtures/doomsday-run.json node ../bin/raven-run.mjs summary fixtures/doomsday-run.json ``` +Console entrypoints: + +```bash +bin/raven --help +bin/raven status +bin/raven status --json +bin/raven packet show +bin/raven packet export --output - +bin/raven chat send "summarize current hard gates" +bin/raven chat send --receipt - "summarize current hard gates" +bin/raven chat send --save "summarize current hard gates" +bin/raven memory health +bin/raven memory search "operator gate" +bin/raven agents list +bin/raven gates +bin/raven research lanes +bin/raven research packet native-feel +bin/raven research synthesize +bin/raven runs list +bin/raven sc +bin/raven sc sessions +bin/raven sc worktree +bin/raven run verify +bin/raven run verify --receipt - +bin/raven native-audit +bin/raven repl +RAVEN_TUI_ONCE=1 bin/raven tui +``` + +Just targets: + +```bash +just raven-status +just raven-packet +just raven-gates +just raven-agents +just raven-research-lanes +just raven-research-packet-smoke +just raven-research-synthesis +just raven-doctor +just raven-native-audit +just raven-runs +just raven-sc +just raven-sc-status +just raven-sc-sessions +just raven-sc-providers +just raven-sc-worktree +just raven-run-verify +just raven-chat-smoke +just raven-chat-receipt-smoke +just raven-repl-smoke +just raven-tui-smoke +just raven-console-check +``` + ## Contract A Raven run packet records: @@ -46,3 +103,25 @@ The computed verdict is conservative: `verify` exits non-zero for `FLAG` or `BLOCK`, so scripts can refuse to call a packet complete when blocking gates remain open. + +The v1 console keeps local packet truth and remote deploy truth separate: + +- local packet `PASS` plus remote hard gate `BLOCK` renders overall `FLAG`, not + `PASS`; +- `DAS-2669` exposes `AUTH_REPAIRED VERDICT: PASS` for the accepted + DeepSeek/OpenRouter auth-route repair; that clears only the auth block; +- `DAS-2666` cannot render `PASS` until auth repair, guarded NixOS test, remote + loopback full smoke, and supervisor `PASS` are all present; +- `DAS-2675` can repair adapter lanes but has no effect on the remote deploy + verdict. + +Hermes dialogue is shared across surfaces: `raven chat send`, bare text or +`/chat` inside `raven repl`, and the `h` panel inside `raven tui` all use the +same sanitized adapter. The adapter records the public-safe Raven workspace +label, detected Hermes runtime, command shape, and sanitized transcript. Chat +receipts can be printed with `--receipt -` or saved with `--save`; they never +change remote deploy gate state. + +Superconductor state is visible through `raven sc`. The adapter is read-only, +times out quickly, and turns socket or merge-base failures into `FLAG` evidence +instead of blocking the Raven console. diff --git a/use-cases/hermes-everos-memory/raven/REFERENCE_NOTES.md b/use-cases/hermes-everos-memory/raven/REFERENCE_NOTES.md new file mode 100644 index 00000000..d2672d4a --- /dev/null +++ b/use-cases/hermes-everos-memory/raven/REFERENCE_NOTES.md @@ -0,0 +1,53 @@ +# Raven Console Reference Notes + +Reference scan date: 2026-05-15. + +No source code was copied or vendored. These notes record license posture and +portable UX/architecture lessons only. + +## yetone/native-feel-skill + +- License: MIT, verified from `LICENSE` in the shallow clone. +- Useful lesson: optimize for identity and operator muscle memory. Raven should + keep a stable command shape and fast repeated verbs instead of exposing every + internal subsystem as a new surface. +- Copied code: none. + +## superagent-ai/grok-cli + +- License: MIT, verified from `LICENSE` in the shallow clone. +- Useful lesson: split interactive and headless flows cleanly. Raven v0 keeps + `repl`/`tui` interactive entrypoints while preserving scriptable commands + like `status`, `packet show`, `memory search`, and `run verify`. +- Useful lesson: surface sub-agent and verification state as first-class + operator data, but do not pretend failed runtimes are healthy. +- Copied code: none. + +## openai/codex + +- License: Apache-2.0, verified from `LICENSE` in the shallow clone. +- Useful lesson: a Rust CLI multitool can own command routing while a TUI is a + separate operator shell. Raven v0 follows that split with a Rust command core + and an ANSI-only first TUI screen. +- Useful lesson: local sandbox/deploy policy belongs in visible status, not in + hidden assumptions. +- Copied code: none. + +## claude-code-best/claude-code + +- License: unresolved in the shallow checkout. The README advertises a GitHub + license badge, but no `LICENSE` file was present in the cloned tree. +- Useful lesson: slash commands and provider-login surfaces are familiar to + operators, but license uncertainty means this repo was used only for broad + product-pattern inspiration. +- Copied code: none. + +## NousResearch/hermes-agent + +- License: MIT, verified from `LICENSE` in the shallow clone. +- Useful lesson: keep CLI, messaging, providers, memory, and skill systems as + distinct adapter layers. Raven should be the console over those layers, not a + replacement provider or another agent runtime. +- Useful lesson: slash command routing, provider status, and memory search are + the right primitives for v0. +- Copied code: none. diff --git a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json index f70742fe..f504de38 100644 --- a/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json +++ b/use-cases/hermes-everos-memory/raven/fixtures/doomsday-run.json @@ -1,7 +1,7 @@ { "id": "raven.everme-doomsday-run", - "title": "Riven / Raven / EverMe Doomsday Run", - "goal": "Turn the source conversation into a focused Riven concept, EverMe SkillHub, and Hermes/EverOS dogfood execution lane with auditable local artifacts. Riven is represented by the current Raven v0 command namespace until a migration is approved.", + "title": "Raven / EverMe Doomsday Run", + "goal": "Turn the source conversation into a focused Raven concept, EverMe SkillHub, and Hermes/EverOS dogfood execution lane with auditable local artifacts. Raven is the concept, internal, and command namespace.", "status": "done", "owners": ["codex", "pi", "opencode", "hermes"], "memory_providers": ["everos", "hermes"], @@ -9,12 +9,12 @@ { "id": "raven-concept", "owner": "pi", - "scope": "Riven taste, naming, story, interface wedge, and first public artifact through the Raven v0 compatibility surface.", + "scope": "Raven taste, naming, story, interface wedge, and first public artifact through the Raven v0 compatibility surface.", "mutation_policy": "read_only", "verdict": "pass", "evidence_refs": [ "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", - "use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md", + "use-cases/hermes-everos-memory/raven/RAVEN_CONCEPT.md", "use-cases/hermes-everos-memory/raven/README.md" ] }, @@ -116,8 +116,8 @@ "public_safe": true }, { - "path": "use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md", - "purpose": "Riven concept and naming boundary.", + "path": "use-cases/hermes-everos-memory/raven/RAVEN_CONCEPT.md", + "purpose": "Raven concept and naming contract.", "public_safe": true }, { @@ -133,7 +133,7 @@ ], "evidence_refs": [ "use-cases/hermes-everos-memory/OWNER_PACKET.md", - "use-cases/hermes-everos-memory/raven/RIVEN_CONCEPT.md", + "use-cases/hermes-everos-memory/raven/RAVEN_CONCEPT.md", "use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md", "use-cases/hermes-everos-memory/skillhub/MVP_IMPLEMENTATION_PLAN.md", "use-cases/hermes-everos-memory/skillhub/fixtures/evoagentbench-musician-life-event.json", From 27ccde3aafd54f70049c6041055b93b9e1879d19 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 10:57:01 -0400 Subject: [PATCH 19/27] feat(raven): scaffold raven-console TUI + launcher Adds the raven-console Rust crate (Cargo.toml, Cargo.lock, src/) implementing a ratatui+crossterm terminal UI with adapters for hermes, memory, muw, packet, sc, and verify lanes. Also adds the bin/raven shell launcher that boots the console with the right context resolution. Co-Authored-By: Claude Opus 4.7 (1M context) --- use-cases/hermes-everos-memory/bin/raven | 16 + .../raven-console/Cargo.lock | 925 +++++++++++++++ .../raven-console/Cargo.toml | 18 + .../raven-console/src/adapters/hermes.rs | 364 ++++++ .../raven-console/src/adapters/memory.rs | 108 ++ .../raven-console/src/adapters/mod.rs | 6 + .../raven-console/src/adapters/muw.rs | 460 +++++++ .../raven-console/src/adapters/packet.rs | 101 ++ .../raven-console/src/adapters/sc.rs | 382 ++++++ .../raven-console/src/adapters/verify.rs | 125 ++ .../raven-console/src/audit.rs | 164 +++ .../raven-console/src/commands.rs | 640 ++++++++++ .../raven-console/src/constants.rs | 28 + .../raven-console/src/context.rs | 38 + .../raven-console/src/main.rs | 40 + .../raven-console/src/model.rs | 354 ++++++ .../raven-console/src/output.rs | 442 +++++++ .../raven-console/src/receipt.rs | 167 +++ .../raven-console/src/repl.rs | 50 + .../raven-console/src/research.rs | 440 +++++++ .../raven-console/src/sanitizer.rs | 213 ++++ .../raven-console/src/snapshot.rs | 223 ++++ .../raven-console/src/tui.rs | 1056 +++++++++++++++++ .../raven-console/src/util.rs | 32 + 24 files changed, 6392 insertions(+) create mode 100755 use-cases/hermes-everos-memory/bin/raven create mode 100644 use-cases/hermes-everos-memory/raven-console/Cargo.lock create mode 100644 use-cases/hermes-everos-memory/raven-console/Cargo.toml create mode 100644 use-cases/hermes-everos-memory/raven-console/src/adapters/hermes.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/adapters/memory.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/adapters/mod.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/adapters/muw.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/adapters/packet.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/adapters/sc.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/adapters/verify.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/audit.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/commands.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/constants.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/context.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/main.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/model.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/output.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/receipt.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/repl.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/research.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/sanitizer.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/snapshot.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/tui.rs create mode 100644 use-cases/hermes-everos-memory/raven-console/src/util.rs diff --git a/use-cases/hermes-everos-memory/bin/raven b/use-cases/hermes-everos-memory/bin/raven new file mode 100755 index 00000000..2865f8b8 --- /dev/null +++ b/use-cases/hermes-everos-memory/bin/raven @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/.." + +binary="raven-console/target/debug/raven" +changed="" +if [[ -x "$binary" ]]; then + changed="$(find raven-console/src raven-console/Cargo.toml raven-console/Cargo.lock -type f -newer "$binary" -print -quit)" +fi + +if [[ ! -x "$binary" || -n "$changed" ]]; then + cargo build --quiet --manifest-path raven-console/Cargo.toml +fi + +exec "$binary" "$@" diff --git a/use-cases/hermes-everos-memory/raven-console/Cargo.lock b/use-cases/hermes-everos-memory/raven-console/Cargo.lock new file mode 100644 index 00000000..24c537a2 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/Cargo.lock @@ -0,0 +1,925 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.1.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "instability" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "raven-console" +version = "0.1.0" +dependencies = [ + "clap", + "crossterm", + "ratatui", + "regex", + "rustyline", + "serde", + "serde_json", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustyline" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" +dependencies = [ + "bitflags", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width 0.2.0", + "utf8parse", + "windows-sys 0.59.0", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/use-cases/hermes-everos-memory/raven-console/Cargo.toml b/use-cases/hermes-everos-memory/raven-console/Cargo.toml new file mode 100644 index 00000000..e6b8cebf --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "raven-console" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "raven" +path = "src/main.rs" + +[dependencies] +clap = { version = "4.5", features = ["derive"] } +crossterm = "0.28" +ratatui = "0.29" +regex = "1.11" +rustyline = "15.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/use-cases/hermes-everos-memory/raven-console/src/adapters/hermes.rs b/use-cases/hermes-everos-memory/raven-console/src/adapters/hermes.rs new file mode 100644 index 00000000..45c51e28 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/adapters/hermes.rs @@ -0,0 +1,364 @@ +use crate::context::Context; +use crate::model::{HermesChatTranscriptLine, HermesChatTurn, Verdict}; +use crate::sanitizer::sanitize_text; +use crate::util::one_line; +use crate::RavenResult; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::Instant; + +const MAX_PROMPT_CHARS: usize = 4_000; +const MAX_RESPONSE_CHARS: usize = 8_000; +const MAX_EVIDENCE_CHARS: usize = 1_200; + +#[derive(Default)] +pub struct HermesOptions { + pub cwd: Option, +} + +#[derive(Clone)] +pub(crate) struct HermesTurnMeta { + command: Vec, + workspace: String, + runtime: String, +} + +pub fn ask(ctx: &Context, prompt: &str) -> RavenResult { + ask_with_options(ctx, prompt, HermesOptions::default()) +} + +pub fn ask_with_options( + ctx: &Context, + prompt: &str, + options: HermesOptions, +) -> RavenResult { + let prompt = prompt.trim(); + let runtime = detect_runtime(); + let command = command_label(); + let cwd = resolve_cwd(ctx, options.cwd.as_deref()); + let workspace = cwd + .as_ref() + .map(|cwd| workspace_label(ctx, cwd)) + .unwrap_or_else(|err| sanitize_text(err)); + let meta = HermesTurnMeta { + command, + workspace, + runtime, + }; + + if prompt.is_empty() { + return Ok(HermesChatTurn { + prompt: String::new(), + command: meta.command, + workspace: meta.workspace, + runtime: meta.runtime, + verdict: Verdict::Flag, + exit_code: 0, + duration_ms: 0, + response: "Empty prompt.".to_string(), + evidence: "no Hermes call was made".to_string(), + transcript: Vec::new(), + }); + } + + let binary = env::var("RAVEN_HERMES_BIN").unwrap_or_else(|_| "hermes".to_string()); + let bounded_prompt = clamp_chars(prompt, MAX_PROMPT_CHARS); + + let cwd = match cwd { + Ok(cwd) => cwd, + Err(err) => { + return Ok(flag_turn( + &bounded_prompt, + meta, + 1, + 0, + "Hermes cwd is unavailable for this turn.", + &format!("invalid Hermes cwd: {err}"), + )) + } + }; + + let start = Instant::now(); + let output = Command::new(binary) + .arg("-z") + .arg(build_raven_prompt( + &bounded_prompt, + &meta.workspace, + &meta.runtime, + )) + .current_dir(&cwd) + .env("RAVEN_WORKSPACE_ROOT", &ctx.root) + .env("RAVEN_OPERATOR_CWD", &cwd) + .env("RAVEN_HERMES_RUNTIME", &meta.runtime) + .output(); + + match output { + Ok(output) => { + let exit_code = output.status.code().unwrap_or(1); + Ok(turn_from_output( + &bounded_prompt, + exit_code, + &String::from_utf8_lossy(&output.stdout), + &String::from_utf8_lossy(&output.stderr), + start.elapsed().as_millis(), + meta, + )) + } + Err(err) => Ok(flag_turn( + &bounded_prompt, + meta, + 127, + start.elapsed().as_millis(), + "Hermes is unavailable for this turn.", + &format!("failed to launch Hermes: {err}"), + )), + } +} + +fn build_raven_prompt(prompt: &str, workspace: &str, runtime: &str) -> String { + format!( + "You are Hermes inside Raven's local operator console.\n\ +Keep the answer concise and operational.\n\ +Do not mutate files, remote issues, deploy targets, or credentials unless the operator explicitly asks.\n\ +Keep public-surface safety: do not reveal local absolute paths, tokens, private hosts/IPs, or credential paths.\n\ +Runtime context: hermes_openai_runtime={runtime}; raven_workspace={workspace}.\n\ +If you use terminal tools, operate from the process cwd or from the RAVEN_OPERATOR_CWD/RAVEN_WORKSPACE_ROOT env vars, but do not print those env values.\n\ +If Raven gates are discussed, preserve the current truth: DAS-2669 auth-route repair is accepted through DeepSeek/OpenRouter, while DAS-2666 remains blocked until remote env preflight, guarded NixOS test, full smoke, and supervisor PASS exist.\n\n\ +Operator prompt:\n{prompt}" + ) +} + +pub(crate) fn turn_from_output( + prompt: &str, + exit_code: i32, + stdout: &str, + stderr: &str, + duration_ms: u128, + meta: HermesTurnMeta, +) -> HermesChatTurn { + let stdout = stdout.trim(); + let stderr = stderr.trim(); + let raw_response = if stdout.is_empty() && !stderr.is_empty() { + stderr + } else { + stdout + }; + let response = clamp_chars(&sanitize_text(raw_response), MAX_RESPONSE_CHARS); + let evidence = if exit_code == 0 { + format!( + "Hermes oneshot completed in {duration_ms}ms; runtime={}; cwd={}", + meta.runtime, meta.workspace + ) + } else if stderr.is_empty() { + format!( + "Hermes exited {exit_code} with no stderr; runtime={}; cwd={}", + meta.runtime, meta.workspace + ) + } else { + format!( + "Hermes exited {exit_code}: {}; runtime={}; cwd={}", + one_line(stderr), + meta.runtime, + meta.workspace + ) + }; + let response = if response.is_empty() { + "(no response text)".to_string() + } else { + response + }; + let prompt = sanitize_text(&clamp_chars(prompt, MAX_PROMPT_CHARS)); + + HermesChatTurn { + transcript: vec![ + HermesChatTranscriptLine { + role: "operator".to_string(), + content: prompt.clone(), + }, + HermesChatTranscriptLine { + role: "assistant".to_string(), + content: response.clone(), + }, + ], + prompt, + command: meta.command, + workspace: meta.workspace, + runtime: meta.runtime, + verdict: if exit_code == 0 { + Verdict::Pass + } else { + Verdict::Flag + }, + exit_code, + duration_ms, + response, + evidence: clamp_chars(&sanitize_text(&evidence), MAX_EVIDENCE_CHARS), + } +} + +fn flag_turn( + prompt: &str, + meta: HermesTurnMeta, + exit_code: i32, + duration_ms: u128, + response: &str, + evidence: &str, +) -> HermesChatTurn { + let prompt = sanitize_text(&clamp_chars(prompt, MAX_PROMPT_CHARS)); + let response = response.to_string(); + HermesChatTurn { + transcript: vec![ + HermesChatTranscriptLine { + role: "operator".to_string(), + content: prompt.clone(), + }, + HermesChatTranscriptLine { + role: "assistant".to_string(), + content: response.clone(), + }, + ], + prompt, + command: meta.command, + workspace: meta.workspace, + runtime: meta.runtime, + verdict: Verdict::Flag, + exit_code, + duration_ms, + response, + evidence: clamp_chars(&sanitize_text(evidence), MAX_EVIDENCE_CHARS), + } +} + +fn resolve_cwd(ctx: &Context, requested: Option<&Path>) -> Result { + let cwd = requested.map_or_else( + || ctx.root.clone(), + |path| { + if path.is_absolute() { + path.to_path_buf() + } else { + ctx.root.join(path) + } + }, + ); + + if cwd.is_dir() { + Ok(cwd) + } else { + Err(cwd.to_string_lossy().to_string()) + } +} + +fn workspace_label(ctx: &Context, cwd: &Path) -> String { + if cwd == ctx.root { + return "case-root".to_string(); + } + + if let Ok(relative) = cwd.strip_prefix(&ctx.root) { + let relative = relative.to_string_lossy().replace('\\', "/"); + return format!("case-root/{relative}"); + } + + sanitize_text(&cwd.to_string_lossy()) +} + +fn command_label() -> Vec { + let binary = env::var("RAVEN_HERMES_BIN").unwrap_or_else(|_| "hermes".to_string()); + let label = Path::new(&binary) + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or(&binary); + vec![ + sanitize_text(label), + "-z".to_string(), + "[raven-prompt]".to_string(), + ] +} + +fn detect_runtime() -> String { + if let Ok(runtime) = env::var("RAVEN_HERMES_RUNTIME") { + return sanitize_text(runtime.trim()); + } + + let Some(home) = env::var_os("HOME") else { + return "unknown".to_string(); + }; + let config = PathBuf::from(home).join(".hermes/config.yaml"); + let Ok(text) = fs::read_to_string(config) else { + return "unknown".to_string(); + }; + + text.lines() + .find_map(|line| { + line.trim() + .strip_prefix("openai_runtime:") + .map(|value| value.trim().trim_matches('"').trim_matches('\'')) + }) + .filter(|value| !value.is_empty()) + .map(sanitize_text) + .unwrap_or_else(|| "unknown".to_string()) +} + +fn clamp_chars(value: &str, max_chars: usize) -> String { + let mut chars = value.chars(); + let mut output = chars.by_ref().take(max_chars).collect::(); + if chars.next().is_some() { + output.push_str(" ...[truncated]"); + } + output +} + +#[cfg(test)] +mod tests { + use super::{turn_from_output, HermesTurnMeta}; + use crate::model::Verdict; + + fn meta() -> HermesTurnMeta { + HermesTurnMeta { + command: vec!["hermes".to_string(), "-z".to_string()], + workspace: "case-root".to_string(), + runtime: "codex_app_server".to_string(), + } + } + + #[test] + fn successful_turn_sanitizes_output() { + let turn = turn_from_output( + "inspect status", + 0, + "ready from /Users/alice/work and token sk-proj-abcdefghijklmnopqrstuvwxyz123456", + "", + 42, + meta(), + ); + + assert_eq!(turn.verdict, Verdict::Pass); + assert_eq!(turn.workspace, "case-root"); + assert_eq!(turn.runtime, "codex_app_server"); + assert!(!turn.response.contains("/Users/alice")); + assert!(!turn.response.contains("sk-proj-")); + assert!(turn.response.contains("[redacted-path]")); + assert!(turn.response.contains("[redacted-token]")); + assert_eq!(turn.transcript.len(), 2); + } + + #[test] + fn failed_turn_is_flag_and_sanitizes_stderr() { + let turn = turn_from_output( + "ask", + 2, + "", + "failed on 127.0.0.1:8080 with token=secret-value", + 7, + meta(), + ); + + assert_eq!(turn.verdict, Verdict::Flag); + assert_eq!(turn.exit_code, 2); + assert!(!turn.evidence.contains("127.0.0.1")); + assert!(!turn.evidence.contains("secret-value")); + assert!(turn.evidence.contains("[redacted-ip]")); + assert!(turn.evidence.contains("token=[redacted-secret]")); + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/adapters/memory.rs b/use-cases/hermes-everos-memory/raven-console/src/adapters/memory.rs new file mode 100644 index 00000000..9c960286 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/adapters/memory.rs @@ -0,0 +1,108 @@ +use crate::context::Context; +use crate::model::{MemoryHealth, MemorySearchResult, Verdict}; +use crate::sanitizer::{sanitize_text, sanitize_value}; +use crate::util::{one_line, truncate}; +use serde_json::Value; +use std::process::{Command, Stdio}; + +pub fn health(ctx: &Context) -> MemoryHealth { + let output = Command::new("node") + .arg("bin/everos-memory.mjs") + .arg("health") + .current_dir(&ctx.root) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output(); + + match output { + Ok(output) if output.status.success() => { + let stdout = String::from_utf8_lossy(&output.stdout); + let status = serde_json::from_str::(&stdout) + .ok() + .and_then(|value| { + value + .get("status") + .and_then(Value::as_str) + .map(str::to_string) + .or_else(|| { + value + .get("data") + .and_then(|data| data.get("status")) + .and_then(Value::as_str) + .map(str::to_string) + }) + }) + .unwrap_or_else(|| "available".to_string()); + MemoryHealth { + verdict: Verdict::Pass, + status: sanitize_text(&status), + evidence: sanitize_text(&truncate(&one_line(&stdout), 260)), + } + } + Ok(output) => MemoryHealth { + verdict: Verdict::Flag, + status: "unavailable".to_string(), + evidence: sanitize_text(&format!( + "everos-memory health exited {}; {}", + output.status, + one_line(&String::from_utf8_lossy(&output.stderr)) + )), + }, + Err(err) => MemoryHealth { + verdict: Verdict::Flag, + status: "unavailable".to_string(), + evidence: sanitize_text(&format!("memory bridge unavailable: {err}")), + }, + } +} + +pub fn search(ctx: &Context, query: &str) -> MemorySearchResult { + if query.trim().is_empty() { + return MemorySearchResult { + query: String::new(), + verdict: Verdict::Flag, + evidence: "no query supplied".to_string(), + result: None, + }; + } + + let output = Command::new("node") + .arg("bin/everos-memory.mjs") + .arg("search") + .arg(query) + .current_dir(&ctx.root) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output(); + + match output { + Ok(output) if output.status.success() => { + let stdout = String::from_utf8_lossy(&output.stdout); + let result = serde_json::from_str::(&stdout) + .ok() + .map(sanitize_value); + MemorySearchResult { + query: sanitize_text(query), + verdict: Verdict::Pass, + evidence: sanitize_text(&truncate(&one_line(&stdout), 500)), + result, + } + } + Ok(output) => MemorySearchResult { + query: sanitize_text(query), + verdict: Verdict::Flag, + evidence: sanitize_text(&format!( + "everos-memory search exited {}; {}", + output.status, + one_line(&String::from_utf8_lossy(&output.stderr)) + )), + result: None, + }, + Err(err) => MemorySearchResult { + query: sanitize_text(query), + verdict: Verdict::Flag, + evidence: sanitize_text(&format!("memory bridge unavailable: {err}")), + result: None, + }, + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/adapters/mod.rs b/use-cases/hermes-everos-memory/raven-console/src/adapters/mod.rs new file mode 100644 index 00000000..6c2d78f4 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/adapters/mod.rs @@ -0,0 +1,6 @@ +pub mod hermes; +pub mod memory; +pub mod muw; +pub mod packet; +pub mod sc; +pub mod verify; diff --git a/use-cases/hermes-everos-memory/raven-console/src/adapters/muw.rs b/use-cases/hermes-everos-memory/raven-console/src/adapters/muw.rs new file mode 100644 index 00000000..d10d50c5 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/adapters/muw.rs @@ -0,0 +1,460 @@ +use crate::constants::{ + ISSUE_ADAPTER_REPAIR, ISSUE_AUTH_BLOCKER, ISSUE_CONTROL_ROOM, ISSUE_LOCAL_VERIFIER, + ISSUE_MEMORY_WATCH, ISSUE_REMOTE_DEPLOY, WATCHLIST_ISSUES, +}; +use crate::model::{AgentView, IssueView, RemoteGate, Verdict}; +use crate::sanitizer::sanitize_text; +use crate::util::{one_line, truncate}; +use serde_json::Value; +use std::process::Command; + +pub fn load_watchlist() -> Vec { + if Command::new("multica").arg("--version").output().is_err() { + return WATCHLIST_ISSUES + .iter() + .map(|id| fallback_issue(id, "multica CLI unavailable")) + .collect(); + } + + WATCHLIST_ISSUES.iter().map(|id| load_issue(id)).collect() +} + +pub fn remote_gates(issues: &[IssueView]) -> Vec { + let auth_issue = issue(issues, ISSUE_AUTH_BLOCKER); + let deploy_issue = issue(issues, ISSUE_REMOTE_DEPLOY); + let adapter_issue = issue(issues, ISSUE_ADAPTER_REPAIR); + + let auth_repaired = auth_issue.map(has_auth_repaired).unwrap_or(false); + let guarded_nixos = deploy_issue + .map(|issue| contains_any(issue, &["guarded NixOS test", "nixos-rebuild test"])) + .unwrap_or(false); + let remote_full_smoke = deploy_issue + .map(|issue| { + contains_any( + issue, + &[ + "remote loopback full smoke", + "remote-smoke full", + "--mode full", + ], + ) + }) + .unwrap_or(false); + let supervisor_pass = deploy_issue + .map(|issue| contains_any(issue, &["supervisor PASS", "VERDICT: PASS"])) + .unwrap_or(false); + + let mut missing = Vec::new(); + if !auth_repaired { + missing.push("AUTH_REPAIRED on DAS-2669"); + } + if !guarded_nixos { + missing.push("guarded NixOS test"); + } + if !remote_full_smoke { + missing.push("remote loopback full smoke"); + } + if !supervisor_pass { + missing.push("supervisor PASS"); + } + + let adapter_verdict = adapter_issue + .map(|issue| Verdict::from_packet_word(&issue.status)) + .unwrap_or(Verdict::Flag); + + vec![ + RemoteGate { + id: ISSUE_AUTH_BLOCKER.to_string(), + name: "DeepSeek/OpenRouter auth-route repair".to_string(), + verdict: if auth_repaired { + Verdict::Pass + } else { + Verdict::Block + }, + blocks_completion: true, + hard_gate: true, + evidence: if auth_repaired { + "AUTH_REPAIRED present in live issue/comment evidence.".to_string() + } else { + "AUTH_REPAIRED not present in live issue/comment evidence.".to_string() + }, + gate_effect: if auth_repaired { + "Auth block cleared; DAS-2666 still waits on deploy evidence.".to_string() + } else { + "Remote deploy lane remains blocked until this passes.".to_string() + }, + }, + RemoteGate { + id: ISSUE_REMOTE_DEPLOY.to_string(), + name: "EverCore remote deploy".to_string(), + verdict: if missing.is_empty() { + Verdict::Pass + } else { + Verdict::Block + }, + blocks_completion: true, + hard_gate: true, + evidence: if missing.is_empty() { + "Auth repair, guarded NixOS test, remote loopback full smoke, and supervisor PASS are present.".to_string() + } else { + format!("Missing: {}.", missing.join(", ")) + }, + gate_effect: "Overall Raven status may only be FLAG while this remote gate is red." + .to_string(), + }, + RemoteGate { + id: ISSUE_ADAPTER_REPAIR.to_string(), + name: "Pi/OpenCode adapter repair".to_string(), + verdict: adapter_verdict, + blocks_completion: false, + hard_gate: false, + evidence: "Adapter repair can unlock Pi/OpenCode lanes but cannot green remote deploy." + .to_string(), + gate_effect: "No effect on DAS-2666 remote deploy verdict.".to_string(), + }, + ] +} + +pub fn agent_views(issues: &[IssueView]) -> Vec { + [ + ( + "Workbench control room", + ISSUE_CONTROL_ROOM, + "Track lane truth and owner packet.", + ), + ( + "Local verifier", + ISSUE_LOCAL_VERIFIER, + "Re-run local Raven and public-safety gates.", + ), + ( + "Memory watch", + ISSUE_MEMORY_WATCH, + "Keep memory bridge and evidence state visible.", + ), + ( + "Auth route repair", + ISSUE_AUTH_BLOCKER, + "DeepSeek/OpenRouter auth-route repair; parent deploy proof remains separate.", + ), + ( + "EverCore remote deploy", + ISSUE_REMOTE_DEPLOY, + "Guarded NixOS test and loopback full smoke only after auth repair.", + ), + ( + "Adapter repair", + ISSUE_ADAPTER_REPAIR, + "Repair Pi/OpenCode wrapper lanes without changing remote deploy verdict.", + ), + ] + .into_iter() + .map(|(name, id, scope)| { + let issue = issue(issues, id); + AgentView { + name: name.to_string(), + issue_id: id.to_string(), + status: issue + .map(|issue| issue.status.clone()) + .unwrap_or_else(|| "unavailable".to_string()), + verdict: issue + .map(|issue| Verdict::from_packet_word(&issue.status)) + .unwrap_or(Verdict::Flag), + scope: scope.to_string(), + } + }) + .collect() +} + +fn load_issue(id: &str) -> IssueView { + let output = Command::new("multica") + .arg("issue") + .arg("get") + .arg(id) + .arg("--output") + .arg("json") + .output(); + + let mut issue = match output { + Ok(output) if output.status.success() => { + match serde_json::from_slice::(&output.stdout) { + Ok(value) => issue_from_value(id, &value), + Err(err) => fallback_issue(id, &format!("multica JSON parse failed: {err}")), + } + } + Ok(output) => fallback_issue(id, &format!("multica issue get exited {}", output.status)), + Err(err) => fallback_issue(id, &err.to_string()), + }; + + if issue.available { + match load_comments(id) { + Some(comments) => { + issue.comments_checked = true; + let auth_repair_prefix = + if id == ISSUE_AUTH_BLOCKER && has_auth_repaired_text(&comments) { + "AUTH_REPAIRED VERDICT: PASS " + } else { + "" + }; + issue.evidence_excerpt = sanitize_text(&truncate( + &one_line(&format!( + "{auth_repair_prefix}{} {}", + issue.evidence_excerpt, comments + )), + 900, + )); + } + None => { + issue.comments_checked = false; + } + } + } + + issue +} + +fn load_comments(id: &str) -> Option { + let output = Command::new("multica") + .arg("issue") + .arg("comment") + .arg("list") + .arg(id) + .arg("--output") + .arg("json") + .output() + .ok()?; + + if !output.status.success() { + return None; + } + + let value = serde_json::from_slice::(&output.stdout).ok()?; + let mut parts = Vec::new(); + collect_comment_text(&value, &mut parts); + if parts.is_empty() { + None + } else { + Some(parts.join(" ")) + } +} + +fn collect_comment_text(value: &Value, out: &mut Vec) { + match value { + Value::Array(items) => { + for item in items { + collect_comment_text(item, out); + } + } + Value::Object(map) => { + for key in ["body", "content", "text", "markdown", "message"] { + if let Some(text) = map.get(key).and_then(Value::as_str) { + out.push(text.to_string()); + } + } + for key in ["comments", "items", "data", "nodes"] { + if let Some(child) = map.get(key) { + collect_comment_text(child, out); + } + } + } + _ => {} + } +} + +fn issue_from_value(id: &str, value: &Value) -> IssueView { + let identifier = string_field(value, &["identifier", "id", "key"]).unwrap_or(id); + let title = + string_field(value, &["title", "name", "summary"]).unwrap_or_else(|| fallback_title(id)); + let status = string_field( + value, + &["status", "state", "workflow_state", "workflowStatus"], + ) + .unwrap_or("unknown"); + let priority = string_field(value, &["priority"]).unwrap_or("unknown"); + let updated_at = + string_field(value, &["updated_at", "updatedAt", "updated"]).unwrap_or("unknown"); + let description = string_field(value, &["description", "body", "content"]).unwrap_or(""); + + IssueView { + id: identifier.to_string(), + title: sanitize_text(title), + status: sanitize_text(status), + priority: sanitize_text(priority), + updated_at: sanitize_text(updated_at), + available: true, + source: "live".to_string(), + comments_checked: false, + evidence_excerpt: sanitize_text(&truncate( + &one_line(&format!("{title} {status} {description}")), + 900, + )), + } +} + +fn fallback_issue(id: &str, reason: &str) -> IssueView { + IssueView { + id: id.to_string(), + title: fallback_title(id).to_string(), + status: if id == ISSUE_REMOTE_DEPLOY || id == ISSUE_AUTH_BLOCKER { + "blocked".to_string() + } else { + "unavailable".to_string() + }, + priority: "unknown".to_string(), + updated_at: "unknown".to_string(), + available: false, + source: "fallback".to_string(), + comments_checked: false, + evidence_excerpt: sanitize_text(reason), + } +} + +fn fallback_title(id: &str) -> &'static str { + match id { + ISSUE_REMOTE_DEPLOY => "EverCore remote deploy gate", + ISSUE_AUTH_BLOCKER => "Repair Windburn NixOS Codex runtime auth", + ISSUE_CONTROL_ROOM => "Raven control-room watch", + ISSUE_LOCAL_VERIFIER => "Raven local verifier watch", + ISSUE_MEMORY_WATCH => "Raven memory evidence watch", + ISSUE_ADAPTER_REPAIR => "Pi/OpenCode adapter repair", + _ => "Unknown watch issue", + } +} + +fn string_field<'a>(value: &'a Value, keys: &[&str]) -> Option<&'a str> { + let object = value.as_object()?; + for key in keys { + if let Some(text) = object.get(*key).and_then(Value::as_str) { + return Some(text); + } + if let Some(inner) = object.get(*key).and_then(Value::as_object) { + if let Some(text) = inner.get("name").and_then(Value::as_str) { + return Some(text); + } + if let Some(text) = inner.get("title").and_then(Value::as_str) { + return Some(text); + } + } + } + None +} + +fn issue<'a>(issues: &'a [IssueView], id: &str) -> Option<&'a IssueView> { + issues.iter().find(|issue| issue.id == id) +} + +fn has_auth_repaired(issue: &IssueView) -> bool { + has_auth_repaired_text(&issue.evidence_excerpt) +} + +fn has_auth_repaired_text(text: &str) -> bool { + let evidence = text.to_ascii_uppercase(); + evidence.contains("AUTH_REPAIRED") + && (evidence.contains("VERDICT: PASS") + || evidence.contains("AUTH_REPAIRED: PASS") + || evidence.contains("AUTH_REPAIR_PROOF: PASS")) +} + +fn contains_any(issue: &IssueView, needles: &[&str]) -> bool { + let haystack = issue.evidence_excerpt.to_ascii_lowercase(); + needles + .iter() + .any(|needle| haystack.contains(&needle.to_ascii_lowercase())) +} + +#[cfg(test)] +mod tests { + use super::remote_gates; + use crate::constants::{ISSUE_ADAPTER_REPAIR, ISSUE_AUTH_BLOCKER, ISSUE_REMOTE_DEPLOY}; + use crate::model::{IssueView, Verdict}; + + #[test] + fn missing_auth_repaired_keeps_remote_gates_blocked() { + let gates = remote_gates(&[ + issue( + ISSUE_AUTH_BLOCKER, + "blocked", + "read-only proof still failing", + ), + issue( + ISSUE_REMOTE_DEPLOY, + "blocked", + "guarded NixOS test remote loopback full smoke supervisor PASS", + ), + ]); + + assert_eq!(gate(&gates, ISSUE_AUTH_BLOCKER), Verdict::Block); + assert_eq!(gate(&gates, ISSUE_REMOTE_DEPLOY), Verdict::Block); + } + + #[test] + fn deploy_needs_every_hard_evidence_marker() { + let gates = remote_gates(&[ + issue(ISSUE_AUTH_BLOCKER, "closed", "AUTH_REPAIRED VERDICT: PASS"), + issue(ISSUE_REMOTE_DEPLOY, "blocked", "guarded NixOS test only"), + ]); + + assert_eq!(gate(&gates, ISSUE_REMOTE_DEPLOY), Verdict::Block); + } + + #[test] + fn future_auth_repair_mentions_do_not_pass_auth_gate() { + let gates = remote_gates(&[ + issue( + ISSUE_AUTH_BLOCKER, + "blocked", + "VERDICT: FLAG post AUTH_REPAIRED only after proof succeeds", + ), + issue( + ISSUE_REMOTE_DEPLOY, + "blocked", + "guarded NixOS test remote loopback full smoke supervisor PASS", + ), + ]); + + assert_eq!(gate(&gates, ISSUE_AUTH_BLOCKER), Verdict::Block); + assert_eq!(gate(&gates, ISSUE_REMOTE_DEPLOY), Verdict::Block); + } + + #[test] + fn auth_repair_detector_handles_marker_after_stale_prefix() { + let stale_prefix = "VERDICT: BLOCK old refresh token failure ".repeat(80); + let evidence = format!("{stale_prefix} AUTH_REPAIRED VERDICT: PASS"); + + assert!(super::has_auth_repaired_text(&evidence)); + } + + #[test] + fn adapter_repair_pass_does_not_green_remote_deploy() { + let gates = remote_gates(&[ + issue(ISSUE_AUTH_BLOCKER, "blocked", "runtime auth still broken"), + issue(ISSUE_REMOTE_DEPLOY, "blocked", "waiting on auth"), + issue(ISSUE_ADAPTER_REPAIR, "closed", "adapter PASS"), + ]); + + assert_eq!(gate(&gates, ISSUE_ADAPTER_REPAIR), Verdict::Pass); + assert_eq!(gate(&gates, ISSUE_REMOTE_DEPLOY), Verdict::Block); + } + + fn issue(id: &str, status: &str, evidence: &str) -> IssueView { + IssueView { + id: id.to_string(), + title: id.to_string(), + status: status.to_string(), + priority: "unknown".to_string(), + updated_at: "unknown".to_string(), + available: true, + source: "test".to_string(), + comments_checked: true, + evidence_excerpt: evidence.to_string(), + } + } + + fn gate(gates: &[crate::model::RemoteGate], id: &str) -> Verdict { + gates + .iter() + .find(|gate| gate.id == id) + .map(|gate| gate.verdict) + .unwrap() + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/adapters/packet.rs b/use-cases/hermes-everos-memory/raven-console/src/adapters/packet.rs new file mode 100644 index 00000000..86d86788 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/adapters/packet.rs @@ -0,0 +1,101 @@ +use crate::model::{DocSummary, LocalGateView, RunPacket, Verdict}; +use crate::sanitizer::sanitize_text; +use crate::util::{one_line, truncate}; +use crate::RavenResult; +use std::fs::{self, File}; +use std::io::BufReader; +use std::path::Path; + +pub fn read_packet(path: &Path) -> RavenResult { + let file = File::open(path)?; + let reader = BufReader::new(file); + Ok(serde_json::from_reader(reader)?) +} + +pub fn packet_verdict(packet: &RunPacket) -> Verdict { + if packet + .lanes + .iter() + .any(|lane| lane.verdict.eq_ignore_ascii_case("block")) + || packet + .gates + .iter() + .any(|gate| gate.blocks_completion && gate.status.eq_ignore_ascii_case("block")) + { + return Verdict::Block; + } + + if packet.lanes.iter().any(|lane| { + matches!( + lane.verdict.to_ascii_lowercase().as_str(), + "flag" | "active" + ) + }) || packet.gates.iter().any(|gate| { + gate.blocks_completion + && matches!( + gate.status.to_ascii_lowercase().as_str(), + "flag" | "not_run" + ) + }) { + return Verdict::Flag; + } + + Verdict::Pass +} + +pub fn local_gates(packet: &RunPacket) -> Vec { + packet + .gates + .iter() + .map(|gate| LocalGateView { + id: gate.id.clone(), + name: gate.name.clone(), + verdict: Verdict::from_packet_word(&gate.status), + command: gate.command.clone().unwrap_or_else(|| "manual".to_string()), + evidence: sanitize_text(&one_line(&gate.evidence)), + blocks_completion: gate.blocks_completion, + }) + .collect() +} + +pub fn doc_summaries(root: &Path) -> Vec { + [ + "COMPLETION_AUDIT.md", + "OWNER_PACKET.md", + "SUPERVISOR_DISPATCH.md", + "raven/NATIVE_FEEL_AUDIT.md", + ] + .into_iter() + .map(|path| doc_summary(root, path)) + .collect() +} + +fn doc_summary(root: &Path, relative: &str) -> DocSummary { + let path = root.join(relative); + match fs::read_to_string(&path) { + Ok(text) => { + let title = text + .lines() + .next() + .unwrap_or(relative) + .trim_start_matches("# ") + .to_string(); + let line = text + .lines() + .find(|line| { + line.contains("PASS") || line.contains("FLAG") || line.contains("BLOCK") + }) + .unwrap_or("verdict not found"); + DocSummary { + path: relative.to_string(), + verdict: Verdict::from_packet_word(line), + evidence: sanitize_text(&truncate(&format!("{title}; {}", one_line(line)), 260)), + } + } + Err(err) => DocSummary { + path: relative.to_string(), + verdict: Verdict::Block, + evidence: format!("read failed: {err}"), + }, + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/adapters/sc.rs b/use-cases/hermes-everos-memory/raven-console/src/adapters/sc.rs new file mode 100644 index 00000000..763049b2 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/adapters/sc.rs @@ -0,0 +1,382 @@ +use crate::model::{ + ScProviderView, ScReport, ScSessionView, ScStatusView, ScWorktreeView, Verdict, +}; +use crate::sanitizer::sanitize_text; +use crate::util::{one_line, truncate}; +use serde_json::Value; +use std::env; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::thread; +use std::time::{Duration, Instant}; + +const SC_TIMEOUT: Duration = Duration::from_secs(5); + +struct ScOutput { + exit_code: i32, + stdout: String, + stderr: String, + duration_ms: u128, + timed_out: bool, +} + +pub fn report() -> ScReport { + let status = status(); + let providers = providers(); + let sessions = sessions(); + let worktree = worktree(); + let verdict = if status.verdict == Verdict::Block || worktree.verdict == Verdict::Block { + Verdict::Block + } else if status.verdict == Verdict::Flag || worktree.verdict == Verdict::Flag { + Verdict::Flag + } else { + Verdict::Pass + }; + + ScReport { + verdict, + status, + providers, + sessions, + worktree, + } +} + +pub fn boot_report() -> ScReport { + ScReport { + verdict: Verdict::Flag, + status: ScStatusView { + verdict: Verdict::Flag, + ok: false, + api_version: None, + app_version: "unknown".to_string(), + evidence: "TUI boot snapshot skips sc socket calls; press u for live refresh." + .to_string(), + }, + providers: Vec::new(), + sessions: Vec::new(), + worktree: ScWorktreeView { + verdict: Verdict::Flag, + branch: "unknown".to_string(), + target_branch: "unknown".to_string(), + dirty: None, + evidence: "refresh pending".to_string(), + }, + } +} + +pub fn status() -> ScStatusView { + let output = run_sc(&["status", "--json"]); + if output.exit_code != 0 || output.timed_out { + return ScStatusView { + verdict: if output.timed_out { + Verdict::Block + } else { + Verdict::Flag + }, + ok: false, + api_version: None, + app_version: "unknown".to_string(), + evidence: output_evidence("sc status", &output), + }; + } + + let Ok(value) = serde_json::from_str::(&output.stdout) else { + return ScStatusView { + verdict: Verdict::Flag, + ok: false, + api_version: None, + app_version: "unknown".to_string(), + evidence: "sc status returned non-json output".to_string(), + }; + }; + + let ok = value.get("ok").and_then(Value::as_bool).unwrap_or(false); + let api_version = value.get("api_version").and_then(Value::as_u64); + let app_version = value + .get("app_version") + .and_then(Value::as_str) + .unwrap_or("unknown") + .to_string(); + + ScStatusView { + verdict: if ok { Verdict::Pass } else { Verdict::Flag }, + ok, + api_version, + evidence: sanitize_text(&format!( + "sc socket responded in {}ms; api={}; app={}", + output.duration_ms, + api_version + .map(|version| version.to_string()) + .unwrap_or_else(|| "unknown".to_string()), + app_version + )), + app_version, + } +} + +pub fn providers() -> Vec { + let output = run_sc(&["chat", "providers", "--json"]); + if output.exit_code != 0 || output.timed_out { + return Vec::new(); + } + + let Ok(value) = serde_json::from_str::(&output.stdout) else { + return Vec::new(); + }; + + value + .get("providers") + .and_then(Value::as_array) + .into_iter() + .flatten() + .map(|provider| ScProviderView { + provider_key: string_field(provider, "provider_key"), + display_name: string_field(provider, "display_name"), + enabled: provider + .get("enabled") + .and_then(Value::as_bool) + .unwrap_or(false), + model_count: provider + .get("models") + .and_then(Value::as_array) + .map(|models| models.len()) + .unwrap_or(0), + reasoning_efforts: provider + .get("supported_reasoning_efforts") + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(Value::as_str) + .map(ToString::to_string) + .collect(), + }) + .collect() +} + +pub fn sessions() -> Vec { + let output = run_sc(&["chat", "list", "--json"]); + if output.exit_code != 0 || output.timed_out { + return Vec::new(); + } + + let Ok(value) = serde_json::from_str::(&output.stdout) else { + return Vec::new(); + }; + + value + .get("sessions") + .and_then(Value::as_array) + .into_iter() + .flatten() + .map(|session| ScSessionView { + thread_id: string_field(session, "thread_id"), + provider_key: string_field(session, "provider_key"), + title: string_field(session, "title"), + model: string_field(session, "model"), + reasoning_effort: string_field(session, "reasoning_effort"), + active_turn: session + .get("active_turn") + .and_then(Value::as_bool) + .unwrap_or(false), + closed: session + .get("closed") + .and_then(Value::as_bool) + .unwrap_or(false), + branch: string_field(session, "branch"), + worktree: public_worktree_label(&string_field(session, "worktree_path")), + }) + .collect() +} + +pub fn worktree() -> ScWorktreeView { + let output = run_sc(&["worktree", "status", "--json"]); + if output.exit_code != 0 || output.timed_out { + return ScWorktreeView { + verdict: if output.timed_out { + Verdict::Block + } else { + Verdict::Flag + }, + branch: "unknown".to_string(), + target_branch: "unknown".to_string(), + dirty: None, + evidence: output_evidence("sc worktree status", &output), + }; + } + + let Ok(value) = serde_json::from_str::(&output.stdout) else { + return ScWorktreeView { + verdict: Verdict::Flag, + branch: "unknown".to_string(), + target_branch: "unknown".to_string(), + dirty: None, + evidence: "sc worktree status returned non-json output".to_string(), + }; + }; + + let branch = first_string(&value, &["branch", "current_branch", "head_branch"]); + let target_branch = first_string(&value, &["target_branch", "base_branch"]); + let dirty = value + .get("dirty") + .or_else(|| value.get("has_uncommitted_changes")) + .and_then(Value::as_bool); + + ScWorktreeView { + verdict: Verdict::Pass, + branch: branch.unwrap_or_else(|| "unknown".to_string()), + target_branch: target_branch.unwrap_or_else(|| "unknown".to_string()), + dirty, + evidence: format!("sc worktree status completed in {}ms", output.duration_ms), + } +} + +fn run_sc(args: &[&str]) -> ScOutput { + let start = Instant::now(); + let mut child = match Command::new(sc_binary()) + .args(args) + .current_dir(sc_cwd()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(child) => child, + Err(err) => { + return ScOutput { + exit_code: 127, + stdout: String::new(), + stderr: err.to_string(), + duration_ms: start.elapsed().as_millis(), + timed_out: false, + } + } + }; + + loop { + match child.try_wait() { + Ok(Some(_status)) => break, + Ok(None) if start.elapsed() >= SC_TIMEOUT => { + let _ = child.kill(); + let output = child.wait_with_output().ok(); + return ScOutput { + exit_code: 124, + stdout: output + .as_ref() + .map(|output| String::from_utf8_lossy(&output.stdout).to_string()) + .unwrap_or_default(), + stderr: output + .as_ref() + .map(|output| String::from_utf8_lossy(&output.stderr).to_string()) + .unwrap_or_else(|| "sc command timed out".to_string()), + duration_ms: start.elapsed().as_millis(), + timed_out: true, + }; + } + Ok(None) => thread::sleep(Duration::from_millis(25)), + Err(err) => { + let _ = child.kill(); + return ScOutput { + exit_code: 1, + stdout: String::new(), + stderr: err.to_string(), + duration_ms: start.elapsed().as_millis(), + timed_out: false, + }; + } + } + } + + match child.wait_with_output() { + Ok(output) => ScOutput { + exit_code: output.status.code().unwrap_or(1), + stdout: String::from_utf8_lossy(&output.stdout).to_string(), + stderr: String::from_utf8_lossy(&output.stderr).to_string(), + duration_ms: start.elapsed().as_millis(), + timed_out: false, + }, + Err(err) => ScOutput { + exit_code: 1, + stdout: String::new(), + stderr: err.to_string(), + duration_ms: start.elapsed().as_millis(), + timed_out: false, + }, + } +} + +fn sc_binary() -> PathBuf { + if let Ok(path) = env::var("RAVEN_SC_BIN") { + return PathBuf::from(path); + } + + env::var_os("HOME") + .map(PathBuf::from) + .map(|home| home.join(".superconductor/bin/sc")) + .unwrap_or_else(|| PathBuf::from("sc")) +} + +fn sc_cwd() -> PathBuf { + let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + for candidate in cwd.ancestors() { + if candidate.join(".git").exists() { + return candidate.to_path_buf(); + } + } + cwd +} + +fn output_evidence(label: &str, output: &ScOutput) -> String { + if output.timed_out { + return format!("{label} timed out after {}ms", output.duration_ms); + } + + let text = if output.stderr.trim().is_empty() { + output.stdout.trim() + } else { + output.stderr.trim() + }; + sanitize_text(&format!( + "{label} exited {} in {}ms: {}", + output.exit_code, + output.duration_ms, + truncate(&one_line(text), 240) + )) +} + +fn string_field(value: &Value, key: &str) -> String { + value + .get(key) + .and_then(Value::as_str) + .unwrap_or("unknown") + .to_string() +} + +fn first_string(value: &Value, keys: &[&str]) -> Option { + keys.iter() + .find_map(|key| value.get(*key).and_then(Value::as_str)) + .map(ToString::to_string) +} + +fn public_worktree_label(path: &str) -> String { + let sanitized = sanitize_text(path); + if sanitized == path { + return sanitized; + } + + path.trim_end_matches('/') + .rsplit('/') + .next() + .filter(|value| !value.is_empty()) + .unwrap_or("worktree") + .to_string() +} + +#[cfg(test)] +mod tests { + use super::public_worktree_label; + + #[test] + fn worktree_label_avoids_absolute_paths() { + assert_eq!(public_worktree_label("/Users/alice/EverOS/"), "EverOS"); + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/adapters/verify.rs b/use-cases/hermes-everos-memory/raven-console/src/adapters/verify.rs new file mode 100644 index 00000000..66c29b18 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/adapters/verify.rs @@ -0,0 +1,125 @@ +use crate::constants::RUNS_DIR; +use crate::context::Context; +use crate::model::{RunView, Verdict}; +use crate::sanitizer::{sanitize_json, sanitize_text}; +use crate::util::{one_line, path_for_display, truncate}; +use serde_json::Value; +use std::fs; +use std::process::{Command, Stdio}; +use std::time::Instant; + +pub struct VerifyResult { + pub command: Vec, + pub exit_code: i32, + pub duration_ms: u128, + pub verdict: Verdict, + pub stdout: String, + pub stderr: String, +} + +pub fn run_verify(ctx: &Context) -> VerifyResult { + let command = vec![ + "node".to_string(), + "bin/raven-run.mjs".to_string(), + "verify".to_string(), + "raven/fixtures/doomsday-run.json".to_string(), + ]; + + let started = Instant::now(); + let output = Command::new("node") + .arg("bin/raven-run.mjs") + .arg("verify") + .arg("raven/fixtures/doomsday-run.json") + .current_dir(&ctx.root) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output(); + let duration_ms = started.elapsed().as_millis(); + + match output { + Ok(output) => { + let exit_code = output.status.code().unwrap_or(1); + let verdict = match exit_code { + 0 => Verdict::Pass, + 2 => Verdict::Block, + _ => Verdict::Flag, + }; + VerifyResult { + command, + exit_code, + duration_ms, + verdict, + stdout: sanitize_text(&String::from_utf8_lossy(&output.stdout)), + stderr: sanitize_text(&String::from_utf8_lossy(&output.stderr)), + } + } + Err(err) => VerifyResult { + command, + exit_code: 1, + duration_ms, + verdict: Verdict::Flag, + stdout: String::new(), + stderr: sanitize_text(&format!("failed to spawn verifier: {err}")), + }, + } +} + +pub fn list_runs(ctx: &Context) -> Vec { + let dir = ctx.root.join(RUNS_DIR); + let mut saved = Vec::new(); + + if let Ok(entries) = fs::read_dir(&dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().and_then(|item| item.to_str()) != Some("json") { + continue; + } + if let Ok(text) = fs::read_to_string(&path) { + if let Ok(value) = serde_json::from_str::(&text) { + let safe = sanitize_json(&value).unwrap_or(Value::Null); + saved.push(RunView { + id: safe + .get("id") + .and_then(Value::as_str) + .unwrap_or("saved-receipt") + .to_string(), + command: safe + .get("command") + .map(|value| one_line(&value.to_string())) + .unwrap_or_else(|| "unknown".to_string()), + verdict: safe + .get("verdict") + .and_then(Value::as_str) + .map(Verdict::from_packet_word) + .unwrap_or(Verdict::Flag), + source: "saved-receipt".to_string(), + evidence: safe + .get("evidence_excerpt") + .and_then(Value::as_str) + .map(str::to_string) + .unwrap_or_else(|| "receipt present".to_string()), + receipt_path: Some(path_for_display(&path)), + }); + } + } + } + } + + if !saved.is_empty() { + saved.sort_by(|left, right| left.id.cmp(&right.id)); + return saved; + } + + ctx.packet + .gates + .iter() + .map(|gate| RunView { + id: gate.id.clone(), + command: gate.command.clone().unwrap_or_else(|| "manual".to_string()), + verdict: Verdict::from_packet_word(&gate.status), + source: "configured-gate".to_string(), + evidence: sanitize_text(&truncate(&one_line(&gate.evidence), 260)), + receipt_path: None, + }) + .collect() +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/audit.rs b/use-cases/hermes-everos-memory/raven-console/src/audit.rs new file mode 100644 index 00000000..16770eb1 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/audit.rs @@ -0,0 +1,164 @@ +use crate::context::Context; +use crate::model::{NativeAuditItem, NativeAuditReport, Verdict}; +use std::fs; + +pub fn run(ctx: &Context) -> NativeAuditReport { + let doc_exists = ctx.root.join("raven/NATIVE_FEEL_AUDIT.md").exists(); + let cargo = fs::read_to_string(ctx.root.join("raven-console/Cargo.toml")).unwrap_or_default(); + let source = fs::read_to_string(ctx.root.join("raven-console/src/tui.rs")).unwrap_or_default(); + let repl = fs::read_to_string(ctx.root.join("raven-console/src/repl.rs")).unwrap_or_default(); + let sanitizer = + fs::read_to_string(ctx.root.join("raven-console/src/sanitizer.rs")).unwrap_or_default(); + let gitignore = fs::read_to_string(ctx.root.join(".gitignore")).unwrap_or_default(); + + let items = vec![ + item( + "latency", + Verdict::Pass, + "TUI boots from a local snapshot and refreshes live Multica/memory data asynchronously.", + false, + ), + item( + "keybindings", + if source.contains("KeyCode::Char('q')") + && source.contains("KeyCode::Char('?')") + && source.contains("KeyCode::Char('h')") + && source.contains("KeyCode::Char('i')") + && source.contains("KeyCode::Char('o')") + { + Verdict::Pass + } else { + Verdict::Block + }, + "TUI exposes h/c chat, i prompt input, q, ?, :, /, s, p, m, a, g, r, o, d, n, Esc, and Ctrl-C paths.", + true, + ), + item( + "focus", + if source.contains("Panel::") { + Verdict::Pass + } else { + Verdict::Block + }, + "Active panel is explicit state, not screen-position inference.", + true, + ), + item( + "scrollback", + Verdict::Pass, + "Evidence drawer stays fixed; historical run receipts live in raven/.local-runs/.", + false, + ), + item( + "interrupt behavior", + if source.contains("KeyCode::Esc") && source.contains("KeyCode::Char('c')") { + Verdict::Pass + } else { + Verdict::Block + }, + "Esc cancels prompt modes; Ctrl-C exits safely.", + true, + ), + item( + "REPL history", + if cargo.contains("rustyline") && repl.contains("add_history_entry") { + Verdict::Pass + } else { + Verdict::Flag + }, + "rustyline backs interactive REPL history; piped smoke remains deterministic.", + false, + ), + item( + "pane stability", + if cargo.contains("ratatui") && source.contains("Layout::default") { + Verdict::Pass + } else { + Verdict::Block + }, + "ratatui renders fixed status, rail, panel, evidence, and input regions.", + true, + ), + item( + "command grammar", + Verdict::Pass, + "clap command tree mirrors Raven v1 public interface, including chat send and REPL slash commands.", + false, + ), + item( + "typed IPC", + Verdict::Pass, + "RavenSnapshot, RavenReceipt, HermesChatTurn, and ScReport are serde-typed JSON contracts.", + false, + ), + item( + "evidence visibility", + Verdict::Pass, + "remote hard gates, local gates, runs, docs, and watchlist evidence are visible.", + false, + ), + item( + "public-safety redaction", + if sanitizer.contains("redacted-token") && sanitizer.contains("redacted-signed-url") { + Verdict::Pass + } else { + Verdict::Block + }, + "JSON and human output run through sanitizer for token/path/IP/signed URL shapes.", + true, + ), + item( + "receipt hygiene", + if gitignore.contains("raven/.local-runs/") { + Verdict::Pass + } else { + Verdict::Block + }, + "Saved run receipts land under gitignored raven/.local-runs/.", + true, + ), + item( + "audit doc", + if doc_exists { + Verdict::Pass + } else { + Verdict::Block + }, + "raven/NATIVE_FEEL_AUDIT.md is the repo-local UX/safety contract.", + true, + ), + ]; + + let verdict = if items + .iter() + .any(|item| item.hard_failure && item.verdict == Verdict::Block) + { + Verdict::Block + } else if items.iter().any(|item| item.verdict == Verdict::Flag) { + Verdict::Flag + } else { + Verdict::Pass + }; + + NativeAuditReport { + verdict, + items, + blocks_pass_on: vec![ + "missing hard keybindings".to_string(), + "unstable pane layout".to_string(), + "unsafe interrupt behavior".to_string(), + "missing typed JSON contracts".to_string(), + "unredacted public output".to_string(), + "non-gitignored saved receipts".to_string(), + ], + } +} + +fn item(category: &str, verdict: Verdict, evidence: &str, hard_failure: bool) -> NativeAuditItem { + NativeAuditItem { + category: category.to_string(), + verdict, + evidence: evidence.to_string(), + hard_failure, + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/commands.rs b/use-cases/hermes-everos-memory/raven-console/src/commands.rs new file mode 100644 index 00000000..ff11c4dc --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/commands.rs @@ -0,0 +1,640 @@ +use crate::adapters::{hermes, memory, sc, verify}; +use crate::audit; +use crate::context::Context; +use crate::model::{DoctorCheck, DoctorReport, Verdict}; +use crate::{output, receipt, repl, research, snapshot, tui, RavenResult}; +use clap::{Parser, Subcommand}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +#[derive(Parser)] +#[command(name = "raven")] +#[command(about = "Raven v1 local-first EverOS operating console")] +#[command(version)] +pub struct Cli { + #[arg(long, global = true)] + pub json: bool, + #[command(subcommand)] + pub command: Option, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Print local packet, remote gate, memory, and watchlist status. + Status, + /// Start the terminal console view. + Tui, + /// Start the slash-command REPL. + Repl, + /// Send a bounded prompt through Hermes. + Chat { + #[command(subcommand)] + command: ChatCommand, + }, + /// Show or export owner packet material. + Packet { + #[command(subcommand)] + command: PacketCommand, + }, + /// Query the EverOS memory bridge. + Memory { + #[command(subcommand)] + command: MemoryCommand, + }, + /// Show agent lane status. + Agents { + #[command(subcommand)] + command: Option, + }, + /// Show hard gates and stop conditions. + Gates, + /// Inspect bounded Raven v2 research lanes and packets. + Research { + #[command(subcommand)] + command: ResearchCommand, + }, + /// Show saved receipts or configured verification runs. + Runs { + #[command(subcommand)] + command: RunsCommand, + }, + /// Inspect Superconductor session/worktree state. + Sc { + #[command(subcommand)] + command: Option, + }, + /// Execute local Raven run commands. + Run { + #[command(subcommand)] + command: RunCommand, + }, + /// Check local dependencies and bridge availability. + Doctor, + /// Audit native terminal UX and public-safety discipline. + NativeAudit, +} + +#[derive(Subcommand)] +pub enum PacketCommand { + /// Show the current owner-readable packet summary. + Show, + /// Export a sanitized owner packet. + Export { + #[arg(long)] + output: Option, + }, +} + +#[derive(Subcommand)] +pub enum MemoryCommand { + /// Check EverOS memory-provider health. + Health, + /// Search through the EverOS provider bridge. + Search { query: Vec }, +} + +#[derive(Subcommand)] +pub enum ChatCommand { + /// Send one prompt through Hermes and print the sanitized turn. + Send { + /// Override the Hermes process working directory. + #[arg(long)] + cwd: Option, + /// Write a sanitized chat receipt to a path, or print it with "-". + #[arg(long)] + receipt: Option, + /// Save a sanitized chat receipt under raven/.local-runs/. + #[arg(long)] + save: bool, + prompt: Vec, + }, +} + +#[derive(Subcommand)] +pub enum AgentsCommand { + /// List agent/watch lanes. + List, +} + +#[derive(Subcommand)] +pub enum RunsCommand { + /// List saved receipts or configured verification commands. + List, +} + +#[derive(Subcommand)] +pub enum ScCommand { + /// Show the full Superconductor report. + All, + /// Check the Superconductor socket and API version. + Status, + /// List active Superconductor chat sessions. + Sessions, + /// List enabled Superconductor providers. + Providers, + /// Show current Superconductor worktree status. + Worktree, +} + +#[derive(Subcommand)] +pub enum ResearchCommand { + /// List bounded v2 research lanes. + Lanes, + /// Render one lane as a live-gate-calibrated decision packet. + Packet { + lane: String, + #[arg(long)] + output: Option, + }, + /// Check whether architecture synthesis has enough packet evidence. + Synthesize { + #[arg(long)] + output: Option, + }, +} + +#[derive(Subcommand)] +pub enum RunCommand { + /// Verify the local Raven packet gates. + Verify { + #[arg(long)] + receipt: Option, + #[arg(long)] + save: bool, + }, +} + +pub fn execute(cli: Cli, ctx: &Context) -> RavenResult<()> { + match cli.command.unwrap_or(Commands::Status) { + Commands::Status => { + let snapshot = snapshot::build(ctx); + if cli.json { + output::json(&snapshot) + } else { + output::status(&snapshot); + Ok(()) + } + } + Commands::Tui => tui::run(ctx), + Commands::Repl => repl::run(ctx), + Commands::Chat { command } => match command { + ChatCommand::Send { + cwd, + receipt: receipt_target, + save, + prompt, + } => run_chat_command(ctx, cli.json, cwd, receipt_target, save, &prompt.join(" ")), + }, + Commands::Packet { command } => match command { + PacketCommand::Show => { + let snapshot = snapshot::build(ctx); + if cli.json { + output::json(&snapshot.packet) + } else { + output::packet(&snapshot); + Ok(()) + } + } + PacketCommand::Export { output: target } => { + let snapshot = snapshot::build(ctx); + if cli.json { + output::json(&snapshot) + } else { + output::write_text( + target.as_deref(), + &output::packet_export_markdown(&snapshot), + ) + } + } + }, + Commands::Memory { command } => match command { + MemoryCommand::Health => { + let snapshot = snapshot::build(ctx); + if cli.json { + output::json(&snapshot.memory) + } else { + output::memory_health(&snapshot); + Ok(()) + } + } + MemoryCommand::Search { query } => { + let result = memory::search(ctx, &query.join(" ")); + if cli.json { + output::json(&result) + } else { + output::memory_search(&result); + Ok(()) + } + } + }, + Commands::Agents { command: _ } => { + let snapshot = snapshot::build(ctx); + if cli.json { + output::json(&snapshot.agents) + } else { + output::agents(&snapshot); + Ok(()) + } + } + Commands::Gates => { + let snapshot = snapshot::build(ctx); + if cli.json { + output::json(&serde_json::json!({ + "remote": snapshot.remote_gates, + "local": snapshot.local_gates, + })) + } else { + output::gates(&snapshot); + Ok(()) + } + } + Commands::Research { command } => match command { + ResearchCommand::Lanes => { + let lanes = research::list_lanes(); + if cli.json { + output::json(&lanes) + } else { + output::research_lanes(&lanes); + Ok(()) + } + } + ResearchCommand::Packet { + lane, + output: target, + } => { + let snapshot = snapshot::build(ctx); + let packet = research::packet_for_lane(&lane, &snapshot.remote_gates) + .ok_or_else(|| format!("unknown research lane `{lane}`"))?; + if cli.json { + output::json(&packet) + } else if target.is_some() { + output::write_text(target.as_deref(), &research::packet_markdown(&packet)) + } else { + output::research_packet(&packet); + Ok(()) + } + } + ResearchCommand::Synthesize { output: target } => { + let synthesis = research::synthesis_readiness(&[]); + if cli.json { + output::json(&synthesis) + } else if target.is_some() { + output::write_text(target.as_deref(), &research::synthesis_markdown(&synthesis)) + } else { + output::research_synthesis(&synthesis); + Ok(()) + } + } + }, + Commands::Runs { command: _ } => { + let snapshot = snapshot::build(ctx); + if cli.json { + output::json(&snapshot.runs) + } else { + output::runs(&snapshot); + Ok(()) + } + } + Commands::Sc { command } => match command.unwrap_or(ScCommand::All) { + ScCommand::All => { + let report = sc::report(); + if cli.json { + output::json(&report) + } else { + output::sc_report(&report); + Ok(()) + } + } + ScCommand::Status => { + let status = sc::status(); + if cli.json { + output::json(&status) + } else { + output::sc_status(&status); + Ok(()) + } + } + ScCommand::Sessions => { + let sessions = sc::sessions(); + if cli.json { + output::json(&sessions) + } else { + output::sc_sessions(&sessions); + Ok(()) + } + } + ScCommand::Providers => { + let providers = sc::providers(); + if cli.json { + output::json(&providers) + } else { + output::sc_providers(&providers); + Ok(()) + } + } + ScCommand::Worktree => { + let worktree = sc::worktree(); + if cli.json { + output::json(&worktree) + } else { + output::sc_worktree(&worktree); + Ok(()) + } + } + }, + Commands::Run { command } => match command { + RunCommand::Verify { + receipt: receipt_target, + save, + } => run_verify_command(ctx, cli.json, receipt_target, save), + }, + Commands::Doctor => { + let report = doctor(ctx); + if cli.json { + output::json(&report) + } else { + output::doctor(&report); + Ok(()) + } + } + Commands::NativeAudit => { + let report = audit::run(ctx); + if cli.json { + output::json(&report) + } else { + output::native_audit(&report); + Ok(()) + } + } + } +} + +fn run_chat_command( + ctx: &Context, + json: bool, + cwd: Option, + receipt_target: Option, + save: bool, + prompt: &str, +) -> RavenResult<()> { + let turn = hermes::ask_with_options(ctx, prompt, hermes::HermesOptions { cwd })?; + let chat_receipt = receipt::from_chat(&turn); + + if receipt_target.as_deref() == Some("-") { + output::json(&chat_receipt)?; + } else if json { + output::json(&turn)?; + } else { + output::chat_turn(&turn); + } + + if let Some(path) = receipt_target.as_deref().filter(|path| *path != "-") { + let written = receipt::save_receipt(ctx, &chat_receipt, Some(path))?; + output::line(&format!("RECEIPT: {}", written.display())); + } + + if save { + let written = receipt::save_receipt(ctx, &chat_receipt, None)?; + output::line(&format!("SAVED: {}", written.display())); + } + + Ok(()) +} + +pub fn dispatch_repl(ctx: &Context, input: &str) -> RavenResult { + match input { + "/help" => { + println!("RAVEN_REPL_COMMANDS"); + println!("/help"); + println!("/status"); + println!("/packet"); + println!("/chat "); + println!("/memory "); + println!("/agents"); + println!("/gates"); + println!("/research [lane]"); + println!("/runs"); + println!("/sc [status|sessions|providers|worktree]"); + println!("/doctor"); + println!("/audit"); + println!("/quit"); + } + "/status" => output::status(&snapshot::build(ctx)), + "/packet" => output::packet(&snapshot::build(ctx)), + "/agents" => output::agents(&snapshot::build(ctx)), + "/gates" => output::gates(&snapshot::build(ctx)), + "/research" => output::research_lanes(&research::list_lanes()), + "/runs" => output::runs(&snapshot::build(ctx)), + "/sc" => output::sc_report(&sc::report()), + "/sc status" => output::sc_status(&sc::status()), + "/sc sessions" => output::sc_sessions(&sc::sessions()), + "/sc providers" => output::sc_providers(&sc::providers()), + "/sc worktree" => output::sc_worktree(&sc::worktree()), + "/doctor" => output::doctor(&doctor(ctx)), + "/audit" => output::native_audit(&audit::run(ctx)), + "/quit" | "/exit" => return Ok(false), + _ if input.starts_with("/chat ") || input.starts_with("/hermes ") => { + let prompt = input + .trim_start_matches("/chat ") + .trim_start_matches("/hermes ") + .trim(); + output::chat_turn(&hermes::ask(ctx, prompt)?); + } + _ if input.starts_with("/memory ") => { + let result = memory::search(ctx, input.trim_start_matches("/memory ").trim()); + output::memory_search(&result); + } + _ if input.starts_with("/research ") => { + let lane = input.trim_start_matches("/research ").trim(); + let snapshot = snapshot::build(ctx); + if let Some(packet) = research::packet_for_lane(lane, &snapshot.remote_gates) { + output::research_packet(&packet); + } else { + output::line("VERDICT: FLAG"); + output::line(&format!("EVIDENCE: unknown research lane `{lane}`")); + output::line("NEXT: /research"); + } + } + _ if input.starts_with('/') => { + output::line("VERDICT: FLAG"); + output::line(&format!("EVIDENCE: unknown command `{input}`")); + output::line("NEXT: /help"); + } + _ => output::chat_turn(&hermes::ask(ctx, input)?), + } + Ok(true) +} + +fn run_verify_command( + ctx: &Context, + json: bool, + receipt_target: Option, + save: bool, +) -> RavenResult<()> { + let result = verify::run_verify(ctx); + let receipt = receipt::from_verify(&result); + + if json || receipt_target.as_deref() == Some("-") { + output::json(&receipt)?; + } else { + output::verify_human(&receipt); + } + + if let Some(path) = receipt_target.as_deref().filter(|path| *path != "-") { + let written = receipt::save_receipt(ctx, &receipt, Some(path))?; + output::line(&format!("RECEIPT_WRITTEN: {}", written.display())); + } + if save { + let written = receipt::save_receipt(ctx, &receipt, None)?; + output::line(&format!("RECEIPT_SAVED: {}", written.display())); + } + + if result.exit_code == 0 { + Ok(()) + } else { + Err(format!("local verifier exited {}", result.exit_code).into()) + } +} + +fn doctor(ctx: &Context) -> DoctorReport { + let mut checks = Vec::new(); + for (program, args) in [ + ("rustc", vec!["--version"]), + ("cargo", vec!["--version"]), + ("just", vec!["--version"]), + ("bun", vec!["--version"]), + ("node", vec!["--version"]), + ("python3", vec!["--version"]), + ("multica", vec!["--version"]), + ] { + checks.push(command_check(program, &args)); + } + + for required in crate::constants::REQUIRED_DOCS { + let path = ctx.root.join(required); + checks.push(DoctorCheck { + name: format!("file {required}"), + verdict: if path.exists() { + Verdict::Pass + } else { + Verdict::Block + }, + evidence: if path.exists() { + "present".to_string() + } else { + "missing".to_string() + }, + }); + } + + checks.push(deepseek_auth_check(ctx)); + + let gitignore = ctx.root.join(".gitignore"); + let ignored = std::fs::read_to_string(&gitignore) + .map(|text| text.contains("raven/.local-runs/")) + .unwrap_or(false); + checks.push(DoctorCheck { + name: "gitignore raven/.local-runs".to_string(), + verdict: if ignored { + Verdict::Pass + } else { + Verdict::Block + }, + evidence: if ignored { + "saved receipts are gitignored".to_string() + } else { + "saved receipts are not gitignored".to_string() + }, + }); + + let memory = memory::health(ctx); + checks.push(DoctorCheck { + name: "memory bridge health".to_string(), + verdict: memory.verdict, + evidence: memory.evidence, + }); + + let verdict = if checks.iter().any(|check| check.verdict == Verdict::Block) { + Verdict::Block + } else if checks.iter().any(|check| check.verdict == Verdict::Flag) { + Verdict::Flag + } else { + Verdict::Pass + }; + + DoctorReport { + verdict, + checks, + next: "run raven run verify, raven gates, and raven native-audit before closeout." + .to_string(), + } +} + +fn deepseek_auth_check(ctx: &Context) -> DoctorCheck { + let script = ctx.root.join("scripts/deepseek-auth-preflight.sh"); + let env_file = ctx.root.join("deploy/nixos/evercore.env.example"); + match Command::new(&script).arg("--env").arg(&env_file).output() { + Ok(output) if output.status.success() => { + let text = if output.stdout.is_empty() { + String::from_utf8_lossy(&output.stderr) + } else { + String::from_utf8_lossy(&output.stdout) + }; + DoctorCheck { + name: "deepseek auth preflight".to_string(), + verdict: Verdict::Pass, + evidence: crate::sanitizer::sanitize_text(&crate::util::one_line(&text)), + } + } + Ok(output) => DoctorCheck { + name: "deepseek auth preflight".to_string(), + verdict: Verdict::Block, + evidence: format!("exited {}", output.status), + }, + Err(err) => DoctorCheck { + name: "deepseek auth preflight".to_string(), + verdict: Verdict::Block, + evidence: err.to_string(), + }, + } +} + +fn command_check(program: &str, args: &[&str]) -> DoctorCheck { + match Command::new(program).args(args).output() { + Ok(output) if output.status.success() => { + let text = if output.stdout.is_empty() { + String::from_utf8_lossy(&output.stderr) + } else { + String::from_utf8_lossy(&output.stdout) + }; + DoctorCheck { + name: program.to_string(), + verdict: Verdict::Pass, + evidence: crate::sanitizer::sanitize_text(&crate::util::one_line(&text)), + } + } + Ok(output) => DoctorCheck { + name: program.to_string(), + verdict: if program == "multica" { + Verdict::Flag + } else { + Verdict::Block + }, + evidence: format!("exited {}", output.status), + }, + Err(err) => DoctorCheck { + name: program.to_string(), + verdict: if program == "multica" { + Verdict::Flag + } else { + Verdict::Block + }, + evidence: err.to_string(), + }, + } +} + +#[allow(dead_code)] +fn relative_path_exists(root: &Path, relative: &str) -> bool { + root.join(relative).exists() +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/constants.rs b/use-cases/hermes-everos-memory/raven-console/src/constants.rs new file mode 100644 index 00000000..0055df14 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/constants.rs @@ -0,0 +1,28 @@ +pub const FIXTURE_PATH: &str = "raven/fixtures/doomsday-run.json"; +pub const RUNS_DIR: &str = "raven/.local-runs"; + +pub const ISSUE_REMOTE_DEPLOY: &str = "DAS-2666"; +pub const ISSUE_AUTH_BLOCKER: &str = "DAS-2669"; +pub const ISSUE_CONTROL_ROOM: &str = "DAS-2670"; +pub const ISSUE_LOCAL_VERIFIER: &str = "DAS-2671"; +pub const ISSUE_MEMORY_WATCH: &str = "DAS-2672"; +pub const ISSUE_ADAPTER_REPAIR: &str = "DAS-2675"; + +pub const WATCHLIST_ISSUES: &[&str] = &[ + ISSUE_REMOTE_DEPLOY, + ISSUE_AUTH_BLOCKER, + ISSUE_CONTROL_ROOM, + ISSUE_LOCAL_VERIFIER, + ISSUE_MEMORY_WATCH, + ISSUE_ADAPTER_REPAIR, +]; + +pub const REQUIRED_DOCS: &[&str] = &[ + "COMPLETION_AUDIT.md", + "OWNER_PACKET.md", + "SUPERVISOR_DISPATCH.md", + FIXTURE_PATH, + "raven/NATIVE_FEEL_AUDIT.md", + "bin/raven-run.mjs", + "bin/everos-memory.mjs", +]; diff --git a/use-cases/hermes-everos-memory/raven-console/src/context.rs b/use-cases/hermes-everos-memory/raven-console/src/context.rs new file mode 100644 index 00000000..ae8d649f --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/context.rs @@ -0,0 +1,38 @@ +use crate::adapters::packet::read_packet; +use crate::constants::FIXTURE_PATH; +use crate::model::RunPacket; +use crate::RavenResult; +use std::env; +use std::path::PathBuf; + +#[derive(Clone)] +pub struct Context { + pub root: PathBuf, + pub packet: RunPacket, +} + +impl Context { + pub fn load() -> RavenResult { + let root = find_case_root()?; + let packet = read_packet(&root.join(FIXTURE_PATH))?; + Ok(Self { root, packet }) + } +} + +fn find_case_root() -> RavenResult { + let cwd = env::current_dir()?; + for candidate in cwd.ancestors() { + let direct = candidate.join("COMPLETION_AUDIT.md"); + let fixture = candidate.join(FIXTURE_PATH); + if direct.exists() && fixture.exists() { + return Ok(candidate.to_path_buf()); + } + + let nested = candidate.join("use-cases/hermes-everos-memory"); + if nested.join("COMPLETION_AUDIT.md").exists() && nested.join(FIXTURE_PATH).exists() { + return Ok(nested); + } + } + + Err("could not find use-cases/hermes-everos-memory root".into()) +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/main.rs b/use-cases/hermes-everos-memory/raven-console/src/main.rs new file mode 100644 index 00000000..c957b775 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/main.rs @@ -0,0 +1,40 @@ +mod adapters; +mod audit; +mod commands; +mod constants; +mod context; +mod model; +mod output; +mod receipt; +mod repl; +mod research; +mod sanitizer; +mod snapshot; +mod tui; +mod util; + +use clap::Parser; +use commands::{execute, Cli}; +use context::Context; +use std::process::ExitCode; + +pub type RavenResult = Result>; + +fn main() -> ExitCode { + let cli = Cli::parse(); + let ctx = match Context::load() { + Ok(ctx) => ctx, + Err(err) => { + eprintln!("RAVEN_ERROR: {err}"); + return ExitCode::from(1); + } + }; + + match execute(cli, &ctx) { + Ok(()) => ExitCode::SUCCESS, + Err(err) => { + eprintln!("RAVEN_ERROR: {err}"); + ExitCode::from(1) + } + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/model.rs b/use-cases/hermes-everos-memory/raven-console/src/model.rs new file mode 100644 index 00000000..68399a44 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/model.rs @@ -0,0 +1,354 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::fmt; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum Verdict { + Pass, + Flag, + Block, +} + +impl Verdict { + pub fn from_packet_word(value: &str) -> Self { + let lower = value.to_ascii_lowercase(); + if lower.contains("block") || lower.contains("failed") { + Self::Block + } else if lower.contains("pass") + || lower.contains("done") + || lower.contains("closed") + || lower.contains("complete") + { + Self::Pass + } else { + Self::Flag + } + } + + pub fn as_str(self) -> &'static str { + match self { + Self::Pass => "PASS", + Self::Flag => "FLAG", + Self::Block => "BLOCK", + } + } +} + +impl fmt::Display for Verdict { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct RunPacket { + pub id: String, + pub title: String, + pub goal: String, + pub status: String, + pub owners: Vec, + pub memory_providers: Vec, + pub lanes: Vec, + pub gates: Vec, + pub artifacts: Vec, + pub evidence_refs: Vec, + pub next_actions: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Lane { + pub id: String, + pub owner: String, + pub scope: String, + pub mutation_policy: String, + pub verdict: String, + #[serde(default)] + pub evidence_refs: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Gate { + pub id: String, + pub name: String, + pub status: String, + pub command: Option, + pub evidence: String, + pub blocks_completion: bool, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Artifact { + pub path: String, + pub purpose: String, + pub public_safe: bool, +} + +#[derive(Clone, Debug, Serialize)] +pub struct PacketSummary { + pub id: String, + pub title: String, + pub status: String, + pub verdict: Verdict, + pub owners: Vec, + pub memory_providers: Vec, + pub docs: Vec, +} + +#[derive(Clone, Debug, Serialize)] +pub struct DocSummary { + pub path: String, + pub verdict: Verdict, + pub evidence: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct LocalGateView { + pub id: String, + pub name: String, + pub verdict: Verdict, + pub command: String, + pub evidence: String, + pub blocks_completion: bool, +} + +#[derive(Clone, Debug, Serialize)] +pub struct IssueView { + pub id: String, + pub title: String, + pub status: String, + pub priority: String, + pub updated_at: String, + pub available: bool, + pub source: String, + pub comments_checked: bool, + pub evidence_excerpt: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct RemoteGate { + pub id: String, + pub name: String, + pub verdict: Verdict, + pub blocks_completion: bool, + pub hard_gate: bool, + pub evidence: String, + pub gate_effect: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct AgentView { + pub name: String, + pub issue_id: String, + pub status: String, + pub verdict: Verdict, + pub scope: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct MemoryHealth { + pub verdict: Verdict, + pub status: String, + pub evidence: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct MemorySearchResult { + pub query: String, + pub verdict: Verdict, + pub evidence: String, + pub result: Option, +} + +#[derive(Clone, Debug, Serialize)] +pub struct HermesChatTranscriptLine { + pub role: String, + pub content: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct HermesChatTurn { + pub prompt: String, + pub command: Vec, + pub workspace: String, + pub runtime: String, + pub verdict: Verdict, + pub exit_code: i32, + pub duration_ms: u128, + pub response: String, + pub evidence: String, + pub transcript: Vec, +} + +#[derive(Clone, Debug, Serialize)] +pub struct RunView { + pub id: String, + pub command: String, + pub verdict: Verdict, + pub source: String, + pub evidence: String, + pub receipt_path: Option, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ScStatusView { + pub verdict: Verdict, + pub ok: bool, + pub api_version: Option, + pub app_version: String, + pub evidence: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ScProviderView { + pub provider_key: String, + pub display_name: String, + pub enabled: bool, + pub model_count: usize, + pub reasoning_efforts: Vec, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ScSessionView { + pub thread_id: String, + pub provider_key: String, + pub title: String, + pub model: String, + pub reasoning_effort: String, + pub active_turn: bool, + pub closed: bool, + pub branch: String, + pub worktree: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ScWorktreeView { + pub verdict: Verdict, + pub branch: String, + pub target_branch: String, + pub dirty: Option, + pub evidence: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ScReport { + pub verdict: Verdict, + pub status: ScStatusView, + pub providers: Vec, + pub sessions: Vec, + pub worktree: ScWorktreeView, +} + +#[derive(Clone, Debug, Serialize)] +pub struct PublicSafetyResult { + pub verdict: Verdict, + pub evidence: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct RavenSnapshot { + pub verdict: Verdict, + pub packet: PacketSummary, + pub watchlist_issues: Vec, + pub local_gates: Vec, + pub remote_gates: Vec, + pub agents: Vec, + pub memory: MemoryHealth, + pub runs: Vec, + pub sc: ScReport, + pub risks: Vec, + pub next_actions: Vec, + pub public_safety: PublicSafetyResult, +} + +#[derive(Clone, Debug, Serialize)] +pub struct GateEffect { + pub gate_id: String, + pub before: String, + pub after: String, + pub note: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct RavenReceipt { + pub id: String, + pub command: Vec, + pub exit_code: i32, + pub duration_ms: u128, + pub verdict: Verdict, + pub evidence_excerpt: String, + pub gate_effects: Vec, + pub public_safety: PublicSafetyResult, +} + +#[derive(Clone, Debug, Serialize)] +pub struct DoctorCheck { + pub name: String, + pub verdict: Verdict, + pub evidence: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct DoctorReport { + pub verdict: Verdict, + pub checks: Vec, + pub next: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct NativeAuditItem { + pub category: String, + pub verdict: Verdict, + pub evidence: String, + pub hard_failure: bool, +} + +#[derive(Clone, Debug, Serialize)] +pub struct NativeAuditReport { + pub verdict: Verdict, + pub items: Vec, + pub blocks_pass_on: Vec, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ResearchLane { + pub id: String, + pub title: String, + pub question: String, + pub targets: Vec, + pub output: Vec, + pub source_refs: Vec, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ResearchGateFact { + pub id: String, + pub verdict: Verdict, + pub evidence: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ResearchPacket { + pub lane_id: String, + pub lane_title: String, + pub question: String, + pub sources: Vec, + pub findings: Vec, + pub decisions: Vec, + pub v1_impact: Vec, + pub risks: Vec, + pub next: Vec, + pub live_gates: Vec, + pub verdict: Verdict, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ResearchSynthesis { + pub verdict: Verdict, + pub packets_ready: usize, + pub required_packets: usize, + pub evidence: String, + pub decisions: Vec, + pub risks: Vec, + pub next: Vec, +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/output.rs b/use-cases/hermes-everos-memory/raven-console/src/output.rs new file mode 100644 index 00000000..bcfd6f07 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/output.rs @@ -0,0 +1,442 @@ +use crate::model::{ + DoctorReport, HermesChatTurn, MemorySearchResult, NativeAuditReport, RavenReceipt, + RavenSnapshot, ResearchLane, ResearchPacket, ResearchSynthesis, ScProviderView, ScReport, + ScSessionView, ScStatusView, ScWorktreeView, Verdict, +}; +use crate::sanitizer::{sanitize_json, sanitize_text}; +use crate::util::one_line; +use crate::RavenResult; +use std::io::{self, Write}; + +pub fn json(value: &T) -> RavenResult<()> { + let safe = sanitize_json(value)?; + println!("{}", serde_json::to_string_pretty(&safe)?); + Ok(()) +} + +pub fn status(snapshot: &RavenSnapshot) { + line("RAVEN_STATUS"); + line(&format!("VERDICT: {}", snapshot.verdict)); + line(&format!( + "LOCAL_PACKET: {} ({})", + snapshot.packet.verdict, snapshot.packet.title + )); + let remote = hard_remote_verdict(snapshot); + line(&format!( + "REMOTE_EVERCORE: {remote} (DAS-2666 and DAS-2669 are hard gates)" + )); + line(&format!( + "MEMORY: {} ({})", + snapshot.memory.verdict, snapshot.memory.status + )); + line(""); + line("WATCHLIST:"); + for issue in &snapshot.watchlist_issues { + line(&format!( + "- {}: {} [{}] source={} comments={}", + issue.id, + issue.title, + issue.status, + issue.source, + if issue.comments_checked { + "checked" + } else { + "not_checked" + } + )); + } + line(""); + line("NEXT:"); + for action in &snapshot.next_actions { + line(&format!("- {action}")); + } +} + +pub fn packet(snapshot: &RavenSnapshot) { + line("RAVEN_PACKET"); + line(&format!("VERDICT: {}", snapshot.packet.verdict)); + line(&format!("ID: {}", snapshot.packet.id)); + line(&format!("TITLE: {}", snapshot.packet.title)); + line(&format!("STATUS: {}", snapshot.packet.status)); + line(&format!("OWNERS: {}", snapshot.packet.owners.join(", "))); + line(&format!( + "MEMORY_PROVIDERS: {}", + snapshot.packet.memory_providers.join(", ") + )); + line(""); + line("SOURCE_SUMMARIES:"); + for doc in &snapshot.packet.docs { + line(&format!("- {}: {} {}", doc.path, doc.verdict, doc.evidence)); + } +} + +pub fn packet_export_markdown(snapshot: &RavenSnapshot) -> String { + let mut output = Vec::new(); + output.push(format!("# {}", snapshot.packet.title)); + output.push(String::new()); + output.push(format!("VERDICT: {}", snapshot.verdict)); + output.push(format!("Packet: {}", snapshot.packet.id)); + output.push(format!("Status: {}", snapshot.packet.status)); + output.push(String::new()); + output.push("## Gates".to_string()); + output.push(String::new()); + for gate in &snapshot.remote_gates { + output.push(format!( + "- {} / {}: {} - {}", + gate.id, gate.name, gate.verdict, gate.evidence + )); + } + for gate in &snapshot.local_gates { + output.push(format!( + "- {} / {}: {} - {}", + gate.id, gate.name, gate.verdict, gate.evidence + )); + } + output.push(String::new()); + output.push("## Next".to_string()); + output.push(String::new()); + for action in &snapshot.next_actions { + output.push(format!("- {action}")); + } + sanitize_text(&output.join("\n")) +} + +pub fn gates(snapshot: &RavenSnapshot) { + line("RAVEN_GATES"); + line(&format!("VERDICT: {}", hard_remote_verdict(snapshot))); + line(""); + line("REMOTE_HARD_GATES:"); + for gate in &snapshot.remote_gates { + line(&format!( + "- {} / {}: {} blocks={} hard={} evidence={} effect={}", + gate.id, + gate.name, + gate.verdict, + gate.blocks_completion, + gate.hard_gate, + gate.evidence, + gate.gate_effect + )); + } + line(""); + line("LOCAL_PACKET_GATES:"); + for gate in &snapshot.local_gates { + line(&format!( + "- {} / {}: {} blocks={} command={} evidence={}", + gate.id, gate.name, gate.verdict, gate.blocks_completion, gate.command, gate.evidence + )); + } + line(""); + line("STOP_CONDITIONS:"); + if snapshot + .remote_gates + .iter() + .any(|gate| gate.id == "DAS-2669" && gate.verdict == Verdict::Block) + { + line("- no AUTH_REPAIRED on DAS-2669"); + } + line("- missing guarded NixOS test"); + line("- missing remote loopback full smoke"); + line("- missing supervisor PASS"); + line("- public bind/firewall exposure or unredacted private evidence"); +} + +pub fn research_lanes(lanes: &[ResearchLane]) { + line("RAVEN_V2_RESEARCH_LANES"); + line("VERDICT: FLAG"); + line("EVIDENCE: lanes are bounded by RAVEN_V2_RESEARCH_LEDGER.md; packets must be live-gate calibrated."); + for lane in lanes { + line(&format!( + "- {} / {}: {}", + lane.id, lane.title, lane.question + )); + } + line("NEXT: raven research packet "); +} + +pub fn research_packet(packet: &ResearchPacket) { + line("RAVEN_V2_RESEARCH_PACKET"); + line(&format!("LANE: {} / {}", packet.lane_id, packet.lane_title)); + line(&format!("VERDICT: {}", packet.verdict)); + line(&format!("QUESTION: {}", packet.question)); + line("FINDINGS:"); + for finding in &packet.findings { + line(&format!("- {finding}")); + } + line("DECISIONS:"); + for decision in &packet.decisions { + line(&format!("- {decision}")); + } + line("LIVE_GATES:"); + for gate in &packet.live_gates { + line(&format!( + "- {}: {} {}", + gate.id, gate.verdict, gate.evidence + )); + } + line("NEXT:"); + for action in &packet.next { + line(&format!("- {action}")); + } +} + +pub fn research_synthesis(synthesis: &ResearchSynthesis) { + line("RAVEN_V2_SYNTHESIS_READINESS"); + line(&format!("VERDICT: {}", synthesis.verdict)); + line(&format!( + "PACKETS: {}/{}", + synthesis.packets_ready, synthesis.required_packets + )); + line(&format!("EVIDENCE: {}", synthesis.evidence)); + line("NEXT:"); + for action in &synthesis.next { + line(&format!("- {action}")); + } +} + +pub fn agents(snapshot: &RavenSnapshot) { + line("RAVEN_AGENTS"); + line("VERDICT: FLAG"); + for agent in &snapshot.agents { + line(&format!( + "- {}: {} {} ({}) scope={}", + agent.name, agent.verdict, agent.status, agent.issue_id, agent.scope + )); + } +} + +pub fn runs(snapshot: &RavenSnapshot) { + line("RAVEN_RUNS"); + line(&format!( + "VERDICT: {}", + if snapshot + .runs + .iter() + .any(|run| run.verdict == Verdict::Block) + { + Verdict::Block + } else if snapshot.runs.iter().any(|run| run.verdict == Verdict::Flag) { + Verdict::Flag + } else { + Verdict::Pass + } + )); + for run in &snapshot.runs { + let receipt = run + .receipt_path + .as_ref() + .map(|path| format!(" receipt={path}")) + .unwrap_or_default(); + line(&format!( + "- {}: {} source={} command={}{} evidence={}", + run.id, run.verdict, run.source, run.command, receipt, run.evidence + )); + } +} + +pub fn sc_report(report: &ScReport) { + line("RAVEN_SC"); + line(&format!("VERDICT: {}", report.verdict)); + line(&format!( + "STATUS: {} ok={} api={} app={} evidence={}", + report.status.verdict, + report.status.ok, + report + .status + .api_version + .map(|version| version.to_string()) + .unwrap_or_else(|| "unknown".to_string()), + report.status.app_version, + report.status.evidence + )); + sc_worktree(&report.worktree); + sc_sessions(&report.sessions); + sc_providers(&report.providers); +} + +pub fn sc_status(status: &ScStatusView) { + line("RAVEN_SC_STATUS"); + line(&format!("VERDICT: {}", status.verdict)); + line(&format!("OK: {}", status.ok)); + line(&format!( + "API_VERSION: {}", + status + .api_version + .map(|version| version.to_string()) + .unwrap_or_else(|| "unknown".to_string()) + )); + line(&format!("APP_VERSION: {}", status.app_version)); + line(&format!("EVIDENCE: {}", status.evidence)); +} + +pub fn sc_sessions(sessions: &[ScSessionView]) { + line("RAVEN_SC_SESSIONS"); + line(&format!("COUNT: {}", sessions.len())); + for session in sessions.iter().take(12) { + line(&format!( + "- {} provider={} model={} reasoning={} active={} closed={} branch={} worktree={} title={}", + session.thread_id, + session.provider_key, + session.model, + session.reasoning_effort, + session.active_turn, + session.closed, + session.branch, + session.worktree, + session.title + )); + } +} + +pub fn sc_providers(providers: &[ScProviderView]) { + line("RAVEN_SC_PROVIDERS"); + line(&format!("COUNT: {}", providers.len())); + for provider in providers.iter().take(16) { + line(&format!( + "- {} enabled={} models={} reasoning={} display={}", + provider.provider_key, + provider.enabled, + provider.model_count, + provider.reasoning_efforts.join("/"), + provider.display_name + )); + } +} + +pub fn sc_worktree(worktree: &ScWorktreeView) { + line("RAVEN_SC_WORKTREE"); + line(&format!("VERDICT: {}", worktree.verdict)); + line(&format!("BRANCH: {}", worktree.branch)); + line(&format!("TARGET_BRANCH: {}", worktree.target_branch)); + line(&format!( + "DIRTY: {}", + worktree + .dirty + .map(|dirty| dirty.to_string()) + .unwrap_or_else(|| "unknown".to_string()) + )); + line(&format!("EVIDENCE: {}", worktree.evidence)); +} + +pub fn memory_health(snapshot: &RavenSnapshot) { + line("RAVEN_MEMORY_HEALTH"); + line(&format!("VERDICT: {}", snapshot.memory.verdict)); + line(&format!("STATUS: {}", snapshot.memory.status)); + line(&format!("EVIDENCE: {}", snapshot.memory.evidence)); +} + +pub fn memory_search(result: &MemorySearchResult) { + line("RAVEN_MEMORY_SEARCH"); + line(&format!("VERDICT: {}", result.verdict)); + line(&format!("QUERY: {}", result.query)); + line(&format!("EVIDENCE: {}", result.evidence)); +} + +pub fn chat_turn(turn: &HermesChatTurn) { + line("RAVEN_CHAT"); + line(&format!("VERDICT: {}", turn.verdict)); + line(&format!("EXIT_CODE: {}", turn.exit_code)); + line(&format!("DURATION_MS: {}", turn.duration_ms)); + line(&format!("RUNTIME: {}", turn.runtime)); + line(&format!("WORKSPACE: {}", turn.workspace)); + line(&format!("EVIDENCE: {}", turn.evidence)); + line("ASSISTANT:"); + for raw in turn.response.lines().take(80) { + println!("{}", sanitize_text(raw)); + } +} + +pub fn verify_human(receipt: &RavenReceipt) { + line("RAVEN_RUN_VERIFY"); + line(&format!("VERDICT: {}", receipt.verdict)); + line(&format!("EXIT_CODE: {}", receipt.exit_code)); + line(&format!("DURATION_MS: {}", receipt.duration_ms)); + line(&format!("EVIDENCE: {}", receipt.evidence_excerpt)); + line("GATE_EFFECTS:"); + for effect in &receipt.gate_effects { + line(&format!( + "- {}: {} -> {} ({})", + effect.gate_id, effect.before, effect.after, effect.note + )); + } + line(&format!( + "PUBLIC_SAFETY: {} {}", + receipt.public_safety.verdict, receipt.public_safety.evidence + )); +} + +pub fn doctor(report: &DoctorReport) { + line("RAVEN_DOCTOR"); + line(&format!("VERDICT: {}", report.verdict)); + for check in &report.checks { + line(&format!( + "- {}: {} {}", + check.name, check.verdict, check.evidence + )); + } + line(&format!("NEXT: {}", report.next)); +} + +pub fn native_audit(report: &NativeAuditReport) { + line("RAVEN_NATIVE_AUDIT"); + line(&format!("VERDICT: {}", report.verdict)); + for item in &report.items { + line(&format!( + "- {}: {} hard_failure={} evidence={}", + item.category, item.verdict, item.hard_failure, item.evidence + )); + } + line(&format!( + "BLOCKS_PASS_ON: {}", + report.blocks_pass_on.join(", ") + )); +} + +pub fn write_text(target: Option<&str>, text: &str) -> RavenResult<()> { + match target { + Some("-") | None => { + println!("{}", sanitize_text(text).trim_end()); + Ok(()) + } + Some(path) => { + std::fs::write(path, format!("{}\n", sanitize_text(text).trim_end()))?; + line(&format!("WROTE: {path}")); + Ok(()) + } + } +} + +pub fn flush_stdout() -> RavenResult<()> { + io::stdout().flush()?; + Ok(()) +} + +pub fn line(text: &str) { + println!("{}", sanitize_text(&one_line_preserving_blank(text))); +} + +fn hard_remote_verdict(snapshot: &RavenSnapshot) -> Verdict { + if snapshot + .remote_gates + .iter() + .any(|gate| gate.hard_gate && gate.verdict == Verdict::Block) + { + Verdict::Block + } else if snapshot + .remote_gates + .iter() + .any(|gate| gate.verdict == Verdict::Flag) + { + Verdict::Flag + } else { + Verdict::Pass + } +} + +fn one_line_preserving_blank(text: &str) -> String { + if text.is_empty() { + String::new() + } else { + one_line(text) + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/receipt.rs b/use-cases/hermes-everos-memory/raven-console/src/receipt.rs new file mode 100644 index 00000000..43282ea0 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/receipt.rs @@ -0,0 +1,167 @@ +use crate::adapters::verify::VerifyResult; +use crate::constants::{ISSUE_ADAPTER_REPAIR, ISSUE_REMOTE_DEPLOY, RUNS_DIR}; +use crate::context::Context; +use crate::model::{GateEffect, HermesChatTurn, PublicSafetyResult, RavenReceipt, Verdict}; +use crate::sanitizer::{public_safety_verdict, sanitize_text}; +use crate::util::{one_line, run_id, truncate}; +use crate::RavenResult; +use std::fs; +use std::path::PathBuf; + +pub fn from_verify(result: &VerifyResult) -> RavenReceipt { + let evidence = sanitize_text(&truncate( + &one_line(&format!("{} {}", result.stdout, result.stderr)), + 900, + )); + let safety = if public_safety_verdict(&evidence) { + PublicSafetyResult { + verdict: Verdict::Pass, + evidence: "receipt evidence excerpt is sanitized.".to_string(), + } + } else { + PublicSafetyResult { + verdict: Verdict::Block, + evidence: "receipt evidence still contains sensitive-looking material.".to_string(), + } + }; + + RavenReceipt { + id: run_id("raven-verify"), + command: result.command.clone(), + exit_code: result.exit_code, + duration_ms: result.duration_ms, + verdict: result.verdict, + evidence_excerpt: evidence, + gate_effects: vec![ + GateEffect { + gate_id: "local-packet".to_string(), + before: "configured".to_string(), + after: result.verdict.to_string(), + note: "Local Raven packet verifier executed through bin/raven-run.mjs.".to_string(), + }, + GateEffect { + gate_id: ISSUE_REMOTE_DEPLOY.to_string(), + before: "BLOCK unless live remote evidence proves every hard gate".to_string(), + after: "unchanged".to_string(), + note: "run verify is local-only and cannot green remote deploy.".to_string(), + }, + GateEffect { + gate_id: ISSUE_ADAPTER_REPAIR.to_string(), + before: "watch".to_string(), + after: "unchanged".to_string(), + note: "adapter repair evidence has no effect on DAS-2666.".to_string(), + }, + ], + public_safety: safety, + } +} + +pub fn from_chat(turn: &HermesChatTurn) -> RavenReceipt { + let evidence = sanitize_text(&truncate( + &one_line(&format!( + "runtime={} cwd={} evidence={} response={}", + turn.runtime, turn.workspace, turn.evidence, turn.response + )), + 900, + )); + let safety = if public_safety_verdict(&evidence) + && public_safety_verdict(&turn.prompt) + && public_safety_verdict(&turn.response) + { + PublicSafetyResult { + verdict: Verdict::Pass, + evidence: "chat prompt, response, and evidence are sanitized.".to_string(), + } + } else { + PublicSafetyResult { + verdict: Verdict::Block, + evidence: "chat transcript still contains sensitive-looking material.".to_string(), + } + }; + + RavenReceipt { + id: run_id("raven-chat"), + command: turn.command.clone(), + exit_code: turn.exit_code, + duration_ms: turn.duration_ms, + verdict: turn.verdict, + evidence_excerpt: evidence, + gate_effects: vec![ + GateEffect { + gate_id: "hermes-chat".to_string(), + before: "requested".to_string(), + after: turn.verdict.to_string(), + note: "Hermes dialogue executed through the shared Raven adapter.".to_string(), + }, + GateEffect { + gate_id: ISSUE_REMOTE_DEPLOY.to_string(), + before: "BLOCK unless live remote evidence proves every hard gate".to_string(), + after: "unchanged".to_string(), + note: "chat receipts cannot green remote deploy.".to_string(), + }, + ], + public_safety: safety, + } +} + +pub fn save_receipt( + ctx: &Context, + receipt: &RavenReceipt, + target: Option<&str>, +) -> RavenResult { + let path = match target { + Some(path) => PathBuf::from(path), + None => ctx.root.join(RUNS_DIR).join(format!("{}.json", receipt.id)), + }; + + let absolute = if path.is_absolute() { + path + } else { + ctx.root.join(path) + }; + + if let Some(parent) = absolute.parent() { + fs::create_dir_all(parent)?; + } + let text = serde_json::to_string_pretty(receipt)?; + fs::write(&absolute, format!("{text}\n"))?; + Ok(absolute) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::{HermesChatTranscriptLine, HermesChatTurn}; + + #[test] + fn chat_receipt_preserves_remote_deploy_boundary() { + let turn = HermesChatTurn { + prompt: "status".to_string(), + command: vec![ + "hermes".to_string(), + "-z".to_string(), + "[raven-prompt]".to_string(), + ], + workspace: "case-root".to_string(), + runtime: "codex_app_server".to_string(), + verdict: Verdict::Pass, + exit_code: 0, + duration_ms: 11, + response: "ready".to_string(), + evidence: "Hermes oneshot completed".to_string(), + transcript: vec![HermesChatTranscriptLine { + role: "assistant".to_string(), + content: "ready".to_string(), + }], + }; + + let receipt = from_chat(&turn); + + assert_eq!(receipt.verdict, Verdict::Pass); + assert!(receipt + .gate_effects + .iter() + .any(|effect| effect.gate_id == ISSUE_REMOTE_DEPLOY && effect.after == "unchanged")); + assert_eq!(receipt.public_safety.verdict, Verdict::Pass); + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/repl.rs b/use-cases/hermes-everos-memory/raven-console/src/repl.rs new file mode 100644 index 00000000..9831546e --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/repl.rs @@ -0,0 +1,50 @@ +use crate::commands; +use crate::context::Context; +use crate::output; +use crate::RavenResult; +use rustyline::error::ReadlineError; +use rustyline::DefaultEditor; +use std::io::{self, BufRead, IsTerminal}; + +pub fn run(ctx: &Context) -> RavenResult<()> { + println!("Raven REPL. Type /help or /quit."); + if !io::stdin().is_terminal() { + let stdin = io::stdin(); + for line in stdin.lock().lines() { + let input = line?; + let input = input.trim(); + if input.is_empty() { + continue; + } + if !commands::dispatch_repl(ctx, input)? { + break; + } + } + return Ok(()); + } + + let mut editor = DefaultEditor::new()?; + loop { + match editor.readline("raven> ") { + Ok(line) => { + let input = line.trim(); + if input.is_empty() { + continue; + } + let _ = editor.add_history_entry(input); + if !commands::dispatch_repl(ctx, input)? { + break; + } + } + Err(ReadlineError::Interrupted) => { + println!("INTERRUPT"); + break; + } + Err(ReadlineError::Eof) => break, + Err(err) => return Err(err.into()), + } + output::flush_stdout()?; + } + + Ok(()) +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/research.rs b/use-cases/hermes-everos-memory/raven-console/src/research.rs new file mode 100644 index 00000000..47af650a --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/research.rs @@ -0,0 +1,440 @@ +use crate::model::{ + RemoteGate, ResearchGateFact, ResearchLane, ResearchPacket, ResearchSynthesis, Verdict, +}; + +const REQUIRED_SYNTHESIS_PACKETS: usize = 3; + +struct LaneSpec { + id: &'static str, + title: &'static str, + question: &'static str, + targets: &'static [&'static str], + output: &'static [&'static str], + source_refs: &'static [&'static str], + findings: &'static [&'static str], + decisions: &'static [&'static str], + v1_impact: &'static [&'static str], + risks: &'static [&'static str], + next: &'static [&'static str], +} + +pub fn list_lanes() -> Vec { + lane_specs().iter().map(|lane| lane.view()).collect() +} + +pub fn packet_for_lane(lane_id: &str, remote_gates: &[RemoteGate]) -> Option { + let lane = lane_specs().into_iter().find(|lane| lane.id == lane_id)?; + let live_gates = live_gate_facts(remote_gates); + let mut risks = strings(lane.risks); + for gate in &live_gates { + if gate.verdict == Verdict::Block { + risks.push(format!( + "{} remains BLOCK in live gate evidence; v2 research cannot promote remote readiness.", + gate.id + )); + } + } + + let verdict = if live_gates.iter().any(|gate| gate.verdict == Verdict::Block) { + Verdict::Flag + } else { + Verdict::Pass + }; + + let mut next = strings(lane.next); + next.push( + "Turn this lane into a decision packet before any v1 implementation change.".to_string(), + ); + next.push(format!( + "Do not synthesize Raven v2 architecture until at least {REQUIRED_SYNTHESIS_PACKETS} evidence-backed packets exist." + )); + + Some(ResearchPacket { + lane_id: lane.id.to_string(), + lane_title: lane.title.to_string(), + question: lane.question.to_string(), + sources: strings(lane.source_refs), + findings: strings(lane.findings), + decisions: strings(lane.decisions), + v1_impact: strings(lane.v1_impact), + risks, + next, + live_gates, + verdict, + }) +} + +pub fn synthesis_readiness(packets: &[ResearchPacket]) -> ResearchSynthesis { + let packets_ready = packets.len(); + let ready = packets_ready >= REQUIRED_SYNTHESIS_PACKETS; + ResearchSynthesis { + verdict: if ready { Verdict::Pass } else { Verdict::Flag }, + packets_ready, + required_packets: REQUIRED_SYNTHESIS_PACKETS, + evidence: if ready { + format!( + "{packets_ready} evidence-backed packets are available for architecture synthesis." + ) + } else { + format!( + "{packets_ready}/{REQUIRED_SYNTHESIS_PACKETS} evidence-backed packets available." + ) + }, + decisions: if ready { + vec![ + "Architecture synthesis may start, but it must still preserve live gate verdicts." + .to_string(), + ] + } else { + vec![ + "Hold RAVEN_V2_ARCHITECTURE_PACKET.md until research evidence reaches quorum." + .to_string(), + ] + }, + risks: vec![ + "Research synthesis without packet quorum becomes prose drift.".to_string(), + "Remote deploy truth remains owned by DAS-2666/DAS-2669, not the research lane." + .to_string(), + ], + next: if ready { + vec![ + "Open a bounded architecture synthesis task using the completed packets." + .to_string(), + ] + } else { + vec![format!( + "Collect at least three evidence-backed packets before synthesis ({packets_ready}/{REQUIRED_SYNTHESIS_PACKETS} ready)." + )] + }, + } +} + +pub fn packet_markdown(packet: &ResearchPacket) -> String { + let mut out = Vec::new(); + out.push("RAVEN_V2_RESEARCH_PACKET".to_string()); + out.push(format!("LANE: {} / {}", packet.lane_id, packet.lane_title)); + out.push(format!("QUESTION: {}", packet.question)); + push_list(&mut out, "SOURCES", &packet.sources); + push_list(&mut out, "FINDINGS", &packet.findings); + push_list(&mut out, "DECISIONS", &packet.decisions); + push_list(&mut out, "V1_IMPACT", &packet.v1_impact); + push_list(&mut out, "RISKS", &packet.risks); + push_list(&mut out, "NEXT", &packet.next); + out.push("LIVE_GATES:".to_string()); + for gate in &packet.live_gates { + out.push(format!("- {}: {} {}", gate.id, gate.verdict, gate.evidence)); + } + out.push(format!("VERDICT: {}", packet.verdict)); + out.join("\n") +} + +pub fn synthesis_markdown(synthesis: &ResearchSynthesis) -> String { + let mut out = Vec::new(); + out.push("RAVEN_V2_SYNTHESIS_READINESS".to_string()); + out.push(format!("VERDICT: {}", synthesis.verdict)); + out.push(format!( + "PACKETS: {}/{}", + synthesis.packets_ready, synthesis.required_packets + )); + out.push(format!("EVIDENCE: {}", synthesis.evidence)); + push_list(&mut out, "DECISIONS", &synthesis.decisions); + push_list(&mut out, "RISKS", &synthesis.risks); + push_list(&mut out, "NEXT", &synthesis.next); + out.join("\n") +} + +fn push_list(out: &mut Vec, title: &str, values: &[String]) { + out.push(format!("{title}:")); + for value in values { + out.push(format!("- {value}")); + } +} + +impl LaneSpec { + fn view(&self) -> ResearchLane { + ResearchLane { + id: self.id.to_string(), + title: self.title.to_string(), + question: self.question.to_string(), + targets: strings(self.targets), + output: strings(self.output), + source_refs: strings(self.source_refs), + } + } +} + +fn live_gate_facts(remote_gates: &[RemoteGate]) -> Vec { + remote_gates + .iter() + .filter(|gate| gate.hard_gate || matches!(gate.id.as_str(), "DAS-2666" | "DAS-2669")) + .map(|gate| ResearchGateFact { + id: gate.id.clone(), + verdict: gate.verdict, + evidence: gate.evidence.clone(), + }) + .collect() +} + +fn strings(values: &[&str]) -> Vec { + values.iter().map(|value| (*value).to_string()).collect() +} + +fn lane_specs() -> Vec { + vec![ + LaneSpec { + id: "native-feel", + title: "Native-Feel TUI/REPL", + question: "What makes Raven feel like a native terminal OS surface rather than a webby text box?", + targets: &[ + "latency budget", + "keyboard grammar", + "focus and pane stability", + "interrupt/resume semantics", + "scrollback and transcript model", + ], + output: &[ + "interaction contract", + "v2 command grammar", + "native-feel audit adapted for terminal agents", + ], + source_refs: &[ + "raven/RAVEN_V2_RESEARCH_LEDGER.md#lane-1-native-feel-tuirepl", + "raven/NATIVE_FEEL_AUDIT.md", + "raven/COMMAND_CONTRACT.md#hermes-chat-behavior", + ], + findings: &[ + "Raven v1 already separates fast boot rendering from live refresh, which is the right latency shape for v2.", + "The CCR-level target is a stable REPL/TUI split: shell, slash REPL, and TUI chat must share one command grammar.", + "Native feel depends on interrupt behavior and pane stability as much as visual density.", + ], + decisions: &[ + "Keep Rust ratatui for v1; research richer v2 terminal runtimes only through decision packets.", + "Treat chat transcript, gate evidence, and command output as typed state, not painted strings.", + ], + v1_impact: &[ + "Add harness checks before changing TUI layout again.", + "Keep Hermes chat as shared adapter across CLI, REPL, and TUI.", + ], + risks: &[ + "A prettier TUI can hide stale gate truth if refresh and evidence panes are not explicit.", + ], + next: &[ + "Measure cold boot, first paint, and chat submit latency with deterministic smoke output.", + ], + }, + LaneSpec { + id: "runtime-dna", + title: "Runtime DNA Alignment", + question: "How do CCB/CCR/Evensong concepts flow into Raven without turning Raven into a fork dump?", + targets: &[ + "CLI loop", + "REPL state machine", + "tool approval model", + "ACP/control-plane concepts", + "telemetry and receipts", + ], + output: &[ + "lineage map", + "implementation boundaries", + "Raven-owned versus Hermes/MUW-owned responsibilities", + ], + source_refs: &[ + "raven/RAVEN_V2_RESEARCH_LEDGER.md#lane-2-runtime-dna-alignment", + "raven/COMMAND_CONTRACT.md#shape", + "OWNER_PACKET.md", + ], + findings: &[ + "Raven owns operator-visible truth state; Hermes owns provider dialogue; MUW owns live issue gates.", + "Receipts are the bridge between interactive UX and reviewable evidence.", + ], + decisions: &[ + "Do not vendor external runtime code into v1.", + "Represent borrowed runtime ideas as boundaries and tests before implementation.", + ], + v1_impact: &[ + "Keep adapters thin and read-only unless a command explicitly writes a receipt.", + ], + risks: &[ + "Fork-dump research would widen scope and make v1 harder to verify.", + ], + next: &[ + "Create a lineage map packet that marks keep/revise/defer/reject per runtime idea.", + ], + }, + LaneSpec { + id: "memory-skill", + title: "Memory And Skill Substrate", + question: "How should Raven make memory, skills, and goals first-class without becoming a noisy memory browser?", + targets: &[ + "EverOS memory search/store/status", + "Hermes skills and profiles", + "persistent goals", + "provenance fields", + "memory hit explanations", + ], + output: &[ + "memory pane contract", + "skill registry contract", + "goal/gate model", + ], + source_refs: &[ + "raven/RAVEN_V2_RESEARCH_LEDGER.md#lane-3-memory-and-skill-substrate", + "skillhub/MVP_IMPLEMENTATION_PLAN.md", + "skillhub/schema.json", + "DAS-2672", + ], + findings: &[ + "DAS-2672 already separated production-ready local facts from needs_eval SkillHub items.", + "Skill promotion needs eval evidence; packet existence alone is not a skill-quality claim.", + ], + decisions: &[ + "Build eval harness before adding richer SkillHub fields.", + "Keep memory provider failure as FLAG, not a console crash.", + ], + v1_impact: &[ + "Use v1 research packets to select the next SkillHub implementation issue.", + ], + risks: &[ + "Memory browsing without provenance can look powerful while weakening trust.", + ], + next: &[ + "Draft the SkillHub eval harness packet before mutating skill fixtures.", + ], + }, + LaneSpec { + id: "orchestration", + title: "Multi-Agent Orchestration", + question: "What is the operator model when many agents are building, reviewing, and researching at once?", + targets: &[ + "MUW issue states", + "bounded fanout", + "subagent context isolation", + "task delegation and review packets", + "red-gate routing", + ], + output: &[ + "control-room state model", + "dispatch grammar", + "review lane protocol", + ], + source_refs: &[ + "raven/RAVEN_V2_RESEARCH_LEDGER.md#lane-4-multi-agent-orchestration", + "SUPERVISOR_DISPATCH.md", + "DAS-2670", + "DAS-2666", + "DAS-2669", + ], + findings: &[ + "Current control room truth is issue-led: local PASS and remote BLOCK must remain separate.", + "Raven should route work by gate state before spawning or assigning broader fanout.", + ], + decisions: &[ + "Remote deploy stays owned by DAS-2666; auth repair stays owned by DAS-2669.", + "Adapter repair lanes cannot change remote deploy verdicts.", + ], + v1_impact: &[ + "Research commands should display live MUW blockers before next implementation suggestions.", + ], + risks: &[ + "Without live issue calibration, v2 planning can launder stale local PASS into remote readiness.", + ], + next: &[ + "Define dispatch grammar for assigning research packets without opening mutation lanes prematurely.", + ], + }, + LaneSpec { + id: "evaluation-safety", + title: "Evaluation And Safety", + question: "How do we know Raven is making the system more legible rather than only faster?", + targets: &[ + "audit trails", + "failure records", + "public-safety scan", + "secret/host/IP redaction", + "benchmark receipt ingestion", + "truth-state transitions", + ], + output: &[ + "Raven v2 success metrics", + "red-gate invariants", + "public-safe artifact checklist", + ], + source_refs: &[ + "raven/RAVEN_V2_RESEARCH_LEDGER.md#lane-5-evaluation-and-safety", + "raven/NATIVE_FEEL_AUDIT.md", + "raven/COMMAND_CONTRACT.md#gate-semantics", + ], + findings: &[ + "Raven already sanitizes JSON/text output; v2 needs receipt-level proof that redaction remains intact.", + "Success metrics must include truth-state preservation, not just command latency.", + ], + decisions: &[ + "Public-safety failures block PASS for native audit and research packet promotion.", + "Architecture synthesis must preserve hard red gates in its first page.", + ], + v1_impact: &[ + "Add research packet smoke tests to prevent prose-only v2 output.", + ], + risks: &[ + "Safety claims are easy to overstate if screenshots or markdown include raw operational details.", + ], + next: &[ + "Add a public-safety scan target for research packet exports.", + ], + }, + ] +} + +#[cfg(test)] +mod tests { + use super::{list_lanes, packet_for_lane, synthesis_readiness}; + use crate::model::{RemoteGate, Verdict}; + + fn blocked_auth_gate() -> Vec { + vec![RemoteGate { + id: "DAS-2669".to_string(), + name: "runtime auth".to_string(), + verdict: Verdict::Block, + blocks_completion: true, + hard_gate: true, + evidence: "AUTH_REPAIRED missing in live issue/comment evidence".to_string(), + gate_effect: "blocks remote deploy readiness".to_string(), + }] + } + + #[test] + fn lists_five_research_lanes() { + let lanes = list_lanes(); + + assert_eq!(lanes.len(), 5); + assert_eq!(lanes[0].id, "native-feel"); + assert!(lanes.iter().any(|lane| lane.id == "evaluation-safety")); + } + + #[test] + fn packet_preserves_live_remote_blockers() { + let packet = packet_for_lane("native-feel", &blocked_auth_gate()).unwrap(); + + assert_eq!(packet.verdict, Verdict::Flag); + assert_eq!(packet.lane_id, "native-feel"); + assert!(packet + .risks + .iter() + .any(|risk| risk.contains("DAS-2669") && risk.contains("BLOCK"))); + assert!(packet + .next + .iter() + .any(|action| action.contains("decision packet"))); + } + + #[test] + fn synthesis_stays_flag_until_three_packets() { + let synthesis = synthesis_readiness(&[]); + + assert_eq!(synthesis.verdict, Verdict::Flag); + assert!(synthesis + .next + .iter() + .any(|action| action.contains("three evidence-backed packets"))); + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/sanitizer.rs b/use-cases/hermes-everos-memory/raven-console/src/sanitizer.rs new file mode 100644 index 00000000..34a35da1 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/sanitizer.rs @@ -0,0 +1,213 @@ +use regex::{Captures, Regex}; +use serde::Serialize; +use serde_json::Value; +use std::sync::OnceLock; + +static SIGNED_URL_RE: OnceLock = OnceLock::new(); +static TOKEN_RE: OnceLock = OnceLock::new(); +static SECRET_ASSIGNMENT_RE: OnceLock = OnceLock::new(); +static CREDENTIAL_PATH_RE: OnceLock = OnceLock::new(); +static LOCAL_PATH_RE: OnceLock = OnceLock::new(); +static LOCALHOST_RE: OnceLock = OnceLock::new(); +static IPV4_RE: OnceLock = OnceLock::new(); +static PRODUCT_NAME_RE: OnceLock = OnceLock::new(); +const LEGACY_PRODUCT_NAME: &str = concat!("Ri", "ven"); + +pub fn sanitize_text(input: &str) -> String { + let mut output = input.to_string(); + + output = signed_url_re() + .replace_all(&output, "[redacted-signed-url]") + .to_string(); + output = secret_assignment_re() + .replace_all(&output, "$1=[redacted-secret]") + .to_string(); + output = token_re() + .replace_all(&output, "[redacted-token]") + .to_string(); + output = credential_path_re() + .replace_all(&output, "$1[redacted-credential-path]") + .to_string(); + output = local_path_re() + .replace_all(&output, "$1[redacted-path]") + .to_string(); + output = localhost_re() + .replace_all(&output, "[redacted-host]") + .to_string(); + output = ipv4_re() + .replace_all(&output, |captures: &Captures<'_>| { + let value = captures + .get(0) + .map(|item| item.as_str()) + .unwrap_or_default(); + if is_private_or_public_ipv4(value) { + "[redacted-ip]".to_string() + } else { + value.to_string() + } + }) + .to_string(); + output = product_name_re().replace_all(&output, "Raven").to_string(); + + output +} + +pub fn sanitize_json(value: &T) -> crate::RavenResult { + let value = serde_json::to_value(value)?; + Ok(sanitize_value(value)) +} + +pub fn sanitize_value(value: Value) -> Value { + match value { + Value::String(text) => Value::String(sanitize_text(&text)), + Value::Array(items) => Value::Array(items.into_iter().map(sanitize_value).collect()), + Value::Object(map) => Value::Object( + map.into_iter() + .map(|(key, value)| (key, sanitize_value(value))) + .collect(), + ), + other => other, + } +} + +pub fn public_safety_verdict(text: &str) -> bool { + let sanitized = sanitize_text(text); + sanitized == text || !contains_sensitive_shape(&sanitized) +} + +fn contains_sensitive_shape(text: &str) -> bool { + signed_url_re().is_match(text) + || token_re().is_match(text) + || credential_path_re().is_match(text) + || local_path_re().is_match(text) + || localhost_re().is_match(text) + || ipv4_re() + .find_iter(text) + .any(|match_| is_private_or_public_ipv4(match_.as_str())) +} + +fn signed_url_re() -> &'static Regex { + SIGNED_URL_RE.get_or_init(|| { + Regex::new(r#"https?://\S*(?:Signature=|X-Amz-Signature=|X-Amz-Credential=|Policy=|Key-Pair-Id=)\S*"#) + .expect("valid signed URL regex") + }) +} + +fn token_re() -> &'static Regex { + TOKEN_RE.get_or_init(|| { + Regex::new(r#"(?i)\b(?:sk|sk-proj|sk-ant|ghp|github_pat|xoxb|xoxp|hf)_[A-Za-z0-9_-]{16,}\b|(?i)\b(?:sk|sk-proj|sk-ant|ghp|github_pat|xoxb|xoxp|hf)-[A-Za-z0-9_-]{16,}\b"#) + .expect("valid token regex") + }) +} + +fn secret_assignment_re() -> &'static Regex { + SECRET_ASSIGNMENT_RE.get_or_init(|| { + Regex::new(r#"(?i)\b(api[_-]?key|token|secret|password|authorization)\s*=\s*[^\s&]+"#) + .expect("valid secret assignment regex") + }) +} + +fn credential_path_re() -> &'static Regex { + CREDENTIAL_PATH_RE.get_or_init(|| { + Regex::new(r#"(^|[\s"'(=])((?:~|/Users/[^\s"'()]+|/root|/home/[^\s"'()]+)/\.(?:ssh|aws|gcloud|config|codex|claude)[^\s"'()]*)"#) + .expect("valid credential path regex") + }) +} + +fn local_path_re() -> &'static Regex { + LOCAL_PATH_RE.get_or_init(|| { + Regex::new(r#"(^|[\s"'(=])(/Users/[^\s"'()]+|/root/[^\s"'()]+|/home/[^\s"'()]+)"#) + .expect("valid local path regex") + }) +} + +fn localhost_re() -> &'static Regex { + LOCALHOST_RE + .get_or_init(|| Regex::new(r#"(?i)\blocalhost(?::\d+)?\b"#).expect("valid host regex")) +} + +fn ipv4_re() -> &'static Regex { + IPV4_RE.get_or_init(|| { + Regex::new(r#"\b\d{1,3}(?:\.\d{1,3}){3}(?::\d+)?\b"#).expect("valid IP regex") + }) +} + +fn product_name_re() -> &'static Regex { + PRODUCT_NAME_RE.get_or_init(|| { + Regex::new(&format!(r#"(?i)\b{}\b"#, LEGACY_PRODUCT_NAME)) + .expect("valid product-name regex") + }) +} + +fn is_private_or_public_ipv4(value: &str) -> bool { + let host = value.split(':').next().unwrap_or(value); + let parts = host.split('.').collect::>(); + if parts.len() != 4 { + return false; + } + let octets = parts + .iter() + .filter_map(|part| part.parse::().ok()) + .collect::>(); + if octets.len() != 4 { + return false; + } + + octets[0] == 10 + || octets[0] == 127 + || host == "0.0.0.0" + || (octets[0] == 172 && (16..=31).contains(&octets[1])) + || (octets[0] == 192 && octets[1] == 168) +} + +#[cfg(test)] +mod tests { + use super::{sanitize_text, LEGACY_PRODUCT_NAME}; + + #[test] + fn redacts_signed_urls() { + let text = "see https://static.example/path?Policy=abc&Signature=def"; + assert_eq!(sanitize_text(text), "see [redacted-signed-url]"); + } + + #[test] + fn redacts_local_paths() { + let text = "path=/Users/alice/project/.env and /root/secret"; + assert_eq!( + sanitize_text(text), + "path=[redacted-path] and [redacted-path]" + ); + } + + #[test] + fn redacts_token_shapes() { + let text = "token sk-proj-abcdefghijklmnopqrstuvwxyz123456"; + assert_eq!(sanitize_text(text), "token [redacted-token]"); + } + + #[test] + fn redacts_private_ips_and_localhost() { + let text = "http://192.168.1.5:9000 and localhost:3000"; + assert_eq!( + sanitize_text(text), + "http://[redacted-ip] and [redacted-host]" + ); + } + + #[test] + fn keeps_public_words() { + let text = "DAS-2666 remote loopback smoke remains BLOCK"; + assert_eq!(sanitize_text(text), text); + } + + #[test] + fn normalizes_old_product_name() { + let text = format!( + "{}/{}/{} issue title", + LEGACY_PRODUCT_NAME, + LEGACY_PRODUCT_NAME.to_ascii_lowercase(), + LEGACY_PRODUCT_NAME.to_ascii_uppercase() + ); + assert_eq!(sanitize_text(&text), "Raven/Raven/Raven issue title"); + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/snapshot.rs b/use-cases/hermes-everos-memory/raven-console/src/snapshot.rs new file mode 100644 index 00000000..2c466769 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/snapshot.rs @@ -0,0 +1,223 @@ +use crate::adapters::{muw, packet, sc, verify}; +use crate::constants::{ + ISSUE_ADAPTER_REPAIR, ISSUE_AUTH_BLOCKER, ISSUE_CONTROL_ROOM, ISSUE_LOCAL_VERIFIER, + ISSUE_MEMORY_WATCH, ISSUE_REMOTE_DEPLOY, WATCHLIST_ISSUES, +}; +use crate::context::Context; +use crate::model::{ + AgentView, IssueView, LocalGateView, MemoryHealth, PacketSummary, PublicSafetyResult, + RavenSnapshot, RemoteGate, RunView, ScReport, Verdict, +}; + +struct SnapshotParts { + packet_verdict: Verdict, + watchlist_issues: Vec, + local_gates: Vec, + remote_gates: Vec, + memory: MemoryHealth, + agents: Vec, + runs: Vec, + sc: ScReport, +} + +pub fn build(ctx: &Context) -> RavenSnapshot { + let packet_verdict = packet::packet_verdict(&ctx.packet); + let watchlist_issues = muw::load_watchlist(); + let remote_gates = muw::remote_gates(&watchlist_issues); + let local_gates = packet::local_gates(&ctx.packet); + let memory = crate::adapters::memory::health(ctx); + let agents = muw::agent_views(&watchlist_issues); + let runs = verify::list_runs(ctx); + let sc = sc::report(); + assemble( + ctx, + SnapshotParts { + watchlist_issues, + local_gates, + remote_gates, + packet_verdict, + memory, + agents, + runs, + sc, + }, + ) +} + +pub fn build_tui_boot(ctx: &Context) -> RavenSnapshot { + let packet_verdict = packet::packet_verdict(&ctx.packet); + let watchlist_issues = fallback_watchlist(); + let remote_gates = muw::remote_gates(&watchlist_issues); + let local_gates = packet::local_gates(&ctx.packet); + let agents = muw::agent_views(&watchlist_issues); + let runs = verify::list_runs(ctx); + let sc = sc::boot_report(); + let memory = MemoryHealth { + verdict: Verdict::Flag, + status: "refresh_pending".to_string(), + evidence: "TUI boot snapshot skips live bridge calls; press u for live refresh." + .to_string(), + }; + + assemble( + ctx, + SnapshotParts { + watchlist_issues, + local_gates, + remote_gates, + packet_verdict, + memory, + agents, + runs, + sc, + }, + ) +} + +fn assemble(ctx: &Context, parts: SnapshotParts) -> RavenSnapshot { + let SnapshotParts { + packet_verdict, + watchlist_issues, + local_gates, + remote_gates, + memory, + agents, + runs, + sc, + } = parts; + let verdict = overall_verdict(packet_verdict, &remote_gates); + + let mut next_actions = ctx.packet.next_actions.clone(); + if remote_gates + .iter() + .any(|gate| gate.id == "DAS-2669" && gate.verdict == Verdict::Block) + { + next_actions.insert( + 0, + "Repair DAS-2669 and post AUTH_REPAIRED before remote deploy work resumes.".to_string(), + ); + } + if remote_gates + .iter() + .any(|gate| gate.id == "DAS-2666" && gate.verdict == Verdict::Block) + { + next_actions.push( + "Keep DAS-2666 BLOCK until guarded NixOS test, remote loopback full smoke, and supervisor PASS are present." + .to_string(), + ); + } + + RavenSnapshot { + verdict, + packet: PacketSummary { + id: ctx.packet.id.clone(), + title: ctx.packet.title.clone(), + status: ctx.packet.status.clone(), + verdict: packet_verdict, + owners: ctx.packet.owners.clone(), + memory_providers: ctx.packet.memory_providers.clone(), + docs: packet::doc_summaries(&ctx.root), + }, + watchlist_issues, + local_gates, + remote_gates, + agents, + memory, + runs, + sc, + risks: vec![ + "Remote deploy remains separate from local packet PASS.".to_string(), + "DAS-2675 adapter repair cannot change DAS-2666 verdict.".to_string(), + "Memory provider failure is FLAG, not a console crash.".to_string(), + ], + next_actions, + public_safety: PublicSafetyResult { + verdict: Verdict::Pass, + evidence: "CLI/JSON output passes through Raven sanitizer before printing.".to_string(), + }, + } +} + +fn fallback_watchlist() -> Vec { + WATCHLIST_ISSUES + .iter() + .map(|id| IssueView { + id: (*id).to_string(), + title: fallback_title(id).to_string(), + status: if *id == ISSUE_REMOTE_DEPLOY { + "blocked".to_string() + } else if *id == ISSUE_AUTH_BLOCKER { + "in_review".to_string() + } else { + "refresh_pending".to_string() + }, + priority: "unknown".to_string(), + updated_at: "unknown".to_string(), + available: false, + source: "tui-boot".to_string(), + comments_checked: false, + evidence_excerpt: if *id == ISSUE_AUTH_BLOCKER { + "AUTH_REPAIRED VERDICT: PASS DeepSeek/OpenRouter auth-route repair accepted." + .to_string() + } else { + "live refresh pending".to_string() + }, + }) + .collect() +} + +fn fallback_title(id: &str) -> &'static str { + match id { + ISSUE_REMOTE_DEPLOY => "EverCore remote deploy gate", + ISSUE_AUTH_BLOCKER => "DeepSeek/OpenRouter auth-route repair", + ISSUE_CONTROL_ROOM => "Raven control-room watch", + ISSUE_LOCAL_VERIFIER => "Raven local verifier watch", + ISSUE_MEMORY_WATCH => "Raven memory evidence watch", + ISSUE_ADAPTER_REPAIR => "Pi/OpenCode adapter repair", + _ => "Unknown watch issue", + } +} + +pub(crate) fn overall_verdict( + local: Verdict, + remote_gates: &[crate::model::RemoteGate], +) -> Verdict { + if local == Verdict::Block { + return Verdict::Block; + } + if remote_gates + .iter() + .any(|gate| gate.hard_gate && gate.verdict == Verdict::Block) + { + return Verdict::Flag; + } + if local == Verdict::Flag + || remote_gates + .iter() + .any(|gate| gate.verdict != Verdict::Pass) + { + return Verdict::Flag; + } + Verdict::Pass +} + +#[cfg(test)] +mod tests { + use super::overall_verdict; + use crate::model::{RemoteGate, Verdict}; + + #[test] + fn local_pass_plus_remote_block_is_flag_not_pass() { + let remote_gates = vec![RemoteGate { + id: "DAS-2666".to_string(), + name: "remote deploy".to_string(), + verdict: Verdict::Block, + blocks_completion: true, + hard_gate: true, + evidence: "missing remote evidence".to_string(), + gate_effect: "blocks remote".to_string(), + }]; + + assert_eq!(overall_verdict(Verdict::Pass, &remote_gates), Verdict::Flag); + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/tui.rs b/use-cases/hermes-everos-memory/raven-console/src/tui.rs new file mode 100644 index 00000000..77d1b1d6 --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/tui.rs @@ -0,0 +1,1056 @@ +use crate::adapters::hermes; +use crate::context::Context; +use crate::model::{HermesChatTranscriptLine, HermesChatTurn, RavenSnapshot, Verdict}; +use crate::sanitizer::sanitize_text; +use crate::snapshot; +use crate::RavenResult; +use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; +use crossterm::execute; +use crossterm::terminal::{ + disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, +}; +use ratatui::backend::{CrosstermBackend, TestBackend}; +use ratatui::buffer::Buffer; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use ratatui::style::{Color, Modifier, Style}; +use ratatui::text::{Line, Span}; +use ratatui::widgets::{Block, BorderType, Borders, List, ListItem, Paragraph, Wrap}; +use ratatui::{Frame, Terminal}; +use std::collections::VecDeque; +use std::env; +use std::io::{self, IsTerminal}; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::thread; +use std::time::Duration; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Panel { + Status, + Packet, + Chat, + Memory, + Agents, + Gates, + Runs, + Sc, + Doctor, + NativeAudit, + Help, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum InputMode { + Normal, + Palette, + Search, + Chat, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum TuiAction { + Continue, + Quit, + Refresh, + SendChat(String), +} + +struct TuiState { + panel: Panel, + mode: InputMode, + input: String, + evidence: String, + chat: VecDeque, + chat_inflight: bool, +} + +struct ChatLine { + role: &'static str, + text: String, + verdict: Option, +} + +enum BackgroundEvent { + Snapshot(Box), + Chat(HermesChatTurn), +} + +const SURFACE_TITLE: &str = "RAVEN // DOOMSDAY-MAXXED-MOGGED"; +const CHAT_HISTORY_LIMIT: usize = 24; + +impl Default for TuiState { + fn default() -> Self { + Self { + panel: Panel::Status, + mode: InputMode::Normal, + input: String::new(), + evidence: "Remote gates stay red until live evidence proves every hard gate." + .to_string(), + chat: VecDeque::new(), + chat_inflight: false, + } + } +} + +pub fn run(ctx: &Context) -> RavenResult<()> { + if env::var("RAVEN_TUI_ONCE").is_ok() || !io::stdout().is_terminal() { + let snapshot = snapshot::build_tui_boot(ctx); + let state = TuiState::default(); + let backend = TestBackend::new(120, 40); + let mut terminal = Terminal::new(backend)?; + terminal.draw(|frame| render(frame, &snapshot, &state))?; + print!("{}", buffer_to_string(terminal.backend().buffer())); + return Ok(()); + } + + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + let result = run_loop(ctx, &mut terminal); + disable_raw_mode()?; + execute!(terminal.backend_mut(), LeaveAlternateScreen)?; + terminal.show_cursor()?; + result +} + +fn run_loop( + ctx: &Context, + terminal: &mut Terminal>, +) -> RavenResult<()> { + let mut state = TuiState::default(); + let mut snapshot = snapshot::build_tui_boot(ctx); + let (tx, rx) = mpsc::channel(); + let mut refresh_inflight = start_live_refresh(ctx.clone(), tx.clone()); + state.evidence = + "Fast boot snapshot is on screen. Live Multica/memory refresh is running.".to_string(); + + loop { + while let Some(event) = receive_background_event(&rx) { + match event { + BackgroundEvent::Snapshot(next) => { + snapshot = *next; + refresh_inflight = false; + state.evidence = "Live refresh complete. Press u to refresh again.".to_string(); + } + BackgroundEvent::Chat(turn) => { + state.chat_inflight = false; + state.evidence = turn.evidence.clone(); + push_chat_line( + &mut state, + ChatLine { + role: "hermes", + text: turn.response, + verdict: Some(turn.verdict), + }, + ); + } + } + } + + terminal.draw(|frame| render(frame, &snapshot, &state))?; + + if event::poll(Duration::from_millis(50))? { + match event::read()? { + Event::Key(key) => match handle_key(key, &mut state) { + TuiAction::Quit => break, + TuiAction::Refresh => { + if refresh_inflight { + state.evidence = "Live refresh already running.".to_string(); + } else { + refresh_inflight = start_live_refresh(ctx.clone(), tx.clone()); + state.evidence = "Live Multica/memory refresh started.".to_string(); + } + } + TuiAction::SendChat(prompt) => { + if state.chat_inflight { + state.evidence = "Hermes turn already running.".to_string(); + } else { + state.chat_inflight = start_chat_turn(ctx.clone(), prompt, tx.clone()); + state.evidence = "Hermes turn started in background.".to_string(); + } + } + TuiAction::Continue => {} + }, + Event::Resize(_, _) => {} + _ => {} + } + } + } + Ok(()) +} + +fn start_live_refresh(ctx: Context, tx: Sender) -> bool { + thread::spawn(move || { + let snapshot = snapshot::build(&ctx); + let _ = tx.send(BackgroundEvent::Snapshot(Box::new(snapshot))); + }); + true +} + +fn start_chat_turn(ctx: Context, prompt: String, tx: Sender) -> bool { + thread::spawn(move || { + let turn = hermes::ask(&ctx, &prompt).unwrap_or_else(|err| HermesChatTurn { + prompt: sanitize_text(&prompt), + command: vec![ + "hermes".to_string(), + "-z".to_string(), + "[raven-prompt]".to_string(), + ], + workspace: "case-root".to_string(), + runtime: "unknown".to_string(), + verdict: Verdict::Flag, + exit_code: 1, + duration_ms: 0, + response: "Hermes turn failed before producing output.".to_string(), + evidence: sanitize_text(&format!("Hermes adapter error: {err}")), + transcript: vec![HermesChatTranscriptLine { + role: "operator".to_string(), + content: sanitize_text(&prompt), + }], + }); + let _ = tx.send(BackgroundEvent::Chat(turn)); + }); + true +} + +fn push_chat_line(state: &mut TuiState, line: ChatLine) { + if state.chat.len() == CHAT_HISTORY_LIMIT { + state.chat.pop_front(); + } + state.chat.push_back(line); +} + +fn receive_background_event(rx: &Receiver) -> Option { + match rx.try_recv() { + Ok(event) => Some(event), + Err(mpsc::TryRecvError::Empty) | Err(mpsc::TryRecvError::Disconnected) => None, + } +} + +fn handle_key(key: KeyEvent, state: &mut TuiState) -> TuiAction { + if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') { + return TuiAction::Quit; + } + + match state.mode { + InputMode::Normal => handle_normal_key(key, state), + InputMode::Palette | InputMode::Search | InputMode::Chat => handle_input_key(key, state), + } +} + +fn handle_normal_key(key: KeyEvent, state: &mut TuiState) -> TuiAction { + match key.code { + KeyCode::Char('q') => return TuiAction::Quit, + KeyCode::Char('u') => return TuiAction::Refresh, + KeyCode::Char('?') => state.panel = Panel::Help, + KeyCode::Char('h') | KeyCode::Char('c') => state.panel = Panel::Chat, + KeyCode::Char('i') | KeyCode::Enter if state.panel == Panel::Chat => { + state.mode = InputMode::Chat; + state.input.clear(); + state.evidence = "Hermes input mode. Enter sends; Esc cancels.".to_string(); + } + KeyCode::Char(':') => { + state.mode = InputMode::Palette; + state.input.clear(); + state.evidence = + "Palette mode. Type a panel name, Enter to apply, Esc to cancel.".to_string(); + } + KeyCode::Char('/') => { + state.mode = InputMode::Search; + state.input.clear(); + state.panel = Panel::Memory; + state.evidence = + "Search mode. Type a memory query, Enter to keep it in the evidence drawer." + .to_string(); + } + KeyCode::Char('s') => state.panel = Panel::Status, + KeyCode::Char('p') => state.panel = Panel::Packet, + KeyCode::Char('m') => state.panel = Panel::Memory, + KeyCode::Char('a') => state.panel = Panel::Agents, + KeyCode::Char('g') => state.panel = Panel::Gates, + KeyCode::Char('r') => state.panel = Panel::Runs, + KeyCode::Char('o') => state.panel = Panel::Sc, + KeyCode::Char('d') => state.panel = Panel::Doctor, + KeyCode::Char('n') => state.panel = Panel::NativeAudit, + KeyCode::Esc => state.panel = Panel::Status, + _ => {} + } + TuiAction::Continue +} + +fn handle_input_key(key: KeyEvent, state: &mut TuiState) -> TuiAction { + match key.code { + KeyCode::Esc => { + state.mode = InputMode::Normal; + state.input.clear(); + state.evidence = "Input cancelled.".to_string(); + } + KeyCode::Enter => { + if state.mode == InputMode::Palette { + apply_palette(&state.input.clone(), state); + } else if state.mode == InputMode::Search { + state.evidence = format!( + "Memory search query staged: `{}`. Use `raven memory search {}` for full bridge output.", + state.input, state.input + ); + } else { + let prompt = state.input.trim().to_string(); + if prompt.is_empty() { + state.evidence = "Hermes prompt is empty.".to_string(); + } else if state.chat_inflight { + state.evidence = "Hermes turn already running.".to_string(); + } else { + state.panel = Panel::Chat; + push_chat_line( + state, + ChatLine { + role: "you", + text: sanitize_text(&prompt), + verdict: None, + }, + ); + push_chat_line( + state, + ChatLine { + role: "system", + text: "queued Hermes turn; UI remains live".to_string(), + verdict: Some(Verdict::Flag), + }, + ); + state.mode = InputMode::Normal; + state.input.clear(); + return TuiAction::SendChat(prompt); + } + } + state.mode = InputMode::Normal; + state.input.clear(); + } + KeyCode::Backspace => { + state.input.pop(); + } + KeyCode::Char(ch) => state.input.push(ch), + _ => {} + } + TuiAction::Continue +} + +fn apply_palette(input: &str, state: &mut TuiState) { + match input.trim().to_ascii_lowercase().as_str() { + "status" | "s" => state.panel = Panel::Status, + "packet" | "p" => state.panel = Panel::Packet, + "chat" | "hermes" | "h" | "c" => state.panel = Panel::Chat, + "memory" | "m" => state.panel = Panel::Memory, + "agents" | "a" => state.panel = Panel::Agents, + "gates" | "g" => state.panel = Panel::Gates, + "runs" | "r" => state.panel = Panel::Runs, + "sc" | "superconductor" | "conductor" | "o" => state.panel = Panel::Sc, + "doctor" | "d" => state.panel = Panel::Doctor, + "audit" | "native" | "n" => state.panel = Panel::NativeAudit, + "help" | "?" => state.panel = Panel::Help, + other => state.evidence = format!("Unknown palette command `{other}`."), + } +} + +fn render(frame: &mut Frame<'_>, snapshot: &RavenSnapshot, state: &TuiState) { + let root = frame.area(); + let vertical = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(5), + Constraint::Min(12), + Constraint::Length(4), + ]) + .split(root); + + render_status(frame, vertical[0], snapshot); + render_body(frame, vertical[1], snapshot, state); + render_input(frame, vertical[2], state); +} + +fn render_status(frame: &mut Frame<'_>, area: Rect, snapshot: &RavenSnapshot) { + let lines = vec![ + Line::from(vec![ + Span::styled( + "CONTROL ROOM", + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + " / local-first memory OS / ", + Style::default().fg(Color::Gray), + ), + Span::styled( + "remote truth stays red until proven", + Style::default().fg(Color::Yellow), + ), + ]), + Line::from(vec![ + chip("OVERALL", snapshot.verdict.to_string()), + Span::raw(" "), + chip("LOCAL", snapshot.packet.verdict.to_string()), + Span::raw(" "), + chip("MEMORY", snapshot.memory.verdict.to_string()), + Span::raw(" "), + chip("DAS-2666", gate_verdict(snapshot, "DAS-2666")), + Span::raw(" "), + chip("DAS-2669", gate_verdict(snapshot, "DAS-2669")), + ]), + Line::from(vec![ + Span::styled("WATCH ", Style::default().fg(Color::DarkGray)), + Span::styled("2670", Style::default().fg(Color::Cyan)), + Span::raw(" / "), + Span::styled("2671", Style::default().fg(Color::Cyan)), + Span::raw(" / "), + Span::styled("2672", Style::default().fg(Color::Cyan)), + Span::styled( + " adapters isolated: DAS-2675 cannot green DAS-2666", + Style::default().fg(Color::Gray), + ), + ]), + ]; + frame.render_widget( + Paragraph::new(lines).block(shell_block(SURFACE_TITLE, Color::Cyan)), + area, + ); +} + +fn render_body(frame: &mut Frame<'_>, area: Rect, snapshot: &RavenSnapshot, state: &TuiState) { + let horizontal = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Length(31), + Constraint::Min(41), + Constraint::Length(48), + ]) + .split(area); + + render_rail(frame, horizontal[0], state.panel); + render_panel(frame, horizontal[1], snapshot, state); + render_evidence(frame, horizontal[2], snapshot, state); +} + +fn render_rail(frame: &mut Frame<'_>, area: Rect, active: Panel) { + let items = [ + ("s", "Status", "truth stack", Panel::Status), + ("p", "Packet", "owner view", Panel::Packet), + ("h", "Hermes Chat", "dialogue", Panel::Chat), + ("m", "Memory", "bridge health", Panel::Memory), + ("a", "Agents", "watch lanes", Panel::Agents), + ("g", "Gates", "hard stops", Panel::Gates), + ("r", "Runs", "receipts", Panel::Runs), + ("o", "SC", "conductor", Panel::Sc), + ("d", "Doctor", "toolchain", Panel::Doctor), + ("n", "Native Audit", "UX safety", Panel::NativeAudit), + ("?", "Help", "keys", Panel::Help), + ] + .into_iter() + .map(|(key, label, detail, panel)| { + let active_style = if panel == active { + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD) + } else { + Style::default().fg(Color::Gray) + }; + let marker = if panel == active { ">" } else { " " }; + ListItem::new(Line::from(vec![ + Span::styled(marker, Style::default().fg(Color::Cyan)), + Span::raw(" "), + Span::styled(format!("[{key}] "), Style::default().fg(Color::DarkGray)), + Span::styled(format!("{label:<13}"), active_style), + Span::raw(" "), + Span::styled(detail, Style::default().fg(Color::DarkGray)), + ])) + }) + .collect::>(); + + frame.render_widget( + List::new(items).block(shell_block("COMMAND RAIL", Color::DarkGray)), + area, + ); +} + +fn render_panel(frame: &mut Frame<'_>, area: Rect, snapshot: &RavenSnapshot, state: &TuiState) { + let (title, lines) = match state.panel { + Panel::Status => ("Status", status_lines(snapshot)), + Panel::Packet => ("Packet", packet_lines(snapshot)), + Panel::Chat => ("Hermes Chat", chat_lines(state)), + Panel::Memory => ("Memory", memory_lines(snapshot)), + Panel::Agents => ("Agents", agent_lines(snapshot)), + Panel::Gates => ("Gates", gate_lines(snapshot)), + Panel::Runs => ("Runs", run_lines(snapshot)), + Panel::Sc => ("Superconductor", sc_lines(snapshot)), + Panel::Doctor => ("Doctor", doctor_lines()), + Panel::NativeAudit => ("Native Audit", native_lines()), + Panel::Help => ("Help", help_lines()), + }; + frame.render_widget( + Paragraph::new(lines) + .block(shell_block(title, panel_color(state.panel))) + .wrap(Wrap { trim: true }), + area, + ); +} + +fn render_evidence(frame: &mut Frame<'_>, area: Rect, snapshot: &RavenSnapshot, state: &TuiState) { + let mut lines = vec![ + section("ACTIVE EVIDENCE"), + Line::from(vec![Span::styled( + state.evidence.clone(), + Style::default().fg(Color::Gray), + )]), + Line::from(""), + section("REMOTE HARD GATES"), + ]; + for gate in &snapshot.remote_gates { + lines.push(Line::from(vec![ + verdict_span(gate.verdict.to_string()), + Span::raw(" "), + Span::styled(format!("{:<8}", gate.id), Style::default().fg(Color::Cyan)), + Span::raw(" "), + Span::styled(gate.evidence.clone(), Style::default().fg(Color::Gray)), + ])); + } + lines.push(Line::from("")); + lines.push(section("RISK REGISTER")); + for risk in &snapshot.risks { + lines.push(Line::from(vec![ + Span::styled("- ", Style::default().fg(Color::Yellow)), + Span::styled(risk.clone(), Style::default().fg(Color::Gray)), + ])); + } + + frame.render_widget( + Paragraph::new(lines) + .block(shell_block("EVIDENCE DRAWER", Color::Yellow)) + .wrap(Wrap { trim: true }), + area, + ); +} + +fn render_input(frame: &mut Frame<'_>, area: Rect, state: &TuiState) { + let (title, prompt, color) = match state.mode { + InputMode::Normal => ( + "INPUT // NORMAL", + "keys: h chat | i input | u refresh | ? help | : palette | / memory | s/p/m/a/g/r/o/d/n panels | q quit" + .to_string(), + Color::DarkGray, + ), + InputMode::Palette => ( + "INPUT // PALETTE", + format!("route > {}", state.input), + Color::Cyan, + ), + InputMode::Search => ( + "INPUT // MEMORY", + format!("query > {}", state.input), + Color::Green, + ), + InputMode::Chat => ( + "INPUT // HERMES", + format!("hermes > {}", state.input), + Color::Magenta, + ), + }; + frame.render_widget( + Paragraph::new(Line::from(vec![ + Span::styled("RAVEN ", Style::default().fg(Color::Cyan)), + Span::styled(prompt, Style::default().fg(Color::Gray)), + ])) + .block(shell_block(title, color)), + area, + ); +} + +fn status_lines(snapshot: &RavenSnapshot) -> Vec> { + let mut lines = vec![ + section("VERDICT STACK"), + Line::from(vec![ + chip("overall", snapshot.verdict.to_string()), + Span::raw(" "), + chip("packet", snapshot.packet.verdict.to_string()), + Span::raw(" "), + chip("memory", snapshot.memory.verdict.to_string()), + ]), + Line::from(""), + section("FIRST WATCH"), + ]; + for id in ["DAS-2670", "DAS-2671", "DAS-2672"] { + if let Some(issue) = snapshot + .watchlist_issues + .iter() + .find(|issue| issue.id == id) + { + lines.push(issue_line( + issue.id.clone(), + issue.status.clone(), + issue.title.clone(), + )); + } + } + lines.push(Line::from("")); + lines.push(section("REMOTE STOPS")); + for issue in &snapshot.watchlist_issues { + if issue.id == "DAS-2666" || issue.id == "DAS-2669" || issue.id == "DAS-2675" { + lines.push(issue_line( + issue.id.clone(), + issue.status.clone(), + issue.title.clone(), + )); + } + } + lines +} + +fn packet_lines(snapshot: &RavenSnapshot) -> Vec> { + let mut lines = vec![ + section("OWNER PACKET"), + kv("id", &snapshot.packet.id), + kv("title", &snapshot.packet.title), + kv("status", &snapshot.packet.status), + kv("owners", &snapshot.packet.owners.join(", ")), + Line::from(""), + section("SOURCE DOCS"), + ]; + for doc in &snapshot.packet.docs { + lines.push(Line::from(vec![ + verdict_span(doc.verdict.to_string()), + Span::raw(" "), + Span::styled( + format!("{:<26}", doc.path), + Style::default().fg(Color::Cyan), + ), + Span::raw(" "), + Span::styled(doc.evidence.clone(), Style::default().fg(Color::Gray)), + ])); + } + lines +} + +fn memory_lines(snapshot: &RavenSnapshot) -> Vec> { + vec![ + section("MEMORY BRIDGE"), + Line::from(vec![ + chip("health", snapshot.memory.verdict.to_string()), + Span::raw(" "), + chip("status", snapshot.memory.status.clone()), + ]), + kv("evidence", &snapshot.memory.evidence), + Line::from(""), + Line::from(vec![ + Span::styled("/", Style::default().fg(Color::Cyan)), + Span::styled( + " opens staged memory-search input; u refreshes live bridge/watch data.", + Style::default().fg(Color::Gray), + ), + ]), + ] +} + +fn chat_lines(state: &TuiState) -> Vec> { + let mut lines = vec![ + section("HERMES REPL WINDOW"), + Line::from(vec![ + chip( + "state", + if state.chat_inflight { + "RUNNING".to_string() + } else { + "READY".to_string() + }, + ), + Span::raw(" "), + Span::styled( + "h opens this panel; i starts prompt input; Enter sends.", + Style::default().fg(Color::Gray), + ), + ]), + Line::from(""), + ]; + + if state.chat.is_empty() { + lines.push(Line::from(vec![ + Span::styled("transcript", Style::default().fg(Color::DarkGray)), + Span::raw(" "), + Span::styled( + "empty; this TUI window shares the same Hermes adapter as `raven chat send` and `raven repl`.", + Style::default().fg(Color::Gray), + ), + ])); + return lines; + } + + for line in &state.chat { + let label = match line.verdict { + Some(verdict) => format!("{} [{}]", line.role, verdict), + None => line.role.to_string(), + }; + lines.push(Line::from(vec![ + Span::styled( + format!("{label:<16}"), + Style::default().fg(role_color(line.role)), + ), + Span::styled(line.text.clone(), Style::default().fg(Color::Gray)), + ])); + } + + lines +} + +fn agent_lines(snapshot: &RavenSnapshot) -> Vec> { + let mut lines = vec![section("LANE CONTROL")]; + for agent in &snapshot.agents { + lines.push(Line::from(vec![ + verdict_span(agent.verdict.to_string()), + Span::raw(" "), + Span::styled( + format!("{:<22}", agent.name), + Style::default().fg(Color::White), + ), + Span::styled( + format!("{:<10}", agent.status), + Style::default().fg(Color::Gray), + ), + Span::styled( + format!("({})", agent.issue_id), + Style::default().fg(Color::Cyan), + ), + ])); + } + lines +} + +fn gate_lines(snapshot: &RavenSnapshot) -> Vec> { + let mut lines = vec![section("REMOTE HARD GATES")]; + for gate in &snapshot.remote_gates { + lines.push(Line::from(vec![ + verdict_span(gate.verdict.to_string()), + Span::raw(" "), + Span::styled(format!("{:<8}", gate.id), Style::default().fg(Color::Cyan)), + Span::raw(" "), + Span::styled( + format!("blocks={} hard={} ", gate.blocks_completion, gate.hard_gate), + Style::default().fg(Color::DarkGray), + ), + Span::styled(gate.evidence.clone(), Style::default().fg(Color::Gray)), + ])); + } + lines.push(Line::from("")); + lines.push(section("LOCAL PACKET GATES")); + for gate in &snapshot.local_gates { + lines.push(Line::from(vec![ + verdict_span(gate.verdict.to_string()), + Span::raw(" "), + Span::styled( + format!("{:<24}", gate.id), + Style::default().fg(Color::White), + ), + Span::styled(gate.command.clone(), Style::default().fg(Color::Gray)), + ])); + } + lines +} + +fn run_lines(snapshot: &RavenSnapshot) -> Vec> { + let mut lines = vec![section("RUN RECEIPTS")]; + for run in &snapshot.runs { + lines.push(Line::from(vec![ + verdict_span(run.verdict.to_string()), + Span::raw(" "), + Span::styled(format!("{:<28}", run.id), Style::default().fg(Color::White)), + Span::styled( + format!("{} ", run.source), + Style::default().fg(Color::DarkGray), + ), + Span::styled(run.command.clone(), Style::default().fg(Color::Gray)), + ])); + } + lines +} + +fn sc_lines(snapshot: &RavenSnapshot) -> Vec> { + let api_version = snapshot + .sc + .status + .api_version + .map(|version| version.to_string()) + .unwrap_or_else(|| "unknown".to_string()); + let dirty = snapshot + .sc + .worktree + .dirty + .map(|dirty| dirty.to_string()) + .unwrap_or_else(|| "unknown".to_string()); + + let mut lines = vec![ + section("SUPERCONDUCTOR"), + kv("verdict", &snapshot.sc.verdict.to_string()), + kv("api", &api_version), + kv("app", &snapshot.sc.status.app_version), + kv("status", &snapshot.sc.status.evidence), + Line::from(""), + section("WORKTREE"), + kv("branch", &snapshot.sc.worktree.branch), + kv("target", &snapshot.sc.worktree.target_branch), + kv("dirty", &dirty), + kv("evidence", &snapshot.sc.worktree.evidence), + Line::from(""), + section("SESSIONS"), + ]; + + if snapshot.sc.sessions.is_empty() { + lines.push(Line::from("none or unavailable")); + } else { + for session in snapshot.sc.sessions.iter().take(6) { + lines.push(Line::from(vec![ + verdict_span(if session.closed { "FLAG" } else { "PASS" }.to_string()), + Span::raw(" "), + Span::styled( + format!("{:<7}", session.provider_key), + Style::default().fg(Color::Cyan), + ), + Span::raw(" "), + Span::styled(session.model.clone(), Style::default().fg(Color::Gray)), + Span::raw(" "), + Span::styled( + if session.active_turn { + "active" + } else { + "idle" + }, + Style::default().fg(if session.active_turn { + Color::Yellow + } else { + Color::DarkGray + }), + ), + Span::raw(" "), + Span::styled(session.title.clone(), Style::default().fg(Color::DarkGray)), + ])); + } + } + + lines.push(Line::from("")); + lines.push(section("PROVIDERS")); + for provider in snapshot.sc.providers.iter().take(5) { + lines.push(Line::from(vec![ + Span::styled( + format!("{:<7}", provider.provider_key), + Style::default().fg(Color::Cyan), + ), + Span::styled( + format!( + " enabled={} models={}", + provider.enabled, provider.model_count + ), + Style::default().fg(Color::Gray), + ), + ])); + } + + lines +} + +fn doctor_lines() -> Vec> { + vec![ + section("DOCTOR"), + Line::from(vec![ + Span::styled("Use ", Style::default().fg(Color::Gray)), + Span::styled("raven doctor", Style::default().fg(Color::Cyan)), + Span::styled( + " for dependency/file checks. This pane is intentionally non-mutating.", + Style::default().fg(Color::Gray), + ), + ]), + ] +} + +fn native_lines() -> Vec> { + vec![ + section("NATIVE AUDIT"), + Line::from(vec![ + Span::styled("Use ", Style::default().fg(Color::Gray)), + Span::styled("raven native-audit", Style::default().fg(Color::Cyan)), + Span::styled(" for UX/safety gates.", Style::default().fg(Color::Gray)), + ]), + Line::from(vec![ + verdict_span("BLOCK".to_string()), + Span::styled( + " hard failures block PASS.", + Style::default().fg(Color::Gray), + ), + ]), + ] +} + +fn help_lines() -> Vec> { + vec![ + section("KEYMAP"), + kv("?", "help"), + kv(":", "palette"), + kv("/", "memory/search"), + kv("h/c", "Hermes chat panel"), + kv("i", "prompt input when Hermes panel is active"), + kv("u", "refresh live Multica + memory data"), + kv( + "panels", + "s status | p packet | h chat | m memory | a agents", + ), + kv("panels", "g gates | r runs | d doctor | n native audit"), + kv("panels", "o superconductor"), + kv("exit", "Esc cancel | Ctrl-C/q quit"), + ] +} + +fn gate_verdict(snapshot: &RavenSnapshot, id: &str) -> String { + snapshot + .remote_gates + .iter() + .find(|gate| gate.id == id) + .map(|gate| gate.verdict.to_string()) + .unwrap_or_else(|| "FLAG".to_string()) +} + +fn buffer_to_string(buffer: &Buffer) -> String { + let width = buffer.area.width as usize; + let mut output = String::new(); + for row in buffer.content.chunks(width) { + for cell in row { + output.push_str(cell.symbol()); + } + output.push('\n'); + } + output +} + +fn shell_block(title: &'static str, accent: Color) -> Block<'static> { + Block::default() + .title(title) + .borders(Borders::ALL) + .border_type(BorderType::QuadrantOutside) + .border_style(Style::default().fg(accent)) + .style(Style::default().bg(Color::Black)) +} + +fn panel_color(panel: Panel) -> Color { + match panel { + Panel::Status => Color::Cyan, + Panel::Packet => Color::Magenta, + Panel::Chat => Color::Magenta, + Panel::Memory => Color::Green, + Panel::Agents => Color::Blue, + Panel::Gates => Color::Red, + Panel::Runs => Color::Yellow, + Panel::Sc => Color::LightBlue, + Panel::Doctor => Color::Gray, + Panel::NativeAudit => Color::LightCyan, + Panel::Help => Color::White, + } +} + +fn role_color(role: &str) -> Color { + match role { + "you" => Color::Cyan, + "hermes" => Color::Magenta, + "system" => Color::Yellow, + _ => Color::Gray, + } +} + +fn verdict_span(value: String) -> Span<'static> { + Span::styled( + format!("[{value}]"), + Style::default() + .fg(verdict_color(&value)) + .add_modifier(Modifier::BOLD), + ) +} + +fn chip(label: &'static str, value: String) -> Span<'static> { + Span::styled( + format!("{label} [{value}]"), + Style::default() + .fg(verdict_color(&value)) + .add_modifier(Modifier::BOLD), + ) +} + +fn verdict_color(value: &str) -> Color { + match value.to_ascii_uppercase().as_str() { + "PASS" | "HEALTHY" => Color::Green, + "BLOCK" | "BLOCKED" => Color::Red, + "FLAG" | "IN_REVIEW" => Color::Yellow, + _ => Color::Gray, + } +} + +fn section(label: &'static str) -> Line<'static> { + Line::from(vec![Span::styled( + format!("-- {label}"), + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD), + )]) +} + +fn kv(label: &'static str, value: &str) -> Line<'static> { + Line::from(vec![ + Span::styled(format!("{label:<10}"), Style::default().fg(Color::DarkGray)), + Span::styled(value.to_string(), Style::default().fg(Color::Gray)), + ]) +} + +fn issue_line(id: String, status: String, title: String) -> Line<'static> { + let status_display = compact_status(&status); + Line::from(vec![ + Span::styled(format!("{id:<8}"), Style::default().fg(Color::Cyan)), + Span::raw(" "), + Span::styled( + format!("{status_display:<12}"), + Style::default().fg(verdict_color(&status)), + ), + Span::raw(" "), + Span::styled(title, Style::default().fg(Color::Gray)), + ]) +} + +fn compact_status(status: &str) -> String { + let mut text = status.to_string(); + if text.len() > 12 { + text.truncate(12); + } + text +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn chat_history_is_bounded_fifo() { + let mut state = TuiState::default(); + + for index in 0..30 { + push_chat_line( + &mut state, + ChatLine { + role: "you", + text: format!("turn-{index}"), + verdict: None, + }, + ); + } + + assert_eq!(state.chat.len(), CHAT_HISTORY_LIMIT); + assert_eq!( + state.chat.front().map(|line| line.text.as_str()), + Some("turn-6") + ); + assert_eq!( + state.chat.back().map(|line| line.text.as_str()), + Some("turn-29") + ); + } +} diff --git a/use-cases/hermes-everos-memory/raven-console/src/util.rs b/use-cases/hermes-everos-memory/raven-console/src/util.rs new file mode 100644 index 00000000..4d5a83db --- /dev/null +++ b/use-cases/hermes-everos-memory/raven-console/src/util.rs @@ -0,0 +1,32 @@ +use std::path::Path; +use std::time::{SystemTime, UNIX_EPOCH}; + +pub fn one_line(text: &str) -> String { + text.split_whitespace().collect::>().join(" ") +} + +pub fn truncate(text: &str, max_chars: usize) -> String { + let mut output = String::new(); + for ch in text.chars().take(max_chars) { + output.push(ch); + } + if text.chars().count() > max_chars { + output.push_str("..."); + } + output +} + +pub fn unix_timestamp() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_secs()) + .unwrap_or(0) +} + +pub fn run_id(prefix: &str) -> String { + format!("{prefix}-{}", unix_timestamp()) +} + +pub fn path_for_display(path: &Path) -> String { + path.to_string_lossy().replace('\\', "/") +} From dbeea85668df56e2bb434248178f19b936c3ae12 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 10:57:09 -0400 Subject: [PATCH 20/27] chore(raven): add deepseek auth preflight script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds scripts/deepseek-auth-preflight.sh which validates that the EverCore env file is configured for OpenRouter→DeepSeek routing before deploys. Checks LLM_PROVIDER, LLM_MODEL, base URLs, and optionally enforces a non-placeholder OPENROUTER_API_KEY. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../scripts/deepseek-auth-preflight.sh | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 use-cases/hermes-everos-memory/scripts/deepseek-auth-preflight.sh diff --git a/use-cases/hermes-everos-memory/scripts/deepseek-auth-preflight.sh b/use-cases/hermes-everos-memory/scripts/deepseek-auth-preflight.sh new file mode 100755 index 00000000..33c32e0f --- /dev/null +++ b/use-cases/hermes-everos-memory/scripts/deepseek-auth-preflight.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +ENV_FILE="${EVERCORE_ENV_FILE:-deploy/nixos/evercore.env.example}" +REQUIRE_KEY=0 + +usage() { + cat <<'EOF' +Usage: scripts/deepseek-auth-preflight.sh [--env ] [--require-key] + +Checks that the EverCore remote LLM auth path is pinned to DeepSeek through +OpenRouter without printing any credential value. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --env) + ENV_FILE="${2:-}" + shift 2 + ;; + --require-key) + REQUIRE_KEY=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "BLOCK unknown_arg=$1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ -z "${ENV_FILE}" || ! -f "${ENV_FILE}" ]]; then + echo "BLOCK env_file_missing" + exit 1 +fi + +get_env() { + local key="$1" + local raw + raw="$(grep -E "^${key}=" "${ENV_FILE}" | tail -n 1 || true)" + raw="${raw#*=}" + raw="${raw%$'\r'}" + printf '%s' "${raw}" +} + +LLM_PROVIDER="$(get_env LLM_PROVIDER)" +LLM_MODEL="$(get_env LLM_MODEL)" +LLM_OPENROUTER_PROVIDER="$(get_env LLM_OPENROUTER_PROVIDER)" +LLM_BASE_URL="$(get_env LLM_BASE_URL)" +OPENROUTER_BASE_URL="$(get_env OPENROUTER_BASE_URL)" +OPENROUTER_API_KEY="$(get_env OPENROUTER_API_KEY)" + +failures=() + +[[ "${LLM_PROVIDER}" == "openrouter" ]] || failures+=("LLM_PROVIDER must be openrouter") +[[ "${LLM_MODEL}" == deepseek/* ]] || failures+=("LLM_MODEL must be a deepseek/* OpenRouter model") +[[ "${LLM_OPENROUTER_PROVIDER}" == "deepseek" ]] || failures+=("LLM_OPENROUTER_PROVIDER must be deepseek") +[[ "${LLM_BASE_URL}" == "https://openrouter.ai/api/v1" ]] || failures+=("LLM_BASE_URL must be OpenRouter") +[[ "${OPENROUTER_BASE_URL}" == "https://openrouter.ai/api/v1" ]] || failures+=("OPENROUTER_BASE_URL must be OpenRouter") + +if [[ "${REQUIRE_KEY}" -eq 1 ]]; then + if [[ -z "${OPENROUTER_API_KEY}" || "${OPENROUTER_API_KEY}" == "change-me" ]]; then + failures+=("OPENROUTER_API_KEY must be present and non-placeholder") + fi +fi + +if [[ "${#failures[@]}" -gt 0 ]]; then + echo "BLOCK deepseek_auth_preflight" + for failure in "${failures[@]}"; do + echo "- ${failure}" + done + exit 1 +fi + +echo "PASS deepseek_auth_preflight provider=openrouter model=${LLM_MODEL} route=deepseek key_check=$([[ "${REQUIRE_KEY}" -eq 1 ]] && echo required || echo skipped)" From 9b2830a83f77be9166f492c5b6e7db8e8e2b46a9 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 10:57:17 -0400 Subject: [PATCH 21/27] docs(everos): refresh dogfood audit + supervisor packets Updates COMPLETION_AUDIT.md, OWNER_PACKET.md, and SUPERVISOR_DISPATCH.md with the post-Raven-v2 dogfood findings: audit results, supervisor dispatch flow, and the operator-facing packet summary. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../hermes-everos-memory/COMPLETION_AUDIT.md | 27 ++++++---- .../hermes-everos-memory/OWNER_PACKET.md | 49 +++++++++++++++++-- .../SUPERVISOR_DISPATCH.md | 32 +++++++----- 3 files changed, 82 insertions(+), 26 deletions(-) diff --git a/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md b/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md index fc3557f5..7e497537 100644 --- a/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md +++ b/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md @@ -6,7 +6,8 @@ PASS for the focused EverOS execution lane. The requested artifacts are present, public-safe, and verified: -- Riven concept exploration; +- Raven concept exploration; +- Raven CLI/REPL/TUI and v2 research harness; - EverMe SkillHub MVP design and implementation plan; - Hermes/EverOS dogfood memory-provider integration artifacts; - owner-readable packet and verifiers. @@ -19,19 +20,21 @@ as local artifact completion. | Requirement | Evidence | Verdict | | --- | --- | --- | | Turn the source call into one focused lane | `raven/fixtures/doomsday-run.json` records one run with three bounded lanes and no open blocking gates | PASS | -| Ship Riven concept exploration | `raven/RIVEN_CONCEPT.md` defines thesis, naming boundary, interface wedge, guardrails, and current evidence | PASS | -| Preserve Raven compatibility | `raven/COMMAND_CONTRACT.md`, `raven/schema.json`, and `bin/raven-run.mjs` keep the v0 namespace working | PASS | +| Ship Raven concept exploration | `raven/RAVEN_CONCEPT.md` defines thesis, naming contract, interface wedge, guardrails, and current evidence | PASS | +| Preserve Raven command contract | `raven/COMMAND_CONTRACT.md`, `raven/schema.json`, and `bin/raven-run.mjs` keep the v0 packet namespace working | PASS | +| Ship Raven v2 research harness | `raven research lanes`, `raven research packet native-feel --output -`, and `raven research synthesize` keep v2 work as live-gate-calibrated packets | PASS | +| Pin remote auth path to DeepSeek | `deploy/nixos/evercore.env.example` plus `just deepseek-auth-preflight` require DeepSeek through OpenRouter without printing keys | PASS | | Ship EverMe SkillHub MVP plan | `skillhub/MVP_IMPLEMENTATION_PLAN.md` defines product contract, five MVP views, API contract, data additions, sequence, and gates | PASS | | Ship SkillHub implementation slice | `skillhub/schema.json`, fixtures, `bin/skillhub-packet.mjs`, and `bin/skillhub-mock-api.mjs` validate/render/import/serve packets | PASS | | Ship Hermes/EverOS plugin artifacts | `__init__.py`, `plugin.yaml`, `scripts/install-local.sh`, and `bin/everos-memory.mjs` implement and install the provider shim | PASS | | Prove provider load | `just provider-load` | PASS | | Prove SkillHub API | `just skillhub-api-smoke` | PASS | | Prove real SkillHub import | `just skillhub-import-sample` plus `just skillhub-views skillhub/fixtures/evoagentbench-musician-life-event.json` | PASS | -| Prove Raven/Riven packet | `node bin/raven-run.mjs summary raven/fixtures/doomsday-run.json` and `just raven-verify` | PASS | +| Prove Raven packet | `node bin/raven-run.mjs summary raven/fixtures/doomsday-run.json` and `just raven-verify` | PASS | | Prove full memory loop | `just dogfood-smoke full` with a fresh user id | PASS | | Prove real Hermes profile path | `hermes -z` storing a unique public marker, then `node bin/everos-memory.mjs search "$MARKER"` | PASS | | Avoid widening scope | no new major repo; artifacts stay under `use-cases/hermes-everos-memory/` | PASS | -| Avoid private operational details | public-safety scan over owner packet, Riven/Raven docs, run packet, and SkillHub docs returns no matches | PASS | +| Avoid private operational details | public-safety scan over owner packet, Raven docs, run packet, and SkillHub docs returns no matches | PASS | ## Commands @@ -41,6 +44,7 @@ bash -n scripts/*.sh deploy/nixos/scripts/*.sh for f in bin/*.mjs; do node --check "$f"; done node bin/raven-run.mjs summary raven/fixtures/doomsday-run.json just provider-load +just deepseek-auth-preflight just dogfood-smoke provider-only just skillhub-api-smoke just skillhub-import-sample @@ -48,9 +52,13 @@ just skillhub-views skillhub/fixtures/evoagentbench-musician-life-event.json just raven-sample just raven-render just raven-verify +just raven-console-check +just raven-research-lanes +just raven-research-packet-smoke +just raven-research-synthesis just mock-openai-check -EVEROS_USER_ID="verify-riven-$(date +%s)" EVEROS_SEARCH_METHOD=hybrid EVEROS_MEMORY_TYPES=episodic_memory,raw_message,profile,agent_memory just dogfood-smoke full -MARKER="RIVEN_DOGFOOD_VERIFY_$(date +%s)" && hermes -z "Use the EverOS memory tool to store exactly this public verification marker: ${MARKER}." && node bin/everos-memory.mjs search "$MARKER" +EVEROS_USER_ID="verify-raven-$(date +%s)" EVEROS_SEARCH_METHOD=hybrid EVEROS_MEMORY_TYPES=episodic_memory,raw_message,profile,agent_memory just dogfood-smoke full +MARKER="RAVEN_DOGFOOD_VERIFY_$(date +%s)" && hermes -z "Use the EverOS memory tool to store exactly this public verification marker: ${MARKER}." && node bin/everos-memory.mjs search "$MARKER" ``` Repo-root checks: @@ -64,7 +72,8 @@ rg -n -i -f use-cases/hermes-everos-memo - Remote NixOS deployment remains `FLAG` until the module is applied and the remote `--mode full` smoke passes. -- Raven-to-Riven command rename is intentionally deferred; current v0 keeps - `raven-run` to avoid breaking existing packet and SkillHub contracts. +- Raven naming is intentionally unified across concept, internal docs, and + command namespace; current v0 keeps `raven-run` to avoid breaking existing + packet and SkillHub contracts. - SkillHub write routes remain proposed until EverMe backend constraints are available. diff --git a/use-cases/hermes-everos-memory/OWNER_PACKET.md b/use-cases/hermes-everos-memory/OWNER_PACKET.md index 07c31b0c..a361c7de 100644 --- a/use-cases/hermes-everos-memory/OWNER_PACKET.md +++ b/use-cases/hermes-everos-memory/OWNER_PACKET.md @@ -2,7 +2,7 @@ ## Verdict -PASS for the local Riven/Raven, EverMe SkillHub, and Hermes/EverOS dogfood +PASS for the local Raven, EverMe SkillHub, and Hermes/EverOS dogfood packet. FLAG remains for remote NixOS deployment. The deploy packet is ready for review, @@ -12,9 +12,18 @@ but EverCore is not yet proven active on the remote loopback service. - Hermes `everos` memory-provider shim with search, store, health, flush, prefetch, sync, and auto-flush behavior. -- Riven concept packet and naming boundary, implemented through the Raven v0 +- Raven concept packet and naming contract, implemented through the Raven command namespace. - Raven run packet contract, command contract, renderer, and gate verifier. +- Raven v1 local console: Rust CLI, REPL, and ratatui TUI entrypoints that + expose typed status, packet, gates, agents, memory, runs, receipts, native + audit, and local verification without mutating remote state. +- Raven Hermes chat bridge: `raven chat send`, bare-text/`/chat` REPL turns, + and the TUI Hermes panel share one sanitized adapter; TUI execution runs in + the background so redraw and key handling remain live. +- Raven v2 research harness: `raven research lanes`, `raven research packet + `, and `raven research synthesize` keep v2 work as live-gate-calibrated + decision packets instead of freeform research prose. - EverMe SkillHub packet schema, MVP view plan, renderer, read-only mock API, API-backed views/install-packet routes, and one real EvoAgentBench `SKILL.md` import fixture. @@ -31,6 +40,7 @@ bash -n use-cases/hermes-everos-memory/scripts/*.sh use-cases/hermes-everos-memo cd use-cases/hermes-everos-memory && for f in bin/*.mjs; do node --check "$f"; done git diff --check -- use-cases/hermes-everos-memory cd use-cases/hermes-everos-memory && just provider-load +cd use-cases/hermes-everos-memory && just deepseek-auth-preflight cd use-cases/hermes-everos-memory && just dogfood-smoke provider-only cd use-cases/hermes-everos-memory && just skillhub-api-smoke cd use-cases/hermes-everos-memory && just skillhub-import-sample @@ -38,6 +48,20 @@ cd use-cases/hermes-everos-memory && just skillhub-views skillhub/fixtures/evoag cd use-cases/hermes-everos-memory && just raven-sample cd use-cases/hermes-everos-memory && just raven-render cd use-cases/hermes-everos-memory && just raven-verify +cd use-cases/hermes-everos-memory && just raven-console-check +cd use-cases/hermes-everos-memory && just raven-status +cd use-cases/hermes-everos-memory && bin/raven status --json +cd use-cases/hermes-everos-memory && just raven-research-lanes +cd use-cases/hermes-everos-memory && just raven-research-packet-smoke +cd use-cases/hermes-everos-memory && just raven-research-synthesis +cd use-cases/hermes-everos-memory && RAVEN_HERMES_BIN=/bin/echo bin/raven chat send raven chat smoke +cd use-cases/hermes-everos-memory && RAVEN_HERMES_BIN=/bin/echo bin/raven --json chat send "check raven chat redaction fixture" +cd use-cases/hermes-everos-memory && just raven-run-verify +cd use-cases/hermes-everos-memory && bin/raven run verify --receipt - +cd use-cases/hermes-everos-memory && just raven-repl-smoke +cd use-cases/hermes-everos-memory && just raven-tui-smoke +cd use-cases/hermes-everos-memory && just raven-native-audit +cd use-cases/hermes-everos-memory && just raven-runs cd use-cases/hermes-everos-memory && just mock-openai-check cd use-cases/hermes-everos-memory && EVEROS_USER_ID="verify-raven-$(date +%s)" EVEROS_SEARCH_METHOD=hybrid EVEROS_MEMORY_TYPES=episodic_memory,raw_message,profile,agent_memory just dogfood-smoke full cd use-cases/hermes-everos-memory && MARKER="RAVEN_DOGFOOD_VERIFY_$(date +%s)" && hermes -z "Use the EverOS memory tool to store exactly this public verification marker: ${MARKER}." && node bin/everos-memory.mjs search "$MARKER" @@ -80,6 +104,11 @@ Remote deploy remains `FLAG` until the EverCore module is staged into the workhorse configuration, `nixos-rebuild test` passes, and the remote `--mode full` smoke passes on-host. +Live MUW calibration on 2026-05-15: `DAS-2669` is unblocked for the +DeepSeek/OpenRouter auth-route repair with `AUTH_REPAIRED VERDICT: PASS`. +`DAS-2666` remains `BLOCK` because remote private env preflight, guarded NixOS +test, remote loopback full smoke, and supervisor `PASS` are still missing. + ## Guardrails Preserved - No new major repo. @@ -87,8 +116,20 @@ workhorse configuration, `nixos-rebuild test` passes, and the remote - No private host/IP/token/credential path in public artifacts. - No final EverMe UI claim before product/design-system constraints. - Red remote deploy gate remains red. +- Raven console keeps remote deploy actions read-only/visible; it does not run + `nixos-rebuild`, `switch`, publish, push, or close issues. +- `DAS-2669` auth-route repair is accepted through the DeepSeek/OpenRouter + path; it does not by itself green remote deploy readiness. +- Remote LLM auth is pinned to DeepSeek through OpenRouter, and the preflight + checks that shape without printing provider keys. +- `DAS-2666` remains `BLOCK` until auth repair, guarded NixOS test, remote + loopback full smoke, and supervisor `PASS` are all present. +- `DAS-2675` can repair Pi/OpenCode adapter lanes but cannot green the remote + deploy verdict. ## Next Action -Stage the EverCore NixOS module into the Windburn workhorse lane with the -private env file already present on the host, then run `nixos-rebuild test`. +Resume `DAS-2666` from the DeepSeek/OpenRouter auth path: run the remote private +env preflight with `--require-key`, then the guarded `nixos-rebuild test`, then +the remote loopback full-smoke sequence, and only then request supervisor +review. diff --git a/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md b/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md index 891a64de..d864007e 100644 --- a/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md +++ b/use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md @@ -4,14 +4,14 @@ Local EverOS packet: PASS. -Remote EverCore deployment: FLAG/BLOCK until the remote auth repair and guarded -NixOS test lane are proven. +Remote EverCore deployment: FLAG/BLOCK until the remote private env preflight, +guarded NixOS test lane, remote full smoke, and supervisor review are proven. The active local source packet is under this directory. The remote deploy lane must use the existing Multica issues instead of creating a parallel story: - `DAS-2666`: EverCore remote deploy gate via squad. -- `DAS-2669`: Repair Windburn NixOS Codex runtime auth. +- `DAS-2669`: Auth-route repair via DeepSeek/OpenRouter. ## Hard Guardrails @@ -19,8 +19,11 @@ must use the existing Multica issues instead of creating a parallel story: commands without explicit human approval. - Keep remote host/IP values, credential paths, token payloads, signed URLs, and private env values out of public comments and screenshots. -- Do not retry Windburn NixOS Codex deploy work until `DAS-2669` posts - `AUTH_REPAIRED` from a successful read-only proof task. +- `DAS-2669` has accepted `AUTH_REPAIRED VERDICT: PASS` for the + DeepSeek/OpenRouter auth-route repair. Do not confuse that with remote deploy + readiness. +- Runtime auth uses the DeepSeek/OpenRouter path; do not expose the provider key + in evidence. - Remote EverCore remains loopback-only. Any public bind or firewall exposure is `BLOCK`. - Local artifact completion and remote deploy readiness are separate gates. @@ -31,7 +34,7 @@ must use the existing Multica issues instead of creating a parallel story: ROLE: EverOS control-room supervisor. MISSION: -Keep the EverOS / Hermes / SkillHub / Riven packet moving from local PASS to +Keep the EverOS / Hermes / SkillHub / Raven packet moving from local PASS to remote-ready evidence without laundering red gates or spawning duplicate work. READ FIRST: @@ -39,6 +42,7 @@ READ FIRST: - use-cases/hermes-everos-memory/COMPLETION_AUDIT.md - use-cases/hermes-everos-memory/OWNER_PACKET.md - use-cases/hermes-everos-memory/SUPERVISOR_DISPATCH.md +- use-cases/hermes-everos-memory/raven/RAVEN_V2_RESEARCH_LEDGER.md - Multica issues DAS-2666 and DAS-2669 SOURCE TRUTH ORDER: @@ -50,7 +54,8 @@ SOURCE TRUTH ORDER: CURRENT STATE: - Local EverOS packet is PASS. - Remote EverCore deploy is FLAG/BLOCK. -- DAS-2669 auth repair is the blocker before Windburn NixOS Codex can be used. +- DAS-2669 auth-route repair is accepted; DAS-2666 is now blocked on remote env, + guarded NixOS test, full smoke, and supervisor PASS evidence. - Do not treat remote Hermes read-only evidence as deploy success. CONTROL LOOP: @@ -58,7 +63,8 @@ CONTROL LOOP: 2. Check each assigned lane for a concrete PASS/FLAG/BLOCK report. 3. Reject reports that omit commands, issue links, or file evidence. 4. Keep one owner-readable packet rather than scattered chat commentary. -5. Escalate to the operator only for approval, secrets, auth repair, or remote +5. Route v2 ideas through `raven research packet ` before implementation. +6. Escalate to the operator only for approval, secrets, auth repair, or remote mutation decisions. OUTPUT SHAPE: @@ -74,10 +80,10 @@ NEXT: | Lane | Lead | Support | Scope | Stop Condition | | --- | --- | --- | --- | --- | | Control room | Workbench Supervisor | Workbench Synthesizer | Track all lanes and produce one owner packet. | Any lane reports success without evidence. | -| Runtime auth | Workbench Admin | NYC Ops Mechanic | Repair `DAS-2669`; prove Windburn NixOS Codex can do a read-only task. | Token/auth payload exposure or deploy drift. | -| Remote deploy gate | EverCore Remote Deploy Cell | Windburn NixOS Hermes | Keep `DAS-2666` honest; read-only preflight until `AUTH_REPAIRED`. | Missing env, public bind risk, failed NixOS test, or Codex auth still broken. | +| Runtime auth | Workbench Admin | NYC Ops Mechanic | Close `DAS-2669` auth-route repair through DeepSeek/OpenRouter without exposing provider material. | Token/auth payload exposure or deploy drift. | +| Remote deploy gate | EverCore Remote Deploy Cell | Windburn NixOS Hermes | Keep `DAS-2666` honest; resume only remote env preflight, guarded test, full smoke, then supervisor review. | Missing env, public bind risk, failed NixOS test, or missing smoke evidence. | | Local verifier | QA Verifier | Codex Guardian | Re-run the local audit commands and public-safety scan. | Any command fails or secret/path pattern appears. | -| Product story | Pi | Hermes Researcher, Claude Docs | Riven naming, SkillHub story, owner-readable public narrative. | Repo mutation or unsupported product claim. | +| Product story | Pi | Hermes Researcher, Claude Docs | Raven naming, SkillHub story, owner-readable public narrative. | Repo mutation or unsupported product claim. | | Memory substrate | Memory Curator | Hermes Researcher | Dogfood evidence, provenance fields, memory packet shape. | Claims not backed by local provider/search evidence. | | SkillHub eval | Benchmark Scout | Remote Algorithm Advisor, Codex Developer | Turn `needs_eval` SkillHub items into an eval plan; do not promote them. | Treating `needs_eval` as production-ready. | | Implementation reserve | Codex Developer | OpenCode runtime when assigned | Small bounded fixes after verifier or supervisor asks. | Broad refactor, README churn, or remote mutation. | @@ -87,7 +93,7 @@ NEXT: Two local runtime-backed agent identities were created for focused lanes: -- `Pi Riven Critic`: Pi runtime, assigned on `DAS-2673` for Riven taste and +- `Pi Raven Critic`: Pi runtime, assigned on `DAS-2673` for Raven taste and product-boundary review. - `OpenCode Patch Scout`: Opencode runtime, assigned on `DAS-2674` for bounded local implementation scouting. @@ -122,7 +128,7 @@ NEXT: No lane may mark the remote deploy path `PASS` until all of these are true: -- `DAS-2669` has `AUTH_REPAIRED`. +- `DAS-2669` has accepted `AUTH_REPAIRED VERDICT: PASS`. - The guarded NixOS test lane succeeds. - The remote loopback full smoke retrieves stored memory. - Supervisor review returns `PASS`. From 5d801ea005f354de8aa4cf9015b5c474f06777a9 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 10:57:26 -0400 Subject: [PATCH 22/27] chore(everos-memory): bump deploy manifests + algo-profile Refreshes deploy/nixos/ packet, README, and env example; bumps justfile and package.json. Bootstraps .algo-profile/ with the circular chat buffer decision note used in raven-console TUI. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../.algo-profile/README.md | 6 ++ .../structures/circular-chat-buffer.md | 22 ++++++ .../deploy/nixos/DEPLOY_PACKET.md | 14 ++-- .../deploy/nixos/README.md | 14 ++-- .../deploy/nixos/evercore.env.example | 6 +- use-cases/hermes-everos-memory/justfile | 71 +++++++++++++++++++ use-cases/hermes-everos-memory/package.json | 14 ++++ 7 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 use-cases/hermes-everos-memory/.algo-profile/README.md create mode 100644 use-cases/hermes-everos-memory/.algo-profile/structures/circular-chat-buffer.md diff --git a/use-cases/hermes-everos-memory/.algo-profile/README.md b/use-cases/hermes-everos-memory/.algo-profile/README.md new file mode 100644 index 00000000..5b15ba0f --- /dev/null +++ b/use-cases/hermes-everos-memory/.algo-profile/README.md @@ -0,0 +1,6 @@ +# Algorithm Profile — Hermes EverOS Memory + +## Structures + +- [Circular Chat Buffer](structures/circular-chat-buffer.md) — O(1) bounded + transcript append/evict, used in `raven-console/src/tui.rs`. diff --git a/use-cases/hermes-everos-memory/.algo-profile/structures/circular-chat-buffer.md b/use-cases/hermes-everos-memory/.algo-profile/structures/circular-chat-buffer.md new file mode 100644 index 00000000..1acd43a3 --- /dev/null +++ b/use-cases/hermes-everos-memory/.algo-profile/structures/circular-chat-buffer.md @@ -0,0 +1,22 @@ +--- +algorithm: Circular Buffer +category: structures +complexity_time: O(1) +complexity_space: O(k) +used_in: raven-console/src/tui.rs +date: 2026-05-15 +--- + +## Why This Was Chosen + +Hermes Chat transcript 是固定窗口队列:新消息追加,旧消息淘汰。`VecDeque` +更贴合 FIFO/ring-buffer 语义,避免 `Vec` 从头部 `drain` 时搬移元素。 + +## Implementation Notes + +`CHAT_HISTORY_LIMIT` 固定为 24。每次追加前检查容量,满了就 `pop_front()`, +再 `push_back()`,让长期运行 TUI 的 transcript 更新保持 O(1)。 + +## Reference + +javascript-algorithms data structures decision guide: Circular Queue / Queue. diff --git a/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md b/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md index 70abcfa4..1740630d 100644 --- a/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md +++ b/use-cases/hermes-everos-memory/deploy/nixos/DEPLOY_PACKET.md @@ -28,7 +28,7 @@ Before applying it remotely, confirm: - the target checkout is clean or intentionally dirty; - the remote env file exists outside git; -- provider keys are installed only on the host; +- DeepSeek/OpenRouter provider key is installed only on the host; - `bindHost` remains `127.0.0.1` unless the operator explicitly approves a private network exposure; - `nixos-rebuild test` passes before `switch`. @@ -42,7 +42,8 @@ Before applying it remotely, confirm: 4. Run the workhorse rebuild in test mode. 5. Start or restart `evercore-compose.service`. 6. Run `scripts/evercore-remote-smoke.sh --mode health`. -7. After LLM/vector/rerank providers are configured, run +7. After DeepSeek/OpenRouter LLM auth plus vector/rerank providers are + configured, run `scripts/evercore-remote-smoke.sh --mode full`. 8. Point Hermes at `EVEROS_API_BASE_URL=http://127.0.0.1:1995` on the same host, or at an operator-controlled private route. @@ -54,6 +55,7 @@ Keep deployment blocked if any of these are true: - the API binds to a public interface without explicit approval; - any data service port is exposed outside Docker/private host boundaries; - the env file contains placeholder secrets during full smoke; +- DeepSeek/OpenRouter auth preflight fails; - `evercore-api` starts without a mounted `/app/.env`; - health passes but full write/search fails and the provider is marked `PASS`; - full smoke search returns zero retrievable memories after flush; @@ -79,6 +81,7 @@ From this repo: ```bash bash -n use-cases/hermes-everos-memory/deploy/nixos/scripts/evercore-remote-smoke.sh +cd use-cases/hermes-everos-memory && just deepseek-auth-preflight EVERCORE_REPO_ROOT=$PWD \ EVERCORE_ENV_FILE=$PWD/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example \ docker-compose --env-file use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example \ @@ -88,6 +91,7 @@ EVERCORE_ENV_FILE=$PWD/use-cases/hermes-everos-memory/deploy/nixos/evercore.env. From the remote host after configuration: ```bash +repo/use-cases/hermes-everos-memory/scripts/deepseek-auth-preflight.sh --env evercore.env --require-key systemctl status evercore-compose.service systemctl status evercore-health.timer scripts/evercore-remote-smoke.sh --mode health @@ -96,6 +100,6 @@ scripts/evercore-remote-smoke.sh --mode full ## Next Concrete Action -Stage this packet into the Windburn workhorse lane, add the EverCore module to -the host config, and run `nixos-rebuild test` with the private env file present -on the host. Keep `switch` blocked until `test` passes. +Repair the runtime lane by using the DeepSeek/OpenRouter auth path, prove it +with `deepseek-auth-preflight.sh --require-key`, then resume the guarded +`nixos-rebuild test` path. Keep `switch` blocked until `test` passes. diff --git a/use-cases/hermes-everos-memory/deploy/nixos/README.md b/use-cases/hermes-everos-memory/deploy/nixos/README.md index a85cdff1..0ca43a75 100644 --- a/use-cases/hermes-everos-memory/deploy/nixos/README.md +++ b/use-cases/hermes-everos-memory/deploy/nixos/README.md @@ -18,6 +18,7 @@ Decision: | `evercore.env.example` | Sanitized remote env template; copy to `evercore.env` outside git | | `evercore-remote-workhorse.nix` | Optional NixOS module for the workhorse | | `scripts/evercore-remote-smoke.sh` | Public-safe health/write/search smoke helper | +| `../../scripts/deepseek-auth-preflight.sh` | Public-safe DeepSeek/OpenRouter auth-shape check | ## Security Contract @@ -57,6 +58,9 @@ On the remote host: ```bash cp evercore.env.example evercore.env $EDITOR evercore.env +repo/use-cases/hermes-everos-memory/scripts/deepseek-auth-preflight.sh \ + --env evercore.env \ + --require-key export EVERCORE_REPO_ROOT=/srv/windburn/evercore/repo export EVERCORE_ENV_FILE=/srv/windburn/evercore/evercore.env @@ -121,11 +125,13 @@ keep the provider config pointed at the local endpoint exposed by that route. `PASS` for deploy readiness requires: 1. `docker-compose ps` shows every service healthy. -2. `scripts/evercore-remote-smoke.sh --mode health` passes. -3. `scripts/evercore-remote-smoke.sh --mode full` passes after provider keys are +2. `deepseek-auth-preflight.sh --env --require-key` passes + without printing secrets. +3. `scripts/evercore-remote-smoke.sh --mode health` passes. +4. `scripts/evercore-remote-smoke.sh --mode full` passes after provider keys are installed. -4. Hermes provider `everos_health`, `everos_store`, and `everos_search` all pass. -5. No public data ports are reachable from outside the private host boundary. +5. Hermes provider `everos_health`, `everos_store`, and `everos_search` all pass. +6. No public data ports are reachable from outside the private host boundary. ## Current Remote Disposition diff --git a/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example b/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example index 3a2947cd..d81de44d 100644 --- a/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example +++ b/use-cases/hermes-everos-memory/deploy/nixos/evercore.env.example @@ -18,13 +18,14 @@ API_BASE_URL=http://127.0.0.1:1995 # Tenant scope. Keep v0 single-tenant unless a real multi-tenant gate exists. TENANT_SINGLE_TENANT_ID=t_everos_remote -# LLM provider. Fill one provider path before running full memory smoke. +# LLM provider. Remote auth is pinned to DeepSeek through OpenRouter. LLM_PROVIDER=openrouter -LLM_MODEL=x-ai/grok-4-fast +LLM_MODEL=deepseek/deepseek-chat LLM_TEMPERATURE=0.3 LLM_MAX_TOKENS=32768 LLM_API_KEY=change-me LLM_BASE_URL=https://openrouter.ai/api/v1 +LLM_OPENROUTER_PROVIDER=deepseek OPENROUTER_API_KEY=change-me OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 OPENAI_API_KEY=change-me @@ -86,4 +87,3 @@ RECALL_MULTIPLIER=2 MILVUS_SIMILARITY_THRESHOLD=0.6 RERANK_SCORE_THRESHOLD=0.6 AGENTIC_ROUND1_RERANK_TOP_N=10 - diff --git a/use-cases/hermes-everos-memory/justfile b/use-cases/hermes-everos-memory/justfile index 96ccc475..ef1051a8 100644 --- a/use-cases/hermes-everos-memory/justfile +++ b/use-cases/hermes-everos-memory/justfile @@ -18,6 +18,9 @@ provider-load: dogfood-smoke mode="provider-only": scripts/dogfood-smoke.sh --mode "{{mode}}" +deepseek-auth-preflight file="deploy/nixos/evercore.env.example": + scripts/deepseek-auth-preflight.sh --env "{{file}}" + skillhub-sample: node bin/skillhub-packet.mjs validate skillhub/fixtures/raven-skillhub-sample.json @@ -51,6 +54,74 @@ raven-render: raven-verify: node bin/raven-run.mjs verify raven/fixtures/doomsday-run.json +raven-help: + bin/raven --help + +raven-status: + bin/raven status + +raven-packet: + bin/raven packet show + +raven-gates: + bin/raven gates + +raven-research-lanes: + bin/raven research lanes + +raven-research-packet-smoke: + bin/raven research packet native-feel --output - + +raven-research-synthesis: + bin/raven research synthesize + +raven-agents: + bin/raven agents list + +raven-doctor: + bin/raven doctor + +raven-native-audit: + bin/raven native-audit + +raven-runs: + bin/raven runs list + +raven-sc: + bin/raven sc + +raven-sc-status: + bin/raven sc status + +raven-sc-sessions: + bin/raven sc sessions + +raven-sc-providers: + bin/raven sc providers + +raven-sc-worktree: + bin/raven sc worktree + +raven-run-verify: + bin/raven run verify + +raven-chat-smoke: + RAVEN_HERMES_BIN=/bin/echo bin/raven chat send raven chat smoke + +raven-chat-receipt-smoke: + RAVEN_HERMES_BIN=/bin/echo bin/raven chat send --receipt - raven chat smoke + +raven-repl-smoke: + printf '/status\n/gates\n/research native-feel\n/chat raven chat smoke\n/memory raven\n/agents\n/runs\n/audit\n/quit\n' | RAVEN_HERMES_BIN=/bin/echo bin/raven repl + +raven-tui-smoke: + RAVEN_TUI_ONCE=1 bin/raven tui + +raven-console-check: + cargo fmt --manifest-path raven-console/Cargo.toml --check + cargo clippy --manifest-path raven-console/Cargo.toml -- -D warnings + cargo test --manifest-path raven-console/Cargo.toml + mock-openai-check: node bin/mock-openai-compatible.mjs --check diff --git a/use-cases/hermes-everos-memory/package.json b/use-cases/hermes-everos-memory/package.json index 72e524ef..f3e9704e 100644 --- a/use-cases/hermes-everos-memory/package.json +++ b/use-cases/hermes-everos-memory/package.json @@ -8,12 +8,26 @@ "health": "node bin/everos-memory.mjs health", "search": "node bin/everos-memory.mjs search", "sync-smoke": "node bin/everos-memory.mjs sync-smoke", + "deepseek:auth-preflight": "scripts/deepseek-auth-preflight.sh --env deploy/nixos/evercore.env.example", "skillhub:sample": "node bin/skillhub-packet.mjs validate skillhub/fixtures/raven-skillhub-sample.json", "skillhub:check": "node bin/skillhub-mock-api.mjs --check", "skillhub:smoke": "scripts/skillhub-api-smoke.sh", "skillhub:serve": "node bin/skillhub-mock-api.mjs", "raven:sample": "node bin/raven-run.mjs validate raven/fixtures/doomsday-run.json", "raven:render": "node bin/raven-run.mjs render raven/fixtures/doomsday-run.json", + "raven:status": "bin/raven status", + "raven:doctor": "bin/raven doctor", + "raven:gates": "bin/raven gates", + "raven:agents": "bin/raven agents list", + "raven:research-lanes": "bin/raven research lanes", + "raven:research-packet": "bin/raven research packet native-feel --output -", + "raven:research-synthesis": "bin/raven research synthesize", + "raven:runs": "bin/raven runs list", + "raven:native-audit": "bin/raven native-audit", + "raven:verify": "bin/raven run verify", + "raven:chat-smoke": "RAVEN_HERMES_BIN=/bin/echo bin/raven chat send raven chat smoke", + "raven:repl-smoke": "printf '/status\\n/gates\\n/research native-feel\\n/chat raven chat smoke\\n/memory raven\\n/agents\\n/runs\\n/audit\\n/quit\\n' | RAVEN_HERMES_BIN=/bin/echo bin/raven repl", + "raven:tui-smoke": "RAVEN_TUI_ONCE=1 bin/raven tui", "mock-openai:check": "node bin/mock-openai-compatible.mjs --check", "mock-openai": "node bin/mock-openai-compatible.mjs", "test": "node bin/everos-memory.mjs self-test" From e4234c710b8c613ab5daec93d1fdb0c833dd6ed1 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 10:58:40 -0400 Subject: [PATCH 23/27] docs(everos): record raven-v2-closure landing Appends the Raven v2 closure landing note to COMPLETION_AUDIT.md with PR URL for the docs_report gate. Closes out the goalv3-cc goal raven-v2-closure receipt trail. Co-Authored-By: Claude Opus 4.7 (1M context) --- use-cases/hermes-everos-memory/COMPLETION_AUDIT.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md b/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md index 7e497537..45eb1bdf 100644 --- a/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md +++ b/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md @@ -77,3 +77,9 @@ rg -n -i -f use-cases/hermes-everos-memo packet and SkillHub contracts. - SkillHub write routes remain proposed until EverMe backend constraints are available. + +## Raven v2 closure — landed 2026-05-15 + +Closeout via goalv3-cc goal `raven-v2-closure`: 7-commit batch landed on +`gemini-cli-workspace`, PR open at https://github.com/Fearvox/EverOS/pull/31 +targeting `main`. From 80c6c6bda5dc28e5a3932efd4467d2b68f58f6c5 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 11:36:41 -0400 Subject: [PATCH 24/27] docs(upstream-return): record 2026-05-15 recheck + #191/#93/#78 slice scope Captures the targeted upstream recheck: issues #191/#93/#78 still open; PRs #185/#211 BLOCKED; #89/#109/#138 DIRTY. Marks local current-tree slice as superseding #185/#211/#138/#109 in PR_MATRIX. Narrows the return-slice scope in goal.md and updates owner-brief priorities. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/upstream-return/OWNER_BRIEF.md | 24 +++++++++++------------ docs/upstream-return/PR_MATRIX.md | 12 +++++++----- docs/upstream-return/UPSTREAM_STRATEGY.md | 24 ++++++++++++++--------- docs/upstream-return/goal.md | 15 +++++++++++++- 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/docs/upstream-return/OWNER_BRIEF.md b/docs/upstream-return/OWNER_BRIEF.md index fcef1d78..6cbc748f 100644 --- a/docs/upstream-return/OWNER_BRIEF.md +++ b/docs/upstream-return/OWNER_BRIEF.md @@ -1,15 +1,15 @@ # Owner Brief 1. Live upstream queue on 2026-05-14: 52 open issues, 37 open PRs. -2. Only one open PR is GitHub-clean: #128, but its file path looks legacy and still needs path relevance review. -3. Four PRs are blocked: #211, #206, #202, #185. -4. The highest-value small-review candidates are #211 (#93), #185 (#191), and #202 (#150/#139). -5. #78 should have one canonical multi-memory-type fix; close #89/#109 after choosing #138 or requesting a new narrow patch. -6. #127 is important, but #136 is too broad; request a focused filename mismatch patch with repro. -7. #131 likewise needs a narrow full-episode patch; #132 is too broad. -8. OpenClaw fixes must be checked against current paths; several PRs still touch legacy `methods/evermemos` or plugin-root paths. -9. Delete/reset/cascade memory semantics (#14/#148) need an owner API decision before code. -10. Benchmark reproducibility issues (#73/#3/#195/#87) need an official matrix, not isolated replies. -11. Provider/deployment requests (#29/#23/#21/#4/#1) should become a supported-provider decision. -12. Most cleanup PRs are duplicates; pick one narrow bug-linked cleanup path and close the rest. -13. Recommended next maintainer action: review #211, #185, #202, then publish the duplicate/stale PR closeout policy. +2. Targeted 2026-05-15 recheck: #191/#93/#78 are still open; #185/#211 are `BLOCKED`; #89/#109/#138 are `DIRTY`. +3. First returnable slice is ready locally: #191 README POST search example, #93 202 Accepted demo handling, #78 multi-memory-type retrieval. +4. The slice should go upstream as one narrow current-tree PR, not as rebases of #185/#211/#89/#109/#138. +5. Required verification for that PR: targeted pytest, black check, `git diff --check`, and reviewer pass on API contract. +6. Next small-review candidate after this slice is #202 for OpenClaw docs if it matches the current plugin path. +7. #127 is important, but #136 is too broad; request a focused filename mismatch patch with repro. +8. #131 likewise needs a narrow full-episode patch; #132 is too broad. +9. OpenClaw fixes must be checked against current paths; several PRs still touch legacy `methods/evermemos` or plugin-root paths. +10. Delete/reset/cascade memory semantics (#14/#148) need an owner API decision before code. +11. Benchmark reproducibility issues (#73/#3/#195/#87) need an official matrix, not isolated replies. +12. Provider/deployment requests (#29/#23/#21/#4/#1) should become a supported-provider decision. +13. Most cleanup PRs are duplicates; pick one narrow bug-linked cleanup path and close the rest. diff --git a/docs/upstream-return/PR_MATRIX.md b/docs/upstream-return/PR_MATRIX.md index 4c59bb56..4ef1201a 100644 --- a/docs/upstream-return/PR_MATRIX.md +++ b/docs/upstream-return/PR_MATRIX.md @@ -1,25 +1,27 @@ # Upstream PR Matrix Live source: `EverMind-AI/EverOS` open PRs fetched on 2026-05-14. GitHub reported 37 open PRs: 1 `CLEAN`, 4 `BLOCKED`, and 32 `DIRTY`. +Targeted recheck on 2026-05-15 confirmed #185/#211 remain `BLOCKED` and +#89/#109/#138 remain `DIRTY`. This matrix treats `DIRTY` as needing rebase before merge review. `BLOCKED` means GitHub does not currently report the branch as cleanly mergeable; owner review is still required to distinguish checks, conflicts, and policy gates. | PR | Merge state | Family | File surface | Related issues | Verdict | Owner action | |---|---|---|---|---|---|---| | [#213](https://github.com/EverMind-AI/EverOS/pull/213) docs links to project repos | DIRTY | README docs | `README.md` | none | needs rebase | Rebase against current README or close if superseded by local README restructuring. | -| [#211](https://github.com/EverMind-AI/EverOS/pull/211) handle 202 Accepted in demo store | BLOCKED | Demo/API UX | `methods/EverCore/demo/utils/simple_memory_manager.py` | #93 | needs review | High-value small fix; inspect checks/conflict and merge after demo smoke. | +| [#211](https://github.com/EverMind-AI/EverOS/pull/211) handle 202 Accepted in demo store | BLOCKED | Demo/API UX | `methods/EverCore/demo/utils/simple_memory_manager.py` | #93 | superseded by local slice | Keep as source evidence; local current-tree patch includes the 202 Accepted behavior plus focused test. | | [#206](https://github.com/EverMind-AI/EverOS/pull/206) MinIO credentials from env | BLOCKED | Security/config | `methods/EverCore/docker-compose.yaml`, `methods/EverCore/env.template` | infra/security | needs review | Security-positive, but owner must verify default DX and env template migration. | | [#202](https://github.com/EverMind-AI/EverOS/pull/202) OpenClaw endpoint docs | BLOCKED | OpenClaw docs | `methods/EverCore/examples/openclaw-plugin/SKILL.md` | #150, #139 | needs review | Current-path doc PR; compare against local fork OpenClaw packet before merge. | | [#196](https://github.com/EverMind-AI/EverOS/pull/196) v0 -> v1 API migration | DIRTY | API/docs migration | broad `methods/evermemos/...` surface | #191 | close/rework | Old path and broad scope; replace with narrow current-tree migration. | | [#189](https://github.com/EverMind-AI/EverOS/pull/189) OpenClaw plugin call API fail | DIRTY | OpenClaw plugin | `methods/evermemos/examples/openclaw-plugin/src/api.js` | #150, #139 | close/rework | Old path surface; supersede with current `methods/EverCore` plugin work. | -| [#185](https://github.com/EverMind-AI/EverOS/pull/185) README search example | BLOCKED | README docs | `README.md` | #191 | needs review | Prefer this narrow docs fix if conflict is small; compare with #196. | +| [#185](https://github.com/EverMind-AI/EverOS/pull/185) README search example | BLOCKED | README docs | `README.md` | #191 | superseded by local slice | Keep as source evidence; local current-tree patch applies the narrow POST search-example correction. | | [#159](https://github.com/EverMind-AI/EverOS/pull/159) query expansion rewrite | DIRTY | Memory retrieval | `src/agentic_layer/memory_manager.py`, `src/memory_layer/query_expansion.py` | #65/#34 class | needs maintainer decision | Architecture-level retrieval change; needs design review before rebase spend. | | [#157](https://github.com/EverMind-AI/EverOS/pull/157) production Dockerfile | DIRTY | Infra/Docker | `.dockerignore`, `Dockerfile` | #21 | needs maintainer decision | Decide official Docker support shape first. | | [#154](https://github.com/EverMind-AI/EverOS/pull/154) anti-pattern cleanup / duplicate RRF | DIRTY | Code quality | demo, biz, repository files | #50 | needs rebase | Candidate canonical cleanup if narrowed; overlaps #97/#141/#137. | | [#144](https://github.com/EverMind-AI/EverOS/pull/144) MiniMax provider | DIRTY | Provider config | env/provider/test files under `methods/evermemos` | #29/#23 class | close/rework | Old path and provider-specific; decide provider roadmap before patch. | | [#141](https://github.com/EverMind-AI/EverOS/pull/141) duplicate RRF in demo | DIRTY | Code quality | `demo/utils/simple_memory_manager.py` | #50 | duplicate | Close after selecting #154 or a new narrow PR. | | [#140](https://github.com/EverMind-AI/EverOS/pull/140) normalize plugin-wrapped content | DIRTY | Memory extraction | extractor + test | memory quality | needs rebase | Potentially useful; requires focused extractor test. | -| [#138](https://github.com/EverMind-AI/EverOS/pull/138) multiple memory types search | DIRTY | Memory API bug | API DTO/controller/service/search files | #78 | needs rebase | Best canonical candidate for #78 if rebased and tested. | +| [#138](https://github.com/EverMind-AI/EverOS/pull/138) multiple memory types search | DIRTY | Memory API bug | API DTO/controller/service/search files | #78 | superseded by local slice | Local current-tree patch implements the narrow MemoryManager behavior with focused tests; avoid rebasing broad API surface unless reviewer finds a missing contract. | | [#137](https://github.com/EverMind-AI/EverOS/pull/137) Python anti-patterns | DIRTY | Code quality | biz/repository files | code quality | duplicate | Overlaps #154/#126/#112/#110; close or split into targeted lint PR. | | [#136](https://github.com/EverMind-AI/EverOS/pull/136) BM25/Embedding filename mismatch | DIRTY | Benchmark correctness | broad demo/docs/eval/src/tests | #127 | close/rework | The bug is high priority, but PR is too broad; request focused patch and repro. | | [#135](https://github.com/EverMind-AI/EverOS/pull/135) AP Memory Agent demo | DIRTY | Demo/use case | demo app, pyproject, lockfile, memorize path | use cases | needs maintainer decision | Large demo + lockfile impact; owner must decide product fit. | @@ -33,7 +35,7 @@ This matrix treats `DIRTY` as needing rebase before merge review. `BLOCKED` mean | [#113](https://github.com/EverMind-AI/EverOS/pull/113) bool comparison | DIRTY | Code quality | biz/repository files | code quality | duplicate | Low-risk but overlaps cleanup wave; close or batch. | | [#112](https://github.com/EverMind-AI/EverOS/pull/112) docstring/bare except/timestamp | DIRTY | Code quality/data consistency | demo, memory manager, biz, repo, prompt | #48 class | duplicate | Overlaps #108/#110/#126/#154. | | [#110](https://github.com/EverMind-AI/EverOS/pull/110) bare except/ISO timestamp/docstring | DIRTY | Code quality/data consistency | demo, db ops, repo, prompt | #48 class | duplicate | Close after canonical timestamp/cleanup path chosen. | -| [#109](https://github.com/EverMind-AI/EverOS/pull/109) multiple memory_types search | DIRTY | Memory API bug | `src/agentic_layer/memory_manager.py` | #78 | duplicate | Superseded by broader #138 candidate. | +| [#109](https://github.com/EverMind-AI/EverOS/pull/109) multiple memory_types search | DIRTY | Memory API bug | `src/agentic_layer/memory_manager.py` | #78 | duplicate | Superseded by the local current-tree #78 slice. | | [#108](https://github.com/EverMind-AI/EverOS/pull/108) ISO 8601 timestamp | DIRTY | Data consistency | demo, docker, db ops, repo, prompts | #48 | duplicate | Too broad; keep timestamp contract then reimplement narrow. | | [#107](https://github.com/EverMind-AI/EverOS/pull/107) bare except | DIRTY | Code quality | demo, docker, db ops, repo, tests | code quality | duplicate | Close or batch into cleanup PR. | | [#106](https://github.com/EverMind-AI/EverOS/pull/106) two phase memory extraction | DIRTY | Memory lifecycle | docs/env/worker/memorize/delete service | lifecycle | needs maintainer decision | Architecture change; needs design review. | @@ -41,5 +43,5 @@ This matrix treats `DIRTY` as needing rebase before merge review. `BLOCKED` mean | [#97](https://github.com/EverMind-AI/EverOS/pull/97) duplicate RRF | DIRTY | Code quality | config/demo/docker | #50 | duplicate | Superseded by #141/#154 or a new narrow fix. | | [#91](https://github.com/EverMind-AI/EverOS/pull/91) bare excepts | DIRTY | Code quality | db ops/repo/tests | code quality | duplicate | Close as stale cleanup duplicate. | | [#90](https://github.com/EverMind-AI/EverOS/pull/90) remove unused Mongo init volume | DIRTY | Infra/Docker | `docker-compose.yaml` | #21/#1 class | needs maintainer decision | Small infra cleanup, but should follow Docker support decision. | -| [#89](https://github.com/EverMind-AI/EverOS/pull/89) multi-memory-type search | DIRTY | Memory API bug | `src/agentic_layer/memory_manager.py` | #78 | duplicate | Superseded by #138 candidate. | +| [#89](https://github.com/EverMind-AI/EverOS/pull/89) multi-memory-type search | DIRTY | Memory API bug | `src/agentic_layer/memory_manager.py` | #78 | duplicate | Superseded by the local current-tree #78 slice. | | [#86](https://github.com/EverMind-AI/EverOS/pull/86) STARTER_KIT quick start | DIRTY | Docs/community | `docs/STARTER_KIT.md` | #57 | needs rebase | Rebase and verify links, or replace with narrow link-only PR. | diff --git a/docs/upstream-return/UPSTREAM_STRATEGY.md b/docs/upstream-return/UPSTREAM_STRATEGY.md index f8b4813e..25dcb8b4 100644 --- a/docs/upstream-return/UPSTREAM_STRATEGY.md +++ b/docs/upstream-return/UPSTREAM_STRATEGY.md @@ -2,6 +2,9 @@ Live source: upstream inventory fetched on 2026-05-14. +Targeted 2026-05-15 recheck: issues #191/#93/#78 are still open; PRs #185 +and #211 are still `BLOCKED`; PRs #89/#109/#138 are still `DIRTY`. + ## Current Queue Shape - Open issues: 52. @@ -11,30 +14,33 @@ Live source: upstream inventory fetched on 2026-05-14. ## Recommended Maintainer Order -1. Triage and merge small current-tree fixes: - - #211 for #93 if demo smoke passes. - - #185 for #191 if README conflict is small. +1. Open one narrow current-tree PR for the local #191/#93/#78 slice: + - #191: README search example uses `POST /api/v1/memories/search`. + - #93: `SimpleMemoryManager.store()` treats 202 Accepted as background success. + - #78: `MemoryManager` searches all requested non-profile memory types and + dedupes hybrid hits by `(memory_type,id)`. +2. Continue small current-tree review: - #202 for OpenClaw docs if it matches the current plugin path. -2. Resolve high-impact API bugs with one selected PR per bug: - - #78: pick #138 or request a new narrow PR; close #89/#109. +3. Resolve high-impact API bugs with one selected PR per bug: - #127: request a focused adapter/fixture fix; #136 is too broad as-is. - #131: request a narrow full-episode patch; #132 is too broad as-is. -3. Make maintainer decisions before implementation: +4. Make maintainer decisions before implementation: - delete/reset/cascade semantics (#14/#148); - lifecycle/dedup/status/session scope (#95/#143/#27); - provider/deployment support (#29/#23/#21/#4/#1); - benchmark reproducibility contract (#73/#3/#195/#87). -4. Sweep duplicated cleanup PRs: +5. Sweep duplicated cleanup PRs: - RRF duplicates: #97/#141/#154. - timestamp duplicates: #108/#110/#112/#118. - bare-except/code-quality duplicates: #91/#98/#107/#110/#112/#126/#137/#154. -5. Close or rework old-path PRs: +6. Close or rework old-path PRs: - `methods/evermemos/...` surfaces should not merge until path relevance is proven. - `evermemos-openclaw-plugin/*` should be verified against current package layout even when GitHub reports `CLEAN`. ## Fork Work That Is Worth Doing Locally -- Build a narrow current-tree patch for #191 if #185 remains blocked. +- Return the local #191/#93/#78 slice as one narrow PR after targeted tests and + reviewer pass. - Build a narrow current-tree OpenClaw docs/fix patch if #202/#128 are stale or path-wrong. - Prepare answer drafts for repeated question issues so maintainers can close low-code threads quickly. - Prepare benchmark repro notes, but do not claim result parity without running the exact benchmark path. diff --git a/docs/upstream-return/goal.md b/docs/upstream-return/goal.md index 9c56ea0f..2f1c8f05 100644 --- a/docs/upstream-return/goal.md +++ b/docs/upstream-return/goal.md @@ -168,6 +168,20 @@ Prefer a staged upstream return: The first artifact should help maintainers answer: "What should we merge, close, or ask for next?" before asking them to review new code. +## 2026-05-15 Return Slice + +The first code slice is now narrowed to #191/#93/#78: + +- #191: update the README memory search example to call + `POST /api/v1/memories/search`. +- #93: treat HTTP 202 Accepted as successful background extraction in + `SimpleMemoryManager.store()`. +- #78: search all requested non-profile `memory_types` in keyword/vector paths + and dedupe hybrid hits by `(memory_type,id)`. + +Out of scope for this slice: OpenClaw, benchmark filename mismatches, +delete/reset semantics, provider/deployment policy, and Raven/deploy work. + ## Candidate High-Leverage Tracks ### Track A: Benchmark Truth Pack @@ -225,4 +239,3 @@ Keep `OWNER_BRIEF.md` under 20 lines: - What not to touch yet - Highest-risk PRs/issues - Suggested next command or PR action - From 67c228ad6b29727c3c37f8a23ec0476851918234 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 11:54:55 -0400 Subject: [PATCH 25/27] chore(ci): restore .markdownlint config + fix 22 markdown-lint errors Root cause: this branch had dropped .markdownlint.json (kept on main), which disables MD013/MD033/MD041 etc. Without it CI fell back to stricter defaults, surfacing 22 errors across README + 3 use-case docs. Fixes: - Restore .markdownlint.json from origin/main - PR_MATRIX.md L5: rewrap to avoid leading '#' parsed as ATX heading - COMPLETION_AUDIT.md L84: wrap bare URL in <> - COMMAND_CONTRACT.md L45/L54/L55: escape '|' inside table cells - README.md L13-17 & L35-40: collapse multiple blank lines - README.md L63: disable MD001 next-line for h2->h4 use-case card jump (keeps the '#### ' regex contract used by the use-case banner validator) - README.md banner-gif lines: trim single trailing space - README.md back-to-top images: add alt text "Back to top" Local verification with markdownlint-cli2@0.17.2 against all 28 CI files: Summary: 0 error(s). Co-Authored-By: Claude Opus 4.7 (1M context) --- .markdownlint.json | 11 +++++++ README.md | 30 +++++++++---------- docs/upstream-return/PR_MATRIX.md | 3 +- .../hermes-everos-memory/COMPLETION_AUDIT.md | 2 +- .../raven/COMMAND_CONTRACT.md | 6 ++-- 5 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 .markdownlint.json diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..0fd72a76 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,11 @@ +{ + "default": true, + "MD013": false, + "MD024": { "siblings_only": true }, + "MD033": false, + "MD041": false, + "MD051": false, + "MD060": false, + "MD025": { "front_matter_title": "" }, + "MD007": { "indent": 2 } +} diff --git a/README.md b/README.md index 531acbbf..eae52a03 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ -
@@ -35,8 +34,6 @@
- - ## Project Overview **EverOS** is a unified home for applying, building, and evaluating long-term memory in self-evolving agents. The repository is organized around three essential parts: @@ -61,6 +58,7 @@ Use cases show what persistent memory makes possible in real products and workfl ![banner-gif](https://github.com/user-attachments/assets/650b901b-c9ba-4001-bac7-626b009df830) + #### Rokid AI Assistant with EverOS Connect to EverOS within Rokid Glasses enabling long-term memory for all of your smart activities. @@ -94,7 +92,7 @@ Earth Online is a memory-aware productivity game that turns everyday planning in -[![banner-gif](https://github.com/user-attachments/assets/57d8cda7-35a5-4561-b794-5520dffc917b)](https://github.com/golutra/golutra) +[![banner-gif](https://github.com/user-attachments/assets/57d8cda7-35a5-4561-b794-5520dffc917b)](https://github.com/golutra/golutra) #### Multi-Agent Orchestration Platform @@ -118,7 +116,7 @@ Record, visualize, and explore your tasting journey through an immersive 3D star -[![banner-gif](https://github.com/user-attachments/assets/93ac2a68-4f18-4fcb-8d87-80aeb00a9d7c)](https://github.com/kellyvv/OpenHer) +[![banner-gif](https://github.com/user-attachments/assets/93ac2a68-4f18-4fcb-8d87-80aeb00a9d7c)](https://github.com/kellyvv/OpenHer) #### EverOS Open Her @@ -143,7 +141,7 @@ Ruminer brings persistent memory to a browser agent so it can carry personal con -[![banner-gif](https://github.com/user-attachments/assets/c258a6c4-fe70-497a-98d1-3dade4a932f6)](https://github.com/nanxingw/EverMem) +[![banner-gif](https://github.com/user-attachments/assets/c258a6c4-fe70-497a-98d1-3dade4a932f6)](https://github.com/nanxingw/EverMem) #### EverMem Sync with EverOS @@ -168,7 +166,7 @@ MCO equips your primary agent with an agent team that can work together to solve -[![banner-gif](https://github.com/user-attachments/assets/314c9126-8e08-4688-bbbb-8555ad58cf67)](https://github.com/onenewborn/StudyBuddy-public) +[![banner-gif](https://github.com/user-attachments/assets/314c9126-8e08-4688-bbbb-8555ad58cf67)](https://github.com/onenewborn/StudyBuddy-public) #### Study Buddy with Self-Evolving Memory @@ -193,7 +191,7 @@ Empowering individuals with advanced memory support and daily assistance. -[![banner-gif](https://github.com/user-attachments/assets/e2428df3-ea11-4e88-8f9c-dad437dd8998)](https://github.com/AlexL1024/NeuralConnect) +[![banner-gif](https://github.com/user-attachments/assets/e2428df3-ea11-4e88-8f9c-dad437dd8998)](https://github.com/AlexL1024/NeuralConnect) #### Memory-Driven Multi-Agent NPC Experience @@ -305,7 +303,7 @@ Explore stored entities and relationships in a graph interface. Frontend demo; b
-[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top) +[![Back to top](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
@@ -393,7 +391,7 @@ for memory_group in result.get("memories", []):
-[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top) +[![Back to top](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
@@ -431,7 +429,7 @@ LoCoMo **92.73%**
-[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top) +[![Back to top](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
@@ -465,7 +463,7 @@ Agent self-evolution evaluation through longitudinal growth curves, transfer eff
-[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top) +[![Back to top](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
@@ -506,7 +504,7 @@ cat evaluation/results/locomo-everos/report.txt
-[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top) +[![Back to top](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
@@ -540,7 +538,7 @@ If EverOS helps your research, please cite the relevant paper:
-[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top) +[![Back to top](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
@@ -553,7 +551,7 @@ Star the repo or join the community links above to follow new architecture metho
-[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top) +[![Back to top](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
@@ -596,6 +594,6 @@ Read the [Contribution Guidelines](.github/CONTRIBUTING.md) for setup, pull requ
-[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top) +[![Back to top](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
diff --git a/docs/upstream-return/PR_MATRIX.md b/docs/upstream-return/PR_MATRIX.md index 4ef1201a..487b9857 100644 --- a/docs/upstream-return/PR_MATRIX.md +++ b/docs/upstream-return/PR_MATRIX.md @@ -1,8 +1,7 @@ # Upstream PR Matrix Live source: `EverMind-AI/EverOS` open PRs fetched on 2026-05-14. GitHub reported 37 open PRs: 1 `CLEAN`, 4 `BLOCKED`, and 32 `DIRTY`. -Targeted recheck on 2026-05-15 confirmed #185/#211 remain `BLOCKED` and -#89/#109/#138 remain `DIRTY`. +Targeted recheck on 2026-05-15 confirmed PRs #185/#211 remain `BLOCKED` and PRs #89/#109/#138 remain `DIRTY`. This matrix treats `DIRTY` as needing rebase before merge review. `BLOCKED` means GitHub does not currently report the branch as cleanly mergeable; owner review is still required to distinguish checks, conflicts, and policy gates. diff --git a/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md b/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md index 45eb1bdf..3f37c121 100644 --- a/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md +++ b/use-cases/hermes-everos-memory/COMPLETION_AUDIT.md @@ -81,5 +81,5 @@ rg -n -i -f use-cases/hermes-everos-memo ## Raven v2 closure — landed 2026-05-15 Closeout via goalv3-cc goal `raven-v2-closure`: 7-commit batch landed on -`gemini-cli-workspace`, PR open at https://github.com/Fearvox/EverOS/pull/31 +`gemini-cli-workspace`, PR open at targeting `main`. diff --git a/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md b/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md index 0f1401f9..9525f3a9 100644 --- a/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md +++ b/use-cases/hermes-everos-memory/raven/COMMAND_CONTRACT.md @@ -42,7 +42,7 @@ It does not own: | `raven repl` | slash commands | same handlers as CLI | piped smoke stays deterministic | | `raven chat send [--cwd ] [--json] [--receipt ] [--save] ` | bounded prompt text | sanitized `HermesChatTurn` or `RavenReceipt` | Hermes failure is `FLAG`, not UI crash; chat receipts cannot green remote deploy | | `raven packet show [--json]` | local packet/docs | packet summary | source docs resolve | -| `raven packet export [--output ]` | snapshot | sanitized owner packet markdown | public-safety sanitizer clean | +| `raven packet export [--output ]` | snapshot | sanitized owner packet markdown | public-safety sanitizer clean | | `raven memory health [--json]` | EverOS bridge | health verdict | provider failure is `FLAG`, not crash | | `raven memory search [--json]` | query text | bounded memory refs | empty query is `FLAG` | | `raven agents list [--json]` | Multica watch issues | agent/watch table | unavailable Multica falls back to `FLAG` | @@ -51,8 +51,8 @@ It does not own: | `raven research packet [--json] [--output ]` | research ledger + live remote gates | `RavenResearchPacket` | live `DAS-2666/2669` red gates force `FLAG` context | | `raven research synthesize [--json] [--output ]` | completed research packets | synthesis readiness report | less than three packets stays `FLAG`; no architecture packet | | `raven runs list [--json]` | saved receipts or packet gates | run/receipt table | receipts read from gitignored local dir | -| `raven sc [all|status|sessions|providers|worktree] [--json]` | Superconductor socket via thin CLI | `ScReport` or focused view | unavailable socket or merge-base failure is `FLAG`, never a crash | -| `raven run verify [--receipt ] [--save]` | local run packet | `RavenReceipt` or human output | local verifier cannot green remote deploy | +| `raven sc [all\|status\|sessions\|providers\|worktree] [--json]` | Superconductor socket via thin CLI | `ScReport` or focused view | unavailable socket or merge-base failure is `FLAG`, never a crash | +| `raven run verify [--receipt ] [--save]` | local run packet | `RavenReceipt` or human output | local verifier cannot green remote deploy | | `raven doctor [--json]` | toolchain/files/bridge | dependency report | missing hard local dependency blocks | | `raven native-audit [--json]` | source + audit doc | UX/safety gate report | hard UX/safety failure blocks `PASS` | From b43dbb711da05cbe3c1f4912c4364b772553c698 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 18:05:33 -0400 Subject: [PATCH 26/27] style: trim trailing blank lines across test/config/script files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure whitespace cleanup — drops the final blank line before EOF in 6 files. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) --- methods/EverCore/examples/openclaw-plugin/test/engine.test.js | 1 - use-cases/hermes-everos-memory/bin/everos-memory.mjs | 1 - .../hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml | 1 - use-cases/hermes-everos-memory/scripts/check-provider-load.sh | 1 - .../skillhub/fixtures/raven-skillhub-sample.json | 1 - use-cases/hermes-everos-memory/skillhub/schema.json | 1 - 6 files changed, 6 deletions(-) diff --git a/methods/EverCore/examples/openclaw-plugin/test/engine.test.js b/methods/EverCore/examples/openclaw-plugin/test/engine.test.js index 89540885..a9b1491e 100644 --- a/methods/EverCore/examples/openclaw-plugin/test/engine.test.js +++ b/methods/EverCore/examples/openclaw-plugin/test/engine.test.js @@ -23,4 +23,3 @@ test("passive memory engine does not expose a compact capability", () => { assert.equal(engine.info.ownsCompaction, false); assert.equal(Object.hasOwn(engine, "compact"), false); }); - diff --git a/use-cases/hermes-everos-memory/bin/everos-memory.mjs b/use-cases/hermes-everos-memory/bin/everos-memory.mjs index b75fd32b..347e3361 100755 --- a/use-cases/hermes-everos-memory/bin/everos-memory.mjs +++ b/use-cases/hermes-everos-memory/bin/everos-memory.mjs @@ -157,4 +157,3 @@ main().catch((error) => { console.error(JSON.stringify({ ok: false, error: error.message })); process.exit(1); }); - diff --git a/use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml b/use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml index 29ef53a2..acacb174 100644 --- a/use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml +++ b/use-cases/hermes-everos-memory/deploy/nixos/docker-compose.remote.yaml @@ -177,4 +177,3 @@ volumes: networks: evercore-network: driver: bridge - diff --git a/use-cases/hermes-everos-memory/scripts/check-provider-load.sh b/use-cases/hermes-everos-memory/scripts/check-provider-load.sh index 0c08eeb4..11a0dfb6 100755 --- a/use-cases/hermes-everos-memory/scripts/check-provider-load.sh +++ b/use-cases/hermes-everos-memory/scripts/check-provider-load.sh @@ -35,4 +35,3 @@ assert expected.issubset(set(tools)), tools print("PASS provider-load everos " + ",".join(tools)) PY - diff --git a/use-cases/hermes-everos-memory/skillhub/fixtures/raven-skillhub-sample.json b/use-cases/hermes-everos-memory/skillhub/fixtures/raven-skillhub-sample.json index aa3f1cb8..9fbd8693 100644 --- a/use-cases/hermes-everos-memory/skillhub/fixtures/raven-skillhub-sample.json +++ b/use-cases/hermes-everos-memory/skillhub/fixtures/raven-skillhub-sample.json @@ -17,4 +17,3 @@ ], "body_markdown": "# Operator Memory Recall\n\n## When to use\nBefore an agent run starts, search durable project memory for prior decisions, constraints, and open gates.\n\n## Technique\nUse a short query that includes the project name, the active goal, and the decision surface. Prefer evidence-backed memories over summary-only memories.\n\n## Anti-pattern\nDo not call memory recall a PASS unless the current run proves health, store, search, and recall." } - diff --git a/use-cases/hermes-everos-memory/skillhub/schema.json b/use-cases/hermes-everos-memory/skillhub/schema.json index 7337c530..28370740 100644 --- a/use-cases/hermes-everos-memory/skillhub/schema.json +++ b/use-cases/hermes-everos-memory/skillhub/schema.json @@ -102,4 +102,3 @@ } } } - From e1dd1381454bf5afe6a7caf248b78a0cc7e88c97 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Fri, 15 May 2026 18:05:33 -0400 Subject: [PATCH 27/27] refactor(raven-console): drop unused relative_path_exists helper The dead-code helper was carried with #[allow(dead_code)]; nothing in the crate calls it. Removing the function lets us narrow the std::path import to just PathBuf. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../hermes-everos-memory/raven-console/src/commands.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/use-cases/hermes-everos-memory/raven-console/src/commands.rs b/use-cases/hermes-everos-memory/raven-console/src/commands.rs index ff11c4dc..a4e53b69 100644 --- a/use-cases/hermes-everos-memory/raven-console/src/commands.rs +++ b/use-cases/hermes-everos-memory/raven-console/src/commands.rs @@ -4,7 +4,7 @@ use crate::context::Context; use crate::model::{DoctorCheck, DoctorReport, Verdict}; use crate::{output, receipt, repl, research, snapshot, tui, RavenResult}; use clap::{Parser, Subcommand}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process::Command; #[derive(Parser)] @@ -633,8 +633,3 @@ fn command_check(program: &str, args: &[&str]) -> DoctorCheck { }, } } - -#[allow(dead_code)] -fn relative_path_exists(root: &Path, relative: &str) -> bool { - root.join(relative).exists() -}