{"repo":"NousResearch/hermes-agent","public_id":"23199","display_label":"Stamp ACP session_id on kanban tasks","summary":"These members all concern a feature to record the originating ACP session ID on kanban tasks created during ACP sessions. The work spans an issue request and corresponding pull request implementations.","subsystem":"acp","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":28447,"merged_at":"2026-05-19T04:15:22+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-05-19T04:15:22+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":23199,"pr_number":28447,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-05-19T04:15:22+00:00"}],"members":[{"kind":"issue","number":23199,"title":"[Feature]: Stamp originating session_id on kanban tasks created during ACP sessions","url":null,"state":"closed"},{"kind":"pr","number":23208,"title":"feat(kanban): stamp originating ACP session_id on tasks","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#28447","number":28447}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":28447,"title":"feat(kanban): stamp originating ACP session_id on tasks (#23208)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["acp_adapter/server.py","hermes_cli/kanban.py","hermes_cli/kanban_db.py","tests/acp/test_server.py","tests/hermes_cli/test_kanban_cli.py","tests/hermes_cli/test_kanban_db.py","tests/tools/test_kanban_tools.py","tools/kanban_tools.py"],"fix_hunks":[{"file":"acp_adapter/server.py","hunks":["@@ -1407,9 +1407,10 @@ def stream_delta_cb(text: str) -> None:\n         previous_approval_cb = None\n         previous_interactive = None\n         edit_approval_token = None\n+        previous_session_id = None\n \n         def _run_agent() -> dict:\n-            nonlocal previous_approval_cb, previous_interactive, edit_approval_token\n+            nonlocal previous_approval_cb, previous_interactive, edit_approval_token, previous_session_id\n             # Bind HERMES_SESSION_KEY for this session so per-session caches\n             # (e.g. the interactive sudo password cache in tools.terminal_tool)\n             # scope to the ACP session rather than leaking across sessions","@@ -1444,6 +1445,13 @@ def _run_agent() -> dict:\n             # and the non-interactive auto-approve path must not fire.\n             previous_interactive = os.environ.get(\"HERMES_INTERACTIVE\")\n             os.environ[\"HERMES_INTERACTIVE\"] = \"1\"\n+            # Propagate the originating ACP session id to tools that want to\n+            # tag side-effects with it (e.g. ``kanban_create`` stamps it on\n+            # the new task so clients can render a per-session board). Save\n+            # and restore around the agent call so a re-used executor thread\n+            # never leaks one session's id into the next session's tools.\n+            previous_session_id = os.environ.get(\"HERMES_SESSION_ID\")\n+            os.environ[\"HERMES_SESSION_ID\"] = session_id\n             try:\n                 result = agent.run_conversation(\n                     user_message=user_content,","@@ -1461,6 +1469,11 @@ def _run_agent() -> dict:\n                     os.environ.pop(\"HERMES_INTERACTIVE\", None)\n                 else:\n                     os.environ[\"HERMES_INTERACTIVE\"] = previous_interactive\n+                # Restore HERMES_SESSION_ID symmetrically.\n+                if previous_session_id is None:\n+                    os.environ.pop(\"HERMES_SESSION_ID\", None)\n+                else:\n+                    os.environ[\"HERMES_SESSION_ID\"] = previous_session_id\n                 if approval_cb:\n                     try:\n                         from tools import terminal_tool as _terminal_tool"]},{"file":"hermes_cli/kanban.py","hunks":["@@ -73,6 +73,7 @@ def _task_to_dict(t: kb.Task) -> dict[str, Any]:\n         \"result\": t.result,\n         \"skills\": list(t.skills) if t.skills else [],\n         \"max_retries\": t.max_retries,\n+        \"session_id\": t.session_id,\n     }\n \n ","@@ -343,6 +344,9 @@ def build_parser(parent_subparsers: argparse._SubParsersAction) -> argparse.Argu\n     p_list.add_argument(\"--status\", default=None,\n                         choices=sorted(kb.VALID_STATUSES))\n     p_list.add_argument(\"--tenant\", default=None)\n+    p_list.add_argument(\"--session\", default=None,\n+                        help=\"Filter by originating chat/agent session id \"\n+                             \"(set on tasks created from inside an ACP loop)\")\n     p_list.add_argument(\"--archived\", action=\"store_true\",\n                         help=\"Include archived tasks\")\n     p_list.add_argument(\"--json\", action=\"store_true\")","@@ -1279,6 +1283,7 @@ def _cmd_list(args: argparse.Namespace) -> int:\n             assignee=assignee,\n             status=args.status,\n             tenant=args.tenant,\n+            session_id=args.session,\n             include_archived=args.archived,\n             order_by=getattr(args, \"sort\", None),\n         )"]},{"file":"hermes_cli/kanban_db.py","hunks":["@@ -649,6 +649,12 @@ class Task:\n     # ``kanban.failure_limit`` config, and then to ``DEFAULT_FAILURE_LIMIT``.\n     # Name matches the ``--max-retries`` CLI flag on ``kanban create``.\n     max_retries: Optional[int] = None\n+    # Originating chat/agent session id, when the task was created from\n+    # within an agent loop that propagated ``HERMES_SESSION_ID``. NULL for\n+    # tasks created from the CLI, the dashboard, or any path that doesn't\n+    # set the env var. Lets clients render a per-session board without\n+    # relying on tenant + time-window heuristics.\n+    session_id: Optional[str] = None\n \n     @classmethod\n     def from_row(cls, row: sqlite3.Row) -> \"Task\":","@@ -714,6 +720,9 @@ def from_row(cls, row: sqlite3.Row) -> \"Task\":\n             max_retries=(\n                 row[\"max_retries\"] if \"max_retries\" in keys else None\n             ),\n+            session_id=(\n+                row[\"session_id\"] if \"session_id\" in keys else None\n+            ),\n         )\n \n ","@@ -844,9 +853,17 @@ class Event:\n     -- ``max_retries=1`` blocks on the first failure. NULL (the common\n     -- case) falls through to the dispatcher-level ``kanban.failure_limit``\n     -- config and then ``DEFAULT_FAILURE_LIMIT``.\n-    max_retries          INTEGER\n+    max_retries          INTEGER,\n+    -- Originating chat/agent session id when the task was created from\n+    -- inside an agent loop that propagated ``HERMES_SESSION_ID``. NULL\n+    -- for tasks created from the CLI, dashboard, or any path that doesn't\n+    -- set the env var. Indexed so per-session list queries stay cheap on\n+    -- larger boards.\n+    session_id           TEXT\n );\n \n+CREATE INDEX IF NOT EXISTS idx_tasks_session_id ON tasks(session_id);\n+\n CREATE TABLE IF NOT EXISTS task_links (\n     parent_id  TEXT NOT NULL,\n     child_id   TEXT NOT NULL,","@@ -1143,6 +1160,20 @@ def _migrate_add_optional_columns(conn: sqlite3.Connection) -> None:\n     if \"model_override\" not in cols:\n         conn.execute(\"ALTER TABLE tasks ADD COLUMN model_override TEXT\")\n \n+    if \"session_id\" not in cols:\n+        # Originating agent/chat session id, populated when the task is\n+        # created from within an agent loop that propagated\n+        # ``HERMES_SESSION_ID`` (e.g. ACP). NULL on legacy rows and on any\n+        # creation path that doesn't set the env var (CLI, dashboard).\n+        # Index keeps per-session list queries cheap.\n+        _add_column_if_missing(\n+            conn, \"tasks\", \"session_id\", \"session_id TEXT\"\n+        )\n+        conn.execute(\n+            \"CREATE INDEX IF NOT EXISTS idx_tasks_session_id \"\n+            \"ON tasks(session_id)\"\n+        )\n+\n     # task_events gained a run_id column; back-fill it as NULL for\n     # historical events (they predate runs and can't be attributed).\n     ev_cols = {row[\"name\"] for row in conn.execute(\"PRAGMA table_info(task_events)\")}","@@ -1312,6 +1343,7 @@ def create_task(\n     skills: Optional[Iterable[str]] = None,\n     max_retries: Optional[int] = None,\n     initial_status: str = \"running\",\n+    session_id: Optional[str] = None,\n     board: Optional[str] = None,\n ) -> str:\n     \"\"\"Create a new task and optionally link it under parent tasks.","@@ -1466,8 +1498,8 @@ def create_task(\n                         id, title, body, assignee, status, priority,\n                         created_by, created_at, workspace_kind, workspace_path,\n                         tenant, idempotency_key, max_runtime_seconds, skills,\n-                        max_retries\n-                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n+                        max_retries, session_id\n+                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n                     \"\"\",\n                     (\n                         task_id,","@@ -1485,6 +1517,7 @@ def create_task(\n                         int(max_runtime_seconds) if max_runtime_seconds is not None else None,\n                         json.dumps(skills_list) if skills_list is not None else None,\n                         int(max_retries) if max_retries is not None else None,\n+                        session_id,\n                     ),\n                 )\n                 for pid in parents:","@@ -1551,6 +1584,7 @@ def list_tasks(\n     assignee: Optional[str] = None,\n     status: Optional[str] = None,\n     tenant: Optional[str] = None,\n+    session_id: Optional[str] = None,\n     include_archived: bool = False,\n     limit: Optional[int] = None,\n     order_by: Optional[str] = None,","@@ -1568,6 +1602,9 @@ def list_tasks(\n     if tenant is not None:\n         query += \" AND tenant = ?\"\n         params.append(tenant)\n+    if session_id is not None:\n+        query += \" AND session_id = ?\"\n+        params.append(session_id)\n     if not include_archived and status != \"archived\":\n         query += \" AND status != 'archived'\"\n     if order_by is not None:"]},{"file":"tests/acp/test_server.py","hunks":["@@ -1090,6 +1090,80 @@ async def test_prompt_sends_final_message_update(self, agent):\n         ]\n         assert any(update.session_update == \"agent_message_chunk\" for update in updates)\n \n+    @pytest.mark.asyncio\n+    async def test_prompt_propagates_hermes_session_id_env(self, agent, monkeypatch):\n+        \"\"\"ACP must propagate the originating session id to the agent loop\n+        via ``HERMES_SESSION_ID`` so tools that want to stamp side-effects\n+        with it (e.g. ``kanban_create``) can read the env var inside\n+        ``run_conversation``. The variable must be visible during the\n+        agent call AND restored afterwards so a re-used executor thread\n+        doesn't leak one session's id into another.\"\"\"\n+        # Pre-condition: env is clean.\n+        monkeypatch.delenv(\"HERMES_SESSION_ID\", raising=False)\n+\n+        new_resp = await agent.new_session(cwd=\".\")\n+        state = agent.session_manager.get_session(new_resp.session_id)\n+\n+        captured: dict[str, str | None] = {}\n+\n+        def mock_run(user_message, conversation_history=None, task_id=None, **kwargs):\n+            # Inside the agent loop the env var must reflect the active\n+            # ACP session id. ``task_id`` is also the session id at this\n+            # boundary; assert both for symmetry.\n+            captured[\"env\"] = os.environ.get(\"HERMES_SESSION_ID\")\n+            captured[\"task_id\"] = task_id\n+            return {\"final_response\": \"ok\", \"messages\": []}\n+\n+        state.agent.run_conversation = mock_run\n+\n+        mock_conn = MagicMock(spec=acp.Client)\n+        mock_conn.session_update = AsyncMock()\n+        agent._conn = mock_conn\n+\n+        prompt = [TextContentBlock(type=\"text\", text=\"hi\")]\n+        await agent.prompt(prompt=prompt, session_id=new_resp.session_id)\n+\n+        assert captured[\"env\"] == new_resp.session_id, (\n+            \"HERMES_SESSION_ID must be set to the originating ACP session id \"\n+            \"while the agent loop is running\"\n+        )\n+        assert captured[\"task_id\"] == new_resp.session_id\n+        # Post-condition: must be restored to the prior value (None here).\n+        assert os.environ.get(\"HERMES_SESSION_ID\") is None, (\n+            \"HERMES_SESSION_ID must be restored after the agent call so \"\n+            \"a re-used executor thread doesn't leak the id into the next \"\n+            \"session's tools\"\n+        )\n+\n+    @pytest.mark.asyncio\n+    async def test_prompt_restores_prior_hermes_session_id(self, agent, monkeypatch):\n+        \"\"\"If the env already had HERMES_SESSION_ID set (e.g. nested\n+        agent loops), the prior value must be restored after the inner\n+        prompt completes — not popped, not left at the inner id.\"\"\"\n+        monkeypatch.setenv(\"HERMES_SESSION_ID\", \"outer-sess\")\n+\n+        new_resp = await agent.new_session(cwd=\".\")\n+        state = agent.session_manager.get_session(new_resp.session_id)\n+\n+        captured: dict[str, str | None] = {}\n+\n+        def mock_run(*args, **kwargs):\n+            captured[\"inner\"] = os.environ.get(\"HERMES_SESSION_ID\")\n+            return {\"final_response\": \"ok\", \"messages\": []}\n+\n+        state.agent.run_conversation = mock_run\n+\n+        mock_conn = MagicMock(spec=acp.Client)\n+        mock_conn.session_update = AsyncMock()\n+        agent._conn = mock_conn\n+\n+        prompt = [TextContentBlock(type=\"text\", text=\"hi\")]\n+        await agent.prompt(prompt=prompt, session_id=new_resp.session_id)\n+\n+        assert captured[\"inner\"] == new_resp.session_id\n+        # Outer scope must be restored.\n+        assert os.environ.get(\"HERMES_SESSION_ID\") == \"outer-sess\"\n+\n     @pytest.mark.asyncio\n     async def test_prompt_does_not_duplicate_streamed_final_message(self, agent):\n         \"\"\"If ACP already streamed response chunks, final_response should not be sent again.\"\"\""]},{"file":"tests/hermes_cli/test_kanban_cli.py","hunks":["@@ -156,6 +156,48 @@ def test_run_slash_tenant_filter(kanban_home):\n     assert \"biz-b task\" in b and \"biz-a task\" not in b\n \n \n+def test_run_slash_session_filter(kanban_home):\n+    \"\"\"`hermes kanban list --session <id>` filters by the originating\n+    chat session id stamped on tasks created from inside an ACP loop.\"\"\"\n+    from hermes_cli import kanban_db as kb\n+    with kb.connect() as conn:\n+        kb.create_task(\n+            conn, title=\"from sess-1 a\", assignee=\"alice\", session_id=\"sess-1\"\n+        )\n+        kb.create_task(\n+            conn, title=\"from sess-1 b\", assignee=\"alice\", session_id=\"sess-1\"\n+        )\n+        kb.create_task(\n+            conn, title=\"from sess-2\", assignee=\"alice\", session_id=\"sess-2\"\n+        )\n+        kb.create_task(conn, title=\"cli only\", assignee=\"alice\")\n+    out_1 = kc.run_slash(\"list --session sess-1\")\n+    out_2 = kc.run_slash(\"list --session sess-2\")\n+    assert \"from sess-1 a\" in out_1\n+    assert \"from sess-1 b\" in out_1\n+    assert \"from sess-2\" not in out_1\n+    assert \"cli only\" not in out_1\n+    assert \"from sess-2\" in out_2\n+    assert \"from sess-1 a\" not in out_2\n+\n+\n+def test_kanban_list_json_includes_session_id(kanban_home):\n+    \"\"\"JSON output exposes `session_id` so external clients (Scarf, web\n+    dashboards) don't need a side query to filter by chat session.\"\"\"\n+    from hermes_cli import kanban_db as kb\n+    with kb.connect() as conn:\n+        kb.create_task(\n+            conn, title=\"acp task\", assignee=\"alice\", session_id=\"acp-x\"\n+        )\n+    raw = kc.run_slash(\"list --json\")\n+    payload = json.loads(raw)\n+    assert any(\n+        row.get(\"title\") == \"acp task\"\n+        and row.get(\"session_id\") == \"acp-x\"\n+        for row in payload\n+    )\n+\n+\n def test_run_slash_usage_error_returns_message(kanban_home):\n     # Missing required argument for create\n     out = kc.run_slash(\"create\")"]},{"file":"tests/hermes_cli/test_kanban_db.py","hunks":["@@ -1035,6 +1035,76 @@ def test_tenant_propagates_to_events(kanban_home):\n     assert created and created[0].payload.get(\"tenant\") == \"biz-a\"\n \n \n+# ---------------------------------------------------------------------------\n+# Originating session id (ACP propagation)\n+# ---------------------------------------------------------------------------\n+\n+def test_create_task_stamps_session_id(kanban_home):\n+    with kb.connect() as conn:\n+        tid = kb.create_task(\n+            conn, title=\"from chat\", session_id=\"acp-sess-123\"\n+        )\n+        t = kb.get_task(conn, tid)\n+    assert t is not None\n+    assert t.session_id == \"acp-sess-123\"\n+\n+\n+def test_create_task_session_id_defaults_to_none(kanban_home):\n+    with kb.connect() as conn:\n+        tid = kb.create_task(conn, title=\"cli-created\")\n+        t = kb.get_task(conn, tid)\n+    assert t is not None\n+    assert t.session_id is None\n+\n+\n+def test_session_id_filters_listings(kanban_home):\n+    with kb.connect() as conn:\n+        kb.create_task(conn, title=\"s1-a\", session_id=\"sess-1\")\n+        kb.create_task(conn, title=\"s1-b\", session_id=\"sess-1\")\n+        kb.create_task(conn, title=\"s2-a\", session_id=\"sess-2\")\n+        kb.create_task(conn, title=\"cli-only\")  # no session\n+        sess1 = kb.list_tasks(conn, session_id=\"sess-1\")\n+        sess2 = kb.list_tasks(conn, session_id=\"sess-2\")\n+        unscoped = kb.list_tasks(conn)\n+    assert sorted(t.title for t in sess1) == [\"s1-a\", \"s1-b\"]\n+    assert [t.title for t in sess2] == [\"s2-a\"]\n+    # Unscoped list still returns everything (legacy NULL rows visible).\n+    assert len(unscoped) == 4\n+\n+\n+def test_session_id_index_exists(kanban_home):\n+    \"\"\"The migration creates an index on session_id for cheap per-session\n+    list queries on busy boards. Without it, a chat-scoped poll would\n+    full-scan the tasks table.\"\"\"\n+    with kb.connect() as conn:\n+        rows = conn.execute(\n+            \"SELECT name FROM sqlite_master WHERE type='index' \"\n+            \"AND tbl_name='tasks'\"\n+        ).fetchall()\n+    names = {r[\"name\"] for r in rows}\n+    assert \"idx_tasks_session_id\" in names\n+\n+\n+def test_session_id_compose_with_tenant_filter(kanban_home):\n+    \"\"\"A client may want both `tenant=scarf:foo` AND `session=acp-x` —\n+    the filters must AND, not replace.\"\"\"\n+    with kb.connect() as conn:\n+        kb.create_task(\n+            conn, title=\"match\", tenant=\"scarf:foo\", session_id=\"acp-x\"\n+        )\n+        kb.create_task(\n+            conn, title=\"wrong-tenant\", tenant=\"other\", session_id=\"acp-x\"\n+        )\n+        kb.create_task(\n+            conn, title=\"wrong-session\",\n+            tenant=\"scarf:foo\", session_id=\"acp-y\",\n+        )\n+        rows = kb.list_tasks(\n+            conn, tenant=\"scarf:foo\", session_id=\"acp-x\"\n+        )\n+    assert [t.title for t in rows] == [\"match\"]\n+\n+\n # ---------------------------------------------------------------------------\n # Shared-board path resolution (issue #19348)\n #","@@ -1557,7 +1627,8 @@ def test_migrate_add_optional_columns_tolerates_concurrent_migration(kanban_home\n             workflow_template_id TEXT,\n             current_step_key TEXT,\n             skills TEXT,\n-            max_retries INTEGER\n+            max_retries INTEGER,\n+            session_id TEXT\n         )\n         \"\"\"\n     )"]},{"file":"tests/tools/test_kanban_tools.py","hunks":["@@ -768,6 +768,75 @@ def test_create_happy_path(worker_env):\n         conn.close()\n \n \n+def test_create_stamps_session_id_from_env(monkeypatch, worker_env):\n+    \"\"\"When the agent loop runs under ACP, the server propagates the\n+    originating chat session id via HERMES_SESSION_ID. ``kanban_create``\n+    reads it and stamps the new task so clients can render a per-session\n+    board (issue: ACP session linkage on kanban tasks).\"\"\"\n+    monkeypatch.setenv(\"HERMES_SESSION_ID\", \"acp-sess-abc\")\n+    from tools import kanban_tools as kt\n+    from hermes_cli import kanban_db as kb\n+    out = kt._handle_create({\n+        \"title\": \"from chat\",\n+        \"assignee\": \"peer\",\n+        \"parents\": [worker_env],\n+    })\n+    d = json.loads(out)\n+    assert d[\"ok\"] is True\n+    conn = kb.connect()\n+    try:\n+        new_task = kb.get_task(conn, d[\"task_id\"])\n+        assert new_task.session_id == \"acp-sess-abc\"\n+    finally:\n+        conn.close()\n+\n+\n+def test_create_session_id_arg_overrides_env(monkeypatch, worker_env):\n+    \"\"\"An explicit ``session_id`` arg from the model wins over the env\n+    propagation. Edge case but exercised: a tool call could carry a\n+    different session id (e.g. cross-session linking) and the explicit\n+    arg should not be silently overwritten.\"\"\"\n+    monkeypatch.setenv(\"HERMES_SESSION_ID\", \"from-env\")\n+    from tools import kanban_tools as kt\n+    from hermes_cli import kanban_db as kb\n+    out = kt._handle_create({\n+        \"title\": \"explicit override\",\n+        \"assignee\": \"peer\",\n+        \"parents\": [worker_env],\n+        \"session_id\": \"explicit-arg\",\n+    })\n+    d = json.loads(out)\n+    assert d[\"ok\"] is True\n+    conn = kb.connect()\n+    try:\n+        new_task = kb.get_task(conn, d[\"task_id\"])\n+        assert new_task.session_id == \"explicit-arg\"\n+    finally:\n+        conn.close()\n+\n+\n+def test_create_session_id_absent_when_env_unset(monkeypatch, worker_env):\n+    \"\"\"No env var, no arg → session_id stays NULL. Important for backwards\n+    compatibility: pre-ACP-propagation hosts and CLI-driven creates must\n+    not accidentally inherit a stale id.\"\"\"\n+    monkeypatch.delenv(\"HERMES_SESSION_ID\", raising=False)\n+    from tools import kanban_tools as kt\n+    from hermes_cli import kanban_db as kb\n+    out = kt._handle_create({\n+        \"title\": \"no session\",\n+        \"assignee\": \"peer\",\n+        \"parents\": [worker_env],\n+    })\n+    d = json.loads(out)\n+    assert d[\"ok\"] is True\n+    conn = kb.connect()\n+    try:\n+        new_task = kb.get_task(conn, d[\"task_id\"])\n+        assert new_task.session_id is None\n+    finally:\n+        conn.close()\n+\n+\n def test_create_rejects_no_title(worker_env):\n     from tools import kanban_tools as kt\n     assert json.loads(kt._handle_create({\"assignee\": \"x\"})).get(\"error\")"]},{"file":"tools/kanban_tools.py","hunks":["@@ -654,6 +654,10 @@ def _handle_create(args: dict, **kw) -> str:\n     body = args.get(\"body\")\n     parents = args.get(\"parents\") or []\n     tenant = args.get(\"tenant\") or os.environ.get(\"HERMES_TENANT\")\n+    # Stamp the originating session id when the agent loop runs under\n+    # ACP (which sets HERMES_SESSION_ID before invoking tools). NULL on\n+    # CLI / dashboard paths and on legacy hosts that don't set the env.\n+    session_id = args.get(\"session_id\") or os.environ.get(\"HERMES_SESSION_ID\")\n     priority = args.get(\"priority\")\n     workspace_kind = args.get(\"workspace_kind\") or \"scratch\"\n     workspace_path = args.get(\"workspace_path\")","@@ -700,6 +704,7 @@ def _handle_create(args: dict, **kw) -> str:\n                 skills=skills,\n                 initial_status=str(initial_status),\n                 created_by=os.environ.get(\"HERMES_PROFILE\") or \"worker\",\n+                session_id=session_id,\n             )\n             new_task = kb.get_task(conn, new_tid)\n             return _ok(\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}