Compare commits
1 Commits
step35/669
...
fix/570-an
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e278758e0 |
22
ansible/playbooks/deploy_mempalace.yml
Normal file
22
ansible/playbooks/deploy_mempalace.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
# ansible/playbooks/deploy_mempalace.yml — Deploy MemPalace v3.0.0 to fleet wizards.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory/hosts.ini playbooks/deploy_mempalace.yml --limit ezra
|
||||
# ansible-playbook -i inventory/hosts.ini playbooks/deploy_mempalace.yml
|
||||
#
|
||||
# Refs: Issue #570
|
||||
|
||||
- name: Deploy MemPalace v3.0.0 to wizard hosts
|
||||
hosts: fleet
|
||||
become: false
|
||||
gather_facts: false
|
||||
vars:
|
||||
mempalace_hermes_home: "{{ ansible_env.HOME }}/.hermes"
|
||||
mempalace_sessions_dir: "{{ mempalace_hermes_home }}/sessions"
|
||||
mempalace_palace_path: "{{ ansible_env.HOME }}/.mempalace/palace"
|
||||
mempalace_wing: "{{ inventory_hostname }}_home"
|
||||
roles:
|
||||
- role: ../roles/mempalace
|
||||
vars:
|
||||
mempalace_venv_path: "{{ ansible_env.HOME }}/.mempalace-venv"
|
||||
16
ansible/roles/mempalace/defaults/main.yml
Normal file
16
ansible/roles/mempalace/defaults/main.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
# MemPalace role defaults
|
||||
mempalace_package_spec: "mempalace==3.0.0"
|
||||
mempalace_hermes_home: "{{ ansible_env.HOME }}/.hermes"
|
||||
mempalace_sessions_dir: "{{ mempalace_hermes_home }}/sessions"
|
||||
mempalace_palace_path: "{{ ansible_env.HOME }}/.mempalace/palace"
|
||||
mempalace_wing: "{{ inventory_hostname }}_home"
|
||||
mempalace_wakeup_dir: "{{ mempalace_hermes_home }}/wakeups"
|
||||
mempalace_wakeup_file: "{{ mempalace_wakeup_dir }}/{{ mempalace_wing }}.txt"
|
||||
mempalace_venv_path: "{{ ansible_env.HOME }}/.mempalace-venv"
|
||||
mempalace_config_path: "{{ mempalace_hermes_home }}/mempalace.yaml"
|
||||
mempalace_mcp_config_path: "{{ mempalace_hermes_home }}/hermes-mcp-mempalace.yaml"
|
||||
mempalace_session_hook_path: "{{ mempalace_hermes_home }}/session-start-mempalace.sh"
|
||||
mempalace_run_mining: true
|
||||
mempalace_run_search_test: true
|
||||
mempalace_run_wake_up: true
|
||||
2
ansible/roles/mempalace/meta/main.yml
Normal file
2
ansible/roles/mempalace/meta/main.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
dependencies: []
|
||||
119
ansible/roles/mempalace/tasks/main.yml
Normal file
119
ansible/roles/mempalace/tasks/main.yml
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
# MemPalace v3.0.0 deployment role for fleet wizards.
|
||||
# Refs: Issue #570
|
||||
|
||||
- name: Ensure mempalace venv directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ mempalace_venv_path }}"
|
||||
state: directory
|
||||
mode: '0750'
|
||||
|
||||
- name: Create mempalace virtual environment
|
||||
ansible.builtin.command:
|
||||
cmd: "python3 -m venv {{ mempalace_venv_path }}"
|
||||
creates: "{{ mempalace_venv_path }}/bin/python"
|
||||
|
||||
- name: Install mempalace package
|
||||
ansible.builtin.pip:
|
||||
name: "{{ mempalace_package_spec }}"
|
||||
virtualenv: "{{ mempalace_venv_path }}"
|
||||
virtualenv_command: "{{ mempalace_venv_path }}/bin/python -m venv"
|
||||
|
||||
- name: Ensure Hermes home directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ mempalace_hermes_home }}"
|
||||
state: directory
|
||||
mode: '0750'
|
||||
|
||||
- name: Ensure sessions directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ mempalace_sessions_dir }}"
|
||||
state: directory
|
||||
mode: '0750'
|
||||
|
||||
- name: Ensure wakeup directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ mempalace_wakeup_dir }}"
|
||||
state: directory
|
||||
mode: '0750'
|
||||
|
||||
- name: Ensure palace directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ mempalace_palace_path }}"
|
||||
state: directory
|
||||
mode: '0750'
|
||||
|
||||
- name: Deploy mempalace.yaml configuration
|
||||
ansible.builtin.template:
|
||||
src: mempalace.yaml.j2
|
||||
dest: "{{ mempalace_config_path }}"
|
||||
mode: '0640'
|
||||
|
||||
- name: Deploy Hermes MCP mempalace config
|
||||
ansible.builtin.template:
|
||||
src: hermes-mcp-mempalace.yaml.j2
|
||||
dest: "{{ mempalace_mcp_config_path }}"
|
||||
mode: '0640'
|
||||
|
||||
- name: Deploy session-start wake-up hook
|
||||
ansible.builtin.template:
|
||||
src: session-start-mempalace.sh.j2
|
||||
dest: "{{ mempalace_session_hook_path }}"
|
||||
mode: '0750'
|
||||
|
||||
- name: Mine Hermes home directory
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
echo "" | {{ mempalace_venv_path }}/bin/mempalace mine {{ mempalace_hermes_home }} --config {{ mempalace_config_path }}
|
||||
args:
|
||||
executable: /bin/bash
|
||||
when: mempalace_run_mining | bool
|
||||
register: mine_home_result
|
||||
changed_when: mine_home_result.rc == 0
|
||||
|
||||
- name: Mine session history
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
echo "" | {{ mempalace_venv_path }}/bin/mempalace mine {{ mempalace_sessions_dir }} --mode convos --config {{ mempalace_config_path }}
|
||||
args:
|
||||
executable: /bin/bash
|
||||
when: mempalace_run_mining | bool
|
||||
register: mine_sessions_result
|
||||
changed_when: mine_sessions_result.rc == 0
|
||||
|
||||
- name: Run search test
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
{{ mempalace_venv_path }}/bin/mempalace search "common queries" --config {{ mempalace_config_path }} | head -20
|
||||
args:
|
||||
executable: /bin/bash
|
||||
when: mempalace_run_search_test | bool
|
||||
register: search_test_result
|
||||
changed_when: false
|
||||
|
||||
- name: Generate wake-up context
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
{{ mempalace_venv_path }}/bin/mempalace wake-up --config {{ mempalace_config_path }} > {{ mempalace_wakeup_file }}
|
||||
export HERMES_MEMPALACE_WAKEUP_FILE="{{ mempalace_wakeup_file }}"
|
||||
printf '[MemPalace] wake-up context refreshed: %s\n' "$HERMES_MEMPALACE_WAKEUP_FILE"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
when: mempalace_run_wake_up | bool
|
||||
register: wake_up_result
|
||||
changed_when: wake_up_result.rc == 0
|
||||
|
||||
- name: Report MemPalace deployment summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "MemPalace deployed for {{ inventory_hostname }}"
|
||||
- "Package: {{ mempalace_package_spec }}"
|
||||
- "Config: {{ mempalace_config_path }}"
|
||||
- "Palace: {{ mempalace_palace_path }}"
|
||||
- "Wake-up: {{ mempalace_wakeup_file }}"
|
||||
- "MCP config: {{ mempalace_mcp_config_path }}"
|
||||
- "Session hook: {{ mempalace_session_hook_path }}"
|
||||
- "Home mine: {{ 'OK' if mine_home_result.rc | default(1) == 0 else 'SKIPPED' }}"
|
||||
- "Sessions mine: {{ 'OK' if mine_sessions_result.rc | default(1) == 0 else 'SKIPPED' }}"
|
||||
- "Search test: {{ 'OK' if search_test_result.rc | default(1) == 0 else 'SKIPPED' }}"
|
||||
- "Wake-up: {{ 'OK' if wake_up_result.rc | default(1) == 0 else 'SKIPPED' }}"
|
||||
@@ -0,0 +1,6 @@
|
||||
mcp_servers:
|
||||
mempalace:
|
||||
command: "{{ mempalace_venv_path }}/bin/python"
|
||||
args:
|
||||
- -m
|
||||
- mempalace.mcp_server
|
||||
21
ansible/roles/mempalace/templates/mempalace.yaml.j2
Normal file
21
ansible/roles/mempalace/templates/mempalace.yaml.j2
Normal file
@@ -0,0 +1,21 @@
|
||||
wing: {{ mempalace_wing }}
|
||||
palace: {{ mempalace_palace_path }}
|
||||
rooms:
|
||||
- name: sessions
|
||||
description: Conversation history and durable agent transcripts
|
||||
globs:
|
||||
- "*.json"
|
||||
- "*.jsonl"
|
||||
- name: config
|
||||
description: Hermes configuration and runtime settings
|
||||
globs:
|
||||
- "*.yaml"
|
||||
- "*.yml"
|
||||
- "*.toml"
|
||||
- name: docs
|
||||
description: Notes, markdown docs, and operating reports
|
||||
globs:
|
||||
- "*.md"
|
||||
- "*.txt"
|
||||
people: []
|
||||
projects: []
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if command -v {{ mempalace_venv_path }}/bin/mempalace >/dev/null 2>&1; then
|
||||
mkdir -p "{{ mempalace_wakeup_dir }}"
|
||||
{{ mempalace_venv_path }}/bin/mempalace wake-up --config {{ mempalace_config_path }} > "{{ mempalace_wakeup_file }}"
|
||||
export HERMES_MEMPALACE_WAKEUP_FILE="{{ mempalace_wakeup_file }}"
|
||||
printf '[MemPalace] wake-up context refreshed: %s\n' "$HERMES_MEMPALACE_WAKEUP_FILE"
|
||||
fi
|
||||
@@ -146,6 +146,23 @@ That bundle writes:
|
||||
- `session-start-mempalace.sh`
|
||||
- `issue-568-comment-template.md`
|
||||
|
||||
## Fleet Ansible deployment
|
||||
|
||||
Deploy MemPalace to Ezra (or the whole fleet) with the Ansible playbook:
|
||||
|
||||
```bash
|
||||
ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/deploy_mempalace.yml --limit ezra
|
||||
```
|
||||
|
||||
This playbook:
|
||||
1. Creates a dedicated venv and installs `mempalace==3.0.0`
|
||||
2. Deploys `mempalace.yaml`, MCP config, and session-start hook
|
||||
3. Mines the Hermes home and sessions directories
|
||||
4. Runs a search smoke test
|
||||
5. Generates the wake-up context file
|
||||
|
||||
Set `mempalace_run_mining=false` to skip mining on hosts where the corpus is already populated.
|
||||
|
||||
## Why this shape
|
||||
|
||||
- `wing: ezra_home` matches the issue's Ezra-specific integration target.
|
||||
|
||||
92
tests/test_mempalace_ansible_role.py
Normal file
92
tests/test_mempalace_ansible_role.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ROLE_PATH = ROOT / "ansible" / "roles" / "mempalace"
|
||||
PLAYBOOK_PATH = ROOT / "ansible" / "playbooks" / "deploy_mempalace.yml"
|
||||
|
||||
|
||||
class TestMempalaceAnsibleRole(unittest.TestCase):
|
||||
def test_role_directory_structure_exists(self):
|
||||
self.assertTrue(ROLE_PATH.exists(), "mempalace role directory missing")
|
||||
for subdir in ["tasks", "templates", "defaults", "meta"]:
|
||||
self.assertTrue(
|
||||
(ROLE_PATH / subdir).exists(),
|
||||
f"mempalace role subdir missing: {subdir}",
|
||||
)
|
||||
|
||||
def test_role_defaults_contains_required_variables(self):
|
||||
defaults_path = ROLE_PATH / "defaults" / "main.yml"
|
||||
self.assertTrue(defaults_path.exists())
|
||||
text = defaults_path.read_text(encoding="utf-8")
|
||||
required_vars = [
|
||||
"mempalace_package_spec",
|
||||
"mempalace_hermes_home",
|
||||
"mempalace_sessions_dir",
|
||||
"mempalace_palace_path",
|
||||
"mempalace_wing",
|
||||
"mempalace_wakeup_dir",
|
||||
"mempalace_wakeup_file",
|
||||
"mempalace_venv_path",
|
||||
"mempalace_config_path",
|
||||
"mempalace_mcp_config_path",
|
||||
"mempalace_session_hook_path",
|
||||
"mempalace_run_mining",
|
||||
"mempalace_run_search_test",
|
||||
"mempalace_run_wake_up",
|
||||
]
|
||||
for var in required_vars:
|
||||
self.assertIn(var, text, f"missing default var: {var}")
|
||||
|
||||
def test_role_tasks_contain_required_steps(self):
|
||||
tasks_path = ROLE_PATH / "tasks" / "main.yml"
|
||||
self.assertTrue(tasks_path.exists())
|
||||
text = tasks_path.read_text(encoding="utf-8")
|
||||
required_steps = [
|
||||
"Create mempalace virtual environment",
|
||||
"Install mempalace package",
|
||||
"Deploy mempalace.yaml configuration",
|
||||
"Deploy Hermes MCP mempalace config",
|
||||
"Deploy session-start wake-up hook",
|
||||
"Mine Hermes home directory",
|
||||
"Mine session history",
|
||||
"Run search test",
|
||||
"Generate wake-up context",
|
||||
]
|
||||
for step in required_steps:
|
||||
self.assertIn(step, text, f"missing task: {step}")
|
||||
|
||||
def test_role_templates_are_valid(self):
|
||||
yaml_template = ROLE_PATH / "templates" / "mempalace.yaml.j2"
|
||||
mcp_template = ROLE_PATH / "templates" / "hermes-mcp-mempalace.yaml.j2"
|
||||
hook_template = ROLE_PATH / "templates" / "session-start-mempalace.sh.j2"
|
||||
|
||||
self.assertTrue(yaml_template.exists())
|
||||
self.assertTrue(mcp_template.exists())
|
||||
self.assertTrue(hook_template.exists())
|
||||
|
||||
yaml_text = yaml_template.read_text(encoding="utf-8")
|
||||
self.assertIn("wing: {{ mempalace_wing }}", yaml_text)
|
||||
self.assertIn("palace: {{ mempalace_palace_path }}", yaml_text)
|
||||
self.assertIn("rooms:", yaml_text)
|
||||
|
||||
mcp_text = mcp_template.read_text(encoding="utf-8")
|
||||
self.assertIn("mcp_servers:", mcp_text)
|
||||
self.assertIn("mempalace:", mcp_text)
|
||||
self.assertIn("mempalace.mcp_server", mcp_text)
|
||||
|
||||
hook_text = hook_template.read_text(encoding="utf-8")
|
||||
self.assertIn("mempalace wake-up", hook_text)
|
||||
self.assertIn("HERMES_MEMPALACE_WAKEUP_FILE", hook_text)
|
||||
|
||||
def test_playbook_exists_and_targets_fleet(self):
|
||||
self.assertTrue(PLAYBOOK_PATH.exists(), "deploy_mempalace.yml playbook missing")
|
||||
text = PLAYBOOK_PATH.read_text(encoding="utf-8")
|
||||
self.assertIn("hosts: fleet", text)
|
||||
self.assertIn("../roles/mempalace", text)
|
||||
self.assertIn("mempalace_venv_path", text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -85,6 +85,8 @@ class TestMempalaceEzraIntegration(unittest.TestCase):
|
||||
"mcp_servers:",
|
||||
"HERMES_MEMPALACE_WAKEUP_FILE",
|
||||
"Metrics reply for #568",
|
||||
"Fleet Ansible deployment",
|
||||
"ansible-playbook",
|
||||
]
|
||||
for snippet in required:
|
||||
self.assertIn(snippet, text)
|
||||
|
||||
Reference in New Issue
Block a user