Skip to content

fix(delegation): forward background flag so delegate_task(background=true) runs async#46968

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-c26b1c8e
Jun 16, 2026
Merged

fix(delegation): forward background flag so delegate_task(background=true) runs async#46968
teknium1 merged 2 commits into
mainfrom
hermes/hermes-c26b1c8e

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

delegate_task(background=true) now actually runs the subagent asynchronously instead of silently blocking. Previously the background flag was dropped before reaching the function, so it always ran synchronously and returned the sync results payload instead of a delegation_id.

Root cause: delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI, gateway, desktop/TUI) routes it through the single chokepoint AIAgent._dispatch_delegate_task (run_agent.py). That forwarder passed every schema field except background. The model sees background in the schema (the call validates), but the value never reached delegate_task, which defaulted it to False and ran synchronously. The generic registry handler does forward it, but that path is only a fallback stub for agent-loop tools and is never the live path.

Not desktop-specific despite the report framing — it affected every surface.

Changes

  • run_agent.py: add background=function_args.get("background") to _dispatch_delegate_task.

Validation

Before After
background reaches delegate_task dropped → forced sync forwarded → async dispatch
tests/tools/test_async_delegation.py 17/17 pass

E2E: stubbed delegate_task and invoked the real _dispatch_delegate_task({"goal":..., "background": True}) — confirmed background=True now arrives at the function (was None/dropped before).

Infographic

background-dropped-fix

teknium1 added 2 commits June 15, 2026 17:03
Port from Kilo-Org/kilocode#11240. Their issue #11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.
delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI,
gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task.
That forwarder passed every schema field except background, so
delegate_task(background=true) was silently downgraded to a synchronous
run and returned the sync results payload instead of a delegation_id.

The model sees background in the schema (the call validates), but the
value never reached the function. Add the one missing kwarg so async
background delegation actually engages.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-c26b1c8e vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10962 on HEAD, 10960 on base (🆕 +2)

🆕 New issues (2):

Rule Count
unresolved-attribute 2
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
run_agent.py:2920: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`

✅ Fixed issues (1):

Rule Count
invalid-assignment 1
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`

