From fb4a2066c319df3b7953a90a2079bf8cd7979b8a Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Thu, 2 Apr 2026 17:28:15 +0000 Subject: [PATCH] =?UTF-8?q?docs(29):=20create=20phase=20plan=20=E2=80=94?= =?UTF-8?q?=20adapter=20probe,=20onboarding=20fallback,=20skill=20injectio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/ROADMAP.md | 7 +- .../phases/29-default-provider/29-01-PLAN.md | 287 ++++++++++++++++++ .../phases/29-default-provider/29-02-PLAN.md | 257 ++++++++++++++++ 3 files changed, 549 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/29-default-provider/29-01-PLAN.md create mode 100644 .planning/phases/29-default-provider/29-02-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index ec550440..53431049 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -59,7 +59,10 @@ Plans: 2. Creating a PM agent or Engineer agent from the predefined templates and assigning it to a Hermes runtime produces a working agent — heartbeats execute and return meaningful results 3. Running a full GSD workflow task (create issue, assign to Engineer, execute heartbeat, mark complete) with Hermes as the sole runtime completes without errors 4. A machine with only Nexus, Hermes, and Ollama installed (no Anthropic/OpenAI key) can complete the entire onboarding-to-first-task flow with no paywalls or missing-key errors -**Plans**: TBD +**Plans:** 2 plans +Plans: +- [ ] 29-01-PLAN.md — Adapter probe route, onboarding wizard Hermes fallback, adapter-neutral templates +- [ ] 29-02-PLAN.md — Hermes skill injection via promptTemplate, integration tests --- @@ -94,4 +97,4 @@ All 16 v1 requirements are mapped to exactly one phase. No orphans. |-------|-----------|----------------|--------|-----------| | 27. Hermes Adapter | v1.4 | 1/1 | Complete | 2026-04-02 | | 28. Ollama Integration & Agent Surface | v1.4 | 3/3 | Complete | 2026-04-02 | -| 29. Default Provider & End-to-End | v1.4 | 0/? | Not started | - | +| 29. Default Provider & End-to-End | v1.4 | 0/2 | In progress | - | diff --git a/.planning/phases/29-default-provider/29-01-PLAN.md b/.planning/phases/29-default-provider/29-01-PLAN.md new file mode 100644 index 00000000..1dd37bf7 --- /dev/null +++ b/.planning/phases/29-default-provider/29-01-PLAN.md @@ -0,0 +1,287 @@ +--- +phase: 29-default-provider +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - server/src/routes/agents.ts + - ui/src/api/agents.ts + - ui/src/components/NexusOnboardingWizard.tsx + - ui/src/components/NewAgentDialog.tsx +autonomous: true +requirements: [DFLT-01, DFLT-02] + +must_haves: + truths: + - "GET /api/adapters/hermes_local/probe returns {available: true} when Hermes CLI is installed" + - "GET /api/adapters/hermes_local/probe returns {available: false} when Hermes CLI is absent" + - "NexusOnboardingWizard creates agents with adapterType hermes_local when Hermes is available and no cloud provider is detected" + - "NexusOnboardingWizard makes directory input optional when hermes_local is selected" + - "AGENT_TEMPLATES in NewAgentDialog no longer hardcode adapterType" + artifacts: + - path: "server/src/routes/agents.ts" + provides: "Board-auth adapter probe route" + contains: "adapters/:type/probe" + - path: "ui/src/api/agents.ts" + provides: "probeAdapter client method" + contains: "probeAdapter" + - path: "ui/src/components/NexusOnboardingWizard.tsx" + provides: "Hermes fallback in onboarding wizard" + contains: "hermes_local" + - path: "ui/src/components/NewAgentDialog.tsx" + provides: "Adapter-neutral templates" + key_links: + - from: "ui/src/components/NexusOnboardingWizard.tsx" + to: "/api/adapters/hermes_local/probe" + via: "agentsApi.probeAdapter or fetch" + pattern: "probe.*hermes_local" + - from: "ui/src/components/NewAgentDialog.tsx" + to: "/agents/new" + via: "navigate with role only, no adapterType" + pattern: "role=.*name=" +--- + + +Add adapter detection probe route and update NexusOnboardingWizard + NewAgentDialog to fall back to Hermes when no cloud provider is available. + +Purpose: Users with only Hermes + Ollama installed get a working onboarding flow without needing any paid API keys. +Output: Probe route, wizard Hermes fallback, adapter-neutral templates. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/29-default-provider/29-RESEARCH.md + + + + +From server/src/routes/agents.ts (line ~667): +```typescript +// Existing adapter routes pattern — company-scoped: +router.get("/companies/:companyId/adapters/:type/models", async (req, res) => { ... }); +router.get("/companies/:companyId/adapters/:type/detect-model", async (req, res) => { ... }); +router.post("/companies/:companyId/adapters/:type/test-environment", ...); + +// findServerAdapter import: +import { findServerAdapter, listAdapterModels, detectAdapterModel } from "../adapters/index.js"; +``` + +From ui/src/api/agents.ts (line ~168): +```typescript +// Existing API client pattern: +detectModel: (companyId: string, type: string) => + api.get( + `/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/detect-model`, + ), +testEnvironment: (companyId: string, type: string, data: {...}) => + api.post(...), +``` + +From ui/src/components/NexusOnboardingWizard.tsx: +```typescript +// Lines 94-119: Three agentsApi.create calls with hardcoded adapterType: "claude_local" +// Line 80: const adapterConfig = { cwd: rootDir.trim() }; +// Line 41: effectiveOnboardingOpen controls wizard visibility +``` + +From ui/src/components/NewAgentDialog.tsx (lines 97-99): +```typescript +const AGENT_TEMPLATES = [ + { id: "pm", label: "Project Manager", role: "pm" as const, adapterType: "claude_local" as const }, + { id: "engineer", label: "Engineer", role: "engineer" as const, adapterType: "claude_local" as const }, +]; +// Line 140: navigate with adapterType in URL +``` + + + + + + + Task 1: Add board-auth adapter probe route and frontend client + + - server/src/routes/agents.ts (lines 658-718 for existing adapter route patterns, line 186 for board auth pattern) + - ui/src/api/agents.ts (lines 168-180 for existing adapter API methods) + - server/src/adapters/index.ts (findServerAdapter export) + + server/src/routes/agents.ts, ui/src/api/agents.ts + +1. In server/src/routes/agents.ts, add a new route BEFORE the existing company-scoped adapter routes (before line 667). This route has NO companyId — it uses board auth only: + +```typescript +router.get("/adapters/:type/probe", async (req, res) => { + if (req.actor.type !== "board") { + res.status(403).json({ error: "Board authentication required" }); + return; + } + const type = req.params.type as string; + const adapter = findServerAdapter(type); + if (!adapter?.testEnvironment) { + res.json({ available: false, status: "unknown" }); + return; + } + try { + const result = await adapter.testEnvironment({ + companyId: "", + adapterType: type, + config: {}, + }); + const hasCliNotFound = result.checks.some( + (c: { level: string; code?: string }) => + c.level === "error" && (c.code?.includes("not_found") || c.code?.includes("cli")) + ); + res.json({ available: !hasCliNotFound, status: result.status, checks: result.checks }); + } catch { + res.json({ available: false, status: "error" }); + } +}); +``` + +2. In ui/src/api/agents.ts, add a `probeAdapter` method to the agentsApi object. It does NOT require companyId: + +```typescript +probeAdapter: (type: string) => + api.get<{ available: boolean; status: string }>(`/adapters/${encodeURIComponent(type)}/probe`), +``` + + + cd /opt/nexus && pnpm --filter server exec tsc --noEmit 2>&1 | head -20 && pnpm --filter ui exec tsc --noEmit 2>&1 | head -20 + + + - grep -q 'adapters/:type/probe' server/src/routes/agents.ts + - grep -q 'probeAdapter' ui/src/api/agents.ts + - grep -q 'req.actor.type !== "board"' server/src/routes/agents.ts (within the probe handler context) + + Board-auth probe route returns {available, status} without companyId. Frontend client can call agentsApi.probeAdapter("hermes_local"). + + + + Task 2: Update NexusOnboardingWizard to fall back to Hermes and make NewAgentDialog templates adapter-neutral + + - ui/src/components/NexusOnboardingWizard.tsx (full file — 228 lines) + - ui/src/components/NewAgentDialog.tsx (lines 90-145 for templates and handleTemplateSelect) + + ui/src/components/NexusOnboardingWizard.tsx, ui/src/components/NewAgentDialog.tsx + +**NexusOnboardingWizard.tsx changes (DFLT-01):** + +1. Add import for agentsApi (already imported on line 14: `import { agentsApi } from "../api/agents"`). + +2. Add state for adapter detection after existing state declarations (around line 50): +```typescript +const [defaultAdapter, setDefaultAdapter] = useState<"claude_local" | "hermes_local">("claude_local"); +const [probing, setProbing] = useState(false); +``` + +3. Add a useEffect that probes for Hermes when the wizard opens (after the existing useEffects, around line 60): +```typescript +useEffect(() => { + if (!effectiveOnboardingOpen) return; + setProbing(true); + agentsApi.probeAdapter("hermes_local") + .then((data) => { + if (data.available) setDefaultAdapter("hermes_local"); + }) + .catch(() => {}) // graceful — keep claude_local + .finally(() => setProbing(false)); +}, [effectiveOnboardingOpen]); +``` + +4. In handleSubmit (line 67), replace the hardcoded adapterConfig and adapterType: +- Change `const adapterConfig = { cwd: rootDir.trim() };` to: +```typescript +const adapterConfig = defaultAdapter === "hermes_local" + ? (rootDir.trim() ? { cwd: rootDir.trim() } : {}) + : { cwd: rootDir.trim() }; +``` +- Replace all three `adapterType: "claude_local"` (lines 97, 106, 115) with `adapterType: defaultAdapter`. + +5. Update the form validation: the submit button disable condition (line 192) currently requires `!rootDir.trim()`. Change to: +```typescript +disabled={loading || probing || (defaultAdapter === "claude_local" && !rootDir.trim())} +``` + +6. Update the directory input label and required state. When hermes_local is selected, the directory is optional. Change the label (line 168): +```typescript + +``` + +7. Update the description text (line 157) to mention local AI when hermes is detected: +```typescript +

+ {defaultAdapter === "hermes_local" + ? `${VOCAB.appName} will set up a local AI workspace with a ${VOCAB.ceo.toLowerCase()}, engineer, and generalist — no API key needed.` + : `Choose a project root directory. ${VOCAB.appName} will set up a ${VOCAB.ceo.toLowerCase()}, engineer, and generalist to start working.`} +

+``` + +**NewAgentDialog.tsx changes (DFLT-02):** + +1. Remove `adapterType` from AGENT_TEMPLATES. Change lines 97-99: +```typescript +const AGENT_TEMPLATES = [ + { id: "pm", label: "Project Manager", role: "pm" as const }, + { id: "engineer", label: "Engineer", role: "engineer" as const }, +]; +``` + +2. Update handleTemplateSelect (around line 136). Remove adapterType from URL: +```typescript +function handleTemplateSelect(template: typeof AGENT_TEMPLATES[number]) { + closeNewAgent(); + setShowAdvancedCards(false); + navigate( + `/agents/new?role=${encodeURIComponent(template.role)}&name=${encodeURIComponent(template.label)}`, + ); +} +``` + +This makes templates adapter-neutral. The /agents/new form will use its own default adapter logic. +
+ + cd /opt/nexus && pnpm --filter ui exec tsc --noEmit 2>&1 | head -20 + + + - grep -q 'hermes_local' ui/src/components/NexusOnboardingWizard.tsx + - grep -q 'probeAdapter' ui/src/components/NexusOnboardingWizard.tsx + - grep -q 'defaultAdapter' ui/src/components/NexusOnboardingWizard.tsx + - grep -v 'adapterType' ui/src/components/NewAgentDialog.tsx | grep -q 'AGENT_TEMPLATES' (templates no longer have adapterType field) + - ! grep -q 'claude_local' ui/src/components/NewAgentDialog.tsx (no hardcoded claude_local in templates) + + + - NexusOnboardingWizard probes for Hermes on open and uses hermes_local when available + - Directory input is optional when hermes_local is selected + - All three agents created with the detected adapter type + - NewAgentDialog templates no longer hardcode adapterType — navigate with role+name only + +
+ +
+ + +- TypeScript compiles for both server and ui packages +- NexusOnboardingWizard contains "hermes_local" and "probeAdapter" +- NewAgentDialog AGENT_TEMPLATES have no adapterType field +- Probe route exists in agents.ts with board auth guard + + + +- Probe route responds to GET /api/adapters/:type/probe with board auth +- Wizard auto-detects Hermes and pre-selects hermes_local when available +- Templates navigate without hardcoded adapter type +- TypeScript compiles cleanly + + + +After completion, create `.planning/phases/29-default-provider/29-01-SUMMARY.md` + diff --git a/.planning/phases/29-default-provider/29-02-PLAN.md b/.planning/phases/29-default-provider/29-02-PLAN.md new file mode 100644 index 00000000..32cd9ac1 --- /dev/null +++ b/.planning/phases/29-default-provider/29-02-PLAN.md @@ -0,0 +1,257 @@ +--- +phase: 29-default-provider +plan: 02 +type: execute +wave: 2 +depends_on: ["29-01"] +files_modified: + - ui/src/components/NexusOnboardingWizard.tsx + - server/src/__tests__/29-default-provider.test.ts +autonomous: true +requirements: [DFLT-03, DFLT-04] + +must_haves: + truths: + - "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" + artifacts: + - path: "ui/src/components/NexusOnboardingWizard.tsx" + provides: "Hermes promptTemplate with skill injection" + contains: "promptTemplate" + - path: "server/src/__tests__/29-default-provider.test.ts" + provides: "Probe route + wizard flow validation tests" + contains: "29-default-provider" + key_links: + - from: "ui/src/components/NexusOnboardingWizard.tsx" + to: "server/src/routes/agents.ts" + via: "agentsApi.create with promptTemplate in adapterConfig" + pattern: "promptTemplate" + - from: "server/src/routes/agents.ts" + to: "server/src/services/default-agent-instructions.ts" + via: "loadDefaultAgentInstructionsBundle resolves promptTemplate to skill files" + pattern: "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. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.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): +```typescript +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): +```typescript +probeAdapter: (type: string) => + api.get<{ available: boolean; status: string }>(`/adapters/${encodeURIComponent(type)}/probe`), +``` + +From server/src/services/default-agent-instructions.ts: +```typescript +export async function loadDefaultAgentInstructionsBundle( + role: DefaultAgentBundleRole +): Promise>; +// 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: +```typescript +// 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): +```typescript +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: + +```typescript +// 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 + + + - 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) + + + - 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): + +```typescript +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: + +3. `it("hermes promptTemplate contains required Mustache variables")` — build the template string and verify it contains `{{agentName}}`, `{{agentId}}`, `{{companyId}}`, `{{paperclipApiUrl}}`, `{{runId}}`, `{{taskId}}`, `{{taskTitle}}` +4. `it("hermes promptTemplate instructs agent to call /api/agents/me")` — verify the template contains `agents/me` +5. `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 + + + - 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 + + + - 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 + + + +- 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) + + + +After completion, create `.planning/phases/29-default-provider/29-02-SUMMARY.md` +