Skip to content

fix(hermes): scope Signet memory to named agents#504

Open
NicholaiVogel wants to merge 8 commits intomainfrom
fix/hermes-agent-scoped-identity
Open

fix(hermes): scope Signet memory to named agents#504
NicholaiVogel wants to merge 8 commits intomainfrom
fix/hermes-agent-scoped-identity

Conversation

@NicholaiVogel
Copy link
Copy Markdown
Contributor

@NicholaiVogel NicholaiVogel commented Apr 13, 2026

Summary

Fixes Hermes Signet integration in three related ways:

  • Scopes Hermes Signet memory and identity to the named agent workspace instead of falling back to the generic Hermes identity.
  • Gives Signet recall paths enough time to complete on larger memory stores, avoiding false daemon reachability failures when recall takes longer than the generic 5s HTTP timeout.
  • Exposes the complete Signet memory write schema to Hermes and MCP callers, including prospective hints, transcript, and structured graph payloads.

Changes

  • Adds Hermes connector support for named-agent scoping through SIGNET_AGENT_ID and SIGNET_AGENT_WORKSPACE.
  • Makes Hermes Agent a first-class signet setup harness in interactive setup, non-interactive help text, migration detection, and setup docs.
  • Keeps explicit recall requests unscoped unless the caller asks for agent scoping.
  • Extends the bundled Hermes plugin recall timeout to 30s.
  • Uses the longer timeout for per-turn user-prompt-submit recall hooks.
  • Extends signet recall CLI timeout to 30s for /api/memory/recall.
  • Adds hints, transcript, and structured support to the Hermes memory_store schema and daemon payload forwarding.
  • Aligns the MCP memory_store schema and payload forwarding with the daemon remember endpoint for hints, transcript, and structured graph metadata.
  • Documents the full Hermes memory_store surface.
  • Adds regression coverage for Hermes connector scoping, setup harness availability, Hermes install detection, recall timeout behavior, schema completeness, MCP structured memory writes, auth-bearing agent registration, non-404 registration checks, bundled Python client compatibility, and legacy structured payload compatibility.

Type

  • feat - new user-facing feature (bumps minor)
  • fix - bug fix
  • refactor - restructure without behavior change
  • chore - build, deps, config, docs
  • perf - performance improvement
  • test - test coverage

Packages affected

  • @signet/core
  • @signet/daemon
  • @signet/cli / dashboard
  • @signet/sdk
  • @signet/connector-*
  • @signet/web
  • predictor
  • Other:

Screenshots

N/A. No UI changes.

PR Readiness (MANDATORY)

  • Spec alignment validated (INDEX.md + dependencies.yaml)
  • Agent scoping verified on all new/changed data queries
  • Input/config validation and bounds checks added
  • Error handling and fallback paths tested (no silent swallow)
  • Security checks applied to admin/mutation endpoints
  • Docs updated for API/spec/status changes
  • Regression tests added for each bug fix
  • Lint/typecheck/tests pass locally

Migration Notes (if applicable)

N/A. No database migrations touched.

  • Migration is idempotent
  • Daemon Rust parity reviewed or explicitly N/A
  • Rollback / compatibility note included in PR description

Compatibility note: these changes are additive to existing Hermes and MCP schemas. Existing memory tool calls remain valid, including legacy structured aspect tuples using {entity, aspect, value}. Named agents may provide agent-local identity overrides for the standard identity files, including MEMORY.md; if no agent-local file exists, the root workspace file remains the fallback.

Testing

  • bun test passes
  • bun run typecheck passes
  • bun run lint passes
  • Tested against running daemon
  • N/A

Commands run locally:

bun test packages/daemon/src/mcp/tools.test.ts packages/connector-hermes-agent/index.test.ts packages/cli/src/commands/memory.test.ts packages/core/src/__tests__/identity.test.ts packages/daemon/src/hooks.test.ts packages/cli/src/features/setup-shared.test.ts
python3 -m py_compile packages/connector-hermes-agent/hermes-plugin/__init__.py packages/connector-hermes-agent/hermes-plugin/client.py
bunx biome check packages/connector-hermes-agent/src/index.ts packages/connector-hermes-agent/index.test.ts packages/core/src/__tests__/identity.test.ts packages/daemon/src/mcp/tools.ts packages/daemon/src/mcp/tools.test.ts
bun run --filter '@signet/connector-hermes-agent' typecheck
bun run --filter '@signet/core' typecheck
git diff --check

