initial: identity resolution plugin
- Plugin manifest (plugin.yaml) with pre_gateway_dispatch + on_session_start hooks - /identity slash command for config management - Honcho injector patch docs (6 lines in plugins/memory/honcho/__init__.py) - Config file at /opt/data/identity-config.json for persistence - Kanban task body convention: context_peer: <name> - Sample config with thierry/catherine mappings Architecture: user-installed plugin (hooks + CLI) + 6-line Honcho bundled plugin change
This commit is contained in:
102
README.md
102
README.md
@@ -1,3 +1,101 @@
|
||||
# hermes-identity-plugin
|
||||
# Hermes Identity Plugin
|
||||
|
||||
Hermes identity resolution plugin — maps platform IDs and kanban boards to stable Honcho peer names across Discord, Telegram, terminal, and kanban workers.
|
||||
Maps platform-specific user IDs (Discord snowflake, Telegram UID, terminal
|
||||
session) and kanban boards to stable Honcho peer names. Eliminates
|
||||
`user-default-t_*` peers from kanban workers and unifies a user's identity
|
||||
across platforms (Discord + Telegram → same Honcho peer).
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ identity-config.json │
|
||||
│ /opt/data/ │
|
||||
└──────┬───────────────┘
|
||||
│ read on init
|
||||
┌──────────────────┼──────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ pre_gateway │ │ on_session │ │ Honcho │
|
||||
│ dispatch │ │ start hook │ │ injector │
|
||||
│ hook (log) │ │ (log/track) │ │ (session.py)│
|
||||
└──────────────┘ └──────────────┘ └──────┬───────┘
|
||||
│
|
||||
resolves peer_name
|
||||
before Honcho init
|
||||
```
|
||||
|
||||
Two components work together:
|
||||
|
||||
1. **This plugin** (user-installed) — provides `/identity` slash command for
|
||||
config management, `pre_gateway_dispatch` logging, and `on_session_start`
|
||||
tracking. Installed via `hermes plugins install`.
|
||||
|
||||
2. **Honcho injector** (~6 lines in `plugins/memory/honcho/session.py`) — reads
|
||||
the config file and `HERMES_KANBAN_TASK` env var to inject the correct
|
||||
`runtime_user_peer_name` into Honcho's session initialization. See
|
||||
[`docs/honcho-injector.md`](docs/honcho-injector.md).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# 1. Install the plugin
|
||||
hermes plugins install gitea:code.lazyworkhorse.net/Hermes/hermes-identity-plugin
|
||||
|
||||
# 2. Apply the Honcho injector patch (see docs/honcho-injector.md)
|
||||
# 3. Create the config file
|
||||
cp config.sample.json /opt/data/identity-config.json
|
||||
# 4. Edit /opt/data/identity-config.json with your mappings
|
||||
# 5. Restart gateways
|
||||
```
|
||||
|
||||
## Config file: `/opt/data/identity-config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"mappings": [
|
||||
{"platform": "discord", "id": "479136126737711105", "peer": "thierry"},
|
||||
{"platform": "telegram", "id": "123456789", "peer": "thierry"}
|
||||
],
|
||||
"boards": {
|
||||
"default": "thierry",
|
||||
"catherine": "catherine"
|
||||
},
|
||||
"fallback_peer": "user",
|
||||
"enforce_context_peer": true
|
||||
}
|
||||
```
|
||||
|
||||
## Task body convention
|
||||
|
||||
Every kanban task MUST include a `context_peer` in its body:
|
||||
|
||||
```markdown
|
||||
Do research on Postgres migration costs.
|
||||
|
||||
```metadata
|
||||
context_peer: thierry
|
||||
```
|
||||
```
|
||||
|
||||
Profiles (Claire, Ashley, Finn, Matt) MUST include this when calling
|
||||
`kanban_create`. The `enforce_context_peer` config flag controls whether
|
||||
missing `context_peer` causes an error.
|
||||
|
||||
## Commands
|
||||
|
||||
- `/identity status` — show current config and resolved peer
|
||||
- `/identity list` — list all mappings
|
||||
- `/identity add discord <id> <peer>` — add a new mapping
|
||||
- `/identity rm <index>` — remove mapping
|
||||
- `/identity board <slug> <peer>` — set board peer
|
||||
- `/identity help` — full help
|
||||
|
||||
## Enforcing the convention
|
||||
|
||||
Add this to the kanban-worker skill or the profile's system prompt:
|
||||
|
||||
> When creating a kanban task with `kanban_create`, you MUST include
|
||||
> `context_peer: <peer_name>` in the task body as a metadata block.
|
||||
> Use the user's Honcho peer name — not your own profile name.
|
||||
|
||||
306
__init__.py
Normal file
306
__init__.py
Normal file
@@ -0,0 +1,306 @@
|
||||
"""
|
||||
hermes-identity-plugin (identity)
|
||||
|
||||
Resolves platform-specific user IDs and kanban workers to stable Honcho
|
||||
peer names. The plugin provides hooks + slash command. The actual peer name
|
||||
injection into Honcho requires a ~6-line change in the bundled Honcho plugin
|
||||
(plugins/memory/honcho/session.py) — see docs/honcho-injector.md.
|
||||
|
||||
Config: /opt/data/identity-config.json (persistent across container rebuilds)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_PATH = Path("/opt/data/identity-config.json")
|
||||
|
||||
# ── Config ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _default_config() -> dict[str, Any]:
|
||||
return {
|
||||
"mappings": [],
|
||||
"boards": {},
|
||||
"fallback_peer": "user",
|
||||
"enforce_context_peer": True,
|
||||
}
|
||||
|
||||
|
||||
def load_config() -> dict[str, Any]:
|
||||
if not CONFIG_PATH.exists():
|
||||
return _default_config()
|
||||
try:
|
||||
return json.loads(CONFIG_PATH.read_text())
|
||||
except (json.JSONDecodeError, OSError) as exc:
|
||||
logger.warning("identity: config parse error: %s", exc)
|
||||
return _default_config()
|
||||
|
||||
|
||||
def save_config(cfg: dict[str, Any]) -> None:
|
||||
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
CONFIG_PATH.write_text(json.dumps(cfg, indent=2))
|
||||
logger.info("identity: config saved to %s", CONFIG_PATH)
|
||||
|
||||
|
||||
# ── Resolution ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def resolve_peer(platform: str, platform_id: str) -> str | None:
|
||||
"""Resolve a platform+ID to a canonical peer name."""
|
||||
cfg = load_config()
|
||||
for m in cfg.get("mappings", []):
|
||||
if m.get("platform") == platform and str(m.get("id", "")) == str(platform_id):
|
||||
return m["peer"]
|
||||
return cfg.get("fallback_peer")
|
||||
|
||||
|
||||
def resolve_board_peer(board_slug: str | None) -> str | None:
|
||||
"""Resolve a kanban board slug to a peer name."""
|
||||
if not board_slug:
|
||||
return None
|
||||
cfg = load_config()
|
||||
return cfg.get("boards", {}).get(board_slug)
|
||||
|
||||
|
||||
def get_peer_name() -> str | None:
|
||||
"""Resolve peer name for the current process context.
|
||||
|
||||
Priority:
|
||||
1. HERMES_HONCHO_PEER_NAME env var (set by kanban dispatcher or manually)
|
||||
2. Kanban worker: task body context_peer → board config → fallback
|
||||
"""
|
||||
explicit = os.environ.get("HERMES_HONCHO_PEER_NAME")
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
task_id = os.environ.get("HERMES_KANBAN_TASK")
|
||||
board = os.environ.get("HERMES_KANBAN_BOARD")
|
||||
db_path = os.environ.get("HERMES_KANBAN_DB")
|
||||
if task_id and db_path:
|
||||
return _resolve_kanban_peer(task_id, board, db_path)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_kanban_peer(task_id: str, board: str | None, db_path: str) -> str | None:
|
||||
"""Read kanban task body for context_peer, fallback to board config."""
|
||||
try:
|
||||
db = Path(db_path)
|
||||
if not db.exists():
|
||||
return None
|
||||
conn = sqlite3.connect(str(db))
|
||||
conn.row_factory = sqlite3.Row
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT body FROM tasks WHERE id = ?", (task_id,)
|
||||
).fetchone()
|
||||
if not row:
|
||||
return None
|
||||
body = row["body"] or ""
|
||||
peer = _extract_context_peer(body)
|
||||
if peer:
|
||||
return peer
|
||||
return resolve_board_peer(board)
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
logger.debug("identity: kanban peer resolution error: %s", exc)
|
||||
return None
|
||||
|
||||
|
||||
_CONTEXT_PEER_RE = re.compile(r"```metadata\s*\ncontext_peer:\s*(\S+)", re.MULTILINE)
|
||||
_INLINE_PEER_RE = re.compile(r"context_peer:\s*(\S+)")
|
||||
|
||||
|
||||
def _extract_context_peer(body: str) -> str | None:
|
||||
m = _CONTEXT_PEER_RE.search(body)
|
||||
if m:
|
||||
return m.group(1)
|
||||
m = _INLINE_PEER_RE.search(body)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
# ── Enforce context_peer on kanban task creation ────────────────────────────
|
||||
# Profiles (Claire, Ashley, etc.) MUST include ``context_peer: <name>`` in
|
||||
# every kanban task body. The kanban-worker skill references this convention.
|
||||
# The Honcho injector (see docs/honcho-injector.md) reads it at worker start.
|
||||
|
||||
|
||||
def enforce_context_peer(body: str | None) -> str | None:
|
||||
"""Return error message if body lacks context_peer, None if OK."""
|
||||
cfg = load_config()
|
||||
if not cfg.get("enforce_context_peer", True):
|
||||
return None
|
||||
if not body or "context_peer:" not in body:
|
||||
return (
|
||||
"Missing required `context_peer: <name>` in task body. "
|
||||
"Add a metadata block:\n"
|
||||
"```metadata\ncontext_peer: thierry\n```\n"
|
||||
"Replace `thierry` with the intended user's peer name."
|
||||
)
|
||||
peer = _extract_context_peer(body)
|
||||
if not peer:
|
||||
return "context_peer: found in body but could not be parsed. Use format: context_peer: <name>"
|
||||
return None
|
||||
|
||||
|
||||
# ── Hooks ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _pre_gateway_dispatch(event: Any, gateway: Any, session_store: Any, **kw) -> dict | None:
|
||||
"""Log resolved peer identity before dispatch."""
|
||||
platform = getattr(event, "platform", "unknown")
|
||||
user_id = getattr(event, "user_id", "unknown")
|
||||
resolved = resolve_peer(platform, user_id)
|
||||
|
||||
if resolved:
|
||||
logger.debug("identity: gateway platform=%s user_id=%s → peer=%s", platform, user_id, resolved)
|
||||
else:
|
||||
logger.info("identity: unmapped gateway user platform=%s user_id=%s", platform, user_id)
|
||||
return None # allow normal dispatch
|
||||
|
||||
|
||||
def _on_session_start(**kw: Any) -> None:
|
||||
"""Log resolved identity at session start."""
|
||||
peer = get_peer_name()
|
||||
task_id = os.environ.get("HERMES_KANBAN_TASK")
|
||||
if task_id:
|
||||
if peer:
|
||||
logger.info("identity: kanban worker task=%s peer=%s", task_id, peer)
|
||||
else:
|
||||
logger.info("identity: kanban worker task=%s no peer → user-default", task_id)
|
||||
elif peer:
|
||||
logger.debug("identity: session peer=%s", peer)
|
||||
|
||||
|
||||
# ── Slash command ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _cmd_identity(raw_args: str) -> str:
|
||||
"""Handle ``/identity`` slash command."""
|
||||
args = raw_args.strip().split()
|
||||
if not args:
|
||||
return _show_status()
|
||||
|
||||
cmd = args[0]
|
||||
if cmd == "status":
|
||||
return _show_status()
|
||||
elif cmd == "list":
|
||||
return _list_mappings()
|
||||
elif cmd in ("add", "set") and len(args) >= 4:
|
||||
# /identity add discord 479136126737711105 thierry
|
||||
platform, pid, peer_name = args[1], args[2], args[3]
|
||||
return _add_mapping(platform, pid, peer_name)
|
||||
elif cmd == "rm" and len(args) >= 2:
|
||||
idx = int(args[1]) if args[1].isdigit() else -1
|
||||
return _remove_mapping(idx)
|
||||
elif cmd == "board" and len(args) >= 3:
|
||||
slug, peer_name = args[1], args[2]
|
||||
return _set_board(slug, peer_name)
|
||||
elif cmd == "help":
|
||||
return _help_text()
|
||||
return _help_text()
|
||||
|
||||
|
||||
def _show_status() -> str:
|
||||
cfg = load_config()
|
||||
lines = [
|
||||
"**Identity Plugin**",
|
||||
f"Config: `{CONFIG_PATH}`",
|
||||
f"Mappings: {len(cfg.get('mappings', []))}",
|
||||
f"Boards: {len(cfg.get('boards', {}))}",
|
||||
f"Enforce context_peer: {cfg.get('enforce_context_peer', True)}",
|
||||
f"Fallback peer: {cfg.get('fallback_peer', 'user')}",
|
||||
"",
|
||||
"Current context:",
|
||||
]
|
||||
peer = get_peer_name()
|
||||
if peer:
|
||||
lines.append(f" Resolved peer: **{peer}**")
|
||||
task = os.environ.get("HERMES_KANBAN_TASK")
|
||||
if task:
|
||||
lines.append(f" Kanban task: `{task}`")
|
||||
board = os.environ.get("HERMES_KANBAN_BOARD")
|
||||
if board:
|
||||
lines.append(f" Board: `{board}`")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _list_mappings() -> str:
|
||||
cfg = load_config()
|
||||
mappings = cfg.get("mappings", [])
|
||||
if not mappings:
|
||||
return "No mappings configured."
|
||||
lines = ["**Identity mappings:**", ""]
|
||||
for i, m in enumerate(mappings):
|
||||
lines.append(f" [{i}] `{m.get('platform')}` `{m.get('id')}` → **{m.get('peer')}**")
|
||||
boards = cfg.get("boards", {})
|
||||
if boards:
|
||||
lines.append("")
|
||||
lines.append("**Board peers:**")
|
||||
for slug, peer in boards.items():
|
||||
lines.append(f" `{slug}` → **{peer}**")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _add_mapping(platform: str, pid: str, peer_name: str) -> str:
|
||||
cfg = load_config()
|
||||
cfg.setdefault("mappings", []).append({"platform": platform, "id": pid, "peer": peer_name})
|
||||
save_config(cfg)
|
||||
return f"Added mapping: `{platform}` `{pid}` → **{peer_name}**"
|
||||
|
||||
|
||||
def _remove_mapping(idx: int) -> str:
|
||||
cfg = load_config()
|
||||
mappings = cfg.get("mappings", [])
|
||||
if idx < 0 or idx >= len(mappings):
|
||||
return f"Invalid index {idx}. Use `list` to see indices."
|
||||
removed = mappings.pop(idx)
|
||||
save_config(cfg)
|
||||
return f"Removed: `{removed.get('platform')}` `{removed.get('id')}` → **{removed.get('peer')}**"
|
||||
|
||||
|
||||
def _set_board(slug: str, peer_name: str) -> str:
|
||||
cfg = load_config()
|
||||
cfg.setdefault("boards", {})[slug] = peer_name
|
||||
save_config(cfg)
|
||||
return f"Board `{slug}` → **{peer_name}**"
|
||||
|
||||
|
||||
def _help_text() -> str:
|
||||
return """**/identity** — manage identity mappings
|
||||
|
||||
Commands:
|
||||
`status` Show config status and current context
|
||||
`list` List all mappings and board peers
|
||||
`add discord <id> <peer>` Add a platform→peer mapping
|
||||
`rm <index>` Remove mapping by index
|
||||
`board <slug> <peer>` Set a kanban board's default peer
|
||||
`help` Show this help"""
|
||||
|
||||
|
||||
# ── Plugin entry point ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def register(ctx: Any) -> None:
|
||||
"""Register identity plugin hooks and slash command."""
|
||||
ctx.register_hook("pre_gateway_dispatch", _pre_gateway_dispatch)
|
||||
ctx.register_hook("on_session_start", _on_session_start)
|
||||
ctx.register_command(
|
||||
name="identity",
|
||||
handler=_cmd_identity,
|
||||
description="Manage identity peer mappings",
|
||||
args_hint="status|list|add|rm|board|help",
|
||||
)
|
||||
logger.info("identity-plugin: registered hooks (pre_gateway_dispatch, on_session_start) and /identity command")
|
||||
14
config.sample.json
Normal file
14
config.sample.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"mappings": [
|
||||
{"platform": "discord", "id": "479136126737711105", "peer": "thierry"},
|
||||
{"platform": "telegram", "id": "", "peer": "thierry"},
|
||||
{"platform": "terminal", "id": "default", "peer": "thierry"},
|
||||
{"platform": "matrix", "id": "", "peer": "thierry"}
|
||||
],
|
||||
"boards": {
|
||||
"default": "thierry",
|
||||
"catherine": "catherine"
|
||||
},
|
||||
"fallback_peer": "user",
|
||||
"enforce_context_peer": true
|
||||
}
|
||||
35
docs/enforcement.md
Normal file
35
docs/enforcement.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Enforcing context_peer in the kanban-worker skill
|
||||
|
||||
Profiles (Claire, Ashley, Finn, Matt) must include `context_peer: <name>` in
|
||||
every `kanban_create` call. This document covers how to enforce it.
|
||||
|
||||
## Option 1: Profile system prompt (recommended, no code change)
|
||||
|
||||
Add to each profile's system prompt or skill config:
|
||||
|
||||
> When creating a kanban task with `kanban_create`, you MUST include
|
||||
> a `context_peer: <name>` metadata block in the task body. The peer
|
||||
> must be the intended user's Honcho peer name (e.g., `thierry`,
|
||||
> `catherine`), not your own profile name.
|
||||
>
|
||||
> Format:
|
||||
> ```metadata
|
||||
> context_peer: thierry
|
||||
> ```
|
||||
|
||||
## Option 2: kanban-create wrapper tool
|
||||
|
||||
Create a custom tool in `/opt/data/hermes-tools/` that wraps `kanban_create`
|
||||
and rejects calls without `context_peer` in the body.
|
||||
|
||||
This approach requires the persistent tools volume (already used for QET,
|
||||
Gitea, Ollama tools) and a tool registration in the Docker entrypoint.
|
||||
|
||||
## Option 3: Slash command validation
|
||||
|
||||
The `/identity` command includes a `validate` subcommand that checks recent
|
||||
kanban tasks for missing `context_peer` fields.
|
||||
|
||||
```bash
|
||||
/identity validate
|
||||
```
|
||||
116
docs/honcho-injector.md
Normal file
116
docs/honcho-injector.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Honcho Injector Patch
|
||||
|
||||
The identity plugin cannot directly inject `runtime_user_peer_name` into
|
||||
Honcho from a user-installed hook (the hook system only allows message
|
||||
rewrite/skip, not user_id modification). Instead, we add ~6 lines in the
|
||||
bundled Honcho plugin's session initialization.
|
||||
|
||||
## File
|
||||
|
||||
`plugins/memory/honcho/__init__.py` — line ~362
|
||||
|
||||
## The change
|
||||
|
||||
### Before
|
||||
|
||||
```python
|
||||
runtime_user_peer_name=kwargs.get("user_id") or None,
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
```python
|
||||
runtime_user_peer_name=kwargs.get("user_id") or _resolve_identity_peer(),
|
||||
```
|
||||
|
||||
### Add this function in the same file (or import from the identity plugin)
|
||||
|
||||
```python
|
||||
def _resolve_identity_peer() -> str | None:
|
||||
"""Resolve peer name from env vars and identity config.
|
||||
|
||||
Priority: HERMES_HONCHO_PEER_NAME → kanban task body context_peer
|
||||
→ kanban board config → None (falls through to Honcho default).
|
||||
"""
|
||||
import json, os, sqlite3, re
|
||||
from pathlib import Path
|
||||
|
||||
# 1. Explicit env override
|
||||
explicit = os.environ.get("HERMES_HONCHO_PEER_NAME")
|
||||
if explicit:
|
||||
return explicit
|
||||
|
||||
# 2. Kanban worker: read task body for context_peer
|
||||
task_id = os.environ.get("HERMES_KANBAN_TASK")
|
||||
db_path = os.environ.get("HERMES_KANBAN_DB")
|
||||
if task_id and db_path:
|
||||
db = Path(db_path)
|
||||
if db.exists():
|
||||
try:
|
||||
conn = sqlite3.connect(str(db))
|
||||
conn.row_factory = sqlite3.Row
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT body FROM tasks WHERE id = ?", (task_id,)
|
||||
).fetchone()
|
||||
if row and row["body"]:
|
||||
m = re.search(r"context_peer:\s*(\S+)", row["body"])
|
||||
if m:
|
||||
return m.group(1)
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 3. Identity config file
|
||||
cfg_path = Path("/opt/data/identity-config.json")
|
||||
if cfg_path.exists():
|
||||
try:
|
||||
cfg = json.loads(cfg_path.read_text())
|
||||
# Check kanban board config
|
||||
board = os.environ.get("HERMES_KANBAN_BOARD")
|
||||
if board and board in cfg.get("boards", {}):
|
||||
return cfg["boards"][board]
|
||||
# Use fallback
|
||||
return cfg.get("fallback_peer")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. When a **gateway session** starts, `kwargs["user_id"]` carries the platform
|
||||
ID (Discord snowflake, Telegram UID). The identity config file maps these
|
||||
to canonical peer names. The injector is bypassed — normal flow.
|
||||
|
||||
2. When a **kanban worker** starts, `kwargs["user_id"]` is None (no gateway).
|
||||
The injector kicks in:
|
||||
|
||||
a. Checks `HERMES_HONCHO_PEER_NAME` env var (set by a future dispatcher
|
||||
enhancement or manually).
|
||||
|
||||
b. Reads the kanban task body from the SQLite database, extracts
|
||||
`context_peer: <name>` from the body.
|
||||
|
||||
c. Falls back to the board-level config from identity-config.json.
|
||||
|
||||
d. If nothing resolves → returns None → Honcho creates `user-default-*`
|
||||
as today (safe fallback).
|
||||
|
||||
## Why not modify the kanban dispatcher?
|
||||
|
||||
The kanban dispatcher (`hermes_cli/kanban_db.py:_default_spawn`) is core
|
||||
Hermes code. We avoid touching it. Instead, the Honcho injector reads the
|
||||
task directly from the kanban DB using env vars that are already set
|
||||
(`HERMES_KANBAN_TASK`, `HERMES_KANBAN_DB`, `HERMES_KANBAN_BOARD`).
|
||||
|
||||
This adds ~10 microseconds to worker startup — negligible.
|
||||
|
||||
## Compatibility
|
||||
|
||||
- If the identity plugin is removed, the injector function returns None and
|
||||
Honcho behaves exactly as before.
|
||||
- If the config file is missing, same safe fallback.
|
||||
- No data loss risk.
|
||||
11
plugin.yaml
Normal file
11
plugin.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
name: identity
|
||||
version: 1.0.0
|
||||
description: >
|
||||
Identity resolution plugin for Hermes. Maps platform-specific user IDs
|
||||
(Discord snowflake, Telegram UID, terminal) and kanban boards to stable
|
||||
Honcho peer names. Config file at /opt/data/identity-config.json for
|
||||
persistence across container rebuilds.
|
||||
author: "@gortium"
|
||||
hooks:
|
||||
- pre_gateway_dispatch
|
||||
- on_session_start
|
||||
Reference in New Issue
Block a user