hermes: close parked kanban tasks atomically

This commit is contained in:
jenkins 2026-08-16 04:31:00 -03:00
parent 7d284871e8
commit 4bab6130bc
4 changed files with 77 additions and 2 deletions

View File

@ -629,6 +629,48 @@ source = source[:runs_start] + runs_source
path.write_text(source)
PY
# A manual evidence-based close must not expose a parked task to the dispatcher
# between separate `unblock` and `complete` commands. Upstream already permits
# direct completion from `blocked`; extend the same atomic path to `scheduled`
# and make that contract visible in CLI help so agents do not create a ready
# window that can launch redundant work.
RUN python - <<'PY'
from pathlib import Path
db_path = Path("/opt/hermes/hermes_cli/kanban_db.py")
db_source = db_path.read_text()
state_before = ''' WHERE id = ?
AND status IN ('running', 'ready', 'blocked')
'''
state_after = ''' WHERE id = ?
AND status IN ('running', 'ready', 'blocked', 'scheduled')
'''
if db_source.count(state_before) != 2:
raise SystemExit(
"Hermes Kanban completion state gate changed: expected 2, "
f"found {db_source.count(state_before)}"
)
# Only the manual path lacks an expected run id. A worker-owned completion must
# continue to match its live run and may not revive an already scheduled run.
db_path.write_text(db_source.replace(state_before, state_after, 1))
cli_path = Path("/opt/hermes/hermes_cli/kanban.py")
cli_source = cli_path.read_text()
help_before = 'p_complete = sub.add_parser("complete", help="Mark one or more tasks done")'
help_after = (
'p_complete = sub.add_parser('
'"complete", '
'help="Atomically mark running, ready, blocked, or scheduled tasks done"'
')'
)
if cli_source.count(help_before) != 1:
raise SystemExit(
"Hermes Kanban complete help changed: expected 1, "
f"found {cli_source.count(help_before)}"
)
cli_path.write_text(cli_source.replace(help_before, help_after, 1))
PY
COPY dockerfiles/hermes-python-sandbox-tool.py /opt/hermes/tools/python_sandbox_tool.py
COPY dockerfiles/hermes-public-extract/__init__.py /opt/hermes/plugins/web/public_extract/__init__.py
COPY dockerfiles/hermes-public-extract/plugin.yaml /opt/hermes/plugins/web/public_extract/plugin.yaml
@ -1409,6 +1451,10 @@ RUN cd /opt/hermes/web \
/opt/hermes/gateway/platforms/api_server.py \
&& grep -Fq 'reasoning_effort=body.get("reasoning_effort")' \
/opt/hermes/gateway/platforms/api_server.py \
&& grep -Fq "status IN ('running', 'ready', 'blocked', 'scheduled')" \
/opt/hermes/hermes_cli/kanban_db.py \
&& grep -Fq 'Atomically mark running, ready, blocked, or scheduled tasks done' \
/opt/hermes/hermes_cli/kanban.py \
&& grep -Fq 'routing_priority=body.get("routing_priority")' \
/opt/hermes/gateway/platforms/api_server.py \
&& grep -Fq 'agent._hermes_explicit_model_pick' \

View File

@ -50,6 +50,7 @@ data:
- clarify
- delegation
- file
- kanban
- memory
- session_search
- skills
@ -62,6 +63,7 @@ data:
- clarify
- delegation
- file
- kanban
- memory
- session_search
- skills
@ -246,6 +248,12 @@ data:
those findings, finish any required repair and verification, and emit the
task's final structured result itself.
When authoritative evidence warrants closing a `blocked` or `scheduled`
task, call `kanban_complete` or `hermes kanban complete` directly. That
operation is atomic for parked tasks. Never unblock and then complete in
separate operations: the gateway dispatcher can claim the transient
`ready` state and launch redundant work.
For an implementation or verification task, "review-ready" is a completed
task outcome with evidence, not a reason to call `kanban_block`. Request a
block only for a genuine external decision or unavailable capability. A

View File

@ -4,7 +4,7 @@ kind: Kustomization
namespace: hermes
images:
- name: registry.bstein.dev/bstein/hermes-agent
digest: sha256:8a4a33df755607f69bc8803181cc8d38edf65330343ec8bf94ac5e24ba8d5e58
digest: sha256:37ebf720c783ae908a602916ffccf88d43d205a157957f5dc4b487867aee45e7
resources:
- namespace.yaml
- vault-serviceaccount.yaml

View File

@ -76,8 +76,16 @@ def test_chat_config_enables_real_research_compute_and_delegation():
def test_agent_config_keeps_delegated_reviewers_from_owning_task_lifecycle():
configmap = _documents(HERMES / "agent-configmap.yaml")[0]
instructions = configmap["data"]["AGENTS.md"]
config = yaml.safe_load(configmap["data"]["config.yaml"])
assert "Only the foreground durable worker owns its Kanban task lifecycle" in instructions
assert (
"Only the foreground durable worker owns its Kanban task lifecycle"
in instructions
)
assert "Never unblock and then complete" in instructions
assert "gateway dispatcher can claim the transient" in instructions
for platform in ("cli", "api_server"):
assert "kanban" in config["platform_toolsets"][platform]
assert "must never complete, block, unblock, reclaim" in instructions
assert "task's final structured result itself" in instructions
assert "Atlas organization has private visibility" in instructions
@ -85,6 +93,19 @@ def test_agent_config_keeps_delegated_reviewers_from_owning_task_lifecycle():
assert "already supplied through `GIT_ASKPASS`" in instructions
def test_agent_image_completes_parked_kanban_tasks_atomically():
dockerfile = (ROOT / "dockerfiles" / "Dockerfile.hermes-agent").read_text()
assert (
"AND status IN ('running', 'ready', 'blocked', 'scheduled')"
in dockerfile
)
assert (
"Atomically mark running, ready, blocked, or scheduled tasks done"
in dockerfile
)
def test_sandbox_shares_only_the_tenant_workspace_without_credentials():
sandbox_docs = _documents(HERMES / "chat-sandbox.yaml")
deployments = [doc for doc in sandbox_docs if doc["kind"] == "Deployment"]