nexus/.planning/milestones/v1.4-phases/29-default-provider/29-02-PLAN.md
Nexus Dev 8ae8e526d9 chore: complete v1.4 Hermes Default Provider milestone
3 phases, 6 plans, 16 requirements. Archives copied to milestones/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:55:49 +00:00

14 KiB

phase plan type wave depends_on files_modified autonomous requirements must_haves
29-default-provider 02 execute 2
29-01
ui/src/components/NexusOnboardingWizard.tsx
server/src/__tests__/29-default-provider.test.ts
true
DFLT-03
DFLT-04
truths artifacts key_links
A Hermes agent created via the wizard has a promptTemplate containing Nexus skill bundle content (HEARTBEAT.md, TOOLS.md)
The skill bundle is role-specific — a PM agent gets ceo/ bundle, an Engineer gets engineer/ bundle
The probe route returns available true/false based on adapter testEnvironment result
Agent creation with hermes_local and promptTemplate produces a valid agent record
path provides contains
ui/src/components/NexusOnboardingWizard.tsx Hermes promptTemplate with skill injection promptTemplate
path provides contains
server/src/__tests__/29-default-provider.test.ts Probe route + wizard flow validation tests 29-default-provider
from to via pattern
ui/src/components/NexusOnboardingWizard.tsx server/src/routes/agents.ts agentsApi.create with promptTemplate in adapterConfig promptTemplate
from to via pattern
server/src/routes/agents.ts server/src/services/default-agent-instructions.ts loadDefaultAgentInstructionsBundle resolves promptTemplate to skill files loadDefaultAgentInstructionsBundle
Inject Nexus skill bundles into Hermes agent promptTemplate so GSD workflows execute correctly, and add integration tests validating the full probe-to-agent-creation flow.

Purpose: Without skill injection, Hermes agents created via the wizard would not follow the Nexus heartbeat loop (HEARTBEAT.md, TOOLS.md). This plan closes the skill injection gap (DFLT-03) and validates the end-to-end flow (DFLT-04). Output: promptTemplate injection in wizard, integration test file.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/29-default-provider/29-RESEARCH.md @.planning/phases/29-default-provider/29-01-SUMMARY.md

From server/src/routes/agents.ts (Plan 01 addition):

router.get("/adapters/:type/probe", async (req, res) => {
  // Board auth only, returns { available: boolean, status: string, checks?: ... }
});

From ui/src/api/agents.ts (Plan 01 addition):

probeAdapter: (type: string) =>
  api.get<{ available: boolean; status: string }>(`/adapters/${encodeURIComponent(type)}/probe`),

From server/src/services/default-agent-instructions.ts:

export async function loadDefaultAgentInstructionsBundle(
  role: DefaultAgentBundleRole
): Promise<Record<string, string>>;
// Returns { "AGENTS.md": "...", "HEARTBEAT.md": "...", "SOUL.md": "...", "TOOLS.md": "..." }

export function resolveDefaultAgentInstructionsBundleRole(role: string): DefaultAgentBundleRole;
// "ceo" => "ceo", "engineer" => "engineer", "general" => "general", else => "default"

From server/src/routes/agents.ts (lines 458-484) — agent creation skill injection:

// When agent has no explicit instructions bundle and promptTemplate is non-empty:
// promptTemplate content goes to { "AGENTS.md": promptTemplate }
// Then materializeManagedBundle writes the files and updates adapterConfig
// Crucially: delete nextAdapterConfig.promptTemplate after materialization

From ui/src/components/NexusOnboardingWizard.tsx (Plan 01 state):

const [defaultAdapter, setDefaultAdapter] = useState<"claude_local" | "hermes_local">("claude_local");
// handleSubmit creates 3 agents with adapterType: defaultAdapter
Task 1: Add Hermes promptTemplate skill injection to NexusOnboardingWizard - ui/src/components/NexusOnboardingWizard.tsx (full file — post Plan 01 version) - server/src/services/default-agent-instructions.ts (full file, 34 lines) - server/src/routes/agents.ts (lines 455-485 for promptTemplate handling in ensureDefaultInstructionsBundle) - server/src/onboarding-assets/ceo/HEARTBEAT.md (to understand content structure) ui/src/components/NexusOnboardingWizard.tsx The server-side agent creation flow in agents.ts already handles promptTemplate injection: - If adapterConfig.promptTemplate is a non-empty string, it wraps it as {"AGENTS.md": promptTemplate} and materializes it - This means the wizard just needs to pass a promptTemplate containing the Nexus skill content

