mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
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:
parent
8e193cf05c
commit
31fe229039
8 changed files with 321 additions and 5 deletions
|
|
@ -73,6 +73,7 @@ def _task_to_dict(t: kb.Task) -> dict[str, Any]:
|
|||
"result": t.result,
|
||||
"skills": list(t.skills) if t.skills else [],
|
||||
"max_retries": t.max_retries,
|
||||
"session_id": t.session_id,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -343,6 +344,9 @@ def build_parser(parent_subparsers: argparse._SubParsersAction) -> argparse.Argu
|
|||
p_list.add_argument("--status", default=None,
|
||||
choices=sorted(kb.VALID_STATUSES))
|
||||
p_list.add_argument("--tenant", default=None)
|
||||
p_list.add_argument("--session", default=None,
|
||||
help="Filter by originating chat/agent session id "
|
||||
"(set on tasks created from inside an ACP loop)")
|
||||
p_list.add_argument("--archived", action="store_true",
|
||||
help="Include archived tasks")
|
||||
p_list.add_argument("--json", action="store_true")
|
||||
|
|
@ -1279,6 +1283,7 @@ def _cmd_list(args: argparse.Namespace) -> int:
|
|||
assignee=assignee,
|
||||
status=args.status,
|
||||
tenant=args.tenant,
|
||||
session_id=args.session,
|
||||
include_archived=args.archived,
|
||||
order_by=getattr(args, "sort", None),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -649,6 +649,12 @@ class Task:
|
|||
# ``kanban.failure_limit`` config, and then to ``DEFAULT_FAILURE_LIMIT``.
|
||||
# Name matches the ``--max-retries`` CLI flag on ``kanban create``.
|
||||
max_retries: Optional[int] = None
|
||||
# Originating chat/agent session id, when the task was created from
|
||||
# within an agent loop that propagated ``HERMES_SESSION_ID``. NULL for
|
||||
# tasks created from the CLI, the dashboard, or any path that doesn't
|
||||
# set the env var. Lets clients render a per-session board without
|
||||
# relying on tenant + time-window heuristics.
|
||||
session_id: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: sqlite3.Row) -> "Task":
|
||||
|
|
@ -714,6 +720,9 @@ class Task:
|
|||
max_retries=(
|
||||
row["max_retries"] if "max_retries" in keys else None
|
||||
),
|
||||
session_id=(
|
||||
row["session_id"] if "session_id" in keys else None
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -844,9 +853,17 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|||
-- ``max_retries=1`` blocks on the first failure. NULL (the common
|
||||
-- case) falls through to the dispatcher-level ``kanban.failure_limit``
|
||||
-- config and then ``DEFAULT_FAILURE_LIMIT``.
|
||||
max_retries INTEGER
|
||||
max_retries INTEGER,
|
||||
-- Originating chat/agent session id when the task was created from
|
||||
-- inside an agent loop that propagated ``HERMES_SESSION_ID``. NULL
|
||||
-- for tasks created from the CLI, dashboard, or any path that doesn't
|
||||
-- set the env var. Indexed so per-session list queries stay cheap on
|
||||
-- larger boards.
|
||||
session_id TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_session_id ON tasks(session_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS task_links (
|
||||
parent_id TEXT NOT NULL,
|
||||
child_id TEXT NOT NULL,
|
||||
|
|
@ -1143,6 +1160,20 @@ def _migrate_add_optional_columns(conn: sqlite3.Connection) -> None:
|
|||
if "model_override" not in cols:
|
||||
conn.execute("ALTER TABLE tasks ADD COLUMN model_override TEXT")
|
||||
|
||||
if "session_id" not in cols:
|
||||
# Originating agent/chat session id, populated when the task is
|
||||
# created from within an agent loop that propagated
|
||||
# ``HERMES_SESSION_ID`` (e.g. ACP). NULL on legacy rows and on any
|
||||
# creation path that doesn't set the env var (CLI, dashboard).
|
||||
# Index keeps per-session list queries cheap.
|
||||
_add_column_if_missing(
|
||||
conn, "tasks", "session_id", "session_id TEXT"
|
||||
)
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_tasks_session_id "
|
||||
"ON tasks(session_id)"
|
||||
)
|
||||
|
||||
# task_events gained a run_id column; back-fill it as NULL for
|
||||
# historical events (they predate runs and can't be attributed).
|
||||
ev_cols = {row["name"] for row in conn.execute("PRAGMA table_info(task_events)")}
|
||||
|
|
@ -1312,6 +1343,7 @@ def create_task(
|
|||
skills: Optional[Iterable[str]] = None,
|
||||
max_retries: Optional[int] = None,
|
||||
initial_status: str = "running",
|
||||
session_id: Optional[str] = None,
|
||||
board: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Create a new task and optionally link it under parent tasks.
|
||||
|
|
@ -1466,8 +1498,8 @@ def create_task(
|
|||
id, title, body, assignee, status, priority,
|
||||
created_by, created_at, workspace_kind, workspace_path,
|
||||
tenant, idempotency_key, max_runtime_seconds, skills,
|
||||
max_retries
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
max_retries, session_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
task_id,
|
||||
|
|
@ -1485,6 +1517,7 @@ def create_task(
|
|||
int(max_runtime_seconds) if max_runtime_seconds is not None else None,
|
||||
json.dumps(skills_list) if skills_list is not None else None,
|
||||
int(max_retries) if max_retries is not None else None,
|
||||
session_id,
|
||||
),
|
||||
)
|
||||
for pid in parents:
|
||||
|
|
@ -1551,6 +1584,7 @@ def list_tasks(
|
|||
assignee: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
tenant: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
include_archived: bool = False,
|
||||
limit: Optional[int] = None,
|
||||
order_by: Optional[str] = None,
|
||||
|
|
@ -1568,6 +1602,9 @@ def list_tasks(
|
|||
if tenant is not None:
|
||||
query += " AND tenant = ?"
|
||||
params.append(tenant)
|
||||
if session_id is not None:
|
||||
query += " AND session_id = ?"
|
||||
params.append(session_id)
|
||||
if not include_archived and status != "archived":
|
||||
query += " AND status != 'archived'"
|
||||
if order_by is not None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue