hermes: close parked kanban tasks atomically
This commit is contained in:
parent
7d284871e8
commit
4bab6130bc
@ -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' \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user