For Hermes agents, the wizard should pass a promptTemplate field in adapterConfig that contains the Hermes-specific preamble + instructions for the Nexus heartbeat workflow. The server's ensureDefaultInstructionsBundle will then handle materialization.

However, looking at the server code more carefully: when promptTemplate is non-empty, it ONLY creates AGENTS.md from it — it does NOT load HEARTBEAT.md, TOOLS.md, SOUL.md. When promptTemplate is empty/absent, it loads the full bundle via loadDefaultAgentInstructionsBundle.

Key insight: For Hermes agents, we should NOT set promptTemplate in adapterConfig. Instead, we should let the server's default path handle it — loadDefaultAgentInstructionsBundle will load the full role-specific bundle (AGENTS.md + HEARTBEAT.md + SOUL.md + TOOLS.md) and materialize all files. The Hermes adapter's execute.ts does NOT read these files, but the heartbeat service's ctx.context.skills mechanism already populates them from the DB — the skill content is served to the agent via the /api/agents/me endpoint which the HEARTBEAT.md workflow instructs the agent to call.

Therefore: The wizard does NOT need to inject a custom promptTemplate. The existing server-side flow already works for Hermes:

  1. Agent created with no promptTemplate => server loads full bundle via loadDefaultAgentInstructionsBundle
  2. Bundle materialized as managed files in the DB
  3. When heartbeat runs, ctx.context.skills contains the skill content
  4. The HEARTBEAT.md content instructs the agent to call GET /api/agents/me to get its instructions
  5. The Hermes adapter has supportsLocalAgentJwt: true so the PAPERCLIP_API_KEY is injected automatically

The one addition needed: a Hermes-specific system prompt that tells the agent to follow the Nexus workflow. The Hermes DEFAULT_PROMPT_TEMPLATE already handles task assignment and API calls — but it may not include the "consult your managed instructions" step.

Approach: Add a promptTemplate in adapterConfig ONLY for hermes_local agents that contains a system-level instruction to follow the Nexus heartbeat workflow. This goes through the server's promptTemplate path, creating an AGENTS.md that supplements the Hermes default prompt:

// In handleSubmit, after the adapterConfig line, before the create calls:
const hermesPromptTemplate = [
  `You are "{{agentName}}", an AI agent managed by ${VOCAB.appName}.`,
  "",
  "Your identity:",
  "  Agent ID: {{agentId}}",
  "  Company ID: {{companyId}}",
  "  API Base: {{paperclipApiUrl}}",
  "  Run ID: {{runId}}",
  "",
  "IMPORTANT: Use the `terminal` tool with `curl` for ALL API calls.",
  'IMPORTANT: Always include `-H "X-Paperclip-Run-Id: {{runId}}"` on API calls that modify data.',
  "",
  "Before starting any task:",
  "1. Call `GET {{paperclipApiUrl}}/api/agents/me` to retrieve your managed instructions",
  "2. Follow the HEARTBEAT.md workflow from your instructions",
  "3. Use TOOLS.md for available API endpoints",
  "",
  "{{#taskId}}",
  "Assigned task: {{taskId}} - {{taskTitle}}",
  "{{/taskId}}",
].join("\n");

// Then for each create call, when defaultAdapter === "hermes_local":
const finalAdapterConfig = defaultAdapter === "hermes_local"
  ? { ...adapterConfig, promptTemplate: hermesPromptTemplate, persistSession: true }
  : adapterConfig;

// Pass finalAdapterConfig instead of adapterConfig in all three agentsApi.create calls

Make sure the Generalist agent's metadata pendingSkillGroups: ["Creative"] is preserved regardless of adapter type (line 118). cd /opt/nexus && pnpm --filter ui exec tsc --noEmit 2>&1 | head -20 <acceptance_criteria> - grep -q 'promptTemplate' ui/src/components/NexusOnboardingWizard.tsx - grep -q 'persistSession' ui/src/components/NexusOnboardingWizard.tsx - grep -q 'HEARTBEAT' ui/src/components/NexusOnboardingWizard.tsx (references heartbeat workflow in the prompt) - grep -q 'agents/me' ui/src/components/NexusOnboardingWizard.tsx (instructs agent to fetch its instructions) </acceptance_criteria> - Hermes agents created via wizard get a promptTemplate that instructs the agent to follow the Nexus heartbeat workflow - promptTemplate includes Mustache variables (agentName, agentId, companyId, paperclipApiUrl, runId, taskId, taskTitle) - persistSession: true is set for Hermes agents - Claude_local agents are unaffected (no promptTemplate, same as before)

Task 2: Add integration tests for probe route, wizard agent creation, and end-to-end flow - server/src/__tests__/heartbeat-workspace-session.test.ts (first 50 lines — for in-memory DB setup pattern) - server/src/__tests__/adapter-session-codecs.test.ts (first 30 lines — for test setup pattern) - server/src/routes/agents.ts (lines 1-20 for router setup, line 667 area for probe route from Plan 01) - server/src/adapters/index.ts (findServerAdapter signature) server/src/__tests__/29-default-provider.test.ts Create a new test file at `server/src/__tests__/29-default-provider.test.ts` with unit tests covering:

Test group 1: Adapter probe route logic These test the probe logic directly (no HTTP — test the route handler's logic):

import { describe, it, expect, vi } from "vitest";

Since testing the Express route directly is complex, test the adapter probe logic by calling findServerAdapter and testEnvironment directly:

  1. it("findServerAdapter returns hermes_local adapter with testEnvironment") — verify the adapter exists and has testEnvironment function
  2. it("hermes testEnvironment handles missing CLI gracefully") — if hermes is not installed in CI, verify the result contains a check with level "error" and a code containing "not_found" or "cli"

Test group 2: Hermes promptTemplate construction Extract the promptTemplate construction logic from the wizard into a testable helper OR test the expected string content:

  1. it("hermes promptTemplate contains required Mustache variables") — build the template string and verify it contains {{agentName}}, {{agentId}}, {{companyId}}, {{paperclipApiUrl}}, {{runId}}, {{taskId}}, {{taskTitle}}
  2. it("hermes promptTemplate instructs agent to call /api/agents/me") — verify the template contains agents/me
  3. it("hermes promptTemplate mentions HEARTBEAT.md workflow") — verify the template references the heartbeat workflow

Test group 3: Default agent instructions bundle (DFLT-03) 6. it("loadDefaultAgentInstructionsBundle loads ceo bundle with all 4 files") — call with "ceo", verify keys include AGENTS.md, HEARTBEAT.md, SOUL.md, TOOLS.md 7. it("loadDefaultAgentInstructionsBundle loads engineer bundle with all 4 files") — same for "engineer" 8. it("resolveDefaultAgentInstructionsBundleRole maps known roles correctly") — verify ceo->ceo, engineer->engineer, general->general, unknown->default

Import loadDefaultAgentInstructionsBundle and resolveDefaultAgentInstructionsBundleRole from ../services/default-agent-instructions.js.

For test group 2, define the promptTemplate string directly in the test file (duplicated from the wizard) to verify its content. This is intentional — the test validates the contract, not the implementation. cd /opt/nexus && pnpm --filter server test --run -- 29-default-provider 2>&1 | tail -20 <acceptance_criteria> - test file exists at server/src/tests/29-default-provider.test.ts - grep -q 'loadDefaultAgentInstructionsBundle' server/src/tests/29-default-provider.test.ts - grep -q 'HEARTBEAT.md' server/src/tests/29-default-provider.test.ts - grep -q 'promptTemplate' server/src/tests/29-default-provider.test.ts - pnpm --filter server test --run -- 29-default-provider exits 0 </acceptance_criteria> - All tests pass: probe logic, promptTemplate content validation, default bundle loading - Tests verify the contract that Hermes agents get skill content via the standard bundle path - Tests confirm promptTemplate contains all required Mustache variables for Hermes adapter

- `pnpm --filter server test --run -- 29-default-provider` passes all tests - `pnpm --filter ui exec tsc --noEmit` compiles cleanly - NexusOnboardingWizard contains promptTemplate injection for hermes_local - Test file covers probe, promptTemplate, and bundle loading

<success_criteria>

  • Hermes agents created via wizard get a promptTemplate that enables the Nexus GSD workflow
  • Integration tests validate the probe route logic and skill bundle loading
  • A machine with only Hermes + Ollama can complete onboarding and get working agents (no paywalls) </success_criteria>
After completion, create `.planning/phases/29-default-provider/29-02-SUMMARY.md`