Architecture
Architecture
Purpose
Hermit is a local, file-first runtime where agents are the application. Agents own domain state, make decisions, and evolve the workspace and code. The workspace on disk is canonical — there is no separate database or service layer.
Code handles the deterministic infrastructure that should not depend on model improvisation: command routing, role resolution, session construction, entity scaffolding, transcript placement, validation, git checkpointing, and local telemetry. Everything else — strategy, judgment, record-keeping, entity updates — is agent work driven by prompts and skills.
Invariants
- Canonical state lives in files. Agents are the writers; code provides structure and plumbing.
- Roles, prompts, skills, templates, and entity definitions are loaded from the workspace at runtime.
- Domain structure stays in markdown and frontmatter; deterministic mechanics stay in code.
- The explorer is intentionally read-only. Agents manage application state; the explorer renders it.
- Telemetry is local and append-only.
Workspace Model
Shared root
entities/holds canonical entity records, including role-managed entities.entity-defs/entities.mddefines entity types and optional explorer renderer config.entity-defs/also holds entity templates and optional renderer modules.skills/holds shared pi skills.inbox/holds uncategorized incoming user files until an agent routes them to the right durable location or removes them as temporary drops.prompts/holds shared prompt files and shared templates.agents/holds one directory per role..hermit/holds Hermit's own runtime state, including chat and heartbeat session history, telemetry, last-role state, and Hermit's sharedagent/record.mdandagent/inbox.mdfor framework stewardship.
Per role
Each agents/<role-id>/ directory contains:
role.mdfor the role manifestAGENTS.mdfor the role-level prompt and prompt indexagent/record.mdandagent/inbox.mdprompts/for role-local prompt filesskills/for role-local skills.role-agent/sessions/for persisted interactive sessions.role-agent/heartbeat-sessions/for persisted heartbeat sessions
Role-owned entities live under entities/, not under agents/<role-id>/. The runtime distinguishes shared versus role-managed entities by the directory roots declared in entity-defs/entities.md.
Runtime scaffolding and doctor hard-require entities/, agents/, entity-defs/, skills/, and inbox/. Role sessions also require prompts/, AGENTS.md, shared agent templates, and any prompt or renderer files referenced by the role.
Runtime Flow
flowchart TD
cli[CLI commands] --> resolve[Resolve workspace and chat or role target]
resolve --> checkpoint[Optional git checkpoint wrapper]
checkpoint --> load[Load role manifest and entity definitions]
load --> scaffold[Ensure workspace and role scaffold]
load --> prompt[Render system prompt]
prompt --> session[Create pi agent session]
session --> tools[Coding tools plus Hermit custom tools]
session --> telemetry[Append local telemetry events]
ingest[Transcript ingest] --> load
ingest --> files[Copy transcript and update files]
files --> session
explorer[Astro explorer] --> source[Import src/roles.ts and src/workspace.ts via tsx]
source --> load
Command Surface
chatstarts an interactive session. Resolution order:--role, inferred role from the current directory underagents/, last used chat role from.hermit/state/last-role.txt, thenHermit. The Hermit fallback applies even when roles exist. Bootstrap mode is enabled only when the resolved target isHermitand no roles are configured. Interactive chat supports runtime role switching through theswitch_roletool.askruns a one-shot prompt in a role-backed session. Resolution:--role, inferred role from cwd, then single-role auto-selection. No last-role fallback, no Hermit fallback. Errors when no role can be resolved.heartbeatruns one persisted role-backed upkeep turn. Uses the same resolution asask. Strategic review is selected when--strategic-reviewis passed or whenlast_strategic_reviewinagent/record.mdis missing or older than 24 hours. The review behavior itself is prompt-driven; the runtime only selects which prompt to use.heartbeat-daemonruns normal role heartbeats on a fixed interval. When any strategic review is due, it runs one combined strategic-review sweep forHermitplus all configured roles, with one separate session per target.ingest transcriptplaces a transcript into the matched entity's evidence directory (or the role's unmatched directory), then runs the role's ingest session. When the transcript is stored as unmatched, the ingest session does not run.telemetry reportaggregates local telemetry into Markdown and JSON reports, including session outcome counts.doctorvalidates shared workspace structure plus the selected role contract.doctor --contextalso prints the rendered system-prompt file breakdown and char counts for the selected role.
Prompt Assembly
System prompt construction:
- Load
prompts/*.md, sorted by filename. Subdirectories are not loaded recursively. - For role-backed sessions, append
agents/<role-id>/AGENTS.md. - Append any explicitly requested role prompt files (e.g.
transcript_ingest.system_prompts). - For Hermit bootstrap chat only, append all
.mdfiles underprompts/bootstrap/.
Role-local prompts are on-demand; they are loaded only when a session explicitly requests them.
PromptLibrary can report the active prompt-part breakdown as source paths plus rendered char counts.
Template placeholders
| Placeholder | Fallback |
|---|---|
{{workspaceRoot}} |
— |
{{roleId}} |
Hermit |
{{roleRoot}} |
. |
{{entityId}} |
not-selected |
{{entityPath}} |
not-selected |
{{transcriptPath}} |
not-selected |
{{currentDateTimeIso}} |
unknown |
{{currentLocalDateTime}} |
unknown |
{{currentTimeZone}} |
unknown |
{{gitBranch}} |
unknown |
{{gitHeadSha}} |
unknown |
{{gitHeadShortSha}} |
unknown |
{{gitHeadSubject}} |
unknown |
{{gitDirty}} |
unknown |
{{gitCheckpointBeforeSha}} |
not-created |
{{gitCheckpointAfterSha}} |
not-created |
Sessions, Skills, and Tools
Session creation lives in src/session-runtime.ts and uses @mariozechner/pi-coding-agent.
Skills:
- Hermit sessions load from
skills/. - Role sessions load from
skills/andagents/<role-id>/skills/. - Skills stay on-demand through pi skill discovery; they are not concatenated into the system prompt.
Session history:
- Hermit interactive:
.hermit/sessions/hermit - Hermit heartbeat:
.hermit/sessions/hermit-heartbeat - Hermit review state:
.hermit/agent/record.mdand.hermit/agent/inbox.md - Role interactive:
agents/<role-id>/.role-agent/sessions - Role heartbeat:
agents/<role-id>/.role-agent/heartbeat-sessions
Every session gets standard coding tools. Custom tools by session type:
- Role sessions:
entity_lookup,web_search, onecreate_<entity>_recordper entity definition - Role interactive (with role switching enabled): adds
switch_role - Hermit sessions:
web_search, plusswitch_rolewhen role switching is enabled
Model selection is provider-aware:
- If
ROLE_AGENT_MODELis unset, Hermit auto-selects the best available configured model. - Preferred model comes from
ROLE_AGENT_MODELwhen explicitly set. - Optional fallback models come from
ROLE_AGENT_FALLBACK_MODELS. - If explicit preferences do not resolve, Hermit falls back to the best available configured model it can use.
web_searchcurrently supports OpenAI and Anthropic credentials. If both are configured, Hermit prefers the provider pinned byROLE_AGENT_MODELwhen that provider is OpenAI or Anthropic.
Thinking level: ROLE_AGENT_THINKING_LEVEL env var, default medium.
Entity and Workspace Mechanics
entity-defs/entities.md is the runtime contract for entity structure. Each definition specifies:
key,label,typecreate_directory, optionalscan_directories, optionalexclude_directory_namesid_strategy:prefixed-slug,year-sequence-slug, orsingletonid_source_fields, optionalid_prefix,name_templatefieldswith per-fieldkey,label,type(stringorstring-array),description, optionalrequiredanddefaultValuefileswith per-filepathandtemplate- Optional
status_field,owner_field,include_in_initialization,extra_directories - Optional
explorer.renderersfor detail-level and file-level custom rendering
ensureWorkspaceScaffold() creates shared root directories and role-local directories. For existing roles, it backfills agent/record.md and agent/inbox.md from prompts/templates/agent/*.md when missing.
The shared inbox/ directory is a transient intake area, not a canonical store. Agents are expected to triage new files dropped there into the correct entity or role directories, preserve source references when those files change canonical understanding, and remove one-off temporary files once their contents are safely captured elsewhere.
createRoleEntityRecord() renders declared entity files, computes a deterministic ID, and writes without overwriting unless forced.
Entity scanning is filesystem-based. A directory containing record.md is an entity leaf. Shared entity scanning excludes directory roots claimed by role entity definitions. Role entity scanning uses each definition's scan_directories or create_directory.
Transcript ingest
Pipeline:
- Resolve the target role and verify
transcript_ingestis declared. - Match the target entity explicitly or infer from the transcript filename.
- If ambiguous, prompt the user to choose or store as unmatched.
- Copy the transcript into the entity evidence directory or the role's unmatched directory.
- Append a line to the configured activity log.
- Run a role-backed ingest session using the command prompt and any configured extra system prompts. Skipped when the transcript is stored as unmatched.
Git Checkpoints
chat, ask, and heartbeat run inside withGitCheckpoint(). heartbeat-daemon does not checkpoint as one process; each delegated heartbeat has its own checkpoint wrapper.
- Before the command: checkpoint if the repo is dirty.
- After the command: checkpoint if the repo is dirty and the command outcome policy allows it.
- Command outcomes are tracked as
success,aborted, orfailedand recorded in checkpoint metadata and telemetry. - Dirty detection:
git status --porcelain=v1 --untracked-files=all. - Checkpoints stage and commit the full changed file set, including untracked files.
- No path allowlist. No automatic rollback.
Validation
doctor is a structural validator. Checks:
- Required shared root directories (
entities,agents,entity-defs,skills,inbox) - Role manifest loading and directory identity
AGENTS.mdpresence and linked markdown files- Required shared agent templates and transcript ingest prompts
- Explorer renderer modules
- Duplicate entity IDs
- Missing frontmatter fields in entity records
- Unresolved template placeholders and generic placeholder lines
- Missing entity files declared by entity definitions
- Presence of at least one supported provider credential (warning, not error)
Exits non-zero only when at least one error-level finding exists.
Explorer
Astro SSR app under explorer/. Read-only by design — agents own and mutate workspace state through sessions; the explorer is a viewer, not an editor. It imports src/roles.ts and src/workspace.ts from the repo root through tsx, so it uses the same entity definitions and scanning logic as the CLI without a separate build step.
| Route | Content |
|---|---|
/ |
Home |
/architecture |
Architecture |
/license |
License |
/entities |
Entity type list |
/entities/:entityType |
Entity list |
/entities/:entityType/:entityId |
Entity detail |
/agents |
Agent list |
/agents/:roleId |
Agent detail |
No per-role entity routes.
Renderers
entity-defs/entities.md may declare detail-level or file-level renderers under explorer.renderers. Modules are loaded dynamically from entity-defs/ and can replace the full detail body or a specific file section. Without a declared renderer, the explorer uses the default markdown renderer.
Extension Surface
Extensions are file-driven:
- Add a role: create
agents/<role-id>/role.md,AGENTS.md, role prompts, and role skills. - Add an entity type: update
entity-defs/entities.mdand add templates. - Add explorer customization: place renderer modules under
entity-defs/and reference them.
No code changes required for standard role and entity additions.