diff --git a/.planning/phases/27-hermes-adapter/27-RESEARCH.md b/.planning/phases/27-hermes-adapter/27-RESEARCH.md
new file mode 100644
index 00000000..cf03be15
--- /dev/null
+++ b/.planning/phases/27-hermes-adapter/27-RESEARCH.md
@@ -0,0 +1,406 @@
+# Phase 27: Hermes Adapter - Research
+
+**Researched:** 2026-04-01
+**Domain:** Hermes Agent adapter integration into Nexus (hermes-paperclip-adapter v0.2.1)
+**Confidence:** HIGH
+
+## Summary
+
+The `hermes-paperclip-adapter` package is already installed at v0.2.1 in both `server/` and `ui/` (at `server/node_modules/hermes-paperclip-adapter` and `ui/node_modules/hermes-paperclip-adapter`). It is fully implemented and already wired into both the server-side `adapters/registry.ts` and the UI-side `adapters/registry.ts`. The adapter type `hermes_local` is registered in `packages/shared/src/constants.ts`, appears in `NewAgentDialog.tsx`, `NewAgent.tsx`, `AgentDetail.tsx`, and has a `HermesIcon` component and `HermesLocalConfigFields` form.
+
+However, research reveals that Phase 27 has **specific gaps** preventing full HERM-01 through HERM-04 compliance. These are mostly small integration bugs and missing wiring rather than large new features:
+
+1. `hermes_local` is absent from `SESSIONED_LOCAL_ADAPTERS` in `heartbeat.ts` — the stale-run reaping logic will incorrectly treat a detached Hermes process as dead.
+2. `config-fields.tsx` has a bug in create-mode: the Toolsets field reads/writes `extraArgs` (the wrong `CreateConfigValues` field) instead of properly passing toolsets through `buildHermesConfig`.
+3. `AGENT_ADAPTER_TYPES` in `packages/shared/src/constants.ts` has a duplicate `gemini_local` entry — low-severity lint issue.
+4. The `hermes-dual-source.test.ts` test file already exists and all 7 tests pass. A `hermes-session-codec.test.ts` is absent from `adapter-session-codecs.test.ts` — there is no test for the hermes session codec round-trip.
+
+**Primary recommendation:** Fix the four gaps above. The core adapter wiring is complete — this phase is about verification, small bug fixes, and ensuring the full HERM-01 through HERM-04 user flow works end to end.
+
+---
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+All implementation choices are at Claude's discretion — discuss phase was skipped per user setting. Use ROADMAP phase goal, success criteria, and codebase conventions to guide decisions.
+
+### Claude's Discretion
+All implementation choices are at Claude's discretion.
+
+### Deferred Ideas (OUT OF SCOPE)
+None — discuss phase skipped.
+
+
+---
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| HERM-01 | Hermes adapter is installed, enabled, and appears in the "Add Agent" dropdown | Package installed. `hermesLocalAdapter` registered in server registry. `hermes_local` in `ADVANCED_ADAPTER_OPTIONS` array in `NewAgentDialog.tsx`. `HermesIcon` exists. Already complete — confirm with smoke test. |
+| HERM-02 | User can create a Hermes agent with config options (model selection, tool permissions) | `HermesLocalConfigFields` exists in `ui/src/adapters/hermes-local/config-fields.tsx` with model + toolsets + persistSession + timeoutSec fields. Bug in create-mode toolsets binding needs fix. |
+| HERM-03 | Heartbeat execution spawns `hermes chat -q`, processes task, returns result | `execute()` in `hermes-paperclip-adapter/dist/server/execute.js` is fully implemented. `hermesLocalAdapter` wired in server registry. Needs `hermes_local` added to `SESSIONED_LOCAL_ADAPTERS`. |
+| HERM-04 | Session persistence works across heartbeats via `--resume` flag | `sessionCodec` in adapter handles `sessionId` serialize/deserialize. `execute()` reads `ctx.runtime?.sessionParams?.sessionId` and passes `--resume`. Session saved in `executionResult.sessionParams`. No codec test exists yet. |
+
+
+---
+
+## Standard Stack
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| hermes-paperclip-adapter | 0.2.1 | Bridges Nexus heartbeat system to `hermes chat -q` CLI | Already installed in both server and UI; fully functional |
+| @paperclipai/adapter-utils | 2026.325.0 | Shared types/utilities for all adapters | Monorepo standard; defines `AdapterExecutionContext`, `AdapterSessionCodec`, etc. |
+
+### Supporting
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| vitest | 3.2.4 | Test framework | All new unit tests follow server test pattern |
+
+**Version verification:** `hermes-paperclip-adapter@0.2.1` confirmed via `node_modules/.pnpm/hermes-paperclip-adapter@0.2.1/` directory and `package.json`. `^0.2.0` is in both `server/package.json` and `ui/package.json`.
+
+**No installation needed** — package is already installed. Run `pnpm install` only if lock file needs updating.
+
+---
+
+## Architecture Patterns
+
+### Existing Adapter Registration Pattern
+
+Every adapter is registered in two registries:
+
+**Server registry** (`server/src/adapters/registry.ts`):
+```typescript
+// Source: server/src/adapters/registry.ts lines 71–189
+const hermesLocalAdapter: ServerAdapterModule = {
+ type: "hermes_local",
+ execute: hermesExecute,
+ testEnvironment: hermesTestEnvironment,
+ sessionCodec: hermesSessionCodec,
+ listSkills: hermesListSkills,
+ syncSkills: hermesSyncSkills,
+ models: hermesModels,
+ supportsLocalAgentJwt: true,
+ agentConfigurationDoc: hermesAgentConfigurationDoc,
+ detectModel: () => detectModelFromHermes(),
+};
+// — Already complete. No changes needed.
+```
+
+**UI registry** (`ui/src/adapters/registry.ts`):
+```typescript
+// Source: ui/src/adapters/registry.ts
+import { hermesLocalUIAdapter } from "./hermes-local";
+// Already included in uiAdapters array. No changes needed.
+```
+
+**UI adapter module** (`ui/src/adapters/hermes-local/index.ts`):
+```typescript
+export const hermesLocalUIAdapter: UIAdapterModule = {
+ type: "hermes_local",
+ label: "Hermes Agent",
+ parseStdoutLine: parseHermesStdoutLine, // from hermes-paperclip-adapter/ui
+ ConfigFields: HermesLocalConfigFields, // local component
+ buildAdapterConfig: buildHermesConfig, // from hermes-paperclip-adapter/ui
+};
+```
+
+### Heartbeat Execution Flow
+
+```
+Nexus heartbeat scheduler
+ → heartbeat.ts: getServerAdapter("hermes_local")
+ → registry.ts: hermesLocalAdapter.execute(ctx)
+ → hermes-paperclip-adapter/server/execute.js
+ → buildPrompt(ctx, config) → {{variable}} template rendering
+ → args = ["chat", "-q", prompt, "-Q"] → hermes chat -q "..." -Q
+ → if (prevSessionId) args.push("--resume", prevSessionId)
+ → runChildProcess(runId, "hermes", args, ...)
+ → parseHermesOutput(stdout, stderr) → extract sessionId, response, usage, cost
+ → return AdapterExecutionResult with sessionParams: { sessionId }
+ → heartbeat.ts: saves sessionParams to agentTaskSessions
+ → next run: ctx.runtime.sessionParams.sessionId = last sessionId
+```
+
+### Session Persistence Flow (HERM-04)
+```
+Run N:
+ ctx.runtime.sessionParams = null (first run)
+ args = ["chat", "-q", prompt, "-Q"]
+ stdout contains: "session_id: hermes-abc123"
+ result.sessionParams = { sessionId: "hermes-abc123" }
+ → saved to DB via agentTaskSessions
+
+Run N+1:
+ ctx.runtime.sessionParams = { sessionId: "hermes-abc123" }
+ args = ["chat", "-q", prompt, "-Q", "--resume", "hermes-abc123"]
+ Hermes resumes the saved conversation context
+```
+
+### SESSIONED_LOCAL_ADAPTERS Pattern
+
+Location: `server/src/services/heartbeat.ts` lines 72–79.
+
+This set controls whether a run's orphaned PID is checked for liveness. All child-process adapters should be in this set. `hermes_local` is currently missing:
+
+```typescript
+// Current (MISSING hermes_local):
+const SESSIONED_LOCAL_ADAPTERS = new Set([
+ "claude_local", "codex_local", "cursor",
+ "gemini_local", "opencode_local", "pi_local",
+]);
+
+// Fix (ADD hermes_local):
+const SESSIONED_LOCAL_ADAPTERS = new Set([
+ "claude_local", "codex_local", "cursor",
+ "gemini_local", "opencode_local", "pi_local",
+ "hermes_local", // ← add this
+]);
+```
+
+### Config-Fields Create-Mode Bug
+
+Location: `ui/src/adapters/hermes-local/config-fields.tsx` lines 75–88.
+
+In create mode (`isCreate === true`), the Toolsets field reads/writes `values!.extraArgs` and `set!({ extraArgs: v })`. This incorrectly stores toolsets as CLI extra-args. The `buildHermesConfig` function (called on form submit) reads `v.extraArgs` and splits it into an array of additional CLI flags — not `-t toolsets`.
+
+The fix in create mode should set `values!.model` (or a custom field in `extraArgs`) or we need to recognize `CreateConfigValues` has no `toolsets` field. Looking at `buildHermesConfig` in the adapter, toolsets are **not read from any `CreateConfigValues` field** — they are only set when editing via `adapterConfig.toolsets` directly. The field binding in create mode is therefore best left mapping to `extraArgs` for now (it is the closest available field), or we can simply note this field only takes effect on edit, not create. The planner should evaluate whether the toolsets create-mode path matters for HERM-02, or whether we can ship without it (toolsets default to "all" if unset, which is fine).
+
+### Anti-Patterns to Avoid
+- **Don't import from `hermes-paperclip-adapter/server` in UI code** — server-only exports use Node APIs that don't work in the browser. The UI only imports from `hermes-paperclip-adapter/ui`.
+- **Don't add toolset UI to `CreateConfigValues`** — the shared type from `@paperclipai/adapter-utils` is upstream; use `extraArgs` or defer toolset selection to the edit screen.
+- **Don't bypass the session codec** — always read/write session state through `sessionCodec.serialize/deserialize`; never store raw strings.
+
+---
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| CLI spawning | Custom child_process wrapper | `runChildProcess` from `@paperclipai/adapter-utils/server-utils` | Handles PID tracking, timeout, SIGTERM/SIGKILL, log streaming |
+| Hermes stdout parsing | Custom line-by-line parser | `parseHermesStdoutLine` from `hermes-paperclip-adapter/ui` | Already handles all Hermes output patterns (tool cards, quiet-mode, session_id, etc.) |
+| Config building | Custom `adapterConfig` builder | `buildHermesConfig` from `hermes-paperclip-adapter/ui` | Handles model, extraArgs, timeout, persistSession defaults |
+| Environment testing | Custom `hermes --version` check | `testEnvironment` from `hermes-paperclip-adapter/server` | Checks CLI, Python version, API keys, provider consistency |
+| Model detection | Read `~/.hermes/config.yaml` manually | `detectModel` from `hermes-paperclip-adapter/server` | Already handles YAML parsing and fallbacks |
+
+**Key insight:** `hermes-paperclip-adapter` is a complete, published package implementing every adapter interface. The plan should fix wiring gaps, not reimplement adapter logic.
+
+---
+
+## Common Pitfalls
+
+### Pitfall 1: hermes_local Missing from SESSIONED_LOCAL_ADAPTERS
+**What goes wrong:** When a Nexus server restarts mid-heartbeat, the orphaned Hermes child process is incorrectly reaped (marked as failed) because `isTrackedLocalChildProcessAdapter("hermes_local")` returns `false`. The liveness check never fires.
+**Why it happens:** `SESSIONED_LOCAL_ADAPTERS` was not updated when `hermes_local` was added to the registry.
+**How to avoid:** Add `"hermes_local"` to the set in `heartbeat.ts`.
+**Warning signs:** Runs showing as "failed" immediately after server restart even though `hermes` process is still running.
+
+### Pitfall 2: Create-Mode Toolsets Binding Uses Wrong Field
+**What goes wrong:** In `HermesLocalConfigFields` on the create form, the Toolsets field maps to `values!.extraArgs`, not a dedicated toolsets key. `buildHermesConfig` processes `extraArgs` as a space-separated list of raw CLI flags. Entering "terminal,file,web" in create mode would pass `terminal,file,web` as a CLI arg, not as `-t terminal,file,web`.
+**Why it happens:** `CreateConfigValues` has no `toolsets` field — it's an upstream type.
+**How to avoid:** Either (a) hide toolsets from create mode (toolsets default to "all" which is fine), or (b) document that toolsets are only configurable post-creation. Do not add a `toolsets` field to `CreateConfigValues`.
+**Warning signs:** Agent created with toolsets specified but running with all toolsets enabled.
+
+### Pitfall 3: hermes_local Has a Duplicate in AGENT_ADAPTER_TYPES
+**What goes wrong:** `packages/shared/src/constants.ts` has `"gemini_local"` twice in `AGENT_ADAPTER_TYPES`. TypeScript's `as const` union deduplicates, so this is silent — but it's a maintenance hazard.
+**Why it happens:** Stale edit from when `hermes_local` was added.
+**How to avoid:** Remove the duplicate `"gemini_local"` entry.
+**Warning signs:** ESLint or tsc --noEmit showing warnings about duplicate array values.
+
+### Pitfall 4: Session Codec Not Tested
+**What goes wrong:** The hermes `sessionCodec` handles both `sessionId` and `session_id` key variants (for legacy output). Without a test, a future refactor could silently break session persistence.
+**Why it happens:** `adapter-session-codecs.test.ts` tests all other adapters but not hermes.
+**How to avoid:** Add a hermes session codec test to `adapter-session-codecs.test.ts`.
+**Warning signs:** Session ID not persisting across heartbeats; run N+1 starts fresh instead of resuming.
+
+### Pitfall 5: --yolo Flag Required for Non-TTY Execution
+**What goes wrong:** Without `--yolo`, Hermes prompts for confirmation before running "dangerous" commands (curl, python3, etc.). Since Nexus runs Hermes as a non-interactive subprocess, these prompts hang indefinitely.
+**Why it happens:** Hermes's safety system is designed for attended interactive use.
+**How to avoid:** The adapter already appends `--yolo` unconditionally. Do not remove this flag.
+**Warning signs:** Heartbeat run never completes; stdout contains "Awaiting confirmation" text.
+
+---
+
+## Code Examples
+
+### Session Codec Round-Trip (for test)
+```typescript
+// Source: hermes-paperclip-adapter/dist/server/index.js
+import { sessionCodec } from "hermes-paperclip-adapter/server";
+
+// Hermes -Q mode outputs: "session_id: hermes-abc123\n"
+// execute() stores resultJson.session_id → executionResult.sessionParams = { sessionId: "hermes-abc123" }
+const params = sessionCodec.deserialize({ sessionId: "hermes-abc123" });
+// params = { sessionId: "hermes-abc123" }
+
+const serialized = sessionCodec.serialize(params);
+// serialized = { sessionId: "hermes-abc123" }
+
+sessionCodec.getDisplayId(serialized);
+// "hermes-abc123"
+
+// Also handles legacy snake_case output:
+const legacy = sessionCodec.deserialize({ session_id: "hermes-legacy-456" });
+// legacy = { sessionId: "hermes-legacy-456" }
+```
+
+### Execute Context Shape
+```typescript
+// Source: hermes-paperclip-adapter/dist/server/execute.js
+const ctx: AdapterExecutionContext = {
+ runId: "run-uuid",
+ agent: {
+ id: "agent-uuid",
+ name: "Hermes Engineer",
+ companyId: "company-uuid",
+ adapterConfig: {
+ model: "anthropic/claude-sonnet-4", // optional
+ toolsets: "terminal,file,web", // optional
+ persistSession: true, // default: true
+ timeoutSec: 300, // default: 300
+ },
+ },
+ runtime: {
+ sessionParams: { sessionId: "hermes-abc123" }, // null on first run
+ },
+ config: {
+ taskId: "TRA-42",
+ taskTitle: "Implement feature X",
+ taskBody: "...",
+ },
+ onLog: async (stream, chunk) => { /* stream stdout/stderr to UI */ },
+};
+```
+
+### SESSIONED_LOCAL_ADAPTERS Fix
+```typescript
+// Source: server/src/services/heartbeat.ts line 72
+const SESSIONED_LOCAL_ADAPTERS = new Set([
+ "claude_local",
+ "codex_local",
+ "cursor",
+ "gemini_local",
+ "opencode_local",
+ "pi_local",
+ "hermes_local", // ← add
+]);
+```
+
+---
+
+## Runtime State Inventory
+
+> This phase does not involve rename/refactor — no runtime state migration required.
+
+**Stored data:** None — no Hermes-specific records to migrate.
+**Live service config:** None — Hermes config lives in `~/.hermes/config.yaml` on the user's machine, not in Nexus state.
+**OS-registered state:** None.
+**Secrets/env vars:** None — API keys are in `~/.hermes/.env`, not Nexus-managed.
+**Build artifacts:** None.
+
+---
+
+## Environment Availability
+
+| Dependency | Required By | Available | Version | Fallback |
+|------------|------------|-----------|---------|----------|
+| hermes-paperclip-adapter npm pkg | HERM-01 through HERM-04 | Yes | 0.2.1 (server + UI node_modules) | — |
+| hermes CLI binary | HERM-03 (runtime execution) | Unknown — user machine | — | testEnvironment returns "fail" gracefully; agent shows setup error in UI |
+| Python 3.10+ | HERM-03 (Hermes runtime) | Unknown — user machine | — | Same as above |
+
+**Missing dependencies with no fallback:** None from Nexus's perspective. The `testEnvironment` function handles missing Hermes CLI gracefully with a `status: "fail"` result and a human-readable hint.
+
+**Missing dependencies with fallback:** Hermes CLI not installed — user sees "fail" status in agent config panel with install instructions. This is expected behavior, not a blocking issue for the phase.
+
+---
+
+## Validation Architecture
+
+### Test Framework
+| Property | Value |
+|----------|-------|
+| Framework | vitest 3.2.4 |
+| Config file | `server/vitest.config.ts` |
+| Quick run command | `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts` |
+| Full suite command | `pnpm test:run` |
+
+### Phase Requirements → Test Map
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| HERM-01 | `hermes_local` appears in UI adapter registry and NewAgentDialog | unit (registry check) | `pnpm --filter server exec vitest run src/__tests__/adapter-skill-config.test.ts` | Yes |
+| HERM-02 | `HermesLocalConfigFields` renders model + toolsets fields | unit/smoke | `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts` | Yes (skill tests) |
+| HERM-03 | Heartbeat execution spawns `hermes chat -q` and returns result | integration | `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts` | Partial |
+| HERM-04 | Session persistence via `--resume` across heartbeats | unit (session codec) | `pnpm --filter server exec vitest run src/__tests__/adapter-session-codecs.test.ts` | Needs hermes case added |
+
+### Sampling Rate
+- **Per task commit:** `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts src/__tests__/adapter-session-codecs.test.ts`
+- **Per wave merge:** `pnpm test:run`
+- **Phase gate:** Full suite green before `/gsd:verify-work`
+
+### Wave 0 Gaps
+- [ ] `server/src/__tests__/adapter-session-codecs.test.ts` — add `hermes sessionCodec` describe block covering serialize, deserialize, getDisplayId, and legacy `session_id` key variant
+
+*(All other test infrastructure is already present)*
+
+---
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| Custom execute function per adapter | Use `runChildProcess` from `@paperclipai/adapter-utils/server-utils` | adapter-utils 2026.x | Standardized PID tracking, timeout, log streaming across all adapters |
+| Adapter inline in server code | Published external package (`hermes-paperclip-adapter`) | hermes-paperclip-adapter 0.2.x | Decoupled from Nexus upstream; versioned independently |
+
+**Deprecated/outdated:**
+- `addListener` for media queries: not applicable here.
+
+---
+
+## Open Questions
+
+1. **Create-mode toolsets field**
+ - What we know: `CreateConfigValues` has no `toolsets` field; the current create-form code incorrectly uses `extraArgs`.
+ - What's unclear: Whether HERM-02's "tool permissions" requirement specifically calls for toolsets to be configurable at agent creation time (vs. post-creation in the edit form).
+ - Recommendation: Ship with toolsets available only in edit mode (post-creation). Default is "all toolsets" which is sensible. Document in agent config panel.
+
+2. **`detectModel` capability in `hermesLocalAdapter`**
+ - What we know: `hermesLocalAdapter` has a `detectModel` property calling `detectModelFromHermes()`, which reads `~/.hermes/config.yaml`. No other adapter currently uses `detectModel`.
+ - What's unclear: Whether the UI currently calls `detectModel` to pre-populate the model field during agent creation.
+ - Recommendation: Check if `agentsApi` exposes a `detectModel` endpoint; if not, this feature silently does nothing in the UI and can be left as-is.
+
+---
+
+## Sources
+
+### Primary (HIGH confidence)
+- `server/src/adapters/registry.ts` — `hermesLocalAdapter` definition, verified complete
+- `ui/src/adapters/hermes-local/index.ts` and `config-fields.tsx` — UI adapter wiring, verified
+- `hermes-paperclip-adapter/dist/server/execute.js` — full execute() implementation, read in full
+- `hermes-paperclip-adapter/dist/server/index.js` — sessionCodec, read in full
+- `hermes-paperclip-adapter/README.md` — canonical usage docs, read in full
+- `server/src/services/heartbeat.ts` (SESSIONED_LOCAL_ADAPTERS, lines 72–79) — gap confirmed
+- `packages/shared/src/constants.ts` — duplicate gemini_local confirmed
+- `server/src/__tests__/hermes-dual-source.test.ts` — 7/7 tests passing, verified by run
+
+### Secondary (MEDIUM confidence)
+- `hermes-paperclip-adapter/dist/ui/build-config.js` — `buildHermesConfig` implementation confirming extraArgs handling
+- `hermes-paperclip-adapter/dist/ui/parse-stdout.js` — full stdout parser implementation
+
+### Tertiary (LOW confidence)
+- None.
+
+---
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — packages installed, imports verified, tests passing
+- Architecture: HIGH — full implementation read from source
+- Pitfalls: HIGH — gaps confirmed by reading actual source files
+- Session persistence: HIGH — codec and execute() both read in full
+
+**Research date:** 2026-04-01
+**Valid until:** 2026-05-01 (30 days — adapter package is stable)