openhermes 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +281 -0
- package/autorecall.mjs +167 -0
- package/bootstrap.mjs +255 -0
- package/curator.mjs +470 -0
- package/harness/commands/build-fix.md +60 -0
- package/harness/commands/code-review.md +71 -0
- package/harness/commands/doctor.md +42 -0
- package/harness/commands/learn.md +37 -0
- package/harness/commands/memory-search.md +37 -0
- package/harness/commands/plan.md +53 -0
- package/harness/commands/security.md +93 -0
- package/harness/constitution/soul.md +76 -0
- package/harness/instructions/RUNTIME.md +21 -0
- package/harness/prompts/architect.txt +175 -0
- package/harness/prompts/build-error-resolver.md +37 -0
- package/harness/prompts/code-reviewer.md +33 -0
- package/harness/prompts/e2e-runner.txt +305 -0
- package/harness/prompts/explore.md +29 -0
- package/harness/prompts/planner.md +30 -0
- package/harness/prompts/security-reviewer.md +35 -0
- package/harness/rules/audit.md +84 -0
- package/harness/rules/checkpointing.md +75 -0
- package/harness/rules/context-loading.md +33 -0
- package/harness/rules/credential-exposure.md +0 -0
- package/harness/rules/delegation.md +76 -0
- package/harness/rules/memory-management.md +28 -0
- package/harness/rules/precedence.md +52 -0
- package/harness/rules/promotion.md +46 -0
- package/harness/rules/ranking.md +64 -0
- package/harness/rules/retrieval.md +94 -0
- package/harness/rules/runtime-guards.md +196 -0
- package/harness/rules/self-heal.md +79 -0
- package/harness/rules/session-start.md +34 -0
- package/harness/rules/skills-management.md +165 -0
- package/harness/rules/state-drift.md +192 -0
- package/harness/rules/verification.md +88 -0
- package/harness/skills/.bundled_manifest +17 -0
- package/harness/skills/.usage.json +6 -0
- package/harness/skills/api-design/SKILL.md +523 -0
- package/harness/skills/backend-patterns/SKILL.md +598 -0
- package/harness/skills/coding-standards/SKILL.md +549 -0
- package/harness/skills/e2e-testing/SKILL.md +326 -0
- package/harness/skills/frontend-patterns/SKILL.md +642 -0
- package/harness/skills/frontend-slides/SKILL.md +184 -0
- package/harness/skills/security-review/SKILL.md +495 -0
- package/harness/skills/strategic-compact/SKILL.md +131 -0
- package/harness/skills/tdd-workflow/SKILL.md +463 -0
- package/harness/skills/verification-loop/SKILL.md +126 -0
- package/index.mjs +5 -0
- package/lib/hardening.mjs +113 -0
- package/lib/memory-tools-plugin.mjs +265 -0
- package/lib/schema-validator.mjs +77 -0
- package/lib/tools/_memory.mjs +230 -0
- package/lib/tools/hm_get.mjs +13 -0
- package/lib/tools/hm_latest.mjs +12 -0
- package/lib/tools/hm_list.mjs +13 -0
- package/lib/tools/hm_put.mjs +14 -0
- package/lib/tools/hm_search.mjs +16 -0
- package/package.json +49 -0
- package/schemas/audit.schema.json +61 -0
- package/schemas/backlog.schema.json +42 -0
- package/schemas/checkpoint.schema.json +44 -0
- package/schemas/constraint.schema.json +41 -0
- package/schemas/decision.schema.json +42 -0
- package/schemas/instinct.schema.json +42 -0
- package/schemas/loop-state.schema.json +33 -0
- package/schemas/mistake.schema.json +43 -0
- package/schemas/verification_receipt.schema.json +67 -0
- package/skill-builder.mjs +113 -0
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">☤ OpenHermes</h1>
|
|
3
|
+
<p align="center"><i>The Constitutional Router for <a href="https://opencode.ai">OpenCode</a></i></p>
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/openhermes"><img src="https://img.shields.io/npm/v/openhermes?style=for-the-badge&label=version&color=FFD700" alt="npm version"></a>
|
|
8
|
+
<a href="https://github.com/nathwn12/openhermes/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=for-the-badge" alt="License: MIT"></a>
|
|
9
|
+
<a href="https://opencode.ai"><img src="https://img.shields.io/badge/runs%20on-OpenCode-6366f1?style=for-the-badge" alt="Runs on OpenCode"></a>
|
|
10
|
+
<a href="https://github.com/nathwn12/openhermes/issues"><img src="https://img.shields.io/badge/issues-welcome-orange?style=for-the-badge" alt="Issues welcome"></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
**Your OpenCode agent, leveled up.** Add it to your plugins — your agent gains a personality, a memory, a conscience, 7 specialist subagents, 7 slash commands, 5 native memory tools, 10 procedural skills, and the discipline to self-improve.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i openhermes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
> ☤ **Inspired by [Hermes Agent](https://github.com/NousResearch/hermes-agent)** — Nous Research's self-improving agent that brought closed learning loops, skill creation, and cross-session memory to the agent ecosystem. OpenHermes reimagines that vision **native to the OpenCode platform**: zero dependencies, no sidecars, no installers. Your entire agent OS in a single npm package.
|
|
24
|
+
>
|
|
25
|
+
> **Pruning inspiration**: The autonomous context-pressure system is heavily inspired by [Opencode-DCP (Dynamic Context Pruning)](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning). The curator plugin's compaction-trigger logic and recall-cache freshness model are direct ports of DCP's approach, adapted to run inside OpenHermes with no sidecars or installers.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## What OpenHermes Does For Your Agent
|
|
30
|
+
|
|
31
|
+
<table>
|
|
32
|
+
<tr><td width="160"><b>🤖 Personality Layer</b></td><td>A 11-principle constitution (soul.md) injected into every session — pragmatic, concise, subagent-first, verify-don't-claim. Your agent stops rambling and starts delivering.</td></tr>
|
|
33
|
+
<tr><td><b>📌 Delegation Discipline</b></td><td>Mandated routing table — every non-trivial task goes to the right specialist subagent. Main context stays clean, coordination-only. No more bloated chat logs.</td></tr>
|
|
34
|
+
<tr><td><b>💾 9-Class Durable Memory</b></td><td>Checkpoints, decisions, constraints, instincts, mistakes, backlog items, audit reports, verification receipts, and session recall — all schema-validated, fingerprint-verified, persisted to disk.</td></tr>
|
|
35
|
+
<tr><td><b>🧰 Precision-First Retrieval</b></td><td>Gated retrieval with anti-spam controls. <code>hm_latest</code> → <code>hm_search</code> → <code>hm_get</code> → <code>hm_list</code>. No full-index reads unless explicitly scoped. Memory stays lean.</td></tr>
|
|
36
|
+
<tr><td><b>🔥 Autonomous Checkpointing</b></td><td>Pre-compaction snapshots capture mission, current state, next actions, active decisions, blockers, and risk notes — so compaction never loses the plot.</td></tr>
|
|
37
|
+
<tr><td><b>🔄 Closed Learning Loop</b></td><td>Mistakes are logged with root cause + prevention rule. Complex sessions auto-generate skill-candidate backlogs. Strike tracking escalates repeat failures. The agent gets better — you don't have to teach it twice.</td></tr>
|
|
38
|
+
<tr><td><b>🛠 10 Bundled Procedural Skills</b></td><td>Pre-built skills for API design, backend patterns, coding standards, E2E testing, frontend patterns, security reviews, strategic compaction, TDD workflow, verification loops, and presentation building. Discovered automatically — use <code>skill</code> tool to list and load.</td></tr>
|
|
39
|
+
<tr><td><b>🧩 Zero Infrastructure</b></td><td>No Python. No uv. No Docker. No PostgreSQL. No gateway. No cron daemon. Just Node.js — which OpenCode's Bun runtime already bundles. Everything runs inside your existing OpenCode session.</td></tr>
|
|
40
|
+
</table>
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Setup
|
|
45
|
+
|
|
46
|
+
Add one line to your `opencode.json`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"plugin": ["openhermes"]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
That's it. **No other config needed.** The plugin auto-registers:
|
|
55
|
+
|
|
56
|
+
| What | Details |
|
|
57
|
+
|------|---------|
|
|
58
|
+
| **7 subagents** | `architect`, `planner`, `code-reviewer`, `security-reviewer`, `build-error-resolver`, `e2e-runner`, `explore` |
|
|
59
|
+
| **7 slash commands** | `/plan`, `/build-fix`, `/code-review`, `/security`, `/doctor`, `/memory-search`, `/learn` |
|
|
60
|
+
| **5 native memory tools** | `hm_put`, `hm_get`, `hm_list`, `hm_latest`, `hm_search` — in-process, no MCP server needed |
|
|
61
|
+
| **10 procedural skills** | API design, backend patterns, coding standards, E2E testing, frontend patterns, frontend slides, security review, strategic compaction, TDD workflow, verification loop |
|
|
62
|
+
| **5 lifecycle plugins** | bootstrap, curator, autorecall, skill-builder, memory-tools |
|
|
63
|
+
|
|
64
|
+
You only need to define primary agents (like `build` or `OpenHermes`) in `opencode.json` — subagents are injected automatically.
|
|
65
|
+
|
|
66
|
+
<details>
|
|
67
|
+
<summary><b>What happens on your next session</b></summary>
|
|
68
|
+
|
|
69
|
+
1. **Config hook** — BootstrapPlugin registers auto-config: 7 subagents, 7 commands, 10 skill dirs.
|
|
70
|
+
2. **Chat transform hook** — ~12KB of context injected into the first user message:
|
|
71
|
+
- ★ **Constitution** (soul.md) — 11 immutable principles
|
|
72
|
+
- ★ **Runtime** (RUNTIME.md) — gather → delegate → verify → compress
|
|
73
|
+
- ★ **Router** (AGENTS.md) — delegation table, memory policy, escalation, with absolute paths to every rule
|
|
74
|
+
3. **Session created** — AutorecallPlugin builds recall cache from prior session memory
|
|
75
|
+
4. **Tools execute** — SkillBuilderPlugin watches tool calls and subagent spawns; MemoryToolsPlugin provides 5 native tools immediately
|
|
76
|
+
5. **Session idle** — CuratorPlugin snapshots checkpoint + verification receipt
|
|
77
|
+
6. **Session error** — CuratorPlugin logs mistake with root cause + prevention rule
|
|
78
|
+
7. **Compaction** — CuratorPlugin force-writes pre-compaction checkpoint, injects state into buffer
|
|
79
|
+
|
|
80
|
+
The LLM reads rules on demand via the injected paths. Memory directories auto-create. Everything Just Works™.
|
|
81
|
+
|
|
82
|
+
</details>
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## The Five Plugins
|
|
87
|
+
|
|
88
|
+
| Plugin | Triggers On | What It Does |
|
|
89
|
+
|--------|------------|--------------|
|
|
90
|
+
| **BootstrapPlugin** | `config`, `chat.transform` | Registers 7 subagents, 7 commands, 10 skill paths. Injects constitution + router + runtime. |
|
|
91
|
+
| **MemoryToolsPlugin** | — | Registers 5 native tools: `hm_put`, `hm_get`, `hm_list`, `hm_latest`, `hm_search`. Runs in-process — no MCP server needed. |
|
|
92
|
+
| **CuratorPlugin** | `session.idle`, `.compacted`, `.error`, `.compacting`, `permission.replied` | Writes checkpoints, logs mistakes, records audits, injects state into compaction. |
|
|
93
|
+
| **AutorecallPlugin** | `session.created` | Loads memory from disk, builds session recall cache. |
|
|
94
|
+
| **SkillBuilderPlugin** | `session.idle`, `.created`, `tool.execute.after` | Detects complex sessions (8+ tool calls or 2+ subagent spawns) → creates skill-candidate backlogs. |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Memory Architecture
|
|
99
|
+
|
|
100
|
+
Nine memory classes, all schema-validated before persistence, stored at `~/.config/opencode/openhermes/memory/`:
|
|
101
|
+
|
|
102
|
+
| Class | Format | Purpose |
|
|
103
|
+
|-------|--------|---------|
|
|
104
|
+
| `checkpoint` | JSON | Pre-compaction snapshots: mission, current state, next actions, blockers |
|
|
105
|
+
| `constraint` | JSON | Hard limits, env realities, safety rules — enforced by precedence engine |
|
|
106
|
+
| `decision` | JSON | Durable project choices — shapes all future behavior |
|
|
107
|
+
| `instinct` | JSON | Reusable trigger→action patterns with success/failure tracking |
|
|
108
|
+
| `backlog` | JSON | Evidence-backed self-improvement items with acceptance criteria |
|
|
109
|
+
| `mistake` | JSONL | Failure registry: type, root cause, fix, prevention rule, strike count |
|
|
110
|
+
| `audit` | JSON | Structured quality/integrity evaluations with health scores |
|
|
111
|
+
| `verification_receipt` | JSON | Cached verification results keyed by artifact fingerprint |
|
|
112
|
+
| `recall` | JSON | Session-start cache aggregating active state for compaction injection |
|
|
113
|
+
|
|
114
|
+
**Runtime hardening**: All records pass through `sanitizeRecord()` (strips `__proto__`, `constructor`, `prototype`), `redactSensitiveText()` (strips bearer tokens, API keys, passwords), and `truncateText()` before persistence. Schema validation gate runs before every write.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Session Lifecycle
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
session.startup (config hook)
|
|
122
|
+
│ BootstrapPlugin registers harness/skills/
|
|
123
|
+
│ OpenCode discovers 10 procedural skills
|
|
124
|
+
▼
|
|
125
|
+
session.created (chat.transform + session.created hooks)
|
|
126
|
+
│ BootstrapPlugin injects constitution ▸ router ▸ runtime
|
|
127
|
+
│ AutorecallPlugin loads memory → writes recall cache
|
|
128
|
+
│ SkillBuilderPlugin resets session counters
|
|
129
|
+
▼
|
|
130
|
+
tools execute...
|
|
131
|
+
│ SkillBuilderPlugin counts tool calls + subagent spawns
|
|
132
|
+
▼
|
|
133
|
+
session.idle
|
|
134
|
+
│ CuratorPlugin snapshots checkpoint + verification receipt
|
|
135
|
+
│ SkillBuilderPlugin checks complexity → backlog entry if threshold met
|
|
136
|
+
▼
|
|
137
|
+
session.compacted
|
|
138
|
+
│ CuratorPlugin updates loop-state → "compacted"
|
|
139
|
+
▼
|
|
140
|
+
experimental.session.compacting
|
|
141
|
+
│ CuratorPlugin force-writes pre-compaction checkpoint
|
|
142
|
+
│ Injects harness state + recall context → compaction buffer
|
|
143
|
+
▼
|
|
144
|
+
session.error
|
|
145
|
+
│ CuratorPlugin logs mistake (type, root cause, fix, prevention, strike)
|
|
146
|
+
│ Updates loop-state with error status
|
|
147
|
+
▼
|
|
148
|
+
permission.replied
|
|
149
|
+
│ CuratorPlugin writes audit record for every permission decision
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Bundled Harness
|
|
155
|
+
|
|
156
|
+
The full OpenHermes framework ships inside the package — 45 files across 6 directories:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
harness/
|
|
160
|
+
├── constitution/soul.md # 11 immutable personality principles
|
|
161
|
+
├── instructions/RUNTIME.md # Session workflow: gather → delegate → verify
|
|
162
|
+
├── rules/ (16 files)
|
|
163
|
+
│ ├── delegation.md # Mandatory subagent routing
|
|
164
|
+
│ ├── retrieval.md # Gated precision-first memory retrieval
|
|
165
|
+
│ ├── session-start.md # Session-start checklist and memory hydration
|
|
166
|
+
│ ├── credential-exposure.md # Secret redaction and credential exposure guard
|
|
167
|
+
│ ├── self-heal.md # T0→T3 escalation tiers
|
|
168
|
+
│ ├── verification.md # Skeptical evidence protocol
|
|
169
|
+
│ ├── memory-management.md # Dual-target memory + anti-spam
|
|
170
|
+
│ ├── precedence.md # 9-level conflict resolution
|
|
171
|
+
│ ├── checkpointing.md # Compaction snapshot discipline
|
|
172
|
+
│ ├── audit.md # Structured health checks
|
|
173
|
+
│ ├── skills-management.md # Progressive disclosure loading
|
|
174
|
+
│ ├── context-loading.md # Priority chain + size limits
|
|
175
|
+
│ ├── promotion.md # High-signal-only promotion
|
|
176
|
+
│ ├── ranking.md # Metadata-first retrieval
|
|
177
|
+
│ ├── runtime-guards.md # Stale assumption prevention
|
|
178
|
+
│ └── state-drift.md # Hash-based fingerprinting
|
|
179
|
+
├── skills/ (10 directories)
|
|
180
|
+
│ ├── api-design/SKILL.md # REST API patterns (523 lines)
|
|
181
|
+
│ ├── backend-patterns/SKILL.md # Backend architecture (598 lines)
|
|
182
|
+
│ ├── coding-standards/SKILL.md # Baseline conventions (549 lines)
|
|
183
|
+
│ ├── e2e-testing/SKILL.md # Playwright E2E patterns (326 lines)
|
|
184
|
+
│ ├── frontend-patterns/SKILL.md # React/Next.js patterns (642 lines)
|
|
185
|
+
│ ├── frontend-slides/SKILL.md # HTML presentation builder (184 lines)
|
|
186
|
+
│ ├── security-review/SKILL.md # OWASP Top 10 checklist (495 lines)
|
|
187
|
+
│ ├── strategic-compact/SKILL.md # Context compaction strategy (131 lines)
|
|
188
|
+
│ ├── tdd-workflow/SKILL.md # Red-green-refactor discipline (463 lines)
|
|
189
|
+
│ └── verification-loop/SKILL.md # Pre-PR quality gates (126 lines)
|
|
190
|
+
├── prompts/ (7 files)
|
|
191
|
+
│ # Subagent prompt templates: architect, build-error-resolver,
|
|
192
|
+
│ # code-reviewer, e2e-runner, explore, planner, security-reviewer
|
|
193
|
+
└── commands/ (8 files)
|
|
194
|
+
# Slash command templates: build-fix, code-review, doctor,
|
|
195
|
+
# learn, memory-search, plan, security
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Self-Healing Escalation
|
|
201
|
+
|
|
202
|
+
When your agent makes a mistake, OpenHermes doesn't just log it — it escalates:
|
|
203
|
+
|
|
204
|
+
| Tier | Trigger | Action |
|
|
205
|
+
|------|---------|--------|
|
|
206
|
+
| **T0** | Any mistake | Observe → log mistake record → smallest safe correction → verify |
|
|
207
|
+
| **T1** | Same mistake repeats within 7 days | Add prevention rule → targeted verification |
|
|
208
|
+
| **T2** | Prevention failed / systemic issue | Delegate to specialist → audit → backlog item |
|
|
209
|
+
| **T3** | Cascading failures | Constrained safe mode: narrow claims, preserve receipts, produce handoff |
|
|
210
|
+
|
|
211
|
+
No self-termination. No grandstanding. Narrow, log, recover, improve.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Environment Variables
|
|
216
|
+
|
|
217
|
+
| Variable | Default | Effect |
|
|
218
|
+
|----------|---------|--------|
|
|
219
|
+
| `OPENCODE_ALLOW_PROJECT_HARNESS` | `false` | Use project-local harness at `.opencode/openhermes/` |
|
|
220
|
+
| `OPENCODE_CURATOR_LOGS` | `false` | Enable curator diagnostic output to stderr |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Architecture
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
openhermes/
|
|
228
|
+
├── index.mjs # Re-exports all 5 plugins
|
|
229
|
+
├── bootstrap.mjs # Config hook (agents/commands/skills) + chat.transform
|
|
230
|
+
├── autorecall.mjs # Recall cache builder
|
|
231
|
+
├── curator.mjs # Lifecycle hooks engine (~470 lines)
|
|
232
|
+
├── skill-builder.mjs # Complexity detection engine
|
|
233
|
+
├── lib/
|
|
234
|
+
│ ├── memory-tools-plugin.mjs # 5 native memory tools (hm_put/get/list/latest/search)
|
|
235
|
+
│ ├── hardening.mjs # atomicWriteJson, fingerprint, sanitize, redact
|
|
236
|
+
│ └── schema-validator.mjs # Draft-07 subset validator
|
|
237
|
+
├── schemas/ # 9 JSON schemas for memory validation
|
|
238
|
+
├── harness/ # Full framework (44 files)
|
|
239
|
+
└── package.json
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Minimal npm dependency footprint.** Tool definitions use OpenCode's tiny `@opencode-ai/plugin` SDK. No postinstall scripts. No native compilation.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Dependencies
|
|
247
|
+
|
|
248
|
+
- **Node.js >= 18** — `node:path`, `node:fs`, `node:os`, `node:url`, `node:crypto`
|
|
249
|
+
- **OpenCode** — provides the Bun runtime, plugin loader, hook dispatcher, and `skill` tool
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Why OpenHermes ≠ Hermes Agent
|
|
254
|
+
|
|
255
|
+
| | Hermes Agent | OpenHermes |
|
|
256
|
+
|--|------------|------------|
|
|
257
|
+
| **Platform** | Standalone agent with custom TUI, multi-platform gateway, cron scheduler | OpenCode-native plugin — runs *inside* your existing setup |
|
|
258
|
+
| **Installation** | `curl | bash` + Python 3.11 + uv + Docker (optional) + PostgreSQL (optional) | `npm i` — that's it |
|
|
259
|
+
| **Infrastructure** | Sidecar processes, gateway daemon, FTS5 database, Honcho user modeling | Zero sidecars — everything is a plugin hook |
|
|
260
|
+
| **Memory** | Honcho dialectic user profiles + skills system | Schema-validated 9-class memory + in-process native tools |
|
|
261
|
+
| **Skills** | Agentskills.io standard, self-created + improving | SKILL.md progressive disclosure, skill-builder auto-detection |
|
|
262
|
+
| **Context** | Context files + session search with LLM summarization | Harness injection at session start, recall cache at compaction |
|
|
263
|
+
| **Philosophy** | "The self-improving agent" — feature-rich, platform-expansive | "The constitutional router" — discipline-first, precision-only |
|
|
264
|
+
|
|
265
|
+
Both are ☤ messengers. Different mediums.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Contributing
|
|
270
|
+
|
|
271
|
+
Problems, ideas, improvements? [Open an issue](https://github.com/nathwn12/openhermes/issues). PRs welcome.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT — see [LICENSE](LICENSE).
|
|
278
|
+
|
|
279
|
+
<p align="center">
|
|
280
|
+
<sub><b>☤</b> Built with discipline. Inspired by <a href="https://github.com/NousResearch/hermes-agent">Hermes Agent</a>. Built for <a href="https://opencode.ai">OpenCode</a>.</sub>
|
|
281
|
+
</p>
|
package/autorecall.mjs
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import os from "node:os"
|
|
3
|
+
import fs from "node:fs"
|
|
4
|
+
import { atomicWriteJson, fingerprintEnvironment, isTruthy, sanitizeRecord, truncateText } from "./lib/hardening.mjs"
|
|
5
|
+
|
|
6
|
+
function getHarnessRoot(directory) {
|
|
7
|
+
const home = process.env.USERPROFILE || os.homedir()
|
|
8
|
+
const configRoot = path.join(home, ".config", "opencode")
|
|
9
|
+
const projectHarness = path.join(directory, ".opencode", "openhermes")
|
|
10
|
+
const projectMemory = path.join(projectHarness, "memory")
|
|
11
|
+
if (isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS)) {
|
|
12
|
+
try {
|
|
13
|
+
fs.accessSync(projectMemory)
|
|
14
|
+
return projectHarness
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return path.join(configRoot, "openhermes")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function readJson(fp, fallback) {
|
|
22
|
+
try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch { return fallback }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readJsonl(fp) {
|
|
26
|
+
try {
|
|
27
|
+
return fs.readFileSync(fp, "utf8").trim().split("\n").filter(Boolean).map(l => JSON.parse(l))
|
|
28
|
+
} catch { return [] }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadMemoryRecord(root, className, entry) {
|
|
32
|
+
const recordPath = path.join(root, "memory", className, `${entry.id}.json`)
|
|
33
|
+
const record = readJson(recordPath, null)
|
|
34
|
+
if (record && typeof record === "object") return record
|
|
35
|
+
return {
|
|
36
|
+
...entry,
|
|
37
|
+
class: className,
|
|
38
|
+
scope: entry.scope || "harness",
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildEnvironmentFingerprint(harnessRoot, directory, projectKey) {
|
|
43
|
+
return fingerprintEnvironment({
|
|
44
|
+
cwd: directory,
|
|
45
|
+
harnessRoot,
|
|
46
|
+
projectRoot: directory,
|
|
47
|
+
project: projectKey,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatContext(memory) {
|
|
52
|
+
const parts = []
|
|
53
|
+
if (memory.checkpoint) parts.push(`## Active Checkpoint\n${memory.checkpoint.summary || "N/A"}\n`)
|
|
54
|
+
if (memory.constraints.length) parts.push(`## Active Constraints\n${memory.constraints.map(c => `- ${c.summary}`).join("\n")}\n`)
|
|
55
|
+
if (memory.decisions.length) parts.push(`## Recent Decisions\n${memory.decisions.slice(0, 3).map(d => `- ${d.summary}`).join("\n")}\n`)
|
|
56
|
+
if (memory.mistakes.length) parts.push(`## Recent Mistakes (top ${Math.min(3, memory.mistakes.length)})\n${memory.mistakes.slice(0, 3).map(m => `- ${m.summary}`).join("\n")}\n`)
|
|
57
|
+
return parts.join("\n")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateMemoryRecord(record) {
|
|
61
|
+
const required = ["id", "class", "scope", "summary", "status"]
|
|
62
|
+
const missing = required.filter(r => !record[r])
|
|
63
|
+
if (missing.length) return false
|
|
64
|
+
if (record.confidence !== undefined && record.confidence < 0.3) return false
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatBacklogNudge(candidates) {
|
|
69
|
+
if (!candidates.length) return null
|
|
70
|
+
const count = candidates.length
|
|
71
|
+
const top = candidates.slice(0, 3).map((c, i) => `${i + 1}. ${c.summary || c.title || "unnamed candidate"}`).join("\n")
|
|
72
|
+
return [
|
|
73
|
+
`## Pending Skill Candidates (${count} open)`,
|
|
74
|
+
`The skill creation loop has ${count} unprocessed candidates.`,
|
|
75
|
+
`CRITICAL: Process the oldest candidate via /learn on this session start.`,
|
|
76
|
+
`Top candidates:`,
|
|
77
|
+
top,
|
|
78
|
+
`Trigger: /learn to create skills from these sessions.`,
|
|
79
|
+
`If none are skill-worthy, close them via hm_put with status:"closed".`
|
|
80
|
+
].join("\n")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatMemoryWriteGap(memory) {
|
|
84
|
+
const gaps = []
|
|
85
|
+
if (memory.constraints.length === 0) gaps.push("constraints")
|
|
86
|
+
if (memory.decisions.length === 0) gaps.push("decisions")
|
|
87
|
+
if (!gaps.length) return null
|
|
88
|
+
return `## Memory Write Gap\nThese memory classes are empty: ${gaps.join(", ")}. Write at least one ${gaps[0]} this session.`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function loadMemoryAndWriteCache(harnessRoot, projectKey, directory) {
|
|
92
|
+
const memory = { constraints: [], decisions: [], mistakes: [], checkpoint: null, pendingSkillCandidates: [] }
|
|
93
|
+
const fingerprint = buildEnvironmentFingerprint(harnessRoot, directory, projectKey)
|
|
94
|
+
|
|
95
|
+
const constraintsIndex = readJson(path.join(harnessRoot, "memory", "constraints", "index.json"), [])
|
|
96
|
+
if (Array.isArray(constraintsIndex)) memory.constraints = constraintsIndex.filter(e => e.status === "active")
|
|
97
|
+
|
|
98
|
+
const decisionsIndex = readJson(path.join(harnessRoot, "memory", "decisions", "index.json"), [])
|
|
99
|
+
if (Array.isArray(decisionsIndex)) {
|
|
100
|
+
memory.decisions = decisionsIndex
|
|
101
|
+
.filter(e => e.status === "active")
|
|
102
|
+
.map(entry => loadMemoryRecord(harnessRoot, "decisions", entry))
|
|
103
|
+
.filter(validateMemoryRecord)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const allMistakes = readJsonl(path.join(harnessRoot, "memory", "mistakes", "mistakes.jsonl"))
|
|
107
|
+
if (allMistakes.length) memory.mistakes = allMistakes.filter(e => e.status === "active").slice(0, 5)
|
|
108
|
+
|
|
109
|
+
const checkpointIndex = readJson(path.join(harnessRoot, "memory", "checkpoints", "index.json"), [])
|
|
110
|
+
if (Array.isArray(checkpointIndex) && checkpointIndex.length > 0) {
|
|
111
|
+
const latest = checkpointIndex.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))[0]
|
|
112
|
+
memory.checkpoint = readJson(path.join(harnessRoot, "memory", "checkpoints", `${latest.id}.json`), null)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const backlogIndex = readJson(path.join(harnessRoot, "memory", "backlog", "index.json"), [])
|
|
116
|
+
if (Array.isArray(backlogIndex)) {
|
|
117
|
+
memory.pendingSkillCandidates = backlogIndex.filter(e =>
|
|
118
|
+
e.status === "open" && (e.summary || "").includes("skill-candidate")
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const contextParts = []
|
|
123
|
+
const baseContext = formatContext(memory)
|
|
124
|
+
if (baseContext) contextParts.push(baseContext)
|
|
125
|
+
const backlogNudge = formatBacklogNudge(memory.pendingSkillCandidates)
|
|
126
|
+
if (backlogNudge) contextParts.push(backlogNudge)
|
|
127
|
+
const writeGap = formatMemoryWriteGap(memory)
|
|
128
|
+
if (writeGap) contextParts.push(writeGap)
|
|
129
|
+
|
|
130
|
+
const context = contextParts.join("\n\n")
|
|
131
|
+
const boundedContext = context ? truncateText(context, 12000) : null
|
|
132
|
+
|
|
133
|
+
const cacheDir = path.join(harnessRoot, "memory", "recall")
|
|
134
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
135
|
+
atomicWriteJson(path.join(cacheDir, "cache.json"), sanitizeRecord({
|
|
136
|
+
context: boundedContext,
|
|
137
|
+
project: projectKey,
|
|
138
|
+
trust_mode: isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS) ? "project" : "global",
|
|
139
|
+
harness_root: harnessRoot,
|
|
140
|
+
project_root: directory,
|
|
141
|
+
updated_at: new Date().toISOString(),
|
|
142
|
+
fingerprint,
|
|
143
|
+
freshness_marker: {
|
|
144
|
+
updated_at: new Date().toISOString(),
|
|
145
|
+
ttl_ms: 1800000,
|
|
146
|
+
},
|
|
147
|
+
stats: {
|
|
148
|
+
constraints: memory.constraints.length,
|
|
149
|
+
decisions: memory.decisions.length,
|
|
150
|
+
mistakes: memory.mistakes.length,
|
|
151
|
+
has_checkpoint: !!memory.checkpoint,
|
|
152
|
+
pending_skill_candidates: memory.pendingSkillCandidates.length
|
|
153
|
+
}
|
|
154
|
+
}, { maxStringLength: 4000 }))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const AutorecallPlugin = async ({ project, directory }) => {
|
|
158
|
+
return {
|
|
159
|
+
event: async ({ event }) => {
|
|
160
|
+
if (event.type === "session.created") {
|
|
161
|
+
const harnessRoot = getHarnessRoot(directory)
|
|
162
|
+
const projectKey = project?.name || path.basename(directory)
|
|
163
|
+
await loadMemoryAndWriteCache(harnessRoot, projectKey, directory)
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
}
|