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)
|
path.write_text(source)
|
||||||
PY
|
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-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/__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
|
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 \
|
/opt/hermes/gateway/platforms/api_server.py \
|
||||||
&& grep -Fq 'reasoning_effort=body.get("reasoning_effort")' \
|
&& grep -Fq 'reasoning_effort=body.get("reasoning_effort")' \
|
||||||
/opt/hermes/gateway/platforms/api_server.py \
|
/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")' \
|
&& grep -Fq 'routing_priority=body.get("routing_priority")' \
|
||||||
/opt/hermes/gateway/platforms/api_server.py \
|
/opt/hermes/gateway/platforms/api_server.py \
|
||||||
&& grep -Fq 'agent._hermes_explicit_model_pick' \
|
&& grep -Fq 'agent._hermes_explicit_model_pick' \
|
||||||
|
|||||||
@ -50,6 +50,7 @@ data:
|
|||||||
- clarify
|
- clarify
|
||||||
- delegation
|
- delegation
|
||||||
- file
|
- file
|
||||||
|
- kanban
|
||||||
- memory
|
- memory
|
||||||
- session_search
|
- session_search
|
||||||
- skills
|
- skills
|
||||||
@ -62,6 +63,7 @@ data:
|
|||||||
- clarify
|
- clarify
|
||||||
- delegation
|
- delegation
|
||||||
- file
|
- file
|
||||||
|
- kanban
|
||||||
- memory
|
- memory
|
||||||
- session_search
|
- session_search
|
||||||
- skills
|
- skills
|
||||||
@ -246,6 +248,12 @@ data:
|
|||||||
those findings, finish any required repair and verification, and emit the
|
those findings, finish any required repair and verification, and emit the
|
||||||
task's final structured result itself.
|
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
|
For an implementation or verification task, "review-ready" is a completed
|
||||||
task outcome with evidence, not a reason to call `kanban_block`. Request a
|
task outcome with evidence, not a reason to call `kanban_block`. Request a
|
||||||
block only for a genuine external decision or unavailable capability. A
|
block only for a genuine external decision or unavailable capability. A
|
||||||
|
|||||||
@ -4,7 +4,7 @@ kind: Kustomization
|
|||||||
namespace: hermes
|
namespace: hermes
|
||||||
images:
|
images:
|
||||||
- name: registry.bstein.dev/bstein/hermes-agent
|
- name: registry.bstein.dev/bstein/hermes-agent
|
||||||
digest: sha256:8a4a33df755607f69bc8803181cc8d38edf65330343ec8bf94ac5e24ba8d5e58
|
digest: sha256:37ebf720c783ae908a602916ffccf88d43d205a157957f5dc4b487867aee45e7
|
||||||
resources:
|
resources:
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
- vault-serviceaccount.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():
|
def test_agent_config_keeps_delegated_reviewers_from_owning_task_lifecycle():
|
||||||
configmap = _documents(HERMES / "agent-configmap.yaml")[0]
|
configmap = _documents(HERMES / "agent-configmap.yaml")[0]
|
||||||
instructions = configmap["data"]["AGENTS.md"]
|
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 "must never complete, block, unblock, reclaim" in instructions
|
||||||
assert "task's final structured result itself" in instructions
|
assert "task's final structured result itself" in instructions
|
||||||
assert "Atlas organization has private visibility" 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
|
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():
|
def test_sandbox_shares_only_the_tenant_workspace_without_credentials():
|
||||||
sandbox_docs = _documents(HERMES / "chat-sandbox.yaml")
|
sandbox_docs = _documents(HERMES / "chat-sandbox.yaml")
|
||||||
deployments = [doc for doc in sandbox_docs if doc["kind"] == "Deployment"]
|
deployments = [doc for doc in sandbox_docs if doc["kind"] == "Deployment"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user