hermes-agent/docs/design/profile-builder.md
Teknium d986bb0c6d
feat(dashboard): full-featured profile builder (model + skills + MCPs) (#39084)
* feat(profiles): extend create endpoint for full profile-builder (model + MCPs + skills)

Backend foundation for the dashboard profile builder. Extends POST /api/profiles
to accept, in one call, everything a profile needs beyond name/clone:

- mcp_servers[]  -> written into the new profile's config.yaml
- keep_skills[]  -> replace-semantics: disable every seeded skill not kept
- hub_skills[]   -> async install via 'hermes -p <name> skills install <id>'

All applied best-effort AFTER the profile dir exists, so a hiccup in any one
never 500s the create. Model/MCP/keep-skills writes are profile-scoped via the
HERMES_HOME context override (same mechanism as the existing _write_profile_model).
Hub installs go through a subprocess scoped with -p because skills_hub.SKILLS_DIR
is import-time-bound and the runtime override can't redirect it.

Adds two helpers (_write_profile_mcp_servers, _disable_unselected_skills) and a
TestClient test asserting all four paths land in the NEW profile's config and
the hub spawn is scoped to it. Design doc at docs/design/profile-builder.md.

* feat(dashboard): full-featured profile builder page

Adds a dedicated /profiles/new builder that composes everything a profile
needs into one stepped create flow, reusing the existing Models/Skills/MCP
data paths instead of duplicating them:

- Identity   name + description
- Model      provider+model picker (api.getModelOptions)
- Skills     keep-which-built-in/optional (replace semantics, default = full
             bundle) + skills-hub search/add (api.getSkills, searchSkillsHub)
- MCPs       add HTTP/stdio servers inline
- Review     blueprint -> single POST /api/profiles create

Nothing writes until Create; the one call commits model+MCPs+skill selection
and spawns hub-skill installs (reported in the success toast). ProfilesPage
header gets a 'Build' button (full builder) alongside 'Create' (quick modal).
Route is page-only (not in the sidebar nav). Verified with vite build (2258
modules, green).
2026-06-10 09:18:32 -07:00

6.8 KiB
Raw Blame History

Profile Builder — Dashboard-Native, Full-Featured Profile Creation

Status: design proposal (not yet implemented) Author: drafted for Teknium Supersedes: PR #31781 (prompt_toolkit hermes profile wizard)

Why this, not the CLI wizard

PR #31781 added a keyboard-driven hermes profile wizard in the terminal. The decision is to not build the profile-creation experience in the CLI. The dashboard already owns mature, separate pages for every element a profile needs, and a profile is just a HERMES_HOME directory — so the dashboard is the right home for a full-featured builder, and it can reuse everything that already exists.

A profile = a full ~/.hermes/profiles/<name>/ directory with its own:

  • config.yaml — holds model/provider, mcp_servers, enabled skills
  • skills/ — physical SKILL.md files (built-in seed + optional + hub installs)
  • .env — secrets
  • SOUL.md / USER.md — identity

So per-profile scoping of Model, MCPs, and Skills is native — no data-model change needed. The gap is purely UX: creation today is a thin modal (name + clone + model + description), and you can only compose skills/MCPs after the profile exists, by visiting other pages and remembering to scope them.

What already exists (reuse, don't rebuild)

Element Existing page Existing API Profile-scopable?
Name / Description ProfilesPage create modal POST /api/profiles (create_profile) yes (args)
Model + Provider ModelsPage _write_profile_model(profile_dir, …) yes — HERMES_HOME override, already wired into create endpoint
MCPs McpPage mcp_config._save_mcp_server + /api/mcp/catalog yes — wrap with HERMES_HOME override
Skills (built-in/optional) SkillsPage GET /api/skills, /api/skills/toggle yes — config write
Skills (hub) SkillsPage /api/skills/hub/search, /api/skills/hub/install only via subprocess — see seam #1

Two architectural seams found while grounding this design

These are load-bearing — they change the implementation, not just the polish.

Seam #1 — hub-skill install cannot use the HERMES_HOME override

tools/skills_hub.py binds SKILLS_DIR = HERMES_HOME / "skills" at module import time. The context-local set_hermes_home_override() swap (which makes _write_profile_model and the MCP write land in the target profile) does NOT retroactively rebind that already-imported module global. So a data-layer wrap of hub install would write into the dashboard's own active profile, not the new one.

The correct mechanism is the existing subprocess path: _spawn_hermes_action runs python -m hermes_cli.main <subcommand>, and _apply_profile_override() re-reads sys.argv at import in the fresh child. Prepend -p <profile>:

_spawn_hermes_action(["-p", profile, "skills", "install", identifier], "skills-install")

A fresh subprocess re-imports skills_hub with the profile's HERMES_HOME bound from the start, so SKILLS_DIR resolves to <profile>/skills/. Correct by construction.

Seam #2 — hub installs are async, so create cannot be fully atomic

Built-in/optional skill enabling and MCP writes are synchronous config ops and can be part of the create call. Hub installs are long-running git fetches spawned detached (_spawn_hermes_action returns a PID immediately). So the create flow is:

  1. create_profile() — make the dir (synchronous)
  2. write model (synchronous, HERMES_HOME override)
  3. write selected MCP servers (synchronous, HERMES_HOME override)
  4. seed/enable selected built-in + optional skills (synchronous)
  5. spawn hermes -p <profile> skills install <id> per hub skill (async, returns PIDs)

Steps 14 commit before the response; step 5 returns a list of action PIDs the UI polls (same pattern as today's SkillsPage hub install). The builder's "Review → Create" returns {ok, name, path, hub_installs: [{id, pid}]} and the final screen shows live install progress for the hub skills.

Proposed backend change (small, follows existing patterns)

Extend ProfileCreate and the create endpoint — no new endpoints, no rewrite:

class ProfileCreate(BaseModel):
    name: str
    clone_from_default: bool = False
    clone_all: bool = False
    no_skills: bool = False
    description: Optional[str] = None
    provider: Optional[str] = None
    model: Optional[str] = None
    # NEW — all optional, all best-effort post-create (profile already exists)
    mcp_servers: List[MCPServerCreate] = []      # synchronous, HERMES_HOME override
    builtin_skills: List[str] = []               # synchronous enable/seed
    hub_skills: List[str] = []                   # async spawn, returns PIDs

The endpoint already does best-effort post-create steps (seed_profile_skills, _write_profile_model). Add two more best-effort blocks (MCP write, hub-skill spawn) in the same style — a failure in any of them must not 500 the create, since the profile dir already exists and the user can fix it from the relevant page afterward. Mirror _write_profile_model's HERMES_HOME-override helper for the MCP write (_write_profile_mcp_servers(profile_dir, servers)).

Proposed frontend — dedicated builder page /profiles/new

A full page (not the cramped modal), stepped, each step reusing the existing page's component + API, targeted at the new profile:

① Identity   Name + Description (+ optional clone-from existing profile)
② Model      Provider + model picker  (reuse ModelsPage picker)
③ Skills     Tabs: Built-in · Optional · Hub-search
             multi-select; "Start from default bundle" preset button
④ MCPs       Tabs: Catalog browse · Manual add  (reuse McpPage form)
⑤ Review     Blueprint preview → Create
             → progress screen for async hub installs

Nothing writes to disk until ⑤.

Open product decisions (need Teknium)

  1. Skills seeding default. Fresh profiles auto-seed the default bundle today. In the builder, should the skill step replace the bundle (pick exactly what you want; offer a "start from default bundle" preset) or augment it? Recommendation: replace + preset button.

  2. Page vs richer modal. Dedicated /profiles/new page (room to grow: SOUL editing, multi-agent fleets later) vs a bigger create modal on ProfilesPage. Recommendation: dedicated page — matches "full-featured / way more options."

Verification plan (when built)

  • Backend E2E with isolated HERMES_HOME: POST a full create body (name + model + 2 MCPs + 3 builtin skills + 1 hub skill), assert the new profile dir has the model in config.yaml, both MCP servers in config.yaml, the builtin skills enabled, and a spawned PID for the hub skill. Negative: a bad MCP entry must not 500 the create.
  • cd web && npm run build (no JS test suite in web/).
  • Targeted: pytest tests/<web_server profile tests> -k profile_create.