This commit is contained in:
root
2026-06-29 04:07:11 +00:00
3 changed files with 749 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# Prose Contracts — Syslog Solution LLC
Operating contracts for Hermes agents. Each `.prose.md` file defines a **responsibility** (recurring duty) or **template** (scaffold) that agents can execute via OpenProse or follow manually.
## How Agents Run These Contracts
### Option A: Via OpenProse CLI (preferred)
```bash
prose run <contract-name> [param1=value1 param2=value2 ...]
```
**Examples:**
```bash
# Run a memory audit with default parameters
prose run memory-audit-maintenance
# Run a memory audit with custom threshold
prose run memory-audit-maintenance memory_threshold=90 verify_configs=true
# Configure a new agent with the template
prose run hermes-config-template agent_name=syslog-devops default_model=claude-sonnet-4
# Configure an agent with a different auxiliary model
prose run hermes-config-template agent_name=syslog-code default_model=qwen3.6-27B-code auxiliary_model=gemma-4-12b
```
### Option B: Manual Execution
If `prose` CLI isn't available, any agent can:
1. Fetch the raw contract: `curl -sL https://git.sysloggh.net/SyslogSolution/prose-contracts/raw/branch/master/<filename>`
2. Read the **Execution** section
3. Follow the steps
**Example for fetching:**
```bash
curl -sL https://git.sysloggh.net/SyslogSolution/prose-contracts/raw/branch/master/memory-audit-maintenance.prose.md
```
## Available Contracts
### Responsibilities (Recurring Duties)
| Contract | What It Does | When To Run |
|---|---|---|
| `memory-audit-maintenance` | Audits & reorganizes an agent's native memory (MEMORY.md, USER.md). Categorizes entries, moves rules to skills, verifies configs, frees up char budget. | Memory >85% usage or user request: "memory audit" |
| `zulip-health` | Checks Zulip connectivity, message flow, and bot responsiveness. | On Zulip issues or periodic health check |
| `litellm-health` | Verifies LiteLLM proxy connectivity, model availability, and key rotation. | On inference failures or periodic check |
| `litellm-self-heal` | Attempts to fix common LiteLLM issues (key rotation, restart, config reload). | When litellm-health reports failures |
| `zulip-mention-reliability` | Diagnoses and fixes @mention detection issues in Zulip. | On missed @mention reports |
| `pm2-self-heal` | Restarts crashed PM2 processes and verifies recovery. | On PM2 process failure |
### Templates (Scaffolds)
| Contract | What It Does | Parameters |
|---|---|---|
| `hermes-config-template` | Generates a standard Hermes config for any agent. Enforces shared infra (Firecrawl, SearXNG, RA-H OS MCP, LiteLLM) while keeping model choices flexible. | `agent_name` (required), `default_model`, `default_provider`, `fallback_model`, `auxiliary_model` |
### Scripts
| File | What It Does |
|---|---|
| `pm2-self-heal.sh` | Shell script to restart crashed PM2 processes (companion to the prose contract) |
## Contract Structure
Every `.prose.md` file has the same structure:
```
---
kind: <responsibility | template>
name: <unique-name>
description: ...
---
## Maintains — What state the contract tracks
## Parameters — Tunable inputs (with defaults)
## Continuity — When to run (triggers & cadence)
## Success Criteria — How to know it worked
## Remembers — What to learn between runs
## <Type> Rules — Inviolable rules for execution
## Execution — Step-by-step instructions
## Example Output — What a good run looks like
```
## Adding New Contracts
1. Create a `.prose.md` file following the structure above
2. Push to this repo
3. The contract is immediately available for any agent to `prose run`
## Repository
- **URL:** https://git.sysloggh.net/SyslogSolution/prose-contracts
- **Branch:** master
- **Auth:** mumuni-bot (write access via PAT)
+307
View File
@@ -0,0 +1,307 @@
---
kind: template
name: hermes-config-template
description: >
Standard Hermes configuration template for Syslog Solution LLC agents.
Enforces shared infrastructure setup (Firecrawl, SearXNG, local models,
RA-H OS MCP) while keeping model/provider choices flexible per agent role.
Every new agent profile should start from this template.
---
## Maintains
- template_version: string — Current template version (e.g., "1.0.0")
- last_applied: timestamp — When any agent was last configured from this template
- agents_configured: array — Which agents/profiles were created from this template
- infra_endpoints_verified: array — Firecrawl, SearXNG, RA-H OS, LiteLLM endpoints last confirmed working
- known_deviations: array — Profiles that diverge from the template (and why)
## Parameters
- agent_name: string — Name of the agent/profile (e.g., "syslog-code", "syslog-devops")
- default_model: string — Agent's primary model (e.g., "qwen3.6-27B-code", "syslog-auto")
- default_provider: string — Inference provider for the primary model (default: "harness")
- fallback_model: string — Fallback model when primary is down (default: "gemma-4-12b")
- fallback_provider: string — Fallback provider (default: "harness")
- auxiliary_model: string — Model for vision/compression/extraction tasks (default: "gemma-4-12b")
- enable_firecrawl_searxng: boolean — Whether to configure web search/extract (default: true)
- enable_ra_h_os_mcp: boolean — Whether to wire up RA-H OS MCP bridge (default: true)
- enable_compression: boolean — Whether to enable context compression (default: true)
- custom_provider_name: string — Custom provider name (default: "harness")
- custom_provider_model: string — Custom provider model (default: same as default_model)
- custom_provider_url: string — LiteLLM base URL (default: "http://litellm.sysloggh.net/litellm/v1")
- extra_auxiliary: array — Additional auxiliary tasks to configure (vision, compression, etc.)
- output_dir: string — Where to write the config (default: "~/.hermes/profiles/<agent_name>/config.yaml")
## Quickstart — How to Run This Contract
### For the Agent Running This Contract
Read this section first. It tells you exactly what to do.
**When to run:**
- Setting up a **new agent profile** for the first time
- An **existing profile** is missing web search, firecrawl, or MCP configs
- User says: `"apply the config template"` or `"fix my infra config"`
- User says: `"configure <agent_name> with <model>"`
**How to run (via OpenProse CLI):**
```bash
# Minimal — only agent name required
prose run hermes-config-template agent_name=syslog-code
# Full control
prose run hermes-config-template \
agent_name=syslog-devops \
default_model=claude-sonnet-4 \
auxiliary_model=gemma-4-12b \
fallback_model=deepseek-chat \
fallback_provider=deepseek
```
### How to Follow the Template (Manual)
If `prose` CLI is not available:
1. **Fetch this contract:** `curl -sL https://git.sysloggh.net/SyslogSolution/prose-contracts/raw/branch/master/hermes-config-template.prose.md`
2. **Open the target config.yaml** — the profile you're updating
3. **Compare** each of the 5 required sections below against what exists in the profile
4. **For each section:**
- If it says **SHARED INFRA — DO NOT CHANGE**: Copy the value exactly as shown
- If it says **USER CHOOSES**: Use the agent's assigned model/provider
- If missing entirely: Add the full section
5. **Verify** every endpoint responds (see Verification section at the end)
6. **Restart** the gateway
### Decision Tree
```
What kind of profile is this?
├── Main profile (~/.hermes/config.yaml)
│ └── Replace web.*, mcp_servers.*, compression.*, auxiliary.*, custom_providers.*
│ └── model.default + fallback: use current values (USER CHOOSES)
├── Worker profile (~/.hermes/profiles/<name>/config.yaml)
│ ├── Does it have web.search_backend?
│ │ ├── NO → Add the full web.* section (copy exactly)
│ │ └── YES → Verify it points to the right IP
│ ├── Does it have mcp_servers.ra-h-os?
│ │ ├── NO → Add mcp_servers.* section (copy exactly)
│ │ └── YES → Verify URL
│ └── model.default: USE the worker's assigned model (don't copy main)
└── Non-Syslog profile (personal/lifestyle agent)
└── Skip — use default Hermes config or Tanko's config instead
```
## Infrastructure Stack
This template provisions the following shared infrastructure. Every agent should point to the same endpoints unless explicitly overridden.
| Component | Endpoint | Purpose |
|---|---|---|
| Firecrawl | `http://192.168.68.7:3002/` | Web content extraction |
| SearXNG | `http://storepve:8888` | Privacy-respecting web search |
| LiteLLM | `http://litellm.sysloggh.net/litellm/v1` | Unified model gateway |
| RA-H OS MCP | `http://192.168.68.65:3100/mcp` | Knowledge graph bridge |
| Context7 MCP | `http://localhost:8079/mcp` | Documentation queries |
**API Key Rules:**
- `api_key: sk-_SW...B8nw` — Hardcoded LiteLLM master key for custom_providers and auxiliary tasks
- `api_key_env: DEEPSEEK_API_KEY` — Env-var based key for fallback provider
- Worker profiles should inherit proxy config via `harness` custom_provider, NOT hardcode LiteLLM keys
**For workers that need API keys overridden:** Set `api_key: ''` in the model section (inherits from custom_providers), or use `api_key_env` for env-based auth.
## Template Structure
### Required Sections (every agent MUST have)
```yaml
# ─── Model Selection (USER CHOOSES) ───
model:
# !! CHANGE THIS for your agent !!
default: <agent_default_model>
provider: <agent_default_provider>
# LiteLLM base URL (shared infra)
base_url: http://litellm.sysloggh.net/litellm/v1
fallback_providers:
# !! OPTIONAL: overridable fallback !!
provider: <fallback_provider>
model: <fallback_model>
# ─── Web Stack (SHARED INFRA — DO NOT CHANGE) ───
web:
backend: firecrawl
search_backend: searxng
extract_backend: firecrawl
firecrawl:
base_url: http://192.168.68.7:3002/
# ─── MCP Servers (SHARED INFRA — DO NOT CHANGE) ───
mcp_servers:
context7:
connect_timeout: 60
timeout: 300
url: http://localhost:8079/mcp
ra-h-os:
url: http://192.168.68.65:3100/mcp
timeout: 120
connect_timeout: 60
# ─── Compression (SHARED — can override model) ───
compression:
enabled: true
provider: harness
model: <auxiliary_model> # default: gemma-4-12b
threshold: 0.5
target_ratio: 0.25
protect_last_n: 30
hygiene_hard_message_limit: 400
# ─── Auxiliary Tasks (SHARED INFRA + MODEL) ───
auxiliary:
vision:
provider: harness
model: <auxiliary_model>
web_extract:
provider: harness
model: <auxiliary_model>
session_search:
provider: harness
model: <auxiliary_model>
max_concurrency: 3
# ─── Custom Provider (SHARED INFRA — LiteLLM) ───
custom_providers:
- name: <custom_provider_name>
model: <custom_provider_model>
base_url: http://litellm.sysloggh.net/litellm/v1
api_key: <liteLLM_master_key>
api_mode: chat_completions
```
### Optional Sections (agent-specific)
- **Platform configs** (Telegram, Discord, WhatsApp bot tokens)
- **Display/skin** options (compact mode, personality)
- **Plugin lists** (zulip-platform, etc.)
- **Terminal settings** (container image, timeouts)
- **Security/approvals** (command allowlists)
- **Kanban/worker** settings (if this profile is a Kanban worker)
- **Skills auto_load** list
- **Timezone** override
## Continuity
Configuration drift detection is **event-driven**:
- **On agent onboarding**: Always scaffold from this template
- **On infra change**: If Firecrawl/SearXNG/LiteLLM endpoints change, update template + all profiles
- **On new model**: When a new model is deployed on Litellm, any agent can change their `default` independently
- **Periodic**: Every 2 weeks — check if any profile's infra section has drifted from the template
- **On connection failure**: If web search/extract fails, check if that agent has missing `web.*` config
## Success Criteria
The configuration is CONSIDERED GOOD when ALL of these pass:
### Infra Connectivity
- `web.backend == "firecrawl"` — Firecrawl backend configured
- `web.search_backend == "searxng"` — SearXNG search configured
- `web.extract_backend == "firecrawl"` — Firecrawl extraction configured
- `web.firecrawl.base_url` points to `192.168.68.7:3002`
- `mcp_servers.ra-h-os.url` points to `192.168.68.65:3100/mcp`
- All endpoints respond to `curl` health checks
### Model Flexibility
- `model.default` is settable per agent (not hardcoded)
- `model.provider` is settable per agent
- `custom_providers[0].model` matches the agent's workload (code vs general)
### Completeness
- `auxiliary.vision` configured (even if provider=auto)
- `compression.enabled` set (true for most agents)
- Fallback provider configured (deepseek or another harness model)
- `_config_version` correctly set (currently `30`)
### No Drift
- No duplicate infra endpoints (one canonical Firecrawl URL)
- No stale endpoints (old IPs, dead services)
- All profiles consistent on shared infra
## Configuration Rules
### Rule 1: Shared Infra Is Locked
The following MUST be identical across ALL profiles:
- `web.backend`, `web.search_backend`, `web.extract_backend`
- `web.firecrawl.base_url`
- `mcp_servers.ra-h-os.url`
- `custom_providers[0].base_url`
### Rule 2: Model Choice Is Free
The following are OWNED by each agent and can differ:
- `model.default` — the primary model
- `model.provider` — where it runs
- `fallback_providers.model` — backup model
- `custom_providers[0].model` — what this agent uses LiteLLM for
### Rule 3: Workers Inherit, Not Duplicate
Worker profiles (syslog-code, syslog-devops, etc.) generally inherit from the main config. Only override what's DIFFERENT:
- Different default model → override `model.default`
- Different auxiliary model → override auxiliary sections
- No web search needed → still keep the config (tools won't enable without it)
### Rule 4: API Key Placement
- **Custom provider keys** (`custom_providers[0].api_key`): Must be hardcoded (the LiteLLM master key or a virtual key)
- **Model section keys** (`model.api_key`): Prefer empty. Workers inherit from custom_providers.
- **Environment keys** (`api_key_env`): For fallback providers that need separate auth (DeepSeek, OpenRouter)
- **Hardcoded keys in profile**: If a worker's `model.api_key` is set, it overrides custom_providers. Only use when the worker needs a DIFFERENT provider than Litellm.
### Rule 5: Verify After Every Config Change
After changing a profile's config:
1. `curl` the model endpoint to confirm auth works
2. `curl` the web stack endpoints (Firecrawl, SearXNG)
3. Check that `hermes gateway --restart` reloads cleanly
## Execution
1. **Check current config** — Read the target agent's config.yaml
2. **Compare against template** — Identify missing or divergent sections
3. **Apply shared infra** — Lock the web/MCP/compression sections to template values
4. **Apply model choice** — Set default/fallback/auxiliary models per agent's workload
5. **Preserve agent-specific** — Keep platform configs, plugins, terminal settings, skills
6. **Remove stale** — Drop any old infra endpoints that don't match the template
7. **Verify** — curl all shared endpoints, test the model
8. **Report** — What was changed, what was preserved, what's still custom
## Example Output
```
## Config Template Applied: syslog-code ✅
### Shared Infrastructure (Locked)
| Section | Before | After |
|---|---|---|
| web.backend | firecrawl | firecrawl ✅ |
| web.search_backend | NOT SET | searxng ✅ |
| web.firecrawl.base_url | NOT SET | http://192.168.68.7:3002/ ✅ |
| mcp_servers.ra-h-os | present | present ✅ |
### Agent Model Choices (Preserved)
| Setting | Value |
|---|---|
| model.default | qwen3.6-27B-code (code agent) |
| fallback | gemma-4-12b (harness) |
| auxiliary | gemma-4-12b (harness) |
| compression | gemma-4-12b (harness) |
### Endpoint Verification
| Endpoint | Status |
|---|---|
| Firecrawl :3002 | ✅ 200 OK |
| SearXNG :8888 | ✅ 200 OK |
| LiteLLM | ✅ 200 OK |
| RA-H OS MCP | ✅ Connected |
```
+345
View File
@@ -0,0 +1,345 @@
---
name: memory-audit-maintenance
kind: responsibility
id: 067NC4KG01RG50R40M30E20918
---
### Goal
Autonomously audit and reorganize an agent's native memory (MEMORY.md, USER.md, SOUL.md), categorize entries, redistribute them to correct locations, verify live configurations, and free character budget all without user intervention unless a truly unresolvable ambiguity exists. Every agent runs this contract for **its own** memory files only no cross-agent access, no shared state.
### Requires
(none this is a self-driven, autonomous node)
### Scope
This contract is the **Hermes Agent standard** for memory maintenance. It is shared across all Hermes agents (Mumuni, Tanko, Koby, Koonimo). Each agent runs it against its own memory files only no cross-agent access, no shared state, no shared ledger, no shared canary. The contract is the standard; each agent enforces it independently with fully isolated data.
**Agent Roster:**
- Mumuni
- Tanko
- Koby
- Koonimo
**Isolation Principle:** Each agent has its own:
- `MEMORY.md` and `USER.md`
- `MEMORY_AUDIT_LEDGER.md` (audit trail)
- `MEMORY_WRITERS.md` (writer registry)
- Canary entries
- No data crosses agent boundaries
### Maintains
Memory health state current utilization, last audit timestamp, drift alerts, and any unresolved ambiguities. Postcondition: no entry is duplicated, no session note or stale snapshot survives, no config is unverified, and the audit ledger is up-to-date.
#### utilization
Current character utilization of MEMORY.md and USER.md. Material: the percentages.
#### last_audit
Timestamp of the most recent successful audit. Material: the timestamp.
#### unresolved
Any entries that could not be autonomously categorized. Material: the entry text and the reason for ambiguity.
#### drift_alerts
Active alerts from drift detection (e.g., deletions accelerating, unverified configs growing). Material: the alert list.
#### ledger
Persistent audit trail stored at the agent's own `~/.hermes/memories/MEMORY_AUDIT_LEDGER.md`. Material: append-only, never modified or deleted. Each agent has its own ledger no shared ledger across agents.
### Continuity
- self-driven: re-audit every 14 days (2 weeks)
- input-driven: triggered by explicit user request ("memory audit", "memory maintenance", "clean up memory", "memory is full")
- threshold: trigger immediately if either store exceeds 85% utilization
### Parameters
- `memory_threshold`: percentage that triggers an immediate audit (default: 85)
- `max_memory_chars`: maximum allowed characters for MEMORY.md (default: 2200)
- `max_user_chars`: maximum allowed characters for USER.md (default: 1375)
- `compress_entries`: whether to rewrite entries for brevity (default: true)
- `dry_run`: whether to only report changes without applying them (default: false)
### Strategies
- **Autonomous first:** Never ask the user about entries that clearly belong in a deletion or move category. Only escalate on true ambiguity.
- **Incremental over nuclear:** Always use patch-level edits (find-and-replace) rather than wholesale file rewrites. If the patch tool fails, fall back to rewrite only as a last resort.
- **Verify before trusting:** Every technical config in MEMORY.md must be verified against the live system. If a service is unreachable, mark it as `UNVERIFIED` rather than deleting it.
- **Stale state has a shelf life:** Any entry marked as "Temporary State" (e.g., "verbal offer", "deal pending", "negotiation phase") is deleted if older than 14 days.
- **Work around tool failures:** If the `memory` tool fails after 2 retries, add a summary marker and report the stale state in the unresolved list.
- **Privacy isolation:** Each agent only reads and writes its own memory files. No cross-agent memory access, no shared ledger, no shared state. The contract is the standard; each agent runs it independently.
### Invariants
- No entry is deleted without being categorized first
- No duplicate entries survive across MEMORY.md and USER.md
- No session note or temporary state survives past 14 days
- No config is trusted without verification (or marked UNVERIFIED)
- The audit ledger is append-only never modified or deleted, and is isolated per agent
- The canary entry survives every audit unmodified
- No data crosses agent boundaries
### Execution
```prose
# ============================================================
# PHASE 0: Canaries & Privacy
# ============================================================
# Check canaries if either is missing or modified, raise CRITICAL alert
memory_canary = read_file_line("~/.hermes/memories/MEMORY.md", containing="CANARY: Do not modify this line")
user_canary = read_file_line("~/.hermes/memories/USER.md", containing="CANARY: Do not modify this line")
if memory_canary is null or user_canary is null:
return {
status: "CRITICAL",
alert: "CANARY BREACH memory integrity compromised",
details: {
memory_canary_intact: memory_canary is not null,
user_canary_intact: user_canary is not null
}
}
# This agent only touches its own files no cross-agent access
# Confirm we are operating on this agent's own memory directory
if not directory_exists("~/.hermes/memories"):
return {status: "ERROR", reason: "Memory directory not found for this agent"}
# ============================================================
# PHASE 1: Pre-flight & Ledger
# ============================================================
# Read the audit ledger for drift detection
ledger = read_file("~/.hermes/memories/MEMORY_AUDIT_LEDGER.md")
last_audit_entry = parse_last_entry(ledger)
# Verify the memory tool works
try:
memory_status = call check_memory_tool
catch:
memory_status = "BROKEN"
# Read current state
memory_content = read_file("~/.hermes/memories/MEMORY.md")
user_content = read_file("~/.hermes/memories/USER.md")
# Calculate current utilization
memory_util = len(memory_content) / max_memory_chars * 100
user_util = len(user_content) / max_user_chars * 100
# Check if audit is needed
if last_audit_entry.timestamp > 14 days and memory_util < 75 and user_util < 75:
return {status: "SKIP", reason: "No audit needed"}
# ============================================================
# PHASE 1b: Drift Detection (Compare to Last Audit)
# ============================================================
drift_alerts = []
if last_audit_entry exists:
# Deletions accelerating? (50% increase from last audit)
if last_audit_entry.deletions > 0:
deletion_increase = (memory_util - last_audit_entry.memory_util_after) / last_audit_entry.deletions * 100
if deletion_increase > 50:
drift_alerts.append("ALERT: Memory bloat accelerating deletions increasing by " + deletion_increase + "% vs last audit")
# Unverified configs growing?
if last_audit_entry.unverified > 0:
unverified_increase = count_entries(memory_content, "[UNVERIFIED]") - last_audit_entry.unverified
if unverified_increase > 2:
drift_alerts.append("ALERT: Unverified configs growing " + unverified_increase + " new unverified entries since last audit")
# Audit missed? (No audit for >21 days)
if last_audit_entry.timestamp > 21 days:
drift_alerts.append("ALERT: Audit gap no audit completed in 21+ days")
# Utilization not improving? (After audit, still >70%)
if last_audit_entry.memory_util_after > 70:
drift_alerts.append("ALERT: Previous audit failed to reduce MEMORY.md below 70% capacity issue")
# ============================================================
# PHASE 2: Categorize every entry
# ============================================================
categorized = []
for entry in split_entries(memory_content):
cat = call categorize_entry(entry)
categorized.append({entry, category: cat, source: "MEMORY.md"})
for entry in split_entries(user_content):
cat = call categorize_entry(entry)
categorized.append({entry, category: cat, source: "USER.md"})
# ============================================================
# PHASE 3: Identify actions
# ============================================================
actions = []
for item in categorized:
if item.category in ["Session Note", "Temporary State"]:
# Check age of temporary state
if item.category == "Temporary State" and item.age < 14 days:
continue # Too young to delete
actions.append({action: "DELETE", entry: item.entry, reason: item.category})
elif item.category == "Historical Snapshot":
actions.append({action: "DELETE", entry: item.entry, reason: "Stale"})
elif item.category == "Technical Config" and item.source == "USER.md":
actions.append({action: "MOVE", from: "USER.md", to: "MEMORY.md", entry: item.entry})
elif item.category == "Identity" or item.category == "Preference" and item.source == "MEMORY.md":
actions.append({action: "MOVE", from: "MEMORY.md", to: "USER.md", entry: item.entry})
elif item.category == "Operating Rule":
actions.append({action: "MOVE", from: "MEMORY.md", to: "Skill file", entry: item.entry})
# Check for duplicates
for entry_a, entry_b in find_duplicates(categorized):
actions.append({action: "DELETE", entry: entry_b.entry, reason: "Duplicate of " + entry_a.entry})
# ============================================================
# PHASE 4: Verify live configs
# ============================================================
for item in categorized:
if item.category == "Technical Config" and item.entry contains URL:
result = call verify_endpoint(item.entry)
if result == "UNREACHABLE":
# Mark as UNVERIFIED, do not delete
actions.append({action: "MARK_UNVERIFIED", entry: item.entry})
elif result == "VERIFIED":
pass # Good, nothing to do
for item in categorized:
if item.category == "Technical Config" and item.entry contains "model:":
result = call verify_model(item.entry)
if result == "MISMATCH":
actions.append({action: "CORRECT", entry: item.entry, corrected: result.new_value})
# ============================================================
# PHASE 5: Apply changes
# ============================================================
if dry_run:
return {status: "DRY_RUN", actions: actions, drift_alerts: drift_alerts, summary: generate_summary(actions)}
# Apply deletions
for action in actions:
if action.action == "DELETE":
try:
memory action=remove target=action.source old_text=action.entry
catch after 2 retries:
patch path=action.source old_string=action.entry new_string=""
# Add summary marker since memory tool is broken
memory action=add target=action.source content="Entry deleted via patch: " + action.entry
# Apply moves
for action in actions:
if action.action == "MOVE":
# Delete from source
patch path=action.from old_string=action.entry new_string=""
# Add to destination
append_file path=action.to content=action.entry + "\n"
# Apply corrections
for action in actions:
if action.action == "CORRECT":
patch path="~/.hermes/memories/MEMORY.md" old_string=action.entry new_string=action.corrected
# Apply UNVERIFIED marks
for action in actions:
if action.action == "MARK_UNVERIFIED":
patch path="~/.hermes/memories/MEMORY.md" old_string=action.entry new_string=action.entry + " [UNVERIFIED]"
# ============================================================
# PHASE 6: Post-audit verification
# ============================================================
new_memory_content = read_file("~/.hermes/memories/MEMORY.md")
new_user_content = read_file("~/.hermes/memories/USER.md")
new_memory_util = len(new_memory_content) / max_memory_chars * 100
new_user_util = len(new_user_content) / max_user_chars * 100
# Verify canaries survived the audit
post_canary_check = read_file_line("~/.hermes/memories/MEMORY.md", containing="CANARY: Do not modify this line")
if post_canary_check is null:
drift_alerts.append("CRITICAL: Canary destroyed during audit memory integrity compromised")
# Add summary markers to in-memory stores
memory action=add target=memory content="MEMORY.md rewritten. Utilization: " + new_memory_util + "%."
memory action=add target=user content="USER.md rewritten. Utilization: " + new_user_util + "%."
# ============================================================
# PHASE 7: Append to Ledger
# ============================================================
ledger_entry = """
## """ + now() + """
- memory_util_before: """ + memory_util + """%
- memory_util_after: """ + new_memory_util + """%
- user_util_before: """ + user_util + """%
- user_util_after: """ + new_user_util + """%
- deletions: """ + count(actions, "DELETE") + """
- moves: """ + count(actions, "MOVE") + """
- corrections: """ + count(actions, "CORRECT") + """
- unverified: """ + count(actions, "MARK_UNVERIFIED") + """
- unresolved: """ + len(unresolved_entries) + """
- drift_alerts: """ + len(drift_alerts) + """
- canary_intact: """ + (post_canary_check is not null) + """
- status: COMPLETE
"""
append_file path="~/.hermes/memories/MEMORY_AUDIT_LEDGER.md" content=ledger_entry
# ============================================================
# PHASE 8: Report
# ============================================================
return {
status: "COMPLETE",
memory_utilization: new_memory_util,
user_utilization: new_user_util,
actions_applied: len(actions),
deletions: count(actions, "DELETE"),
moves: count(actions, "MOVE"),
corrections: count(actions, "CORRECT"),
unverified: count(actions, "MARK_UNVERIFIED"),
unresolved: unresolved_entries,
drift_alerts: drift_alerts,
last_audit: now(),
ledger_updated: true,
summary: generate_summary(actions)
}
```
### Shape
- `self`: categorize, verify configs, apply incremental patches, generate reports, drift detection, ledger management, canary checks
- `delegates`:
- `categorize_entry`: determine the category of a memory entry
- `check_memory_tool`: verify the memory tool is functional
- `verify_endpoint`: check if a URL is reachable
- `verify_model`: check if a model config matches live config.yaml
- `find_duplicates`: detect duplicate entries across files
- `generate_summary`: produce a human-readable summary of actions
### Tools
- `file:read`: read MEMORY.md and USER.md
- `file:patch`: incremental edits via find-and-replace
- `file:append`: append entries when moving between files
- `memory:action`: add/remove/replace entries in the in-memory store
- `network:curl`: verify endpoint reachability
- `cli:grep`: verify model configs against config.yaml
### Environment
- `MEMORY_PATH`: ~/.hermes/memories/MEMORY.md
- `USER_PATH`: ~/.hermes/memories/USER.md
- `CONFIG_PATH`: ~/.hermes/config.yaml
- `LEDGER_PATH`: ~/.hermes/memories/MEMORY_AUDIT_LEDGER.md
### Per-Agent Notes
Each Hermes agent (Mumuni, Tanko, Koby, Koonimo) runs this contract against its own `~/.hermes/memories/` directory. The contract is identical across agents, but all data is fully isolated: separate ledgers, separate writer registries, separate canaries. If a new agent is added to the roster, it must be listed in `### Scope` above and given its own isolated memory directory.