Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/contributor-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
check-attribution:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
fetch-depth: 0 # Full history needed for git log

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:
fi

- name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: _site

Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ jobs:
with:
submodules: recursive

# The image bundles a large Python venv (ctranslate2, torch, etc.) and
# `load: true` imports the whole thing into the local daemon, which can
# exhaust the runner's ~14 GB free disk and fail with "no space left on
# device". Reclaim space from preinstalled toolchains we don't use.
- name: Free up disk space
run: |
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \
/usr/local/share/boost /usr/local/lib/node_modules \
"${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}" || true
sudo docker image prune --all --force || true
df -h

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4

Expand Down Expand Up @@ -146,6 +158,16 @@ jobs:
with:
submodules: recursive

# See build-amd64: free space before `load: true` imports the large
# venv-bearing image into the local daemon.
- name: Free up disk space
run: |
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \
/usr/local/share/boost /usr/local/lib/node_modules \
"${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}" || true
sudo docker image prune --all --force || true
df -h

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs-site-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
docs-site-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
fetch-depth: 0 # need full history for merge-base + worktree

Expand Down Expand Up @@ -124,7 +124,7 @@ jobs:
- name: Post / update PR comment
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
continue-on-error: true
uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const fs = require('fs');
Expand Down Expand Up @@ -167,7 +167,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8
Expand All @@ -191,7 +191,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/nix-lockfile-fix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:
echo "::error::Failed to push after 3 rebase attempts"
exit 1

# ── PR fix (manual dispatch) ─────────────────────────────────────
# ── PR fix (manual dispatch) ───────────────────────────────────────
fix:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
Expand All @@ -130,8 +130,7 @@ jobs:
uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9
with:
script: |
// 1. Verify the actor has write access — applies to both checkbox
// clicks and manual dispatch.
// 1. Verify the actor has write access for manual dispatch.
const { data: perm } =
await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/skills-index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
if: github.repository == 'NousResearch/hermes-agent'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
Expand Down Expand Up @@ -53,7 +53,7 @@ jobs:
# Only deploy on schedule or manual trigger (not on every push to the script)
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5

- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
Expand Down Expand Up @@ -92,7 +92,7 @@ jobs:
echo "hermes-agent.nousresearch.com" > _site/CNAME

- name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: _site

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/supply-chain-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
fetch-depth: 0

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/uv-lockfile-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8
Expand Down
13 changes: 13 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Optimization: Bulk Task Link Insertion in kanban_db.py

**Date**: 2026-06-01
**File**: `hermes_cli/kanban_db.py` (`create_task()`)

### What
Replaced a `for` loop executing individual `INSERT OR IGNORE` queries with a single `conn.executemany` call for inserting task links (parent-child relationships).

### Why
The `for` loop caused an N+1 query issue. By using `executemany`, the SQLite engine can process all insertions efficiently in a single batch, reducing overhead.

### Expected Performance Impact
Measured improvement: Creation time for a task with 10,000 parents decreased from ~0.1575s to ~0.1344s (~15% speedup).
77 changes: 5 additions & 72 deletions agent/copilot_acp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import json
import os
import queue
import re
import shlex
import subprocess
import threading
Expand All @@ -27,8 +26,6 @@
ACP_MARKER_BASE_URL = "acp://copilot"
_DEFAULT_TIMEOUT_SECONDS = 900.0

_TOOL_CALL_BLOCK_RE = re.compile(r"<tool_call>\s*(\{.*?\})\s*</tool_call>", re.DOTALL)
_TOOL_CALL_JSON_RE = re.compile(r"\{\s*\"id\"\s*:\s*\"[^\"]+\"\s*,\s*\"type\"\s*:\s*\"function\"\s*,\s*\"function\"\s*:\s*\{.*?\}\s*\}", re.DOTALL)


def _resolve_command() -> str:
Expand Down Expand Up @@ -210,76 +207,12 @@ def _render_message_content(content: Any) -> str:


def _extract_tool_calls_from_text(text: str) -> tuple[list[SimpleNamespace], str]:
if not isinstance(text, str) or not text.strip():
if not isinstance(text, str):
return [], ""

extracted: list[SimpleNamespace] = []
consumed_spans: list[tuple[int, int]] = []

def _try_add_tool_call(raw_json: str) -> None:
try:
obj = json.loads(raw_json)
except Exception:
return
if not isinstance(obj, dict):
return
fn = obj.get("function")
if not isinstance(fn, dict):
return
fn_name = fn.get("name")
if not isinstance(fn_name, str) or not fn_name.strip():
return
fn_args = fn.get("arguments", "{}")
if not isinstance(fn_args, str):
fn_args = json.dumps(fn_args, ensure_ascii=False)
call_id = obj.get("id")
if not isinstance(call_id, str) or not call_id.strip():
call_id = f"acp_call_{len(extracted)+1}"

extracted.append(
SimpleNamespace(
id=call_id,
call_id=call_id,
response_item_id=None,
type="function",
function=SimpleNamespace(name=fn_name.strip(), arguments=fn_args),
)
)

for m in _TOOL_CALL_BLOCK_RE.finditer(text):
raw = m.group(1)
_try_add_tool_call(raw)
consumed_spans.append((m.start(), m.end()))

# Only try bare-JSON fallback when no XML blocks were found.
if not extracted:
for m in _TOOL_CALL_JSON_RE.finditer(text):
raw = m.group(0)
_try_add_tool_call(raw)
consumed_spans.append((m.start(), m.end()))

if not consumed_spans:
return extracted, text.strip()

consumed_spans.sort()
merged: list[tuple[int, int]] = []
for start, end in consumed_spans:
if not merged or start > merged[-1][1]:
merged.append((start, end))
else:
merged[-1] = (merged[-1][0], max(merged[-1][1], end))

parts: list[str] = []
cursor = 0
for start, end in merged:
if cursor < start:
parts.append(text[cursor:start])
cursor = max(cursor, end)
if cursor < len(text):
parts.append(text[cursor:])

cleaned = "\n".join(p.strip() for p in parts if p and p.strip()).strip()
return extracted, cleaned
# ACP currently provides only free-form assistant text. Treating text as a
# trusted tool-call transport is unsafe because quoted/untrusted content can
# be promoted into executable tool calls.
return [], text



Expand Down
Loading
Loading