Merge branch 'master' of https://git.sysloggh.net/SyslogSolution/prose-contracts
This commit is contained in:
@@ -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)
|
||||
@@ -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 |
|
||||
```
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user