Unchanged: 5771 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1 teknium1 merged commit 0a8f3e2 into main Jun 16, 2026
34 checks passed
@teknium1 teknium1 deleted the hermes/hermes-c26b1c8e branch June 16, 2026 01:52
jhjaggars-hermes added a commit to jhjaggars/hermes-agent that referenced this pull request Jun 16, 2026
* fix(skills): guard recursive skill delete against tree-escape (NousResearch#46929)

Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.

* fix(delegation): forward background flag so delegate_task(background=true) runs async (NousResearch#46968)

* fix(skills): guard recursive skill delete against tree-escape

Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.

* fix(delegation): forward background flag in delegate_task dispatch

delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI,
gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task.
That forwarder passed every schema field except background, so
delegate_task(background=true) was silently downgraded to a synchronous
run and returned the sync results payload instead of a delegation_id.

The model sees background in the schema (the call validates), but the
value never reached the function. Add the one missing kwarg so async
background delegation actually engages.

* feat(desktop): open new sessions in compact windows

Add the Electron IPC bridge and rebindable shortcut for opening an unkeyed scratch window on the new-session draft.

* feat(desktop): trim scratch window chrome

Hide nonessential Hermes chrome in the new-session pop-out while preserving native window controls and stable first-message positioning.

* fix(desktop): sync new sessions across windows

Broadcast session-list mutations from scratch windows so the main sidebar refreshes without manual reloads.

* Hide hosted dashboard update controls

* Detect containerized dashboard update management

* Simplify dashboard update detection to containers

* fix(desktop): route global remote profile REST calls (NousResearch#47011)

* fix(desktop): route global remote profile REST calls

* fix(dashboard): scope oauth provider routes by profile

* test(tui): isolate notification poller queue

* fix(desktop): open remote-gateway artifacts via authenticated download (NousResearch#46895)

On a remote gateway connection, agent-written files live on the gateway
host, not the desktop's disk, so the Artifacts view's file:// hrefs failed
("Invalid external URL") and image thumbnails broke.

Make mediaExternalUrl() remote-aware in one place: in remote mode it
rewrites gateway-local paths to GET /api/files/download (a new endpoint
that streams the file as a Content-Disposition: attachment). The artifacts
view now resolves through it, and so do the existing chat-media and
generated-image callers, for free.

The download endpoint stays auth-gated; auth_middleware additionally
accepts the session token as a ?token= query param for this one path so a
shell/browser-opened download (which can't set the session header) still
authenticates — the same query-token tradeoff as the /api/pty WebSocket.
It is NOT added to PUBLIC_API_PATHS.

Salvages NousResearch#46663 (which carried ~19k lines of CRLF noise and made the
endpoint public). Reimplemented on a clean LF base with the security hole
closed and tests added.

Co-authored-by: qingshan89 <qs2816661685@gmail.com>

* Sync homelab/main to upstream/main (9d2ec8d) with minimal carried patches

Rebased onto current upstream/main and reapplied the minimal homelab
patch set:
- Dockerfile: add iproute2 + GitHub CLI (gh) from official apt repo
- pyproject.toml: add langfuse optional extra
- plugins/observability/langfuse/__init__.py: Responses API serialization
- plugins/platforms/discord/adapter.py: role-mention invocation support
- tests/gateway/test_discord_role_mentions.py: role-mention test coverage
- tests/plugins/test_langfuse_plugin.py: Responses API test coverage
- .github/workflows/build.yml: GHCR image publish workflow

---------

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
Co-authored-by: Shannon Sands <shannon.sands.1979@gmail.com>
Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
Co-authored-by: qingshan89 <qs2816661685@gmail.com>
Co-authored-by: Hermes Agent <hermes-agent@users.noreply.github.com>
pib-hermes-bot pushed a commit to Partners-in-Biz/hermes-agent that referenced this pull request Jun 16, 2026
…true) runs async (NousResearch#46968)

* fix(skills): guard recursive skill delete against tree-escape

Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.

* fix(delegation): forward background flag in delegate_task dispatch

delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI,
gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task.
That forwarder passed every schema field except background, so
delegate_task(background=true) was silently downgraded to a synchronous
run and returned the sync results payload instead of a delegation_id.

The model sees background in the schema (the call validates), but the
value never reached the function. Add the one missing kwarg so async
background delegation actually engages.
alanbratu pushed a commit to alanbratu/hermes-agent that referenced this pull request Jun 17, 2026
…true) runs async (NousResearch#46968)

* fix(skills): guard recursive skill delete against tree-escape

Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.

* fix(delegation): forward background flag in delegate_task dispatch

delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI,
gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task.
That forwarder passed every schema field except background, so
delegate_task(background=true) was silently downgraded to a synchronous
run and returned the sync results payload instead of a delegation_id.

The model sees background in the schema (the call validates), but the
value never reached the function. Add the one missing kwarg so async
background delegation actually engages.
zmlgit pushed a commit to zmlgit/hermes-agent that referenced this pull request Jun 17, 2026
…true) runs async (NousResearch#46968)

* fix(skills): guard recursive skill delete against tree-escape

Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.

* fix(delegation): forward background flag in delegate_task dispatch

delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI,
gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task.
That forwarder passed every schema field except background, so
delegate_task(background=true) was silently downgraded to a synchronous
run and returned the sync results payload instead of a delegation_id.

The model sees background in the schema (the call validates), but the
value never reached the function. Add the one missing kwarg so async
background delegation actually engages.
al3xar pushed a commit to al3xar/hermes-agent that referenced this pull request Jun 17, 2026
…true) runs async (NousResearch#46968)

* fix(skills): guard recursive skill delete against tree-escape

Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.

* fix(delegation): forward background flag in delegate_task dispatch

delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI,
gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task.
That forwarder passed every schema field except background, so
delegate_task(background=true) was silently downgraded to a synchronous
run and returned the sync results payload instead of a delegation_id.

The model sees background in the schema (the call validates), but the
value never reached the function. Add the one missing kwarg so async
background delegation actually engages.
T02200059 pushed a commit to T02200059/hermes-agent that referenced this pull request Jun 18, 2026
…true) runs async (NousResearch#46968)

* fix(skills): guard recursive skill delete against tree-escape

Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.

* fix(delegation): forward background flag in delegate_task dispatch

delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI,
gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task.
That forwarder passed every schema field except background, so
delegate_task(background=true) was silently downgraded to a synchronous
run and returned the sync results payload instead of a delegation_id.

The model sees background in the schema (the call validates), but the
value never reached the function. Add the one missing kwarg so async
background delegation actually engages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant