Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1543,6 +1543,10 @@ def _ensure_hermes_home_managed(home: Path):
# behaviour — e.g. for a profile that prefers explicit
# ``kanban_notify-subscribe`` calls per task.
"auto_subscribe_on_create": True,
# Worker execution backend per lane. Only ``memo-evaluator`` supports
# ``modal`` in this phase; every other lane remains local even if a
# stray config value names Modal.
"worker_backends": {"memo-evaluator": "local"},
},

# Anthropic prompt caching (Claude via OpenRouter or native Anthropic API).
Expand Down
34 changes: 30 additions & 4 deletions hermes_cli/kanban_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -5576,6 +5576,7 @@ def complete_task(
metadata: Optional[dict] = None,
created_cards: Optional[Iterable[str]] = None,
expected_run_id: Optional[int] = None,
scan_prose_artifacts: bool = True,
) -> bool:
"""Transition ``running|ready -> done`` and record ``result``.

Expand Down Expand Up @@ -5604,6 +5605,16 @@ def complete_task(
Any suspected phantom references are recorded as a
``suspected_hallucinated_references`` event. This pass is advisory
and never blocks.

``scan_prose_artifacts`` (default True) controls the legacy
prose-artifact promotion in :func:`_merge_completion_prose_artifacts`,
which discovers scratch-workspace file paths named in ``summary`` /
``result`` and copies them into Kanban attachments. Callers applying an
**untrusted** completion (e.g. a remote Modal worker verdict) must pass
``False``: a prompt-injected summary could otherwise name an arbitrary
workspace file (``<workspace>/.env``) and expose it to later board
users. Trusted local workers keep the default so their promised
deliverables survive scratch cleanup.
"""
now = int(time.time())

Expand Down Expand Up @@ -5634,9 +5645,10 @@ def complete_task(
else:
verified_cards = []

metadata = _merge_completion_prose_artifacts(
conn, task_id, metadata, summary=summary, result=result,
)
if scan_prose_artifacts:
metadata = _merge_completion_prose_artifacts(
conn, task_id, metadata, summary=summary, result=result,
)
with write_txn(conn):
if expected_run_id is None:
cur = conn.execute(
Expand Down Expand Up @@ -9739,7 +9751,7 @@ def _dispatch_once_locked(
if claimed.workspace_kind == "worktree":
set_branch_name(conn, claimed.id, resolved_branch_name or (claimed.branch_name or "").strip() or f"wt/{claimed.id}")
_maybe_emit_scratch_tip(conn, claimed.id, claimed.workspace_kind)
_spawn = spawn_fn if spawn_fn is not None else _default_spawn
_spawn = spawn_fn if spawn_fn is not None else _configured_worker_spawn
try:
# Back-compat: older spawn_fn signatures accept only
# (task, workspace). Test stubs in the suite rely on that.
Expand Down Expand Up @@ -10504,6 +10516,20 @@ def _default_spawn(
return proc.pid


def _configured_worker_spawn(
task: Task,
workspace: str,
*,
board: Optional[str] = None,
) -> Optional[int]:
"""Route the narrowly opted-in Modal lane; retain local spawn otherwise."""
from hermes_cli.kanban_modal import resolve_worker_backend, spawn_modal_worker

if resolve_worker_backend(task.assignee, _load_kanban_cfg()) == "modal":
return spawn_modal_worker(task, workspace, board=board)
return _default_spawn(task, workspace, board=board)


# ---------------------------------------------------------------------------
# Long-lived dispatcher daemon
# ---------------------------------------------------------------------------
Expand Down
Loading
Loading