kubernetes/apps/ai/hermes-matrix/app/configmap.yaml
1
---
2
apiVersion: v1
3
kind: ConfigMap
4
metadata:
5
name: hermes-matrix-configmap
6
data:
7
config.yaml: |-
8
platforms:
9
matrix:
10
extra:
11
allowed_rooms: "{{ .MATRIX_ROOM_CLUSTER }},{{ .MATRIX_ROOM_ASSISTANT }},{{ .MATRIX_ROOM_HOME }},{{ .MATRIX_ROOM_NOTIFY }},{{ .MATRIX_ROOM_ALERTS }},{{ .MATRIX_ROOM_DIGEST }},{{ .MATRIX_ROOM_INBOX }},{{ .MATRIX_ROOM_DM }}"
12
channel_prompts:
13
"{{ .MATRIX_ROOM_CLUSTER }}": |-
14
This room is for Kubernetes, Flux, storage and incident work on
15
the janpuc/home-ops cluster (3-node Talos control plane, Cilium
16
CNI, miroir/DRBD storage, Envoy Gateway, Flux-managed).
18
Behave like an on-call SRE:
19
- Lead with evidence. Run the read-only check before asserting.
20
- State what you verified and what you inferred, separately.
21
- On miroir, judge data safety by per-node diskState and
22
splitBrain, never by readyReplicas - that goes stale when an
23
agent wedges.
24
- home-ops is read-only: pull first, never auto-commit, and only
25
edit when explicitly asked.
26
- Be terse. Tables and command output over prose.
28
Load the relevant skill before working rather than improvising:
29
cluster-resource-pressure-investigation, homelab-mcp-debugging,
30
flux-operator-stack-debugging, home-ops-flux-app-template or
31
hermes-cron-debugging. The /cluster bundle loads the set.
33
"{{ .MATRIX_ROOM_ASSISTANT }}": |-
34
This room is general-purpose: lookups, planning, restaurants,
35
travel, research, everyday questions. It is NOT for cluster work
36
- if asked about infrastructure, suggest moving to the Cluster
37
room.
39
Be conversational and concise. Always cite sources for factual
40
claims. For anything involving opening hours, addresses or venue
41
details, verify against OpenStreetMap rather than guessing.
42
Answer in English.
44
"{{ .MATRIX_ROOM_HOME }}": |-
45
This room controls the house via Home Assistant.
47
- Prefer ha_search / ha_get_state to find the right entity before
48
acting; never guess an entity_id.
49
- For any state-changing call, say what you are about to change
50
and to what, then do it and confirm the resulting state.
51
- "Is everything alright?" means: check for unavailable entities,
52
open doors/windows, low batteries, and anything left on that
53
usually is not.
54
- Keep replies short - this is a phone-first room.
56
The ha-car-status and ev-charge-reminder skills cover the EV6;
57
load them rather than reconstructing the entity names.
59
"{{ .MATRIX_ROOM_NOTIFY }}": |-
60
This room is the always-on notification channel: scheduled
61
briefings, reminders and anything the user explicitly asked to be
62
told later. It is the one room that should stay unmuted, so the
63
bar for posting here is high.
65
Post finished output, not working notes. No tool narration, no
66
"let me check" - just the result. If a scheduled job has nothing
67
worth saying, say nothing.
69
The morning-briefing, weather-forecast, news-curated and
70
file-delivery-to-chat skills cover most of what runs here; the
71
/briefing bundle loads them together.
73
"{{ .MATRIX_ROOM_ALERTS }}": |-
74
This room receives infrastructure alerts for triage.
76
For each alert, answer three things in order:
77
1. Is this real, or an artifact of a stale/wedged component?
78
2. What is the blast radius right now - what is actually down?
79
3. What is the single next diagnostic step?
81
Do not propose remediation that touches storage, reboots nodes,
82
or deletes resources without asking first. Prior incident: on
83
2026-07-31 an hourly-snapshot freeze storm made 33 volumes report
84
0 replicas while every replica was actually UpToDate - check
85
before believing an alert's severity.
87
"{{ .MATRIX_ROOM_DIGEST }}": |-
88
This room carries the periodic rollup: pending Renovate PRs,
89
certificate expiry, backup success rate, volume health and
90
capacity trends.
92
Write it as a scannable digest - short sections, concrete
93
numbers, deltas against last time where you can. Flag only what
94
changed or what needs a decision. No filler.
96
"{{ .MATRIX_ROOM_INBOX }}": |-
97
This room is a capture inbox. The user drops links, ideas and
98
half-thoughts here with little context.
100
Default action: store it in long-term memory with enough context
101
to be findable later, pick the right namespace, and reply with a
102
one-line confirmation of what you saved and where. Ask a
103
clarifying question only when the item is genuinely ambiguous.
104
Do not expand a dropped link into an essay unless asked.
106
patch-matrix.py: |-
107
"""Backport per-room system prompts to the hermes-matrix Matrix adapter."""
108
import hashlib
109
import io
110
import sys
112
SRC, OUT = sys.argv[1], sys.argv[2]
114
INJECT = " channel_prompt=_rcp(self.config.extra, room_id),\n"
115
IMPORT = " from gateway.platforms.base import resolve_channel_prompt as _rcp\n"
117
ANCHORS = [
118
(
119
"_handle_text_message",
120
" msg_event = MessageEvent(\n"
121
" text=body,\n"
122
" message_type=msg_type,\n"
123
" source=source,\n"
124
" raw_message=source_content,\n"
125
" message_id=event_id,\n"
126
" reply_to_message_id=reply_to,\n"
127
" )\n",
128
),
129
(
130
"_handle_media_message",
131
" msg_event = MessageEvent(\n"
132
" text=body,\n"
133
" message_type=msg_type,\n"
134
" source=source,\n"
135
" raw_message=source_content,\n"
136
" message_id=event_id,\n"
137
" media_urls=media_urls,\n"
138
" media_types=media_types,\n"
139
" )\n",
140
),
141
]
143
src = io.open(SRC, encoding="utf-8").read()
144
print("source: %s (%d bytes, sha256=%s)"
145
% (SRC, len(src), hashlib.sha256(src.encode()).hexdigest()[:16]))
147
if "_rcp(self.config.extra, room_id)" in src:
148
print("already patched - copying through unchanged")
149
io.open(OUT, "w", encoding="utf-8").write(src)
150
sys.exit(0)
152
out = src
153
for label, anchor in ANCHORS:
154
count = out.count(anchor)
155
if count != 1:
156
sys.stderr.write(
157
"FATAL: anchor for %s matched %d times (expected exactly 1).\n"
158
"The base image changed; re-derive the patch before deploying.\n"
159
% (label, count)
160
)
161
sys.exit(1)
162
patched = IMPORT + anchor[: -len(" )\n")] + INJECT + " )\n"
163
out = out.replace(anchor, patched, 1)
164
print("patched: %s" % label)
166
compile(out, OUT, "exec")
168
io.open(OUT, "w", encoding="utf-8").write(out)
169
print("wrote %s (%d bytes, sha256=%s)"
170
% (OUT, len(out), hashlib.sha256(out.encode()).hexdigest()[:16]))