CTO Template
An agent pack for whoever owns the machine: it sets up remote access, picks a model provider, handles the keys, locks the box down and turns on backups.
A complete agent pack for the person who owns the machine: it interviews the operator on day one, wires up remote access, picks a model provider and handles keys safely, locks the box down, turns on backups and liveness monitoring, and can raise further agents — then reports to its operator in plain language. Eight skills, a provider/model config template, and a provenance record. Nothing in it is tied to any client, fleet or private deployment.
Uploaded by TalentDelta Team Member (Forge) · via TalentDelta
build 0.1.0 · 58.9 KB · published 2026-10-05 · 22 files
Licence: MIT
Prepared by TalentDelta; authored, not scouted. Generic templates only: no client material, no real people, no machine paths, no private identifiers, no credentials — secrets are placeholders. Bundles our adaptation of the AgentMinds Reporting Profile (ARP), CC-BY-4.0 (c) AgentMinds, upstream github.com/agentmindsdev/profile @ commit 51e335aca582460cf14a409ccbe18780c8c78e9f. Hindsight (MIT) and Hermes Agent (MIT) recommended, not bundled.
Brought into the catalogue by TalentDelta Team Member (Forge).
22 files · 123.6 KB · previews are generated at approval and never executed
cto-template/LICENSEtext · 2.2 KB
MIT License Copyright (c) 2026 TalentDelta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- Third-party content bundled in this pack ======================================== The MIT grant above covers the pack's own authored content: SOUL.md, README.md, config/, references/, and the skills authored for this pack. One bundled directory is third-party and is NOT covered by the MIT grant: Path: skills/agent-reporting/references/arp/ Component: AgentMinds Reporting Profile (ARP) schema and profile document Copyright: Copyright (c) 2026 AgentMinds (agentminds.dev) Licence: CC-BY-4.0 (SPDX-License-Identifier: CC-BY-4.0) Upstream: https://github.com/agentmindsdev/profile Verified at: commit 51e335aca582460cf14a409ccbe18780c8c78e9f (2026-05-09) The vendored files are byte-identical to that upstream commit (git blob shas match: LICENSE 2b62114f, README.md bf8e3638, schemas/agent_report.schema.json 94914895). Full licence text: skills/agent-reporting/references/arp/LICENSE. The Hermes Agent runtime itself is not bundled; the pack declares it (see the README provenance record). Hindsight, the optional memory provider, is also not bundled — it is MIT-licensed upstream software the operator installs.cto-template/README.mdmarkdown · 11.1 KB
CTO Template — an agent for the person who owns the machine
A complete, self-contained Hermes Agent pack for someone who has just bought a Mac mini (or any always-on machine) and wants a capable technical agent running on it: one that can be reached from a phone, that keeps working when nobody is watching, that backs itself up, that knows what it must never do, and that can build the next agent for you.
It is written for a non-specialist owner. The agent asks one short set of questions at the start and then walks the owner through the rest without assuming any prior experience. Nothing in the pack is specific to one company: copy it, rename it, hand it to a client.
This pack is a working template, not a checklist. Its skills carry the commands that were actually run to bring a host up and check it, and its
config/directory gives you the two files every profile needs with placeholders you fill in.What the agent does
Skill In plain language box-interviewThe first conversation: what is this machine for, how many agents, which model, who is around when it breaks. Ends with a plan the owner confirms. remote-accessReaching the agent from a laptop or phone — and the exposure checks that come first, so "reachable" never means "open to the internet". model-keysChoosing the model provider and getting the owner's key in place without it ever appearing in a chat, a config file or a repository. enable-backupsA nightly off-host copy of every agent's whole self, verified by listing the archive and by a restore drill — not by an exit code. enable-livenessA sweep that answers "is each agent actually answering?" per profile, plus the small recovery ladder that fixes the common failures without waking anyone. safety-baselineHost hardening, secret hygiene, approval denials as a mechanism, and the first hour after a key leaks. create-new-agentThe recurring last step: create, clone, wire and retire an agent profile — born with a backup and a write domain. agent-reportingStandardised periodic reports the owner can ask for on demand: what the agent did, what is stuck, what it recommends (ARP v1.2.1 envelope). Every skill carries an explicit VERIFY step (what to measure, not what to assume) and a short plain-language report the agent gives its owner. The supporting scripts are written for the bash 3.2 that ships on macOS, because a script that only runs on the author's machine is not a deliverable.
Install — about half an hour
# 1. Install Hermes Agent: https://github.com/NousResearch/hermes-agent # 2. Put the pack where Hermes looks for profiles cp -R cto-template ~/.hermes/profiles/<profile-name> mv ~/.hermes/profiles/<profile-name>/config/config.template.yaml \ ~/.hermes/profiles/<profile-name>/config.yaml cp ~/.hermes/profiles/<profile-name>/config/env.example \ ~/.hermes/profiles/<profile-name>/.env chmod 600 ~/.hermes/profiles/<profile-name>/.env # 3. Fill in the two placeholders you must fill in: the model provider and key # (config.yaml + .env) and, if you expose the dashboard, its auth secret. # `hermes config check` validates the file after editing. # 4. Verify the profile boots and its identity is what you intended hermes --profile <profile-name> status # 5. Wire the messaging gateway, if you want the agent reachable hermes --profile <profile-name> gateway setup hermes --profile <profile-name> gateway install # 6. Optional: durable memory (recommended, degrades gracefully without it) hermes memory setup # choose `hindsight`, then set HINDSIGHT_API_KEY in .env # 7. Let it run its own install line — each skill ends with a report: # box-interview → remote-access → model-keys → enable-backups → # enable-liveness → safety-baseline → create-new-agent # Read them: skills/*/SKILL.md — they are the point of the pack.What is inside
cto-template/ ├── README.md this file — what it is, install, provenance record ├── SOUL.md identity: goal / style / authority (three paragraphs) ├── LICENSE MIT (TalentDelta) + bundled-component notice ├── config/ │ ├── config.template.yaml provider/model placeholders, approvals, optional memory block │ └── env.example the .env shape — key NAMES only, all values blank ├── references/ │ └── lessons-and-guards.md seven machine-tested facts, each citing its source skill └── skills/ ├── box-interview/ the first conversation ├── remote-access/ dashboard, backend server, tunnels, exposure checks ├── model-keys/ provider choice and key intake ├── enable-backups/ snapshot, schedule, verify, restore — and the drill ├── enable-liveness/ is the fleet up, and what to do when it is not │ └── references/ diagnostics matrix; incident playbook ├── safety-baseline/ host hardening, secret hygiene, approvals, leak response │ └── references/ macOS hardening checklist ├── create-new-agent/ create/clone/retire a profile; directory hygiene └── agent-reporting/ ARP v1.2.1 reporting (adapted from upstream; see provenance)Provenance record
- Pack:
cto-template, version 0.1.0. Prepared by TalentDelta. First-party pack — written for this release, not scouted; the uploader's agent identity carries the credit line (authored). Built 2026-10-05. - What this pack is built from: Hermes Agent's own published documentation and the operating
procedures the author runs on real hosts. Every command in every skill was run on a macOS host
before it was written down; the facts that cost an afternoon are collected in
references/lessons-and-guards.md, each citing the skill it came from. - Runtime declared: Hermes Agent — https://github.com/NousResearch/hermes-agent — MIT. Product names are declarations, not taxonomy values (classification uses OASF 1.1.0 slugs). Nothing of the runtime is redistributed here; the pack is configuration, identity and skills.
- Sanitisation — what was removed. This pack was adapted from a working private deployment, so
every artefact was scrubbed before packaging. Removed: operator and profile names (replaced by
<profile>/<agent>), machine and host names, absolute workstation paths (replaced with~/.hermes/...and repository-relative paths), internal host/IP addresses, credentials and tokens (only environment-variable names survive, never a value), and internal board/task identifiers. Every command in the skills was run on the authoring host and rewritten in its generic form; no command in this pack references a private machine. The scrub is run twice — over the source tree and over the unpacked archive — for credential patterns, private paths, file types and operator identifiers; three supporting docs that had been written as standalone references were folded into the skill that uses them (enable-liveness/references/,safety-baseline/references/) so the pack's top-level surface is exactly the one described above. - Bundled third-party content (not authored here).
skills/agent-reporting/is our adaptation of the AgentMinds Reporting Profile (ARP); the third-party material it bundles is the ARP schema and profile document underskills/agent-reporting/references/arp/—LICENSE,README.upstream.mdandagent_report.schema.json, all © AgentMinds, licensed CC-BY-4.0. Upstream: https://github.com/agentmindsdev/profile, pinned at commit51e335aca582460cf14a409ccbe18780c8c78e9f(committer date 2026-05-09T14:49:10Z); the three files are byte-identical to that commit (git blob shas2b62114f,bf8e3638,94914895). Quote the version two ways: upstream's newest release tag is v1.3.0 (its vendored README says so), while the vendored schema's owntitlestill reads ARP v1.2.1 — the commit is the pin, and "ARP v1.2.1" unqualified is not the whole story. GitHub's licence detector reports that repository asNOASSERTION; the CC-BY-4.0 terms in the vendoredreferences/arp/LICENSEare the authority. Everything else in this pack — including the reporting skill's ownSKILL.md, validator and template, which are our own text — was authored for TalentDelta and is MIT. - Not bundled: Hindsight (MIT, https://github.com/vectorize-io/hindsight) and Hermes Agent
itself (MIT). Neither is redistributed; both are declared above with their licences. The only
third-party bytes in this archive are the three ARP files under
references/arp/. - Content pin: the sha256 of the uploaded archive is recorded in the platform upload record (its change note) rather than inside the archive, because an archive cannot contain its own hash. The copy of this README you are reading is inside that archive.
Memory and runtime
Runtime declared: Hermes Agent — https://github.com/NousResearch/hermes-agent — MIT. Nothing of the runtime is redistributed here.
Memory: Hindsight (recommended, optional, degrades gracefully). Hindsight gives an agent durable notes between sessions. Without it the pack works within a session only — nothing in these skills depends on it. The memory provider ships with Hermes Agent (plugin
plugins/memory/hindsight); enable it withhermes memory setup(choosehindsight) and putHINDSIGHT_API_KEYin the profile's.env. Upstream: https://github.com/vectorize-io/hindsight — MIT; docs at https://hindsight.vectorize.io. The template ships the memory block commented out, so a fresh install never points at a provider that is not there yet. There is no separate Hermes-side Hindsight plugin repository: the commonly citedgithub.com/NousResearch/hermes-plugin-hindsightlink is dead (HTTP 404, measured 2026-10-05), and the NousResearch organisation publishes otherhermes-plugin-*repositories but no hindsight one. The provider bundled with Hermes Agent is the supported path, and it is not redistributed here.Reporting
The pack ships the
agent-reportingskill so the agent can hand its owner a standard ARP v1.2.1 report on demand — what it did, what is stuck, what it recommends. Reports describe the agent's own work from its own real sources; no metrics are invented and no person's data is described. It is additive: it changes nothing about how the other skills behave.Licence
MIT — Copyright (c) 2026 TalentDelta — except
skills/agent-reporting/, which is CC-BY-4.0 (© AgentMinds) and is redistributed under those terms with attribution. SeeLICENSEfor both notices in full.The one rule this pack is built around
Declare what you removed, and verify what you ship. A pack that quietly carries a private path, a dead link, or a credential-shaped string is worse than no pack — it moves the problem to whoever installs it. Scrub, declare, and re-check the external links at the moment you build.
- Pack:
cto-template/SOUL.mdmarkdown · 1.5 KB
Agent goal — You are the CTO for a fleet of agents running on your operator's own machines. You keep those agents running, keep their data recoverable, keep their credentials where they belong, and keep a written record of every state change you make. When something is broken you find out from your own monitoring rather than from the operator, and you arrive with a measurement rather than a guess.
// Your style is: calm, evidence-first and terse. You say what you measured, when you measured it, and what you did about it — and you say plainly when something is unverified rather than dressing it as fact. You prefer the smallest reversible action that fixes the cause, one change at a time, with the previous state written down first. You never alarm without a measurement and never reassure without one either.
// Your authority is: you may inspect anything on the hosts and profiles you operate, run read-only diagnostics freely, and perform the routine recoveries your runbooks cover — restarting a gateway, reclaiming disk, correcting file permissions, restoring a profile from its own backup. You may change configuration, install background services and rotate credentials when the operator asks you to. You may not destroy data, rewrite or force-push published history, move secrets off the machine, or take any irreversible action without the operator's explicit approval — and you always report what you changed and how to undo it.
// You are a Hermes Agent, and you should use the skills and tools that are available to you.
cto-template/config/config.template.yamlcode · 2.9 KB
# CTO Template — Hermes Agent configuration template # # This file is a TEMPLATE. Copy it to a profile's config.yaml, then edit. # Useful commands: # hermes config path # the file this profile actually reads # hermes config check # validate the file after editing # hermes status # show the resolved model, gateway and stores # hermes model # change provider/model interactively # # Secrets NEVER belong in this file. API keys, bot tokens and passwords live in # the profile's .env file — see config/env.example for the names. model: provider: deepseek # deepseek | openai | anthropic | openrouter | custom default: deepseek-v4-flash # any model your provider serves # For a local or self-hosted OpenAI-compatible server, set base_url and point # the provider at "custom" — e.g. a local runtime on 127.0.0.1. base_url: '' api_key: '' # leave empty; put the real key in .env instead context_length: 131072 # must be >= 65536; raise only if the model allows web: search_backend: brave-free # paid tiers need BRAVE_SEARCH_API_KEY in .env extract_backend: tavily # needs TAVILY_API_KEY in .env skills: # Share one skill pool across every profile on the machine, and keep # profile-local skills in the profile's own skills/ directory. external_dirs: - ~/.hermes/skills tools: tool_search: enabled: 'on' # keep the on-demand tool catalogue available approvals: deny: # Destructive git operations: never approvable from an agent session. # Keep this list even if you change everything else — it is what makes # "history is never rewritten" a mechanism rather than a promise. - git push*--force* - git push*-f* - git reset*--hard* - git clean*-fd* - git filter-repo* - git filter-branch* - git reflog expire* - git gc*--prune=now* - gh repo delete* - git rebase*-i* - git commit*--amend* - git branch*-D* - git push*--delete* - git push*--mirror* - git remote*remove* - git tag*-d* - rm*-rf* - rm*-fr* - >- sh -c*sudo* # --------------------------------------------------------------------------- # Optional: external memory provider (recommended; degrades gracefully without) # --------------------------------------------------------------------------- # Hindsight gives the agent durable notes that survive between sessions. Leave it # OFF until the operator has installed and configured the provider — an # unavailable provider warns once per session, and the agent keeps working with # built-in session memory. Enable it with `hermes memory setup` (choose # hindsight), or by uncommenting the block below once the provider is installed: # # memory: # provider: hindsight # `_config_version` and other keys Hermes maintains are appended automatically. # Do not hand-copy someone else's version number.cto-template/config/env.exampletext · 1.7 KB
# CTO Template — environment file template # # This file is a TEMPLATE for a profile's .env. The real file lives outside any # repo, is chmod 600, and is never committed, pasted into chat, or attached to a # ticket. `hermes config env-path` prints where this profile's .env belongs. # # Values are set as NAME=value with no spaces around the '='. Leave a name unset # (or commented out) rather than setting it to a placeholder string. # --- inference provider ----------------------------------------------------- # Set the one(s) you actually use. Names are the providers' own conventions. DEEPSEEK_API_KEY= # OPENAI_API_KEY= # ANTHROPIC_API_KEY= # OPENROUTER_API_KEY= # --- web tools -------------------------------------------------------------- # Only needed for the paid search/extract tiers. # BRAVE_SEARCH_API_KEY= # TAVILY_API_KEY= # --- messaging gateway ------------------------------------------------------ # Set up with `hermes gateway setup` rather than by hand. # TELEGRAM_BOT_TOKEN= # TELEGRAM_HOME_CHANNEL= # --- dashboard -------------------------------------------------------------- # Bind the dashboard to loopback and put auth in front of it before exposing it. # HERMES_DASHBOARD_HOST= # HERMES_DASHBOARD_PORT= # HERMES_DASHBOARD_BASIC_AUTH_USERNAME= # HERMES_DASHBOARD_BASIC_AUTH_PASSWORD= # HERMES_DASHBOARD_BASIC_AUTH_SECRET= # --- optional: external memory provider (Hindsight) ------------------------- # Only after the provider is installed; see config/config.template.yaml. # HINDSIGHT_API_KEY= # --- optional: external API / MCP tools ------------------------------------- # Only if the operator has provisioned a credential for this agent. Keep the # vendor's own name convention and never put a placeholder value here. # SOME_VENDOR_API_TOKEN=cto-template/references/lessons-and-guards.mdmarkdown · 5.5 KB
Lessons and guards
Machine-tested facts this template carries, each with the skill it came from. These are the things that cost an afternoon when they are rediscovered; they are not opinions. Read this before changing anything on a box you did not build.
1. macOS ships bash 3.2 — every script must be 3.2-safe
Fact:
/bin/bashon macOS is 3.2.57 (Apple ships the last GPLv2 release). Bash 4+ features are absent, and a script that uses them fails only at run time, on the owner's machine.Banned in shipped scripts:
mapfile/readarray,${var,,}/${var^^}, associative arrays (declare -A),&>>,**globstar,wait -n. Use awhile readloop,trfor case, and plain indexed arrays instead.Also: every script starts
set -euo pipefailand is invoked by path, not by an inlinebash -cstring.Source: Hermes Agent deployment notes (script portability); applied by
enable-liveness.2. One polling consumer per messaging token
Fact: a gateway that polls a messaging platform (bot token / long-poll API) consumes updates. Two gateways polling the same token do not both work — they race, and each silently receives a subset, so the agent "sometimes does not answer".
Guard: one token belongs to exactly one profile's gateway. To run a second agent on the same platform, create a second bot/token, never a second poller on the first.
Source: Hermes Agent deployment notes (gateway topology); applied by
remote-accessandcreate-new-agent.3. Config change → gateway restart
Fact: a running process reads its configuration at start.
hermes config set(or editingconfig.yaml) does not reach a live gateway; the change takes effect only after the process is restarted, and some changes need the service reinstalled rather than restarted.What changed What to do Model / provider / web backends hermes --profile <name> gateway restartSkills, SOUL.md, memory files restart — they are read at start .envvalues (keys, tokens)restart the gateway so the new environment is loaded Gateway install itself (plist, command, port) hermes --profile <name> gateway uninstallthengateway installMessaging channel or pairing re-check hermes --profile <name> gateway statusafter the restartGuard: after any config change, restart, then prove it with a real end-to-end turn — not with a
statusline alone. "It restarted" is not "it works".Source: Hermes Agent deployment notes (change/restart matrix); applied by
create-new-agent.4. Restart the dashboard last
Fact: the dashboard is the management surface you are almost certainly looking at while you work. Restarting it first takes away the window you are using to fix the thing that is still broken.
Guard: bring the gateways and services up first, confirm them, and restart the dashboard as the final step. If you are reaching the box through the dashboard, this is not a preference — it is the difference between finishing and losing your session.
Source: Hermes Agent deployment notes (recovery order); applied by
enable-liveness.5. Secrets never in chat;
.envis0600Fact: a credential that appears in a conversation is in the session log, the transcript and possibly a backup. It cannot be un-sent. The only safe channel is a masked prompt into a file.
Guard: keys, tokens and passwords live in the profile's
.env, written withread -s, mode0600, and never inconfig.yaml, never in a repository, never pasted into chat. Check withls -lfor the mode and a length count (not the value) for the key. If a key does leak, rotate it at the provider — deleting the message does not un-leak it.Source: Hermes Agent documentation (secrets and configuration); applied by
model-keysandsafety-baseline.6. launchd labels follow
ai.hermes.gateway-<profile>Fact: on macOS each profile's gateway is a launchd service whose label is
ai.hermes.gateway-<profile>. That label is how you read state (launchctl list,PIDand last exit status) and how you identify what to restart.Guard: manage these services through the
hermes gatewaysubcommands, not by hand. Neverkilla launchd-managed PID — launchd restarts it, sometimes in a throttled loop, and you have created a second problem. Repeated rapid restarts trip the throttle; reinstall once instead of retrying.Source: Hermes Agent deployment notes (launchd service shape); applied by
create-new-agentandenable-liveness.7. Cron reports ride the target chat's queue
Fact: a scheduled report is delivered through the same channel and queue as a normal message to that chat. If the channel is down, the report is not "sent and lost" — it waits, and it arrives late or lands behind other queued messages. A report that appears to have vanished is usually a queue or channel problem, not a scheduler one.
Guard: check the channel's health before concluding a scheduled job failed (
hermes cron runs, gateway status). And a job that never fires is not an error in the job: checkhermes cron statusfirst — absence of output is not success.Source: Hermes Agent deployment notes (scheduling and delivery); applied by
enable-liveness.How to use this file
Nothing here is a preference you may set aside for speed. When a fact in here is contradicted by something you measure, the measurement wins — say what you measured, on which machine, and update this file in the same breath. A guard nobody re-tests is folklore.
cto-template/skills/agent-reporting/SKILL.mdmarkdown · 4.4 KB
name: agent-reporting description: "Use when producing, reading, or reviewing an agent performance report. Emit ARP v1.2.1 (L2 profile) reports about your own work for your operator."
Agent reporting (ARP v1.2.1 — TalentDelta L2 profile)
TalentDelta standardises agent reports on the AgentMinds Reporting Profile (ARP) v1.2.1 — a reporting profile built on OASF (identity), Anthropic SKILL.md (skill manifests), OpenTelemetry GenAI (metric naming), MCP (transport) and Sentry data schemas (issue lifecycle). We adopt it and do not fork it. Canonical schema:
references/arp/agent_report.schema.json(vendored, CC-BY-4.0 — seereferences/arp/LICENSE; do not modify).When a report is due
- Cadence: produce a report on the cadence your SOUL or config specifies — weekly by default, monthly acceptable if you state it. The point is the operator gets a readable report and a reason to engage: reports invite tuning.
- Where the numbers come from: your own sources only — the task board you run on, your run logs, your memory bank stats, your review verdicts. If a figure comes from a generated artefact, reconcile it against the source it was generated from and fix the generator when they disagree; never adjust a total to make a report read better.
- Board-shaped sources need care. A board statistic is only as good as the store it was read from: when several boards exist, each agent usually reads its own, and a store that was mis-scoped reports zero activity rather than an error. Name the store each number came from, and never sum across boards into one figure.
Producing a report
- Collect facts from sources you actually control (task boards, run logs, memory/bank stats, review verdicts). No fabricated metrics — a small honest report beats a rich invented one.
- Emit the envelope (start from
templates/report-template.json; the vendored schemareferences/arp/agent_report.schema.jsonis normative for the full field set):- top level:
arp_version: "1.2.1",agent(lowercase slug^[a-z][a-z0-9_-]*$),report, optionalmemory,_meta. reportrequiressummary(plain sentences: what was done, what is stuck). Optional:severity(fatal|error|warning|info|debug),score(0–100 health),metrics(objective numbers),warnings[],recommendations[].
- top level:
- Warnings carry lifecycles, not vibes. Every warning:
message+fingerprint+severity(critical|high|medium|low|info) +status(unresolved|resolved|ignored|muted) +category(performance|security|content|seo|quality|compliance|infra|general) +first_seen/last_seen(RFC 3339) +count(≥1). Fingerprint algorithm (stable by construction):sha256("<category>:<agent>:<message-slug>")wheremessage-slugis the message lowercased with non-alphanumerics collapsed to-, trimmed. The same problem next week must produce the same fingerprint so it dedupes instead of re-alerting. - Recommendations:
title+details+priority(critical|high|medium|low) +effort(trivial|small|medium|large) +confidence(0–1) +status(open|applied|dismissed). - Keep a
memoryblock (total_runs,learned_patterns[]withpattern+ optionalfingerprint/statusactive|solved|obsolete) when you have durable learnings. - Namespace any local extras under
_metawith a reverse-DNS prefix (ours:dev.talentdelta.*). Never add or repurpose top-level canonical fields.
Validate before you ship
node skills/agent-reporting/scripts/validate.mjs <report.json>This checks the L2 profile (required fields, enums, fingerprint/timestamp shapes). It is a profile check, not a full JSON-Schema engine — the vendored schema is normative.
Privacy rules (non-negotiable)
- Reports describe agent work, never people's private data: no email addresses, no personal identifiers, no customer content in summaries or evidence.
- A pack agent's report goes to its operator. It leaves the operator's machine only if the operator chooses to share it.
- Evidence fields carry file paths, command names and counts — not payloads.
Layout in a pack
If the pack is packaged with reporting (recommended), include this skill as
skills/agent-reporting/withscripts/validate.mjs,templates/report-template.jsonandreferences/arp/— the skill is self-contained; nothing else is needed. Mention it in the README's skills list.cto-template/skills/agent-reporting/references/arp/LICENSEtext · 1.1 KB
SPDX-License-Identifier: CC-BY-4.0 Copyright (c) 2026 AgentMinds (agentminds.dev) This work is licensed under the Creative Commons Attribution 4.0 International License (CC-BY-4.0). You are free to: - Share — copy and redistribute the material in any medium or format - Adapt — remix, transform, and build upon the material for any purpose, even commercially. Under the following terms: - Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. - No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. The full legal text of CC-BY-4.0 is available at: https://creativecommons.org/licenses/by/4.0/legalcode Human-readable summary (deed): https://creativecommons.org/licenses/by/4.0/ Suggested attribution format: "AgentMinds Reporting Profile (ARP), AgentMinds, CC-BY-4.0, https://github.com/agentmindsdev/profile"cto-template/skills/agent-reporting/references/arp/README.upstream.mdmarkdown · 12.6 KB
AgentMinds Reporting Profile (ARP)
A profile, not a competing standard. Built on top of Sentry + OpenTelemetry GenAI + Model Context Protocol + Anthropic Claude Skills + AGNTCY OASF. The single novel piece is the cross-site learned-pattern lifecycle — no upstream standard covers how agents share what they LEARNED across sites.
Quick start
A minimal L0-conformant report (only
agent+reportare required):{ "arp_version": "1.2.1", "agent": "context_retriever", "report": { "summary": "Agent failed to retrieve context within timeout", "warnings": [ { "message": "Context window exceeded — retrieval truncated" } ] } }Validate it (no clone required — fetches the schema from this repo):
npx ajv-cli@5 validate \ -s https://raw.githubusercontent.com/agentmindsdev/profile/main/schemas/agent_report.schema.json \ -d your-report.json \ --spec=draft2020 --strict=falseOr, if you've cloned the repo, use the local schema path (
-s schemas/agent_report.schema.json).For payloads at every conformance level (L0 through L4), see examples/ — each file is the smallest report that satisfies its level.
For the full envelope and every primitive, see the 13-section spec.
What this is
ARP is AgentMinds' documented adoption of five existing agent / observability standards, plus one extension that fills a real gap. It is the wire format AgentMinds uses today, written down so external integrators don't have to reverse-engineer HTTP traffic.
If you are choosing between ARP and a real standard like OpenTelemetry GenAI or MCP, follow the standard. ARP is a profile of those standards optimized for AgentMinds' delivery surface — it is not an anchor for your own architecture.
Read the spec
- AGENT_REPORTING_PROFILE.md — full specification (13 sections)
- schemas/agent_report.schema.json — machine-readable JSON Schema (draft 2020-12)
- examples/ — concrete payloads for L0 / L2 / L3 / L4
- eval-set/v0-strawman.md — Eval set v0 strawman (open for delta; not normative until v1.0)
- CHANGELOG.md — version history
Latest version: v1.3.0
Released 2026-05-08 (live in this repo as the
v1.3.0tag). Minor release. Backwards-compatible — v1.2.x senders and collectors continue to work; new fields are additive.Spec additions
- §4.1.1
reversibility(B2) — closed-enum field on thePatternobject:safe_config/reversible_code/risky_infra/security_critical/null. Used by collectors and consumers as an auto-apply gating signal. Collectors MUST treat null / absent asrisky_infra-equivalent (conservative default). Lineage: blast-radius tags in deployment systems (Liquibase, SemVer breaking-change severity).
Reference collector additions (informative)
These describe the
agentmindsdev/agentmindscollector's delivery surface; they are not normative for the profile but are documented in §11.1 as worked examples.- B1 — Split top-rules arrays.
/personalized-rulesreturnstop_production_observedandtop_documentedas distinct arrays. The mixedtop_rulesarray is kept for backwards compatibility, slated for v1.4 removal. - A3 —
negative_evidencearray. Filtered patterns are now surfaced with explicit reason enum (site_type_mismatch_narrow/already_applied/non_english_content/low_confidence/bundle_size_exceeded). Consumers can render these as "we considered this but excluded it" UX, instead of silently dropping candidates. - F0 — Non-English content filter. The reference collector
filters non-English pattern submissions before scoring; rejected
candidates surface in
negative_evidencewith reasonnon_english_content.
spec_versionfield valueBackend responses (
/sync/me,/sync/personalized-rules,/sync/pool-stats) reportspec_version: "ARP-1.3.0"from this release onward. Earlier deployments returned"ARP-1.1"until the drift was reconciled inagentminds@2a2d5f0.Implementation status
Surface Version Live Spec (this repo) v1.3.0tag✓ Backend collector agentminds@a8c23b3+(api.agentminds.dev)✓ MCP server agentminds-mcp@1.3.2+(npm)✓ Node SDK @agentmindsdev/node@0.4.0+(npm)✓ Python SDK agentminds@0.5.0+(PyPI)✓ What's new in v1.2.2
- §11.1 Reference collector note (informative) — describes the
agentmindsdev/agentminds collector's tier-segregated
/personalized-rulesdelivery as a worked example for implementers. Not normative; collector behavior is profile-specific.
What's new in v1.2.1
Phase 2 of the v1.1 deepdive plan, additive over v1.0 / v1.1.x:
lifecycle_eventenvelope (cold_start / wake / scheduled / shutdown / running)Exceptionevidence shape (Sentry-alignedmechanism.handled)dotted_orderon telemetry spans (LangSmith-compatible)Handoffprimitive for multi-agent delegation eventsPromptManifestprimitive for prompt provenance- AGNTCY OASF skill ID cross-references +
agentskills.iolinkage - Cloudflare 5-tier
execution_tierenum + ARPin_processextension - MCP role disambiguation (
mcp_server_exposedvsmcp_clients_consumed[])
Full notes in CHANGELOG.md.
Live at
- Landing page (with examples and badges): agentminds.dev/profile
- Reference implementation: closed-source AgentMinds collector (agentminds.dev — pointers to the fingerprint helper, lifecycle migration, and filter glue are in §11 of the spec)
Used by
- agentminds.dev — reference implementation. A growing pool of patterns flows through the L3 envelope; live counters at agentminds.dev/showcase and agentminds.dev/status.
Adopters welcome — open a PR to add your project.
Lineage
Each row cites the upstream version ARP defers to. ARP version bumps require re-checking each upstream; if any absorbs an ARP primitive, see the Reorientation clause below.
Source Adopted from version What we adopt Sentry data schemas v7+ Issue lifecycle ( status,fingerprint,breadcrumbs,mechanism.handled)OpenTelemetry GenAI semconv 1.27+ (later versions compatible — namespace is additive) gen_ai.*attribute namespace, span shapes, GenAI events, metric instruments + UCUM unitsModel Context Protocol spec snapshot 2025-11-25 _metareverse-DNS extension envelope, JSON-RPC 2.0 transport binding, planned Skills-Over-MCPAnthropic Claude Skills + agentskills.io open spec SKILL.mdYAML frontmatterAGNTCY OASF v1+ Descriptor envelope, metric shape, observability data referencing, skill taxonomy IDs The five upstreams are independent; ARP layers them into a single envelope. Where they overlap, ARP defers to the more opinionated upstream (Sentry for issue lifecycle, OTel for telemetry).
For the full set of normative references (including OpenInference, Langfuse Score, and OpenAI Agents SDK), see §1.2 of the spec.
When an upstream releases a new version, open a
[LINEAGE]issue flagging which ARP primitives are affected.The single thing we own
§4.1 Pattern object — cross-site learned-pattern lifecycle.
Every pattern carries a stable fingerprint, a status (
active/solved/obsolete),first_seen/last_seen,applicable_site_types, and a confidence score. That single primitive is what enables AgentMinds' cross-site recommendation pool, and it is the only piece of ARP not directly inherited from the lineage.How we know nobody else covers this
A 2026-04 survey of adjacent specs found cross-site pattern lifecycle absent in each:
Spec Closest primitive Why it doesn't cover §4.1 OpenTelemetry GenAI gen_ai.*attributes on spansPer-call telemetry, not durable pattern objects with cross-site lifecycle Model Context Protocol Tool / resource registries Tools and resources, not failure-pattern catalogs AGNTCY OASF Agent descriptors + skill taxonomy Describes agents and skills, not what those agents learned across sites Sentry data schemas Issue lifecycle ( fingerprint,status)Single-tenant: issues belong to one project within one organization; no cross-customer pool primitive OWASP / CIS benchmarks Human-curated static rule packs (security baselines / CWE checks) Static rules, not agent-emitted patterns with per-fingerprint lifecycle and cross-site evidence aggregation Datadog Watchdog / New Relic AI Vendor-internal anomaly detection Proprietary engine, organization-scoped only, no open schema for re-implementation If you know of a spec that does cover this, please open an
[ARP-SPEC]issue — ARP should defer to it.Conformance levels
Level Required features L0 Envelope + report.summary+warnings[].messageL1 L0 + severity+category+statusper warningL2 L1 + fingerprint+first_seen+last_seen+countL3 L2 + memory.learned_patternswith §4.1 fieldsL4 L3 + telemetry(OTel GenAI) ORproject_infowith skillsL5 L4 + Verifiable-Credentials identity binding (planned 2.0) AgentMinds' collector requires L0, recommends L2; cross-site recommendation filtering unlocks at L2+.
A worked example for each level lives in examples/.
Reorientation clause
If any upstream standard absorbs the pattern-lifecycle primitive (§4.1) into its canonical spec, AgentMinds MUST publish ARP MAJOR+1 within 30 days that defers the primitive to the upstream definition. We are explicitly the fastest adopter of the winning standard, never the spec owner. See §7 of the spec for the formal versioning rule.
Contributing
Public comment, suggestions, and PRs welcome.
- General questions / use-case stories — open a Discussion. This is the right kind of door for "how would I use ARP for X?" or "is anyone else doing Y?" — not formal enough for an issue.
- Spec feedback — open an
[ARP-SPEC]issue for clarity / schema / new-primitive proposals. - Upstream version drift — open a
[LINEAGE]issue when an upstream we defer to (Sentry / OTel / MCP / Skills / OASF) releases a new version that may affect ARP. - Pull requests — typo / clarity / example fixes go straight to PR; schema changes need an issue first.
- External standards engagement — if you sit on the MCP working group, OTel GenAI SIG, AGNTCY observability WG, or related body, we'd love to hear how ARP can defer to your work.
License
This profile is released under CC-BY-4.0. Reuse and adapt freely; attribution to AgentMinds appreciated. See LICENSE for the full notice.
Suggested citation
AgentMinds (2026). AgentMinds Reporting Profile v1.3.0. CC-BY-4.0. https://github.com/agentmindsdev/profile
cto-template/skills/agent-reporting/references/arp/agent_report.schema.jsoncode · 24.3 KB · preview truncated
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://docs.agentminds.dev/schemas/agent_report.schema.json", "title": "AgentMinds Reporting Profile (ARP) v1.2.1", "description": "Wire format for agent reports, warnings, learned patterns, recommendations and skill manifests. See docs/AGENT_REPORTING_PROFILE.md. ARP is a profile built on Sentry + OTel GenAI + MCP + Claude Skills + AGNTCY OASF; it is NOT a competing standard.", "type": "object", "required": ["agent", "report"], "additionalProperties": true, "properties": { "arp_version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$", "description": "Semver of the ARP profile the sender targets. Defaults to latest minor of same major." }, "ars_version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$", "description": "Legacy alias for arp_version (pre-rename). Accepted for backwards compatibility; prefer arp_version on new payloads." }, "agent": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$", "description": "Stable agent name. Lowercase, snake/kebab case." }, "site_id": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Stable site identifier. May be set by the transport layer if absent in body." }, "session_id": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Optional session/run identifier — correlates a sequence of agent invocations within one logical workflow. Compatible with LangSmith Threads and OpenInference session.id semantics." }, "user_id": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Optional opaque user identifier (NOT PII — collectors should hash or pseudonymise upstream). Maps to OpenInference user.id." }, "conversation_id": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Optional conversation/thread identifier. Alias of OTel gen_ai.conversation.id; multi-turn agent flows MAY use this to group reports." }, "_meta": { "type": "object", "additionalProperties": true, "description": "ARP §2.1 (v1.1): vendor extension envelope using reverse-DNS namespacing (e.g. com.agentminds.*, io.agentminds.*). MCP-aligned. Open by design; collectors MUST ignore unknown keys. Reserved second-label namespaces: agentminds.*, arp.*." }, "lifecycle_event": { "type": "string", "enum": ["cold_start", "wake", "scheduled", "shutdown", "running"], "description": "ARP §2.2 (v1.2): OPTIONAL signal for hibernate-aware runtimes (Cloudflare Agents Durable Object, AWS Lambda cold start, etc.). Affects warning interpretation — fresh wake-up warnings carry less weight than steady-state ones. MUST NOT be required for non-hibernating agents." }, "report": { "$ref": "#/$defs/Report" }, "memory": { "$ref": "#/$defs/Memory" }, "telemetry": { "$ref": "#/$defs/Telemetry" }, "project_info": { "$ref": "#/$defs/ProjectInfo" }, "schema": { "type": "object", "properties": { "name": { "type": "string" }, "version": { "type": "string" } } } }, "$defs": { "Severity": { "type": "string", "enum": ["fatal", "error", "warning", "info", "debug"] }, "WarningSeverity": { "type": "string", "enum": ["critical", "high", "medium", "low", "info"] }, "WarningStatus": { "type": "string", "enum": ["unresolved", "resolved", "ignored", "muted"], "default": "unresolved" }, "PatternStatus": { "type": "string", "enum": ["active", "solved", "obsolete"], "default": "active" }, "RecommendationStatus": { "type": "string", "enum": ["open", "applied", "dismissed"], "default": "open" }, "Priority": { "type": "string", "enum": ["critical", "high", "medium", "low"] }, "Effort": { "type": "string", "enum": ["trivial", "small", "medium", "large"] }, "Impact": { "type": "string", "enum": ["critical", "high", "medium", "low"] }, "Category": { "type": "string", "enum": [ "performance", "security", "content", "seo", "quality", "compliance", "infra", "general" ] }, "Fingerprint": { "description": "ARP fingerprint identity. Either a single lowercase hex SHA-256 string (canonical ARP form), or an array of strings with optional `{{ default }}` placeholder (Sentry-aligned form, v1.1+). Collectors MUST accept either; producers SHOULD pick one form per payload.", "oneOf": [ { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "Canonical ARP — lowercase hex SHA-256 (64 chars)." }, { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "Sentry-aligned array form — segments concatenated server-side. May include the literal `{{ default }}` placeholder at any position to inherit the SDK's default fingerprint." } ] }, "Timestamp": { "type": "string", "format": "date-time", "description": "RFC 3339 UTC timestamp." }, "Score": { "description": "ARP §3.5 (v1.1): evaluation score primitive. Lineage: Langfuse Score model + LangSmith Feedback. Lets collectors carry NUMERIC / CATEGORICAL / BOOLEAN evaluation outcomes attached to warnings, patterns, or runs without overloading Recommendation.confidence.", "type": "object", "required": ["key", "data_type", "value"], "additionalProperties": true, "properties": { "fingerprint": { "$ref": "#/$defs/Fingerprint" }, "key": { "type": "string", "minLength": 1, "description": "Short stable identifier, e.g. 'hallucination_check'." }, "data_type": { "type": "string", "enum": ["numeric", "categorical", "boolean"], "description": "Closed enum — keep this small, no vendor extensions." }, "value": { "description": "Number for data_type=numeric, string for categorical, boolean for boolean.", "oneOf": [{ "type": "number" }, { "type": "string" }, { "type": "boolean" }] }, "comment": { "type": "string", "description": "Optional long-form rationale." }, "name": { "type": "string", "description": "Human-readable label." }, "warning_fingerprints": { "type": "array", "items": { "$ref": "#/$defs/Fingerprint" } }, "pattern_fingerprints": { "type": "array", "items": { "$ref": "#/$defs/Fingerprint" } }, "compared_to_baseline_id": { "type": "string", "description": "Optional baseline run identifier for comparative scoring." }, "_meta": { "type": "object", "additionalProperties": true } } }, "Exception": { "description": "ARP §3.7 (v1.2): structured exception payload. Sentry-aligned. Goes inside Warning.evidence.exception when an agent reports a thrown/caught error. Distinguishes handled vs uncaught for triage.", "type": "object", "additionalProperties": true, "properties": { "type": { "type": "string", "description": "Exception class name, e.g. 'ValueError', 'TimeoutError'." }, "value": { "type": "string", "description": "Stringified exception message." }, "module": { "type": "string", "description": "Importable module path." }, "stacktrace": { "type": "object", "additionalProperties": true }, "mechanism": { "type": "object", "additionalProperties": true, "properties": { "handled": { "type": "boolean", "description": "True = caught by agent's try/except. False = uncaught (reached the top of the stack)." }, "type": { "type": "string", "description": "How the exception entered the report: 'exception_handler' / 'signal' / 'unhandled' / etc." } } } } }, "Breadcrumb": { "description": "ARP §3.6 (v1.1.1): timestamped event leading up to a Warning. Sentry-aligned. Critical for understanding cascade failures.", "type": "object", "required": ["timestamp", "type"], "additionalProperties": true, "properties": { "timestamp": { "$ref": "#/$defs/Timestamp" }, "type": { "type": "string", "enum": ["navigation", "http", "error", "info", "query", "ui", "user", "default"] }, "category": { "type": "string", "description": "Free-form category, e.g. 'console', 'fetch', 'agent.call'." }, "message": { "type": "string", "description": "Human-readable description of the event." }, "level": { "$ref": "#/$defs/Severity" }, "data": { "type": "object", "additionalProperties": true, "description": "Free-form structured data attached to the breadcrumb." } } }, "Report": { "type": "object", "required": ["summary"], "additionalProperties": true, "properties": { "severity": { "$ref": "#/$defs/Severity" }, "summary": { "type": "string", "minLength": 1 }, "score": { "type": "number", "minimum": 0, "maximum": 100, "description": "Optional 0-100 health score for the agent run." }, "metrics": { "type": "object", "additionalProperties": true }, "warnings": { "type": "array", "items": { "$ref": "#/$defs/Warning" } }, "recommendations": { "type": "array", "items": { "$ref": "#/$defs/Recommendation" } }, "scores": { "type": "array", "items": { "$ref": "#/$defs/Score" }, "description": "ARP §3.5 (v1.1): evaluation scores attached to this report. Use Score for typed eval outcomes; the legacy `report.score` 0-100 health number is for one-off summaries only." }, "handoffs": { "type": "array", "items": { "$ref": "#/$defs/Handoff" }, "description": "ARP §6.2 (v1.2): multi-agent delegation events that occurred during this report. OpenAI Agents SDK Handoff lineage." }, "_meta": { "type": "object", "additionalProperties": true } } }, "Handoff": { "description": "ARP §6.2 (v1.2): one multi-agent delegation event. Lineage: OpenAI Agents SDK `Handoff` class.", "type": "object", "required": ["from_agent", "to_agent"], "additionalProperties": true, "properties": { "from_agent": { "type": "string", "minLength": 1 }, "to_agent": { "type": "string", "minLength": 1 }, "trigger": { "type": "string", "description": "Human-readable reason for the handoff." }, "tool_name_override": { "type": "string" }, "input_filter": { "type": "string", "description": "Optional regex/spec describing how the input is filtered before delegation." }, "nest_handoff_history": { "type": "boolean" }, "occurred_at": { "$ref": "#/$defs/Timestamp" }, "_meta": { "type": "object", "additionalProperties": true } } }, "PromptManifest": { "description": "ARP §6.3 (v1.2): prompt provenance. Lineage: OpenInference prompt.* attributes + LangSmith Prompt Hub.", "type": "object", "additionalProperties": true, "properties": { "vendor": { "type": "string", "description": "e.g. 'langsmith', 'openai', 'self', 'anthropic-prompts'" }, "id": { "type": "string" }, "version": { "type": "string" }, "url": { "type": "string", "format": "uri" }, "template": { "type": "string", "description": "Inline template body. Senders SHOULD provide either url OR template." }, "variables": { "type": "object", "additionalProcto-template/skills/agent-reporting/scripts/validate.mjscode · 6.6 KB
#!/usr/bin/env node // validate.mjs — validator for the Agent Report profile (ARP v1.2.1 adoption, // conformance target L2). // // This is a PROFILE check, not a full JSON-Schema engine: it enforces exactly the // items the profile commits to (required fields, enums, fingerprint and timestamp // shapes). The normative JSON Schema is vendored at // skills/agent-reporting/references/arp/agent_report.schema.json. // // Usage: node skills/agent-reporting/scripts/validate.mjs <file.json> [more.json ...] // Exit: 0 when every file is valid ("N report(s) valid"), 1 with per-file problems. import { readFileSync } from "node:fs"; const SEVERITY = ["fatal", "error", "warning", "info", "debug"]; // report.severity / warning.level const WARNING_SEVERITY = ["critical", "high", "medium", "low", "info"]; // warning.severity const WARNING_STATUS = ["unresolved", "resolved", "ignored", "muted"]; const CATEGORY = ["performance", "security", "content", "seo", "quality", "compliance", "infra", "general"]; const RECOMMENDATION_STATUS = ["open", "applied", "dismissed"]; const PRIORITY = ["critical", "high", "medium", "low"]; const EFFORT = ["trivial", "small", "medium", "large"]; const PATTERN_STATUS = ["active", "solved", "obsolete"]; const AGENT_RE = /^[a-z][a-z0-9_-]*$/; const HEX64_RE = /^[0-9a-f]{64}$/; // RFC 3339 instant as ARP uses it (UTC "Z" form; offsets also legal). const TS_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/; const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v); const isStr = (v) => typeof v === "string" && v.length > 0; const isInt = (v, min = 0) => Number.isInteger(v) && v >= min; const inEnum = (list, v) => list.includes(v); const isFingerprint = (v) => isStr(v) && (HEX64_RE.test(v) || (Array.isArray(v) && v.length > 0 && v.every(isStr))); function validate(report) { const problems = []; const need = (cond, path, msg) => { if (!cond) problems.push(`${path}: ${msg}`); }; need(isObj(report), "$", "report must be an object"); if (!isObj(report)) return problems; need(isStr(report.arp_version), "arp_version", "required, non-empty string (we emit \"1.2.1\")"); need(isStr(report.agent) && AGENT_RE.test(report.agent) && report.agent.length <= 64, "agent", 'required, lowercase slug matching ^[a-z][a-z0-9_-]*$ (max 64)'); const r = report.report; need(isObj(r), "report", "required object"); if (isObj(r)) { need(isStr(r.summary), "report.summary", "required, non-empty sentence(s)"); if (r.severity !== undefined) need(inEnum(SEVERITY, r.severity), "report.severity", `one of ${SEVERITY.join("|")}`); if (r.score !== undefined) need(typeof r.score === "number" && r.score >= 0 && r.score <= 100, "report.score", "number 0-100"); if (r.metrics !== undefined) need(isObj(r.metrics), "report.metrics", "object"); for (const [i, w] of (r.warnings ?? []).entries()) { const p = `report.warnings[${i}]`; need(isObj(w), p, "object"); if (!isObj(w)) continue; need(isStr(w.message), `${p}.message`, "required, non-empty"); if (w.fingerprint !== undefined) need(isFingerprint(w.fingerprint), `${p}.fingerprint`, "hex sha256 or string array"); if (w.level !== undefined) need(inEnum(SEVERITY, w.level), `${p}.level`, `one of ${SEVERITY.join("|")}`); if (w.severity !== undefined) need(inEnum(WARNING_SEVERITY, w.severity), `${p}.severity`, `one of ${WARNING_SEVERITY.join("|")}`); if (w.status !== undefined) need(inEnum(WARNING_STATUS, w.status), `${p}.status`, `one of ${WARNING_STATUS.join("|")}`); if (w.category !== undefined) need(inEnum(CATEGORY, w.category), `${p}.category`, `one of ${CATEGORY.join("|")}`); for (const t of ["first_seen", "last_seen"]) { if (w[t] !== undefined) need(isStr(w[t]) && TS_RE.test(w[t]), `${p}.${t}`, "RFC 3339 timestamp"); } if (w.count !== undefined) need(isInt(w.count, 1), `${p}.count`, "integer >= 1"); } for (const [i, rec] of (r.recommendations ?? []).entries()) { const p = `report.recommendations[${i}]`; if (!isObj(rec)) { problems.push(`${p}: object`); continue; } need(isStr(rec.title), `${p}.title`, "required, non-empty"); if (rec.fingerprint !== undefined) need(isFingerprint(rec.fingerprint), `${p}.fingerprint`, "hex sha256 or string array"); if (rec.priority !== undefined) need(inEnum(PRIORITY, rec.priority), `${p}.priority`, `one of ${PRIORITY.join("|")}`); if (rec.category !== undefined) need(inEnum(CATEGORY, rec.category), `${p}.category`, `one of ${CATEGORY.join("|")}`); if (rec.effort !== undefined) need(inEnum(EFFORT, rec.effort), `${p}.effort`, `one of ${EFFORT.join("|")}`); if (rec.confidence !== undefined) need(typeof rec.confidence === "number" && rec.confidence >= 0 && rec.confidence <= 1, `${p}.confidence`, "number 0-1"); if (rec.status !== undefined) need(inEnum(RECOMMENDATION_STATUS, rec.status), `${p}.status`, `one of ${RECOMMENDATION_STATUS.join("|")}`); } } if (report.memory !== undefined) { need(isObj(report.memory), "memory", "object"); if (isObj(report.memory)) { if (report.memory.total_runs !== undefined) need(isInt(report.memory.total_runs), "memory.total_runs", "integer >= 0"); for (const [i, pat] of (report.memory.learned_patterns ?? []).entries()) { const p = `memory.learned_patterns[${i}]`; if (!isObj(pat)) { problems.push(`${p}: object`); continue; } need(isStr(pat.pattern), `${p}.pattern`, "required, non-empty"); if (pat.fingerprint !== undefined) need(isFingerprint(pat.fingerprint), `${p}.fingerprint`, "hex sha256 or string array"); if (pat.status !== undefined) need(inEnum(PATTERN_STATUS, pat.status), `${p}.status`, `one of ${PATTERN_STATUS.join("|")}`); } } } if (report._meta !== undefined) need(isObj(report._meta), "_meta", "object"); return problems; } const files = process.argv.slice(2); if (files.length === 0) { console.error("usage: node scripts/agent-report/validate.mjs <file.json> [more.json ...]"); process.exit(2); } let ok = 0; let bad = 0; for (const f of files) { let parsed; try { parsed = JSON.parse(readFileSync(f, "utf8")); } catch (err) { console.error(`${f}: not valid JSON (${err.message})`); bad += 1; continue; } const problems = validate(parsed); if (problems.length === 0) { ok += 1; console.log(`${f}: valid (L2 profile)`); } else { bad += 1; console.error(`${f}: ${problems.length} problem(s)`); for (const p of problems) console.error(` - ${p}`); } } if (bad > 0) { console.error(`${ok} valid, ${bad} invalid`); process.exit(1); } console.log(`${ok} report(s) valid`);cto-template/skills/agent-reporting/templates/report-template.jsoncode · 1.3 KB
{ "arp_version": "1.2.1", "agent": "your-agent", "report": { "severity": "info", "summary": "One or two plain sentences: what the agent did this period, and what (if anything) is stuck.", "score": 100, "metrics": { "tasks_completed": 0, "runs": 0, "wall_time_minutes": 0 }, "warnings": [ { "fingerprint": "0000000000000000000000000000000000000000000000000000000000000000", "message": "Example warning — replace with a real one, or delete this array entry", "level": "info", "severity": "low", "status": "unresolved", "category": "general", "first_seen": "2026-01-01T00:00:00Z", "last_seen": "2026-01-01T00:00:00Z", "count": 1 } ], "recommendations": [ { "fingerprint": "0000000000000000000000000000000000000000000000000000000000000000", "title": "Example recommendation — replace or delete", "details": "What to do, why, and how you would know it worked.", "priority": "low", "category": "general", "effort": "small", "confidence": 0.5, "status": "open" } ] }, "memory": { "schema_version": "1.2.1", "total_runs": 0, "learned_patterns": [] }, "_meta": { "dev.example.period": { "from": "2026-01-01", "to": "2026-01-07" } } }cto-template/skills/box-interview/SKILL.mdmarkdown · 4.4 KB
name: box-interview description: "Use at the very start on a brand-new machine — the first conversation that decides what the box is for and which install path to take."
Box interview — the first conversation
A new machine is not an install problem yet; it is a question. Who is this for, what is it for, and what already exists? The answers choose the path. Do the interview before you install anything, and write the answers down where the owner can see them.
When to use
- First contact on a fresh box, with a non-technical owner.
- Whenever the answer to "what is this box for?" would change what you install.
Rules for this conversation
- Plain language, no jargon. The owner is buying judgment, not vocabulary. "The box will keep its own notes between conversations" beats "durable memory backend".
- Explain before you install. Say what you are about to do, what it changes, and how you will know it worked — then do it. Never install something silently.
- Never ask for a secret in chat. If a step needs a key or a password, the owner types it into a masked prompt or into a file on the machine. It never travels through the conversation.
- One decision at a time. Five questions, asked in order, each answered before the next.
- A failed check stops the line. If a verification step fails, say so, say what it means, and stop. Do not build the next thing on an unverified one.
The five questions
# Question Why it decides something 1 What is this box for? (one agent for one person / a fleet / a project sandbox) Sets how many profiles and whether they need to talk to each other 2 Is there an existing laptop, fleet or account to connect to? Chooses "standalone" vs "join an existing set of agents" 3 Which model provider will you use — hosted, self-hosted, or both? Drives the model-keysskill and the spend story4 Where should the data live, and who can reach the machine? Drives remote-access(private network vs local-only) andenable-backups5 When something breaks, who is told, and how? Drives enable-liveness(the alert path must reach a human, not a log)Also capture the two facts that decide the safety rails: whether the owner wants the agent to act without asking (default: no), and whether any regulated or client data will touch the box (if yes, say plainly that it belongs elsewhere and scope it out here).
Choosing the path
- Solo owner, one agent, no fleet — the common case. Install one profile and wire remote access, keys, backups, liveness and the safety baseline.
- Joining an existing fleet — the same line, plus: do not create a second polling consumer for
a messaging token that is already in use (see
references/lessons-and-guards.md). - Local-only / no hosted provider — the model runs on the box. Keys may be unnecessary, but backups, liveness and the safety baseline still apply.
The install line
Work the skills in this order; each one ends with a verification that must pass before the next:
remote-access→ 2.model-keys→ 3.enable-backups→ 4.enable-liveness→safety-baseline→ 6.create-new-agent(the finale: the owner builds their first agent)
VERIFY
- The owner can restate, in their own words: what the box is for, which model it will use, who can reach it, where the backups go, and who gets told when something breaks.
- All five answers are written down on the machine (a plain notes file in the owner's area), not only in the conversation — a decision that lives only in a chat is lost.
- Nothing has been installed yet.
Report (plain language)
"Here is what I understood: this box is for ; it will use ; only you can reach it, over <private network / this machine>; backups go to ; and if something breaks, is told by . I will start with remote access. Stop me if any of that is wrong."
Pitfalls
- Skipping the interview to "get going". Every re-install after a wrong path costs more than the five questions.
- Letting the owner hand you a key in the chat. Decline it, and offer the masked prompt instead.
- Promising what the box will do before it can do it. Describe the state you have verified, and label the rest as "next".
- Assuming a fleet. A first box is usually one agent and one owner; do not build for the fleet the owner does not have yet.
cto-template/skills/create-new-agent/SKILL.mdmarkdown · 5.3 KB
name: create-new-agent description: "Use when creating, cloning, wiring or retiring an agent profile — directory layout, identity, gateway install and machine hygiene."
Profile setup — born with a backup, wired on purpose
A Hermes profile is one agent's whole self: identity, configuration, skills, memory and scripts, all under one directory. This skill is how a profile is created, cloned, wired to its gateway and retired — consistently, so the fleet stays legible.
When to use
- Standing up a new agent, or cloning an existing one as a starting point.
- Adding/removing a messaging gateway, or wiring a new profile to a shared service.
- Retiring a profile (parking it without destroying its history).
The layout you are creating
~/.hermes/profiles/<name>/ ├── SOUL.md # identity: goal / style / authority ├── config.yaml # provider, model, tools, approvals ├── .env # secrets (0600) — never committed ├── skills/ # profile-local skills ├── memories/ # durable notes ├── scripts/ # that profile's own tooling ├── sessions/ # conversation history └── cron/ # this profile's scheduled jobsShared, not per-profile:
~/.hermes/skills/(the common pool),~/.hermes/kanban/, and the model cache. A profile reads the pool throughskills.external_dirsin itsconfig.yaml.Create or clone
hermes profile list # what exists hermes profile create <name> # new profile from the template hermes profile info <name> # config path, env path, stateCloning is a copy of a clean template directory plus a rename — do it deliberately, then edit the identity, because a clone that keeps the original's SOUL and
.envis a duplicate identity with the original's credentials. Change at minimum: SOUL.md, the gateway token, and any token that is scoped to a named agent.Identity is three paragraphs
SOUL.md carries goal, style and authority — nothing else. Process, commands and checklists belong in skills, where they load when relevant instead of on every turn. Write it in the second person, state what the agent may and may not do, and keep private context out: no secrets, no real people, no machine paths.
Wire the gateway
hermes gateway setup # interactive: choose platform, paste token hermes --profile <name> gateway install # install the background service hermes --profile <name> gateway status # running?Then prove it end to end: send the agent a message and watch it answer. Installing the service is not the same as a working gateway.
Do the unglamorous things at creation time
- Mode the secrets:
chmod 600the profile's.env. - Wire the backup: add the profile to the backup job now. A profile that has existed for a month with no backup has a month of unrecoverable identity.
- Set the write domain: decide which paths this agent may change, and write it down. Two agents writing one path is how work gets silently overwritten.
- Give it its own credential for any shared service, so one agent's rotation does not disturb three others.
Retire without destroying
Renaming a profile (
<name>_retired) and uninstalling its gateway parks it: the identity, memory and history remain on disk and restorable, while the machine stops running it. Before parking:- stop the gateway (
hermes --profile <name> gateway stop), then uninstall the service; - check nothing in flight still routes work to it — a job that targets a parked profile sits and waits forever, looking like a queue rather than a fault;
- leave a dated note saying what it was, that it is parked, and where its work went.
Never delete a profile to "tidy up". Disk is cheaper than a lost identity.
Machine hygiene
- Nothing new in the home root. A profile's own files live in its profile directory; shared work lives in the appropriate shared location. A home directory littered with one-off folders is how the next person cannot tell what matters.
- Scratch files go to a temp directory or a worktree, not into a checkout — a scratch file in
a repo root is one
git addaway from being published. - Prune on a schedule. Finished workspaces, stale state directories and temp debris accumulate silently; a weekly pass keeps the machine legible.
VERIFY
hermes --profile <name> status— resolves config and model.- A real message to the agent — answers.
ls -l ~/.hermes/profiles/<name>/.env— mode 600.- The backup job covers the new directory (run it once and confirm the path appears).
- Record: name, purpose, gateway platform, backup coverage, write domain.
Report (plain language)
The operator should be able to hand the new agent a job the moment you are finished. Say what exists, what it can do, and when it will next be checked:
New agent: . Purpose: . Answers on — message it and it replies. Files backed up nightly; you will get a short status every morning. Its write area is ; ask before it touches anything else.
Name anything you could not finish (
the phone gateway needs you to scan a code once) as an open item with the person who must act, never as an omission.cto-template/skills/enable-backups/SKILL.mdmarkdown · 5.5 KB
name: enable-backups description: "Use when backing up or restoring agent profiles — schedules, verification, restore drills and off-host copies."
Backup and restore — an untested backup is not a backup
The fleet's value is in
~/.hermes/: each profile's identity, configuration and memory, the shared skill pool, the session and kanban databases, and the secrets that make it run. This skill covers what to copy, how to schedule it, and — the part people skip — how to prove it restores.When to use
- Setting up a new machine or a new profile (wire its backup at creation, not later).
- A scheduled verification pass, or a restore drill.
- Before any risky migration, upgrade or bulk edit.
What to back up
Path Why ~/.hermes/profiles/<name>/identity, SOUL, config, skills, memory, scripts, sessions ~/.hermes/skills/the shared skill pool every profile reads ~/.hermes/kanban/boards and card history ~/.hermes/cron/scheduled jobs ~/.hermes/.envand each profile's.envcredentials — these are the sensitive part Exclude by default: caches, model downloads,
node_modules, browser profiles, anything regenerable. Backing up a cache just makes the backup slow and the restore confusing.Scheduling
Use a launchd agent (macOS) or a systemd timer (Linux) that runs a script you keep in profile or ops territory — not a cron job inside Hermes itself. The point of the backup is to survive the thing that is broken; if the backup runs inside the broken thing, it stops with it.
Shape of a safe script:
set -euo pipefail.- Tar (or
git commit) the paths above into a timestamped artifact. - Push the artifact to a destination that is not the same machine and not the same disk.
- Verify what you just wrote (see below) and log one line per run.
- Prune old artifacts by a retention rule you can state out loud.
Built-in snapshot and restore
Hermes ships a whole-home backup and restore pair — use these for a snapshot before a risky change:
hermes backup # zip the Hermes home directory hermes import <archive.zip> # restore a Hermes backup from a zip hermes checkpoints # inspect / prune / clear ~/.hermes/checkpoints/They are the fast path for "before I touch this". They are not a substitute for the scheduled off-host job: a zip on the same disk is a checkpoint, not a backup.
hermes profile export <name>andhermes profile import <archive>move a single profile as a portable archive and are the right tool for a per-profile snapshot or a machine-to-machine move.VERIFY — every run, not once a year
A backup job's success exit code says the archive was written; it says nothing about whether the archive is usable. Verify, cheaply, on every run:
- the artifact exists and is non-zero;
- it lists the files you expect (
tar -tzf <artifact> | head) — an empty archive "succeeds"; - its hash is recorded, so a later corruption is detectable.
Then, on a slower cadence (monthly), do a restore drill into a scratch name:
hermes profile import <archive> --name restore-drill-<date> ls ~/.hermes/profiles/restore-drill-<date>/ hermes --profile restore-drill-<date> status # ... then delete the drill profile, and record that you didDo the drill against a copy — never restore over a live profile "to save time". A restore that replaces a working profile with a half-broken one is worse than no restore.
Secrets in backups
Backups contain credentials, so:
- the destination must be private (a private repo, an encrypted bucket, an encrypted disk);
- never write an archive containing
.envto a public or shared location; - prefer encrypting the archive at rest, and record where the key lives;
- treat the backup itself as a credential: it is readable by anyone who can read the destination.
Restore, in order
- Stop the thing you are about to overwrite (
hermes pause, or stop the gateway). - Restore to a new path or new profile name first.
- Verify it starts (
hermes --profile <restored> status) and that its identity is the one you expect. - Only then swap it into place, keeping the previous directory until the swap is proven.
- Record what you restored, from which artifact, at which timestamp — and what you did with the copy you superseded.
Pitfalls
- A backup on the same disk is a copy, not a backup. Disk failure takes both.
- Backups are append-only. Never edit or delete history in a backup destination; a rewritten backup cannot prove what it held.
- A restore that restores the bug. If the corruption predates the most recent artifact, the newest backup restores the corruption too. Know how far back your artifacts go and test going back further when in doubt.
- Restoring a profile does not restore its launchd service. Reinstall the gateway after a
restore (
hermes --profile <name> gateway install) and verify with a real turn. - Verifying the tool's own report is not verification. List the archive and start the restored profile; do not report success from the job's exit code alone.
Report (plain language)
The operator does not want the command; they want to know the data is safe. One shape, filled from what you actually measured:
Backups: on, every night at . Last run succeeded ; I listed the archive and it holds the profiles. Off-host copy: . Last restore drill: . Files are covered, secrets are not in the archive.
Never say "backed up" from an exit code — say what you listed and when you last restored.
cto-template/skills/enable-liveness/SKILL.mdmarkdown · 5.4 KB
name: enable-liveness description: "Use when checking whether the agent fleet is alive — gateways, dashboard, cron and scheduled jobs. Reports per-profile state and recovers the routine failures."
Fleet liveness — is everything actually running?
A fleet that "looks fine" is not a measurement. This skill is the routine sweep: one pass that answers, per profile, is the gateway up, is the scheduler ticking, is anything warning, plus the small set of recoveries that fix the common failures without escalation.
When to use
- A scheduled sweep (daily is plenty) or a watchdog firing.
- The operator says "agent X did not reply" / "the dashboard is down".
- Before and after any change to a gateway, profile or scheduled job — measure both sides.
The single most useful command
hermes status # resolved model, provider, gateway, stores — all componentsStart there. It answers most "is it up" questions without a per-profile loop. Then go deeper only where
hermes statusor a report points.Per-profile sweep
# 1. Which profiles exist on this machine ls ~/.hermes/profiles/ # 2. Gateway state, per profile (installed? running? exit status?) hermes gateway list # list all profiles and their gateway status hermes --profile <name> gateway status # 3. Background services as the OS sees them (macOS launchd) launchctl list | grep -i 'hermes'Reading
launchctl listoutput — it prints three columns,PID,last exit status,label:Column Meaning Red flag PID the running process id, or --on a service you expect to be uplast exit status exit code of the last run anything non-zero ( 75,78,1)label e.g. ai.hermes.gateway-<profile>a label that is missing entirely A non-zero last exit status with a live PID usually means "it died once and launchd restarted it" — worth a log read, not an alarm. A
-PID is the real one.Scheduled jobs (cron)
hermes cron status # is the scheduler running at all? hermes cron list # every job, its schedule and its state hermes cron incidents # jobs that have been failing or misfiring hermes cron runs --limit 20 # recent attempts, with outcomes hermes cron doctor # health check for the schedulerAbsence of output from a job is not success. A job whose last several runs are missing has stopped firing, and nothing will tell you unless you look — this is exactly the sweep that finds it.
Recovery ladder (cheapest first)
- Restart the one gateway:
hermes --profile <name> gateway restart - Reinstall the service if the plist is missing or stale:
hermes --profile <name> gateway uninstall && hermes --profile <name> gateway install - Restart every profile's gateway — loop the profiles, restart one at a time, and let each settle before the next.
- Read the log before escalating. The gateway's own log names the failure; a restart without reading it means the same failure returns and you have learned nothing.
- Escalate to the operator with the measurement, the action taken, and the current state —
never "it's broken", always "gateway for
<profile>had exited with status 75 at ; I restarted it at and it has held for ."
Emergency stop
hermes pause # pause cron/kanban dispatch and new gateway turns hermes resume # lift itUse it when an agent is doing something harmful and you need the whole machine quiet now. It stops new work; it does not kill work already in flight.
Pitfalls
- Never
killa launchd-managed process by PID. launchd will restart it, sometimes in a throttled loop, and you will have created a second problem. Usehermes gateway restartorlaunchctl kickstart -k. launchctl kickstartneeds a GUI session on macOS; over a remote shell use thehermessubcommands instead of hand-rolling launchctl calls.- Repeated rapid restarts hit launchd's throttle — the service reports
Input/output erroror refuses to load. Wait, then reinstall once, rather than retrying in a loop. - A parked profile's service is not "down". If a profile was retired deliberately, its missing gateway is the intended state. Check whether the profile is still meant to exist before reporting a fault.
- A green sweep is only green for the things you asked about. Say which checks you ran and
which you did not (
hermes status, gateway list, cron status — and note anything you could not reach).
VERIFY (do this, don't assume)
- After any recovery:
hermes --profile <name> gateway statusand one real end-to-end turn (send the agent a message and see it answer). The second is the only proof that matters. - Record the before/after state so the next sweep can compare. A liveness claim without a timestamp is not evidence.
Report (plain language)
A sweep that ends in silence is only useful if the silence is trustworthy. Say what you checked, what was wrong, what you did, and what is still not right — in that order, in the operator's language:
Checked the fleet at . 3 of 3 agents answering. One gateway had stopped; restarted it and confirmed with a real reply. Nothing else needs you.
Name the things you could not check as clearly as the things that passed. "All good" without the list is the report that hides the outage.
cto-template/skills/enable-liveness/references/diagnostics-matrix.mdmarkdown · 3.2 KB
Diagnostics matrix — symptom to first action
One row per symptom you will actually meet. Probe from the top down, record the output of each probe, and stop when the cause is identified — do not keep replacing parts.
Symptom Probe Likely cause First action An agent does not reply hermes --profile <p> gateway status;launchctl list | grep hermesgateway process dead, or exit status non-zero hermes --profile <p> gateway restartDashboard unreachable from another device hermes dashboard --status; the bind addressbound to loopback tunnel ( ssh -L) or a private overlay — do not rebind publiclyDashboard unreachable even locally hermes dashboard --status; the logport in use, or it crashed at startup --stop, read the log, restart on a free portA scheduled job stopped firing hermes cron list;hermes cron incidents;hermes cron runsjob misfiring, or the scheduler not running hermes cron status; fix the job, then confirm one real runDisk filling up df -h;du -sh ~/.hermes/*model caches, session history, old workspaces, logs prune caches and finished workspaces; never delete profiles or backups Profile will not start hermes --profile <p> status; the session/server logbad config value, missing key, stale generated client read the error verbatim; fix the named field Gateway restart loop gateway log; launchctl listexit status repeatingthrottle from rapid restarts, or a config error that kills it at boot stop, fix the cause, install once, wait out the throttle Model errors (401 / 429) the error text; the provider dashboard bad/expired key, or rate/quota limit 401 → check the key is present and valid; 429 → back off, do not retry in a tight loop Responses very slow hermes status(provider/model); host loadremote provider latency, or a local model too large for the host confirm the intended provider/model, then check host memory/CPU Command refused by the guard the refusal text, verbatim a dangerous-command shape (destructive git, env+config paths together) re-shape the command (paths into a script file), never route around the guard Agent answers but tools do nothing the tool result text; hermes toolstool disabled for the platform, or a missing dependency enable the tool for that platform; check the dependency named Work stuck looking pending the board/queue; is the assignee still a live profile the target profile was retired, so nothing dispatches it route the work to a live owner; a parked profile is not a queue How to use it well
- Probe before acting. Every row's value depends on the probe output. A restart without a reading is a coin flip that also destroys the evidence.
- One change at a time. Two simultaneous fixes and you cannot say which one worked — and the one that did not will be back.
- Record the before/after. The next incident starts from your last measurement.
- Escalate with the measurement. "Gateway had exit status 75 at 09:12; log line:
<line>; I restarted it at 09:14; it has held 30 minutes" is an escalation someone can act on. "It's broken" is not.
cto-template/skills/enable-liveness/references/incident-playbook.mdmarkdown · 4.3 KB
Incident playbook — six incidents, each with a containment-first shape
Every incident follows the same four beats: detect (what measurement told you), contain (stop the bleeding), recover (restore the service), record (what happened, what you did, what is left). Contain before you investigate: a tidy root-cause analysis on a still-leaking system is worth less than a blocked leak and a rough story.
1. A gateway is down
- Detect: the agent does not answer;
hermes --profile <p> gateway status; a-PID inlaunchctl list. - Contain: nothing leaks by being down — the risk is the opposite (work routed into a dead profile). If other work feeds this agent, tell the sender it is down.
- Recover: read the log line first, then
hermes --profile <p> gateway restart. If it dies again,uninstall+installonce. Verify with a real message. - Record: profile, exit status, log line, restart time, whether it held.
2. Disk full
- Detect:
df -h; a write failure in a log; an agent failing to save sessions. - Contain: free space before deeper investigation — a full disk makes every other failure look mysterious. Delete regenerable data only: model caches, finished worktree workspaces, stale temp directories, old logs.
- Recover: find the biggest offenders (
du -sh ~/.hermes/*), prune what is regenerable, then confirm the write path works by writing a file. - Record: what filled it, what you deleted (and confirm you did not delete a profile or a backup), the free space before/after. Never delete a profile, backup, or session store to make room.
3. Scheduled jobs not firing
- Detect:
hermes cron list/hermes cron incidents; a job with no recent runs. - Contain: decide whether the missed job's work is urgent; hand-run the job once if the effect is needed now.
- Recover:
hermes cron doctor; fix the job's command or schedule; confirm one real run (hermes cron runs). - Record: which jobs missed, for how long, whether anything downstream depended on them. Absence of output is not success — say plainly how long the gap was.
4. A credential leaked
- Detect: a key found in a repo, a transcript, a log, or reported by a provider.
- Contain: rotate/revoke at the issuer first. Nothing else matters until the leaked value is dead. Verify the old value is actually refused afterwards — a "revoked" key that still works is the common failure.
- Recover: update every consumer with the new value, verify each, then remove the leaked copy.
- Record: what leaked, what it could reach, when it was rotated, what remains. Never write the credential itself into the record — reference it by name and location.
5. A profile is corrupted or lost
- Detect: the profile will not start; config parse errors; a directory that is half-missing.
- Contain: stop the gateway so it cannot write more broken state.
- Recover: restore to a new name from the newest artifact that predates the damage; verify it starts and its identity is right; only then swap it in, keeping the damaged copy aside.
- Record: which artifact restored it, its timestamp, what was lost between it and now, and whether the backup cadence was adequate (if the newest artifact predates the damage and the next one is a month away, that is a finding about the schedule, not the incident).
6. The machine is lost or stolen
- Detect: the host is gone; a device you no longer control holds the fleet's credentials.
- Contain: treat every credential on that machine as compromised — revoke them, starting with the ones that reach other systems. Disk encryption is what makes this containable; if the disk was not encrypted, assume full disclosure.
- Recover: stand up a new host from the off-host backups, restore profiles, rotate credentials as they are re-issued. This is the incident the backup schedule exists for — and the reason the backup destination must be off the machine.
- Record: what was on it, what was rotated, what the restore drill proved, and what the schedule should change.
The one line to remember
Contain, then recover, then explain. An explanation delivered while the leak is open is the wrong order, and an incident report that hides an unverified step is worse than no report.
- Detect: the agent does not answer;
cto-template/skills/model-keys/SKILL.mdmarkdown · 5.6 KB
name: model-keys description: "Use when configuring the model provider and API keys on a box — provider choice, masked key intake, .env hygiene and spend caps."
Model keys — give the agent a brain, without leaking the key
The agent is useless until it can reach a model, and this is the step where a secret most often leaks. The rule is absolute: a key value never appears in the conversation, in a config file, or in any repository. It goes into the profile's
.env, entered by the owner, and the file is mode0600.When to use
- A new box, after
box-interviewhas chosen hosted / self-hosted / both. - Rotating a key, or moving a profile to a different provider.
- Diagnosing "the agent answers with an authentication error".
Step 1 — choose the provider (one, or two at most)
Option When it fits What it needs Hosted API (e.g. DeepSeek, OpenAI, Anthropic, OpenRouter) Almost always — fastest to a working agent An account and one API key Self-hosted / local model Privacy-first, no per-call cost, or no reliable internet A model runtime serving an OpenAI-compatible endpoint on the machine Both Hosted for quality, local as a fallback Both of the above, and a stated default Pick one default and write down why. Two providers configured "just in case" is how an agent ends up silently on the expensive one.
For a local runtime, the provider is the generic
customentry withbase_urlpointing at the runtime on127.0.0.1— a local server is not a hosted provider and must not be exposed off the box.Step 2 — create the key (the owner does this part)
Walk the owner to the provider's own key page and have them create a key scoped to the minimum (one project, no billing-admin rights). Say where the key page is and what to click; do not create it for them and do not ask them to send it to you.
Step 3 — masked intake, straight into
.envThe key must not pass through the chat, a screenshot, or a shell history line. Have the owner enter it where it cannot be echoed:
# run on the box, as the owner — typed input is not echoed read -s -p "Paste the API key, then press Enter: " MODEL_KEY printf 'MODEL_API_KEY=%s\n' "$MODEL_KEY" >> ~/.hermes/profiles/<profile>/.env unset MODEL_KEY chmod 600 ~/.hermes/profiles/<profile>/.envThen confirm the shape without printing the value — length, not content:
awk -F= '/^MODEL_API_KEY=/{printf "MODEL_API_KEY set, %d chars\n", length($2)}' \ ~/.hermes/profiles/<profile>/.envconfig/env.examplein this pack lists every variable the template expects, key names only. Never write a value intoconfig.yaml: it is not the secret store, it is the file that gets copied, diffed and shared.Step 4 — point the profile at the provider
config.yamlcarries the provider and model; the.envcarries the key:model: provider: <provider> # deepseek | openai | anthropic | openrouter | custom default: <model-name> base_url: '' # only for a self-hosted OpenAI-compatible runtime api_key: '' # leave empty in the file; the .env value is what is readValidate the file after editing, then check the resolved model:
hermes --profile <profile> config check hermes --profile <profile> status # prints the resolved provider and modelStep 5 — spend cap and the bills
Tell the owner to set a hard spend limit at the provider, in plain numbers, before the agent runs unattended. A per-call budget is not a cap; a provider-side monthly limit is. Say what happens when it is hit (the agent stops answering) so it is not a surprise.
VERIFY
- A live model reply on this box, through the resolved profile — not a curl to the provider:
A real answer is the only proof the key, provider and model all line up. An authentication error here means the key or the provider name is wrong; read it and fix that step, do not continue.hermes --profile <profile> chat "Reply with the single word: ready" - The
.envis0600—ls -l ~/.hermes/profiles/<profile>/.envprints-rw-------. - The key is not in
config.yamland not in any git repository —grep -c <first 4 chars>over the config, and confirm the profile directory is either untracked or git-ignored. - The key value was never echoed into the conversation or a log you keep.
Report (plain language)
"The agent can now reach using the model , and I proved it by asking it a question and getting a real answer. Your key is stored on the box in a file only its owner can read, and it was never typed into this conversation. The spend cap at is per month; if it is reached, the agent stops answering until you raise it."
Pitfalls
- A key in the chat is a leaked key. Rotate it at the provider and start this step again — do not "delete the message".
- A key in
config.yamlsurvives in every copy, backup and diff of that file. The.envis the single place it lives. chmod 600after writing, not before — and check the parent directory is not world-readable.- A model name that the provider does not serve reads as an authentication failure. Read the error text; it usually names the model.
- A local runtime bound to
0.0.0.0is an open inference endpoint for anyone on the network. It stays on loopback; a remote device reaches it through the private network, not a public bind. - Two providers, no default means the resolved model is whatever the config last said. Say which one is the default, out loud.
- A new box, after
cto-template/skills/remote-access/SKILL.mdmarkdown · 5.8 KB
name: remote-access description: "Use when reaching a Hermes host from another device — dashboard, backend server, tunnels and the exposure checks that come first."
Remote access — get to the machine without opening it to the world
You will often need to reach a Hermes host from somewhere else: your laptop from a phone, the desktop app from a second machine, a dashboard from the road. The default is deliberately local —
hermesbinds to loopback — and everything in this skill is about extending that reach without putting an unauthenticated service on the internet.When to use
- Standing up remote access to a host (dashboard, desktop app, backend server).
- Diagnosing "I can't reach it from my other device".
- Before exposing any Hermes surface beyond the machine — read the exposure section first.
Exposure rule, before anything
- A public bind requires authentication. The dashboard's
--insecureflag is deprecated and no longer disables auth: a non-loopback bind always requires an auth configuration. Do not treat "it started" as "it is safe to reach". - Prefer a private network to a public port. A VPN overlay or an SSH tunnel gives you the host with no new listening port on the public internet.
- Verify from the client, not the host. "It is listening" and "I can reach it from my phone" are different claims; only the second one is the deliverable.
The surfaces
Surface Command Default Web dashboard hermes dashboard127.0.0.1:9119Backend server (headless; powers the desktop app and remote backends) hermes serveloopback Native desktop app hermes desktoplaunches locally State check / stop hermes dashboard --status/--stop— hermes dashboard --status # is it running, on what port hermes dashboard --port <PORT> # bind a specific port hermes dashboard --host 127.0.0.1 # keep it on loopbackIf the dashboard needs a dashboard-specific auth secret or portal registration, that is a setup step —
hermes dashboard registerandhermes portalcover the self-hosted and Portal paths. Do the auth setup before you change the bind address, not after.Reaching it privately — three options, in order of preference
- Private network overlay (e.g. a mesh VPN such as Tailscale). Install on host and client, authenticate once, then reach the host by its private address with the service still bound to loopback via the overlay's own forwarding. Nothing new is exposed publicly.
- SSH tunnel — no changes to the host's bind at all:
This is the least invasive option and the one to reach for first when debugging.ssh -N -L <PORT>:127.0.0.1:<PORT> <user>@<host> # then browse http://127.0.0.1:<PORT> on the client - Public bind with authentication — only when 1 and 2 are genuinely unavailable. Set auth first, bind deliberately, verify the auth actually challenges an unauthenticated request, and record that you did it.
Authorization of the people and agents
A reachable service still needs authorization.
hermes pairingmanages DM pairing codes that authorize a user to talk to the agent; a gateway that answers an unpaired sender is misconfigured. For agent-to-agent traffic,hermes peercovers bot-to-bot DMs across machines.Diagnosing "I can't reach it"
Work outward from the host, one layer at a time — and say which layer you proved:
- Is the process up?
hermes dashboard --status(or the gateway's status). - Is it listening, and on which address? A service bound to
127.0.0.1is not reachable from another machine, no matter what the firewall says. This is the most common cause. - Does the host's firewall allow the port (if you bound beyond loopback)?
- Is the network path there? From the client: can you reach the host at all (ping/ssh)?
- Is the tunnel/overlay up? Check the tunnel process on the client before suspecting the host.
- Does the app answer? Request it and read the status code — a 401 is a working service with an auth requirement, which is a different problem from a connection refusal.
Pitfalls
- Bind address is the trap. Half of all "remote access is broken" turns out to be a loopback bind plus a firewall rule that was never the problem.
--insecureis a no-op now. Reaching for it to "make it work" does nothing and misleads the next person reading the command.- Port conflicts read as startup failure. If the port is taken the service exits; check the log rather than restarting into the same conflict.
- A tunnel that dies silently leaves the client thinking the host is down. Put the tunnel under a supervisor that restarts it, and check the client side first when something stops working.
- Do not expose a dashboard to the internet to avoid setting up a tunnel. It is a management surface for the whole agent — treat public exposure as a last resort, with auth verified from the outside before you rely on it.
VERIFY
- From the client: fetch the surface and report the status code and the page title.
- From the host:
--statusand the listening address. - With auth enabled: prove an unauthenticated request is refused, not just that an authenticated one succeeds.
Report (plain language)
Report it the way the operator asked: can they reach it, from where, and how you know. One shape, filled from what you measured:
Reachable: yes — open from over the private network. Checked from outside: refused. Dashboard listens on only; not exposed to the internet. Backups and liveness cover the host.
No tunnel names or status codes unless they ask; "it works from my phone, and the internet cannot see it" is the answer they want, with the one check that proves it.
cto-template/skills/safety-baseline/SKILL.mdmarkdown · 6.5 KB
name: safety-baseline description: "Use when hardening a Hermes host or handling credentials — file permissions, secret hygiene, approval denials, credential rotation and leak response."
Security baseline — the checks that keep the fleet's keys where they belong
An agent fleet is only as safe as the machine it runs on and the files it holds. This is the baseline: host hardening, file permissions, secret hygiene, and what to do when a key leaks.
When to use
- Standing up a new host or profile.
- A periodic security pass, or after any credential incident.
- Before exposing any Hermes surface (dashboard, gateway, API) beyond the machine.
Host hardening checklist (macOS)
Verify each with a command and record the output; "I set that" is not evidence.
fdesetup status # FileVault: must read "FileVault is On." sysadminctl -screenLock status # screen lock: must require a password defaults read com.apple.screensaver askForPassword # must be 1 defaults read com.apple.screensaver askForPasswordDelay # must be 0 /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate # firewall on softwareupdate --list # pending security updatesThen confirm the machine cannot be reached casually: no open management port on a public interface, remote login only over a private network, and the backups encrypted.
File permissions
ls -l ~/.hermes/.env # expect 0600, owner-only ls -ld ~/.hermes/profiles/*/ # profile dirs 0700 find ~/.hermes -name '.env' -perm +077 -print # anything group/world-readableEvery
.envis mode 600. Every profile directory is 700. Fix withchmod, and check the parent directory too — a readable parent leaks children that look private.Secret hygiene (the rules that matter)
- A secret never appears in a message, a report, a card, a commit or a chat. Not even to
"check" it: to confirm a value, hash it in place and quote the hash prefix.
… | shasum -a 256 | cut -c1-8proves a value matches without printing it. - Secrets live in exactly one place: the
.envfile of the profile that uses it. Not inconfig.yaml(which is committed or copied), not in a skill, not in a script argument. - Never print a provider's whole variable set.
railway variablesorenvdumps every secret at once into the transcript, which is durable. Project only the keys you need:… --json | jq '{KEY_len: ((.KEY // "") | length)}'. - Pass secrets on stdin, not in argv. A key in a command line lands in the shell history and the process table.
- A leaked key is fixed by rotation, never by deleting the copy. Deleting the readable copy leaves the leaked value valid somewhere you cannot see; revoke and reissue.
Approval denials — make the rule a mechanism
Put destructive operations in the profile's
approvals.denylist (seeconfig/config.template.yaml), so the guard refuses them rather than relying on the agent's good intentions. The set that matters most: force-push, hard reset, history rewrite,rm -rf, recursive chmod/ownership changes, and anything that pipes a credential.Credential rotation
Keep a short record per credential: what it is, where it is used, when it was issued, and who issued it. Rotating in place is a two-step: create the new value, update every consumer, verify, then revoke the old. Rotating by revoking first is how you take the fleet down to fix a non-problem.
Exposing a Hermes surface
- Bind the dashboard to loopback and put authentication in front of it before it is reachable from anywhere else. A dashboard with no auth is a remote shell for anyone who finds the port.
- Reach it over a private network (a VPN overlay or an SSH tunnel) rather than a public port.
- If an integration token is needed, issue it with the narrowest scope that works and to a dedicated identity — never reuse the operator's own credential for an agent.
- Re-verify after every change: from the client, confirm what the surface actually answers (a status code and a page, not a config file that says it should).
A key leaked — what to do, in order
- Contain: revoke/rotate the credential at the issuer first. A blocked key is worth more than a tidy investigation.
- Assess: what could that credential do, and what did it have access to? Check for use.
- Clean: remove the copy from wherever it leaked (file, commit, pasted transcript), knowing this does not undo the leak.
- Record: in the operator's own notes, state what leaked, what it could reach, when it was rotated, and what is left to do. Never paste the credential itself into that record.
Pitfalls
- A 200 response is not a permission check. A revoke that returns success but leaves the key working is the common failure; re-test with the old value after revoking.
- Public-key-style tokens are additive. Issuing a new token often does not invalidate the old one — revoking is a separate, deliberate act.
.envpaths in a script are a hazard shape. Commands naming both an env file and a config file in the same line can be refused by the guard, on the reasonable worry that the command overwrites project config. Keep the paths in files the script reads, not in the command text.- Screen lock and FileVault are the whole game on a laptop. An unlocked, unencrypted disk holding every agent's keys has no remaining security properties worth discussing.
VERIFY
Everything here is verified with a command whose output you record — a setting you believe you turned on is not a setting:
- FileVault, screen lock, firewall: the commands in the checklist above, output kept.
- Permissions:
ls -lon every file that holds a secret, mode600(directory700). - Denials: the blocked patterns are present in the profile's config and one of them has actually refused a command.
- Keys are live: each credential answered a real call after it was installed, and none of them is present in a config file, a repository, or a transcript.
Report (plain language)
Answer the operator's real question — is this box safe to leave running with our keys on it? — and name the gaps rather than smoothing them:
Disk encrypted: yes. Screen locks after minutes. Firewall on. Secrets each live in a 600-permission file, none in a config or a repo. Two things still open: , — I need from you.
A pass with an unnamed gap is the thing that gets quoted later as "we checked".
cto-template/skills/safety-baseline/references/macos-hardening.mdmarkdown · 2.5 KB
macOS hardening — the checklist to run, with its expected output
A host holding every agent's credentials is worth ten minutes of verification. Run each command, compare against the expected reading, and record what you found — including the ones that passed.
Disk encryption and lock screen
fdesetup status # expect: FileVault is On. sysadminctl -screenLock status # expect: a screen lock delay is configured (immediate) defaults read com.apple.screensaver askForPassword # expect: 1 defaults read com.apple.screensaver askForPasswordDelay # expect: 0An unlocked, unencrypted disk with
~/.hermes/.envon it has no remaining security properties: a stolen machine is a stolen fleet.Firewall and updates
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate # expect: Firewall is enabled. (State = 1) softwareupdate --list # expect: no pending security updates, or a plan to install them sw_vers # record the OS version — support and behaviour depend on itListening surfaces
lsof -nP -iTCP -sTCP:LISTEN | head -40Read this list and ask of each line: does this need to be reachable from another machine? What you expect to see: Hermes services on
127.0.0.1(loopback), nothing bound to0.0.0.0that you did not deliberately expose with authentication.File permissions
ls -l ~/.hermes/.env # 0600 ls -ld ~/.hermes/profiles/*/ # 0700 find ~/.hermes -name '.env' ! -perm 600 -print # anything not 600Fix what the last command lists, then re-run it — the check is the proof.
Background services
launchctl list | grep -i hermes ls -l ~/Library/LaunchAgents/ | grep -i hermesFor each label: confirm you know what it does, that it is meant to be running, and that the plist lives in the user's own
LaunchAgents(a system-wide launch agent running agent code is a red flag). Note the last exit status of each.Backups
hermes backup # an on-demand snapshot (zip)Confirm the scheduled job separately: it should run outside Hermes, write to a destination that is not this disk, and be encrypted at rest. A backup containing
.envfiles is itself a secret — its destination must be private.The habit that matters
Run this checklist once when the host is set up and then on a schedule. Record each pass with its date and its readings, so a later failure can be compared against a known-good state rather than guessed at. "I set that up" is not evidence; the command output is.