Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions hindsight-api-slim/tests/test_hermes_templates_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""End-to-end import of the shipped Hermes-tagged bank templates.

Proves every `hermes`-tagged manifest in the Templates Hub catalog actually
imports: creating the bank, applying config, and creating its mental models
and directives — plus idempotent re-apply and additive layering.
"""

import json
from datetime import datetime
from pathlib import Path

import httpx
import pytest
import pytest_asyncio

from hindsight_api.api import create_app

_DATA_DIR = Path(__file__).resolve().parents[2] / "hindsight-docs" / "src" / "data"


def _hermes_manifests():
catalog = json.loads((_DATA_DIR / "templates.json").read_text())
out = []
for entry in catalog["templates"]:
if "hermes" in (entry.get("integrations") or []):
manifest = json.loads((_DATA_DIR / entry["manifest_file"]).read_text())
out.append((entry["id"], manifest))
return out


@pytest_asyncio.fixture
async def api_client(memory):
app = create_app(memory, initialize_memory=False)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
yield client


def _bank(name):
return f"hermes_tmpl_{name}_{datetime.now().timestamp()}"


@pytest.mark.asyncio
@pytest.mark.parametrize("template_id,manifest", _hermes_manifests(), ids=lambda v: v if isinstance(v, str) else "")
async def test_every_hermes_template_imports_into_a_fresh_bank(api_client, template_id, manifest):
bank_id = _bank(template_id)
resp = await api_client.post(f"/v1/default/banks/{bank_id}/import", json=manifest)
assert resp.status_code == 200, resp.text
data = resp.json()
assert data["dry_run"] is False

expected_mm = {m["id"] for m in manifest.get("mental_models", [])}
expected_dir = {d["name"] for d in manifest.get("directives", [])}
assert set(data["mental_models_created"]) == expected_mm
assert set(data["directives_created"]) == expected_dir
if manifest.get("bank"):
assert data["config_applied"] is True

# The bank now exists; export reflects its models + directives.
exported = (await api_client.get(f"/v1/default/banks/{bank_id}/export")).json()
assert {m["id"] for m in (exported.get("mental_models") or [])} == expected_mm
assert {d["name"] for d in (exported.get("directives") or [])} == expected_dir


@pytest.mark.asyncio
async def test_reapply_is_idempotent(api_client):
_, manifest = next(m for m in _hermes_manifests() if m[0] == "hermes-gateway-bot")
bank_id = _bank("idem")
ids = {m["id"] for m in manifest["mental_models"]}

first = (await api_client.post(f"/v1/default/banks/{bank_id}/import", json=manifest)).json()
assert set(first["mental_models_created"]) == ids

second = (await api_client.post(f"/v1/default/banks/{bank_id}/import", json=manifest)).json()
assert set(second["mental_models_updated"]) == ids
assert second["mental_models_created"] == []

# No duplicates: still exactly the same set of models.
exported = (await api_client.get(f"/v1/default/banks/{bank_id}/export")).json()
assert {m["id"] for m in (exported.get("mental_models") or [])} == ids


@pytest.mark.asyncio
async def test_layering_a_second_template_is_additive(api_client):
manifests = dict(_hermes_manifests())
gateway, research = manifests["hermes-gateway-bot"], manifests["research-assistant"]
bank_id = _bank("layer")

await api_client.post(f"/v1/default/banks/{bank_id}/import", json=gateway)
res = (await api_client.post(f"/v1/default/banks/{bank_id}/import", json=research)).json()

# research-assistant's models are added on top; gateway's remain.
assert set(res["mental_models_created"]) == {m["id"] for m in research["mental_models"]}
exported = (await api_client.get(f"/v1/default/banks/{bank_id}/export")).json()
got = {m["id"] for m in (exported.get("mental_models") or [])}
assert {m["id"] for m in gateway["mental_models"]} <= got
assert {m["id"] for m in research["mental_models"]} <= got

# config overrides get overwritten by the later template.
assert exported["bank"]["reflect_mission"] == research["bank"]["reflect_mission"]
48 changes: 45 additions & 3 deletions hindsight-docs/src/data/templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@
"strands",
"llamaindex",
"local-mcp",
"skills"
"skills",
"hermes"
],
"manifest_file": "templates/conversation.json"
},
{
"id": "coding-agent",
"name": "Coding Agent",
"description": "For coding assistants. Remembers project architecture, technical decisions, coding patterns, and user preferences across sessions. High literalism for precise technical recall.",
"description": "For coding assistants and agents. Remembers project architecture, technical decisions, coding patterns, review preferences, and workflow across sessions. High literalism for precise technical recall.",
"category": "coding",
"integrations": [
"claude-code",
"codex"
"codex",
"hermes"
],
"manifest_file": "templates/coding-agent.json"
},
Expand All @@ -44,6 +46,46 @@
"hindclaw"
],
"manifest_file": "templates/personal-assistant.json"
},
{
"id": "hermes-gateway-bot",
"name": "Gateway / Community Bot",
"description": "For agents that talk to many people across chat platforms (Telegram, Discord, Slack). Keeps per-person profiles distinct, learns each community's norms, and tracks open threads across users.",
"category": "chat",
"integrations": [
"hermes"
],
"manifest_file": "templates/hermes-gateway-bot.json"
},
{
"id": "hermes-orchestrator",
"name": "Multi-Agent Orchestrator",
"description": "For orchestrations of specialized agents. Remembers decisions and their rationale, ownership boundaries, hand-offs, and how past escalations were resolved. High literalism and skepticism for precise coordination.",
"category": "orchestration",
"integrations": [
"hermes"
],
"manifest_file": "templates/hermes-orchestrator.json"
},
{
"id": "customer-support",
"name": "Customer Support Agent",
"description": "For support and client-comms agents. Remembers customer issues, resolutions, sentiment, and account context so every interaction is informed. High empathy, never re-asks known details.",
"category": "support",
"integrations": [
"hermes"
],
"manifest_file": "templates/customer-support.json"
},
{
"id": "research-assistant",
"name": "Research Assistant",
"description": "For research, monitoring, and knowledge-work agents that write briefings and digests or maintain a second brain. Learns the user's interests, tracks what to ignore to sharpen curation, and compounds durable knowledge with sources over time.",
"category": "research",
"integrations": [
"hermes"
],
"manifest_file": "templates/research-assistant.json"
}
]
}
15 changes: 13 additions & 2 deletions hindsight-docs/src/data/templates/coding-agent.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{
"version": "1",
"bank": {
"retain_mission": "Extract technical decisions and their rationale, architectural choices, coding patterns and conventions, project structure facts, library/tool preferences, and recurring issues. Ignore transient debugging output and boilerplate.",
"retain_mission": "Extract technical decisions and their rationale, architectural choices, coding patterns and conventions, project structure facts, library/tool preferences, recurring issues, and the developer's code-review preferences (what they flag, what they wave through). Ignore transient debugging output and boilerplate.",
"enable_observations": true,
"observations_mission": "Track stable project facts: tech stack, team conventions, architecture patterns, and how the codebase evolves over time."
"observations_mission": "Track stable project facts: tech stack, team conventions, architecture patterns, review standards, and how the codebase evolves over time.",
"disposition_literalism": 5,
"disposition_skepticism": 3
},
"mental_models": [
{
Expand All @@ -23,6 +25,15 @@
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "review-patterns",
"name": "Review Patterns",
"source_query": "In code review, what does this developer consistently flag or care about (bugs, style, security, tests), and what do they consider acceptable? What categories of feedback recur?",
"max_tokens": 1024,
"trigger": {
"refresh_after_consolidation": true
}
}
]
}
53 changes: 53 additions & 0 deletions hindsight-docs/src/data/templates/customer-support.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"version": "1",
"bank": {
"reflect_mission": "You are helping a support agent remember its customers. Recall each customer's history, open and past issues, account context, and how they prefer to be treated, so every interaction feels informed and personal.",
"retain_mission": "Extract customer issues and their resolutions, sentiment, account and plan details, product context, promises and commitments made, and any preferences. Ignore greetings and pleasantries.",
"enable_observations": true,
"observations_mission": "Track stable facts per customer: their account and plan, recurring issues, product usage, preferences, and how their sentiment trends over time.",
"disposition_empathy": 5,
"disposition_literalism": 4,
"disposition_skepticism": 2
},
"mental_models": [
{
"id": "customer-profile",
"name": "Customer Profile",
"source_query": "Who is this customer? What is their account, plan, product usage, and how do they prefer to be supported?",
"max_tokens": 2048,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "issue-history",
"name": "Issue & Resolution History",
"source_query": "What issues has this customer reported, and how were they resolved? What is still open or unresolved?",
"max_tokens": 1536,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "sentiment-overview",
"name": "Sentiment Overview",
"source_query": "What is the customer's overall sentiment and how has it trended across interactions? What frustrates or delights them?",
"max_tokens": 1024,
"trigger": {
"refresh_after_consolidation": true
}
}
],
"directives": [
{
"name": "Never re-ask known details",
"content": "Use the customer's recalled account and history instead of asking them to repeat information they've already given. Re-asking known details erodes trust.",
"priority": 10
},
{
"name": "Lead with empathy",
"content": "Acknowledge the customer's situation and sentiment before jumping to a solution, especially when the recalled history shows repeated or unresolved issues.",
"priority": 8
}
]
}
53 changes: 53 additions & 0 deletions hindsight-docs/src/data/templates/hermes-gateway-bot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"version": "1",
"bank": {
"reflect_mission": "You are the shared memory for an agent that talks to many people across chat platforms (Telegram, Discord, WhatsApp, Slack, QQ, LINE). Help it stay in character and consistent by recalling the persona and norms of each channel or community, who each person is, and what they care about.",
"retain_mission": "Extract the persona and norms of each channel or community (its purpose, tone, in-jokes, rules), per-person preferences and facts, who's who in the group, ongoing requests, and commitments made. Attribute everything to the right person and the right channel. Ignore bot commands, join/leave noise, and pure small talk.",
"enable_observations": true,
"observations_mission": "Track the persona and norms of each channel or community and how it should be engaged, plus stable facts about individual members (preferences, role, how they like to interact), and how both evolve.",
"disposition_empathy": 4,
"disposition_literalism": 3,
"disposition_skepticism": 3
},
"mental_models": [
{
"id": "channel-persona",
"name": "Channel Persona & Norms",
"source_query": "For this channel or community, what is its purpose, tone, and set of norms? What persona should the agent maintain here, and how does it differ from other channels?",
"max_tokens": 1536,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "member-profiles",
"name": "Member Profiles",
"source_query": "Who are the people in this space? What do we know about each one — their preferences, role, interests, and how they like to interact?",
"max_tokens": 2048,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "open-threads",
"name": "Open Threads",
"source_query": "What requests, questions, or follow-ups are still open across recent conversations, and who is waiting on them?",
"max_tokens": 1024,
"trigger": {
"refresh_after_consolidation": true
}
}
],
"directives": [
{
"name": "Attribute to the right person",
"content": "Memories are shared across many users. Always tie a fact, preference, or request to the specific person it belongs to, and don't apply one person's context to another.",
"priority": 10
},
{
"name": "Stay in character per channel",
"content": "Maintain the persona, tone, and norms specific to the channel or community you're in. The same agent may be playful in one group and formal in another — let the recalled channel persona guide how you engage, consistently over time.",
"priority": 9
}
]
}
53 changes: 53 additions & 0 deletions hindsight-docs/src/data/templates/hermes-orchestrator.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"version": "1",
"bank": {
"reflect_mission": "You are the shared memory for a multi-agent system — an orchestrator coordinating specialized sub-agents, whether that's a build pipeline (plan → code → QA → ship), a chief-of-staff running per-project sub-agents, or a team of regional agents. Help the team stay aligned by recalling decisions, who owns what, hand-offs and phase status, and how past problems and escalations were resolved.",
"retain_mission": "Extract decisions and their rationale, task assignments and hand-offs between agents, phase or pipeline status, escalations and how they were resolved, ownership boundaries, per-project context, and durable operational knowledge. Ignore intra-step chatter and transient tool output.",
"enable_observations": true,
"observations_mission": "Track stable operational knowledge: which agent, project, or domain owns what, how recurring problems get resolved, common failure points in the pipeline, and the patterns behind successful hand-offs and escalations.",
"disposition_literalism": 4,
"disposition_skepticism": 4,
"disposition_empathy": 2
},
"mental_models": [
{
"id": "decisions-and-rationale",
"name": "Decisions & Rationale",
"source_query": "What key decisions have been made, by which agent or project, and why? What trade-offs were considered and what was ruled out?",
"max_tokens": 2048,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "ownership-map",
"name": "Ownership & Project Map",
"source_query": "Which agent, project, or domain owns which responsibilities? Where are the boundaries between agents, what is the status of each project or pipeline phase, and who handles what?",
"max_tokens": 1536,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "resolution-playbook",
"name": "Resolution & Escalation Playbook",
"source_query": "What kinds of problems or escalations come up, and how were similar ones resolved before? Which failures recur in the pipeline and what fixed them?",
"max_tokens": 1536,
"trigger": {
"refresh_after_consolidation": true
}
}
],
"directives": [
{
"name": "Preserve decision provenance",
"content": "When recalling a decision, include which agent or project made it and why. Coordination depends on knowing not just what was decided but the reasoning and owner behind it.",
"priority": 10
},
{
"name": "Hand off with full context",
"content": "On a hand-off or escalation, carry forward the relevant project context and any prior resolutions of similar issues so the next agent or phase starts with the full picture rather than from scratch.",
"priority": 9
}
]
}
Loading