feat(kanban): stamp originating ACP session_id on tasks

Salvages #23208 by @awizemann. Tracks which chat session created a
kanban task so clients can render a per-session board without falling
back to tenant + time-window heuristics.

- Schema: tasks gains nullable session_id TEXT column with index
  (additive migration in _migrate_add_optional_columns).
- ACP: server.py exposes the originating session id via HERMES_SESSION_ID
  with save/restore around the agent loop.
- Tool: kanban_create reads HERMES_SESSION_ID (with explicit override).
- CLI: 'hermes kanban list --session <id>' filter; JSON output exposes
  session_id.
This commit is contained in:
awizemann
2026-05-18 21:15:15 -07:00
committed by Teknium
parent 8e193cf05c
commit 31fe229039
8 changed files with 321 additions and 5 deletions

View File

@@ -768,6 +768,75 @@ def test_create_happy_path(worker_env):
conn.close()
def test_create_stamps_session_id_from_env(monkeypatch, worker_env):
"""When the agent loop runs under ACP, the server propagates the
originating chat session id via HERMES_SESSION_ID. ``kanban_create``
reads it and stamps the new task so clients can render a per-session
board (issue: ACP session linkage on kanban tasks)."""
monkeypatch.setenv("HERMES_SESSION_ID", "acp-sess-abc")
from tools import kanban_tools as kt
from hermes_cli import kanban_db as kb
out = kt._handle_create({
"title": "from chat",
"assignee": "peer",
"parents": [worker_env],
})
d = json.loads(out)
assert d["ok"] is True
conn = kb.connect()
try:
new_task = kb.get_task(conn, d["task_id"])
assert new_task.session_id == "acp-sess-abc"
finally:
conn.close()
def test_create_session_id_arg_overrides_env(monkeypatch, worker_env):
"""An explicit ``session_id`` arg from the model wins over the env
propagation. Edge case but exercised: a tool call could carry a
different session id (e.g. cross-session linking) and the explicit
arg should not be silently overwritten."""
monkeypatch.setenv("HERMES_SESSION_ID", "from-env")
from tools import kanban_tools as kt
from hermes_cli import kanban_db as kb
out = kt._handle_create({
"title": "explicit override",
"assignee": "peer",
"parents": [worker_env],
"session_id": "explicit-arg",
})
d = json.loads(out)
assert d["ok"] is True
conn = kb.connect()
try:
new_task = kb.get_task(conn, d["task_id"])
assert new_task.session_id == "explicit-arg"
finally:
conn.close()
def test_create_session_id_absent_when_env_unset(monkeypatch, worker_env):
"""No env var, no arg → session_id stays NULL. Important for backwards
compatibility: pre-ACP-propagation hosts and CLI-driven creates must
not accidentally inherit a stale id."""
monkeypatch.delenv("HERMES_SESSION_ID", raising=False)
from tools import kanban_tools as kt
from hermes_cli import kanban_db as kb
out = kt._handle_create({
"title": "no session",
"assignee": "peer",
"parents": [worker_env],
})
d = json.loads(out)
assert d["ok"] is True
conn = kb.connect()
try:
new_task = kb.get_task(conn, d["task_id"])
assert new_task.session_id is None
finally:
conn.close()
def test_create_rejects_no_title(worker_env):
from tools import kanban_tools as kt
assert json.loads(kt._handle_create({"assignee": "x"})).get("error")