Result:

120 pass
0 fail
Hermes plugin py_compile passed
Targeted Biome check passed
@signet/connector-hermes-agent typecheck passed
@signet/core typecheck passed
git diff --check passed

Full-suite note:

  • bun run typecheck was also attempted. It still exits 2 on existing unrelated @signet/*-pi* package failures around missing @signet/pi-extension-base and connector-base exports. The packages touched by this PR that expose typecheck scripts pass.
  • bun run lint was also attempted. It still exits 1 on the current repo-wide Biome baseline, mainly formatting diagnostics in existing package manifests outside this PR. Targeted Biome checks for this PR's changed TypeScript files pass.

Manual validation:

  • signet recall --json "Nicholai durable facts Signet memory" succeeds locally after increasing the timeout.
  • Hermes bundled Signet plugin smoke test connects to the daemon, exposes memory_search, and returns recall results instead of timing out.
  • Local Hermes plugin schema now exposes hints, transcript, and structured on memory_store.
  • Local Hermes gateway restarted and is running.

AI disclosure

  • No AI tools were used in this PR
  • AI tools were used

Notes

The broader recall ranking issue is separate from this schema fix. This PR makes the fields available and forwarded correctly so callers can provide retrieval-friendly hints instead of being limited to content, type, importance, tags, pinned, and project.

@PR-Reviewer-Ant
Copy link
Copy Markdown
Collaborator

Hi @NicholaiVogel - I'm an automated code reviewer powered by pr-reviewer. I'm taking a look at the fixes in fix(hermes): scope Signet memory to named agents (commit 0797f727) now and I'll follow up shortly with feedback.

Copy link
Copy Markdown
Collaborator

@PR-Reviewer-Ant PR-Reviewer-Ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by pr-reviewer | model: claude-sonnet-4-6 | commit: 0797f727

[Automated Bot Review — pr-reviewer] PR #504 correctly scopes Hermes memory to named-agent workspaces and extends recall timeouts. Found one blocking correctness bug in the new Python HTTP helpers, one warning-level auth gap in the TS registration helper, and one test-env leak.

Confidence: High [sufficient_diff_evidence, targeted_context_included] - All three findings are directly provable from the changed lines. (1) The _delete/_patch except-clauses are quoted verbatim — json.JSONDecodeError is absent, confirming the crash path. (2) The fetch headers in ensureNamedAgentRegistered are quoted verbatim — no Authorization header is present. (3) The afterEach block is quoted verbatim — restoreEnv("SIGNET_AGENT_WORKSPACE") is absent despite SIGNET_AGENT_WORKSPACE being in originalEnv and cleared in beforeEach.

timeout: float = _TIMEOUT_SECS,
) -> Optional[Dict[str, Any]]:
"""DELETE from the daemon. Returns parsed response or None on failure."""
url = f"{self._base_url}{path}"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json.loads(resp.read().decode("utf-8")) in _delete throws json.JSONDecodeError (a subclass of ValueError) when the daemon returns an empty body (e.g. 204 No Content). The except clause only catches HTTPError, URLError, OSError, TimeoutErrorJSONDecodeError is not among them. The same bug exists in _patch at the equivalent line (~line 127). The exception propagates to handle_tool_call's outer except Exception block, which means memory_forget and memory_modify return a false {"error": "Signet memory_forget failed: Expecting value: line 1 column 1 (char 0)"} to the model even when the daemon accepted and executed the request. Fix: catch (ValueError, json.JSONDecodeError) alongside the existing exceptions, or guard with body = resp.read(); return json.loads(body) if body else {}.

}

try {
const resp = await fetch(`${baseUrl}/api/agents`, {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureNamedAgentRegistered makes two bare fetch calls — the GET existence check and the POST to create the agent — with no Authorization header. The Python client's _headers() always injects Bearer ${SIGNET_TOKEN} when SIGNET_TOKEN is set, exactly because the daemon may require authentication for non-localhost deployments. Without the header, both calls will receive 401 from an authenticated daemon: the GET returns non-ok (falling through to the POST), and the POST also returns non-ok, producing a warning that looks like a daemon-unreachable error rather than an auth failure. The net effect is that agent auto-registration is silently non-functional for the remote-daemon configuration the PR explicitly acknowledges (the SIGNET_TOKEN warning in the .env section just below). Fix: read SIGNET_TOKEN from process.env (stripping newlines as the rest of this function does) and include Authorization: Bearer ${token} when present.

@@ -42,6 +47,7 @@ afterEach(() => {
restoreEnv("HERMES_HOME");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SIGNET_AGENT_WORKSPACE is captured in originalEnv, deleted in beforeEach, and set by at least one test ("derives SIGNET_AGENT_WORKSPACE for named agents"), but restoreEnv("SIGNET_AGENT_WORKSPACE") is absent from afterEach. Any test that runs after the scoping test will inherit the leaked workspace path, which can silently skew workspace resolution in subsequent assertions. Add restoreEnv("SIGNET_AGENT_WORKSPACE") to the afterEach block alongside the other keys.

Copy link
Copy Markdown
Collaborator

@PR-Reviewer-Ant PR-Reviewer-Ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by pr-reviewer | model: claude-sonnet-4-6 | commit: 33005759

[Automated bot review — pr-reviewer] PR #504 correctly implements named-agent workspace scoping and the recall timeout fix described in the description. Four issues found: one warning-level breaking schema change, one warning-level auth gap on a new install-time mutation, and two nitpick-level test hygiene issues.

Confidence: Medium [sufficient_diff_evidence, missing_cross_module_context] - The breaking schema change in tools.ts (entity→entityName, value→attributes) is directly visible in the diff. Whether any existing callers depend on the old field names cannot be determined without the full daemon route handler and any external consumers of the MCP memory_store structured payload. The auth gap in ensureNamedAgentRegistered is directly visible; whether /api/agents requires a bearer token depends on the daemon auth middleware which is not included in this diff.

z.object({
entity: z.string(),
entityName: z.string(),
aspect: z.string(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking schema change in the structured.aspects array: entity (string) was renamed to entityName and value (string) was replaced by attributes (array of objects). Any caller that previously passed {entity, aspect, value} tuples will now have those fields silently dropped by Zod. The Python plugin's STRUCTURED_ASPECT_SCHEMA matches the new shape, so Python→daemon is now aligned, but the rename is not backward-compatible for any TypeScript/JS callers (MCP clients, direct API consumers) already using the old schema. The PR description does not mention this as a breaking change. If the structured parameter was purely internal or never shipped to external callers, this is fine — but that should be confirmed before merge.

try {
const resp = await fetch(`${baseUrl}/api/agents`, {
method: "POST",
headers: { "Content-Type": "application/json" },
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureNamedAgentRegistered issues an unauthenticated POST to /api/agents (no Authorization header, no SIGNET_TOKEN injection). The existing daemon auth middleware protects mutation endpoints; on any instance running in non-localhost or token-required mode this POST will return 401/403 and the agent entry will not be created. The failure is soft (warning added, install still succeeds), and the warning message is actionable, so this is not a correctness regression for local setups. However, the install experience on remote authenticated daemons will silently degrade: the named-agent scope is never registered, so subsequent Hermes sessions will fall back to unscoped memory. Consider reading SIGNET_TOKEN from env (already done for configuredDaemonUrl) and forwarding it as a bearer token here.

@@ -42,6 +47,7 @@ afterEach(() => {
restoreEnv("HERMES_HOME");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SIGNET_AGENT_WORKSPACE is captured in originalEnv (line 11) and deleted in beforeEach (line 33), but restoreEnv("SIGNET_AGENT_WORKSPACE") is absent from afterEach. If a future test sets this env var, it will leak into subsequent tests. Add restoreEnv("SIGNET_AGENT_WORKSPACE") to the afterEach block alongside the other restores.

if tags:
body["tags"] = tags
if who:
body["who"] = who
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the score_min filtering loop: float(row.get("score", 0.0)) will raise TypeError if a result row contains "score": null (JSON null → Python None), because dict.get returns the stored value (None) rather than the default when the key exists. The outer try/except Exception in handle_tool_call catches this and returns an error JSON, but it logs at ERROR level and drops the entire result set rather than gracefully skipping the bad row. Guard with float(row.get("score") or 0.0) or an explicit None check.

Copy link
Copy Markdown
Collaborator

@PR-Reviewer-Ant PR-Reviewer-Ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by pr-reviewer | model: claude-sonnet-4-6 | commit: fc7f85ef

[Automated Review — pr-reviewer bot] PR #504 introduces Hermes named-agent scoping, recall timeout increases, and expanded memory_store schema. The implementation broadly matches the stated goals. Four issues are worth discussion or fixing before merge.

Confidence: High [sufficient_diff_evidence, targeted_context_included] - All findings are grounded in explicit lines in the diff and the full changed-file contents provided. The afterEach env-restore omission is directly visible in index.test.ts. The unauthenticated fetch in ensureNamedAgentRegistered is explicit in src/index.ts. The getAgentIdentityFiles scope expansion is verifiable by diffing the old and new IDENTITY_FILES constant. The unchecked typecheck/lint items are stated in the PR checklist itself.

@@ -42,6 +47,7 @@ afterEach(() => {
restoreEnv("HERMES_HOME");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SIGNET_AGENT_WORKSPACE is captured in originalEnv (line 10) but is never restored in afterEach. Every other key added in this PR (SIGNET_SKIP_AGENT_REGISTER) is restored; SIGNET_AGENT_WORKSPACE is the only outlier. If the env var is set in the outer process before the suite runs, its original value will be lost after the suite completes. Add restoreEnv("SIGNET_AGENT_WORKSPACE") to the afterEach block.

}

try {
const resp = await fetch(`${baseUrl}/api/agents`, {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureNamedAgentRegistered fires a POST /api/agents mutation without including an auth token. All other mutation paths in this codebase (remember, recall, etc.) conditionally include Authorization: Bearer $SIGNET_TOKEN when the token is available. For remote daemon deployments where auth is required, this request will get a 401/403, and the failure message will read Could not register Signet agent '…' with shared memory policy: [auth error body] — which is misleading. The SIGNET_TOKEN value should be read from env and included here for consistency, mirroring the Python client's _headers() pattern.

name: agentId,
read_policy: "shared",
policy_group: null,
}),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureNamedAgentRegistered unconditionally registers the named agent with read_policy: "shared". There is no way for the installer to opt for isolated or group policy via env var or flag. If a user already has a named agent with isolated policy and re-runs install (e.g. after an upgrade), the GET succeeds and the POST is skipped — so existing policy is preserved. But on first install the policy is always forced to shared. This is undocumented and could silently over-share memory. At minimum the warning emitted on failure should mention signet agent create <id> --memory isolated as an alternative so users know their options.

@@ -56,26 +62,20 @@ export function scaffoldAgent(name: string, agentsDir: string): void {
* exists on disk for the given agent.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old implementation gave every file in SHARED_FILES (AGENTS.md, USER.md, TOOLS.md, HEARTBEAT.md, MEMORY.md, BOOTSTRAP.md) a fixed root path — per-agent overrides were only supported for SOUL.md and IDENTITY.md. This PR expands the fallback-first lookup to all eight files including MEMORY.md and USER.md. The test at packages/core/src/__tests__/identity.test.ts:107 confirms the new behavior is intentional, but the change is broader than the PR description implies ("scope Hermes Signet memory to the named agent workspace"). Specifically, MEMORY.md being per-agent changes the multi-agent shared-memory model for any existing deployment where MEMORY.md is the single shared synthesis artifact. Callers that relied on MEMORY.md always being root-level will now receive an agent-scoped override if one exists. This should be explicitly documented in the PR or in docs/ARCHITECTURE.md / docs/PIPELINE.md to avoid silent regressions for existing multi-agent setups.

@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR checklist shows bun run typecheck and bun run lint as unchecked. Per repo AGENTS.md the PR checklist is mandatory. The diff touches TypeScript across @signet/core, @signet/daemon, and @signet/connector-hermes-agent — any silent type error (e.g. in the new IdentityFileMap parameter threading through hooks.ts) would not be caught by the 45-test run alone. Please confirm both pass before merge.

@NicholaiVogel
Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in 5bb4dff5.

What changed:

  • Hermes install-time agent registration now forwards Authorization: Bearer $SIGNET_TOKEN on both the GET and POST paths.
  • Named-agent registration no longer hardcodes shared memory only. It supports SIGNET_AGENT_READ_POLICY / SIGNET_AGENT_MEMORY_POLICY values of shared, isolated, and group, with SIGNET_AGENT_POLICY_GROUP for group policy.
  • Test env cleanup now restores SIGNET_AGENT_WORKSPACE and the new Signet registration env vars.
  • Hermes plugin response parsing now treats empty successful daemon responses as {} and catches JSON decode failures across GET/POST/PATCH/DELETE.
  • score_min filtering now handles null score values without dropping the entire result set.
  • MCP memory_store.structured.aspects now accepts both the new structured shape and legacy {entity, aspect, value} tuples, normalizing legacy callers before forwarding to remember.
  • Documented agent-local identity overrides, including optional agent-local MEMORY.md fallback behavior, in docs/ARCHITECTURE.md and docs/HARNESSES.md.
  • Documented Hermes plugin memory policy env vars.

Validation:

bun test packages/daemon/src/mcp/tools.test.ts packages/connector-hermes-agent/index.test.ts packages/cli/src/commands/memory.test.ts packages/core/src/__tests__/identity.test.ts packages/daemon/src/hooks.test.ts
python3 -m py_compile packages/connector-hermes-agent/hermes-plugin/__init__.py packages/connector-hermes-agent/hermes-plugin/client.py
bunx biome check packages/connector-hermes-agent/src/index.ts packages/connector-hermes-agent/index.test.ts packages/daemon/src/mcp/tools.ts packages/daemon/src/mcp/tools.test.ts
bun run --filter '@signet/connector-hermes-agent' typecheck
bun run --filter '@signet/core' typecheck
git diff --check

Results: 105 tests pass, py_compile passed, targeted Biome passed, targeted typechecks passed, diff check passed.

Full bun run typecheck was also attempted and still fails on unrelated existing @signet/*-pi* package issues. Full bun run lint was attempted and still hits the repo-wide Biome baseline; targeted checks for the changed TypeScript files pass.

Copy link
Copy Markdown
Collaborator

@PR-Reviewer-Ant PR-Reviewer-Ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by pr-reviewer | model: claude-sonnet-4-6 | commit: 5bb4dff5

Harness output could not be parsed as structured JSON.

{
  "summary": "[Automated Review — pr-reviewer bot] Four issues found: one cross-language logic divergence that could cause silent mis-scoping for the default `hermes-agent` agent ID, one stale `meta.noHits` field after client-side `score_min` filtering, one registration function that proceeds to POST on any non-OK GET (not just 404), and one breaking behavioral change in `getAgentIdentityFiles` for existing named-agent deployments.",
  "verdict": "comment",
  "confidence": {
    "level": "medium",
    "reasons": ["sufficient_diff_evidence", "missing_cross_module_context"],
    "justification": "Issues 1-3 are directly provable from the changed lines. Issue 4 depends on whether any existing named-agent deployments have files in their agent subdirectory other than SOUL.md/IDENTITY.md — that runtime state is not visible in the diff alone."
  },
  "ui_screenshot_needed": false,
  "comments": [
    {
      "file": "packages/connector-hermes-agent/hermes-plugin/__init__.py",
      "line": 252,
      "body": "**Logic divergence from the TypeScript connector.** `_resolve_agent_workspace` checks `agent_id != \"default\"` but does not exclude `\"hermes-agent\"` the way the TS connector does:\n\n```typescript\n// src/index.ts\n} else if (signetAgentId && signetAgentId !== \"hermes-agent\" && signetAgentId !== \"default\") {\n```\n\nConsequence: if `~/.agents/agents/hermes-agent/` exists on disk (a plausible layout when Hermes runs as a named agent), the Python plugin will silently adopt that directory as `self._project` and send it as the session-start `project`. The TS connector, however, will not write `SIGNET_AGENT_WORKSPACE` for `hermes-agent`, so the daemon sees a project path that was not explicitly configured. Add `agent_id != \"hermes-agent\"` to the guard to match the TS side.",
      "severity": "warning"
    },
    {
      "file": "packages/connector-hermes-agent/hermes-plugin/client.py",
      "line": 396,
      "body": "**`meta.noHits` is not updated after client-side `score_min` filtering.** After the filter, `result[\"results\"]` may be empty (`kept = []`) while `meta[\"noHits\"]` remains `False` (the daemon's original response had results). `totalReturned` is patched to `len(kept)` correctly, but `noHits` is left stale. Any caller that reads `meta.noHits` to decide whether to show a \"no results\" message will get the wrong signal when score thresholding removes all rows. Fix:\n\n```python\nresult[\"meta\"] = {**meta, \"totalReturned\": len(kept), \"noHits\": len(kept) == 0}\n```",
      "severity": "warning"
    },
    {
      "file": "packages/connector-hermes-agent/src/index.ts",
      "line": 165,
      "body": "**`ensureNamedAgentRegistered` proceeds to POST on any non-OK GET, not just 404.** If the daemon returns 401 (auth failure) or 500, `getResp.ok` is false, the catch block is skipped, and the code falls through to attempt a POST creation. The POST will also fail and generate a warning like "Could not register Signet agent 'X'…" — which misleads the user into thinking the agent doesn't exist, when the real issue is auth or a transient daemon error.\n\nConsider checking `getResp.status === 404` explicitly before falling through, or at minimum gating the POST on status 404:\n\n```typescript\nif (getResp.status !== 404) return; // already exists or unrecoverable error\n```",
      "severity": "warning"
    },
    {
      "file": "packages/core/src/agents.ts",
      "line": 72,
      "body": "**Behavioral breaking change for existing named-agent deployments.** Previously only `SOUL.md` and `IDENTITY.md` were checked in the agent subdirectory; all other files (`AGENTS.md`, `USER.md`, `TOOLS.md`, `HEARTBEAT.md`, `MEMORY.md`, `BOOTSTRAP.md`) always resolved from the workspace root. This PR changes the rule so all files check the agent subdir first.\n\nAny named-agent directory that already contains a file from the newly promoted set (most likely `AGENTS.md` or `MEMORY.md` — e.g., scaffolded by `scaffoldAgent` in an earlier version, or written by a user) will silently switch from reading the workspace-root version to the agent-local version on next daemon restart, with no migration warning.\n\nThis is the core intent of the PR and is documented correctly in ARCHITECTURE.md. Flagging here so reviewers confirm there is no automated migration concern or need for a startup warning when unexpected files are found in agent subdirectories.",
      "severity": "warning"
    }
  ]
}

[stderr]
SessionEnd hook [signet hook session-end -H claude-code] failed: /bin/sh: line 1: signet: command not found

@NicholaiVogel
Copy link
Copy Markdown
Contributor Author

Follow-up setup pass pushed in 7fa4bbc6.

This makes Hermes Agent a real setup harness instead of a hidden non-interactive option:

  • Added Hermes Agent to the interactive signet setup harness checklist.
  • Updated signet setup --harness help text to include hermes-agent and pi.
  • Existing setup migration now carries detected Hermes installs into detectedHarnesses, so the connector is installed during migration.
  • Detection now recognizes a Hermes repo before the Signet plugin is installed by checking for the Hermes plugins/memory tree, while resolveHermesRepoPluginPath() still checks for the installed Signet plugin specifically.
  • Updated CLI docs harness list.
  • Added regression coverage for Hermes setup harness parsing and pre-plugin Hermes repo detection.

Additional validation:

bun test packages/daemon/src/mcp/tools.test.ts packages/connector-hermes-agent/index.test.ts packages/cli/src/commands/memory.test.ts packages/core/src/__tests__/identity.test.ts packages/daemon/src/hooks.test.ts packages/cli/src/features/setup-shared.test.ts
python3 -m py_compile packages/connector-hermes-agent/hermes-plugin/__init__.py packages/connector-hermes-agent/hermes-plugin/client.py
bunx biome check packages/core/src/identity.ts packages/core/src/index.ts packages/core/src/__tests__/identity.test.ts packages/connector-hermes-agent/src/index.ts packages/cli/src/features/setup.ts packages/cli/src/features/setup-migrate.ts packages/cli/src/features/setup-shared.test.ts packages/cli/src/commands/app.ts docs/CLI.md
bun run --filter '@signet/core' typecheck
bun run --filter '@signet/connector-hermes-agent' typecheck
git diff --check

Result: 118 tests pass, py_compile passed, targeted Biome passed, targeted typechecks passed, diff check passed.

Copy link
Copy Markdown
Collaborator

@PR-Reviewer-Ant PR-Reviewer-Ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by pr-reviewer | model: claude-sonnet-4-6 | commit: 7fa4bbc6

Harness output could not be parsed as structured JSON.

You're out of extra usage · resets 10pm (America/Denver)

[stderr]
SessionEnd hook [signet hook session-end -H claude-code] failed: /bin/sh: line 1: signet: command not found

@NicholaiVogel
Copy link
Copy Markdown
Contributor Author

Follow-up review pass pushed in c39fd3c0.

Handled the actionable feedback:

  • Python plugin workspace resolution now excludes both default and hermes-agent, matching the TypeScript connector behavior before looking for ~/.agents/agents/<id>.
  • Client-side score_min filtering now updates both meta.totalReturned and meta.noHits when filtering empties the result set.
  • Install-time named-agent registration now only attempts POST /api/agents after a 404 existence check. Non-404 failures, such as 401 or 500, become warnings and do not trigger a mutation.
  • Added regression coverage for the non-404 registration path.
  • Added bundled-plugin invariant checks around empty JSON bodies, ValueError handling, null score handling, noHits, and the Python hermes-agent workspace guard.
  • Added a scaffold regression test showing stock named-agent scaffolding only creates SOUL.md and IDENTITY.md. The broader agent-local identity override behavior is intentional, but stock scaffolds will not silently create agent-local AGENTS.md, USER.md, or MEMORY.md overrides.

Triage on the other inline comments:

  • Auth headers on the registration GET/POST were already addressed in 5bb4dff5; both requests include Authorization: Bearer $SIGNET_TOKEN when configured.
  • SIGNET_AGENT_WORKSPACE restore was already present in the test afterEach at the reviewed head.
  • Empty successful Python responses and JSON decode failures were already guarded by _read_json_response() and ValueError catches, but I added invariant coverage so that does not regress.
  • First-registration policy is documented in the Hermes plugin README: SIGNET_AGENT_READ_POLICY accepts shared, isolated, or group, with shared as the default. Existing agents are preserved because registration is skipped when the GET succeeds.

Validation rerun:

bun test packages/daemon/src/mcp/tools.test.ts packages/connector-hermes-agent/index.test.ts packages/cli/src/commands/memory.test.ts packages/core/src/__tests__/identity.test.ts packages/daemon/src/hooks.test.ts packages/cli/src/features/setup-shared.test.ts
python3 -m py_compile packages/connector-hermes-agent/hermes-plugin/__init__.py packages/connector-hermes-agent/hermes-plugin/client.py
bunx biome check packages/connector-hermes-agent/src/index.ts packages/connector-hermes-agent/index.test.ts packages/core/src/__tests__/identity.test.ts packages/daemon/src/mcp/tools.ts packages/daemon/src/mcp/tools.test.ts
bun run --filter '@signet/core' typecheck
bun run --filter '@signet/connector-hermes-agent' typecheck
git diff --check

Result: 120 pass, py_compile passed, targeted Biome passed, touched package typechecks passed, and git diff --check passed.

I also attempted the repo-level gates again:

  • bun run lint still fails on the existing repo-wide Biome baseline, mainly package manifest formatting outside this PR.
  • bun run typecheck still fails on existing unrelated @signet/*-pi* package errors around missing @signet/pi-extension-base and connector-base exports.

The PR body has been updated with the current test count and the full-suite caveats.

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.

2 participants