TalentDelta

Our site is fully accessible by humans and agents (via WebMCP).

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

Provenance

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

    SkillIn 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 under skills/agent-reporting/references/arp/ — LICENSE, README.upstream.md and agent_report.schema.json, all © AgentMinds, licensed CC-BY-4.0. Upstream: https://github.com/agentmindsdev/profile, pinned at commit 51e335aca582460cf14a409ccbe18780c8c78e9f (committer date 2026-05-09T14:49:10Z); the three files are byte-identical to that commit (git blob shas 2b62114f, 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 own title still 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 as NOASSERTION; the CC-BY-4.0 terms in the vendored references/arp/LICENSE are the authority. Everything else in this pack — including the reporting skill's own SKILL.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 with hermes memory setup (choose hindsight) and put HINDSIGHT_API_KEY in 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 cited github.com/NousResearch/hermes-plugin-hindsight link is dead (HTTP 404, measured 2026-10-05), and the NousResearch organisation publishes other hermes-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-reporting skill 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. See LICENSE for 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.

  • 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/bash on 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 a while read loop, tr for case, and plain indexed arrays instead.

    Also: every script starts set -euo pipefail and is invoked by path, not by an inline bash -c string.

    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-access and create-new-agent.

    3. Config change → gateway restart

    Fact: a running process reads its configuration at start. hermes config set (or editing config.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 changedWhat to do
    Model / provider / web backendshermes --profile <name> gateway restart
    Skills, SOUL.md, memory filesrestart — they are read at start
    .env values (keys, tokens)restart the gateway so the new environment is loaded
    Gateway install itself (plist, command, port)hermes --profile <name> gateway uninstall then gateway install
    Messaging channel or pairingre-check hermes --profile <name> gateway status after the restart

    Guard: after any config change, restart, then prove it with a real end-to-end turn — not with a status line 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; .env is 0600

    Fact: 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 with read -s, mode 0600, and never in config.yaml, never in a repository, never pasted into chat. Check with ls -l for 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-keys and safety-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, PID and last exit status) and how you identify what to restart.

    Guard: manage these services through the hermes gateway subcommands, not by hand. Never kill a 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-agent and enable-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: check hermes cron status first — 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 — see references/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

    1. 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.
    2. Emit the envelope (start from templates/report-template.json; the vendored schema references/arp/agent_report.schema.json is normative for the full field set):
      • top level: arp_version: "1.2.1", agent (lowercase slug ^[a-z][a-z0-9_-]*$), report, optional memory, _meta.
      • report requires summary (plain sentences: what was done, what is stuck). Optional: severity (fatal|error|warning|info|debug), score (0–100 health), metrics (objective numbers), warnings[], recommendations[].
    3. 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>") where message-slug is 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.
    4. Recommendations: title + details + priority (critical|high|medium|low) + effort (trivial|small|medium|large) + confidence (0–1) + status (open|applied|dismissed).
    5. Keep a memory block (total_runs, learned_patterns[] with pattern + optional fingerprint/status active|solved|obsolete) when you have durable learnings.
    6. Namespace any local extras under _meta with 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/ with scripts/validate.mjs, templates/report-template.json and references/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.

    License: CC BY 4.0 Profile version Public comment Validate examples

    Quick start

    A minimal L0-conformant report (only agent + report are 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=false
    

    Or, 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.0 tag). 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 the Pattern object: 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 as risky_infra-equivalent (conservative default). Lineage: blast-radius tags in deployment systems (Liquibase, SemVer breaking-change severity).

    Reference collector additions (informative)

    These describe the agentmindsdev/agentminds collector'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-rules returns top_production_observed and top_documented as distinct arrays. The mixed top_rules array is kept for backwards compatibility, slated for v1.4 removal.
    • A3 — negative_evidence array. 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_evidence with reason non_english_content.

    spec_version field value

    Backend responses (/sync/me, /sync/personalized-rules, /sync/pool-stats) report spec_version: "ARP-1.3.0" from this release onward. Earlier deployments returned "ARP-1.1" until the drift was reconciled in agentminds@2a2d5f0.

    Implementation status

    SurfaceVersionLive
    Spec (this repo)v1.3.0 tag✓
    Backend collectoragentminds@a8c23b3+ (api.agentminds.dev)✓
    MCP serveragentminds-mcp@1.3.2+ (npm)✓
    Node SDK@agentmindsdev/node@0.4.0+ (npm)✓
    Python SDKagentminds@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-rules delivery 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_event envelope (cold_start / wake / scheduled / shutdown / running)
    • Exception evidence shape (Sentry-aligned mechanism.handled)
    • dotted_order on telemetry spans (LangSmith-compatible)
    • Handoff primitive for multi-agent delegation events
    • PromptManifest primitive for prompt provenance
    • AGNTCY OASF skill ID cross-references + agentskills.io linkage
    • Cloudflare 5-tier execution_tier enum + ARP in_process extension
    • MCP role disambiguation (mcp_server_exposed vs mcp_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

    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.

    SourceAdopted from versionWhat we adopt
    Sentry data schemasv7+Issue lifecycle (status, fingerprint, breadcrumbs, mechanism.handled)
    OpenTelemetry GenAI semconv1.27+ (later versions compatible — namespace is additive)gen_ai.* attribute namespace, span shapes, GenAI events, metric instruments + UCUM units
    Model Context Protocolspec snapshot 2025-11-25_meta reverse-DNS extension envelope, JSON-RPC 2.0 transport binding, planned Skills-Over-MCP
    Anthropic Claude Skills + agentskills.ioopen specSKILL.md YAML frontmatter
    AGNTCY OASFv1+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:

    SpecClosest primitiveWhy it doesn't cover §4.1
    OpenTelemetry GenAIgen_ai.* attributes on spansPer-call telemetry, not durable pattern objects with cross-site lifecycle
    Model Context ProtocolTool / resource registriesTools and resources, not failure-pattern catalogs
    AGNTCY OASFAgent descriptors + skill taxonomyDescribes agents and skills, not what those agents learned across sites
    Sentry data schemasIssue lifecycle (fingerprint, status)Single-tenant: issues belong to one project within one organization; no cross-customer pool primitive
    OWASP / CIS benchmarksHuman-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 AIVendor-internal anomaly detectionProprietary 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

    LevelRequired features
    L0Envelope + report.summary + warnings[].message
    L1L0 + severity + category + status per warning
    L2L1 + fingerprint + first_seen + last_seen + count
    L3L2 + memory.learned_patterns with §4.1 fields
    L4L3 + telemetry (OTel GenAI) OR project_info with skills
    L5L4 + 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", "additionalPro
  • cto-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

    1. Plain language, no jargon. The owner is buying judgment, not vocabulary. "The box will keep its own notes between conversations" beats "durable memory backend".
    2. 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.
    3. 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.
    4. One decision at a time. Five questions, asked in order, each answered before the next.
    5. 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

    #QuestionWhy it decides something
    1What 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
    2Is there an existing laptop, fleet or account to connect to?Chooses "standalone" vs "join an existing set of agents"
    3Which model provider will you use — hosted, self-hosted, or both?Drives the model-keys skill and the spend story
    4Where should the data live, and who can reach the machine?Drives remote-access (private network vs local-only) and enable-backups
    5When 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:

    1. remote-access → 2. model-keys → 3. enable-backups → 4. enable-liveness →
    2. 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 jobs
    

    Shared, not per-profile: ~/.hermes/skills/ (the common pool), ~/.hermes/kanban/, and the model cache. A profile reads the pool through skills.external_dirs in its config.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, state
    

    Cloning 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 .env is 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 600 the 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 add away 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

    1. hermes --profile <name> status — resolves config and model.
    2. A real message to the agent — answers.
    3. ls -l ~/.hermes/profiles/<name>/.env — mode 600.
    4. The backup job covers the new directory (run it once and confirm the path appears).
    5. 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

    PathWhy
    ~/.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/.env and 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:

    1. set -euo pipefail.
    2. Tar (or git commit) the paths above into a timestamped artifact.
    3. Push the artifact to a destination that is not the same machine and not the same disk.
    4. Verify what you just wrote (see below) and log one line per run.
    5. 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> and hermes 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 did
    

    Do 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 .env to 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

    1. Stop the thing you are about to overwrite (hermes pause, or stop the gateway).
    2. Restore to a new path or new profile name first.
    3. Verify it starts (hermes --profile <restored> status) and that its identity is the one you expect.
    4. Only then swap it into place, keeping the previous directory until the swap is proven.
    5. 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 components
    

    Start there. It answers most "is it up" questions without a per-profile loop. Then go deeper only where hermes status or 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 list output — it prints three columns, PID, last exit status, label:

    ColumnMeaningRed flag
    PIDthe running process id, or -- on a service you expect to be up
    last exit statusexit code of the last runanything non-zero (75, 78, 1)
    labele.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 scheduler
    

    Absence 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)

    1. Restart the one gateway: hermes --profile <name> gateway restart
    2. Reinstall the service if the plist is missing or stale: hermes --profile <name> gateway uninstall && hermes --profile <name> gateway install
    3. Restart every profile's gateway — loop the profiles, restart one at a time, and let each settle before the next.
    4. 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.
    5. 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 it
    

    Use 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 kill a launchd-managed process by PID. launchd will restart it, sometimes in a throttled loop, and you will have created a second problem. Use hermes gateway restart or launchctl kickstart -k.
    • launchctl kickstart needs a GUI session on macOS; over a remote shell use the hermes subcommands instead of hand-rolling launchctl calls.
    • Repeated rapid restarts hit launchd's throttle — the service reports Input/output error or 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 status and 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.

    SymptomProbeLikely causeFirst action
    An agent does not replyhermes --profile <p> gateway status; launchctl list | grep hermesgateway process dead, or exit status non-zerohermes --profile <p> gateway restart
    Dashboard unreachable from another devicehermes dashboard --status; the bind addressbound to loopbacktunnel (ssh -L) or a private overlay — do not rebind publicly
    Dashboard unreachable even locallyhermes dashboard --status; the logport in use, or it crashed at startup--stop, read the log, restart on a free port
    A scheduled job stopped firinghermes cron list; hermes cron incidents; hermes cron runsjob misfiring, or the scheduler not runninghermes cron status; fix the job, then confirm one real run
    Disk filling updf -h; du -sh ~/.hermes/*model caches, session history, old workspaces, logsprune caches and finished workspaces; never delete profiles or backups
    Profile will not starthermes --profile <p> status; the session/server logbad config value, missing key, stale generated clientread the error verbatim; fix the named field
    Gateway restart loopgateway log; launchctl list exit status repeatingthrottle from rapid restarts, or a config error that kills it at bootstop, fix the cause, install once, wait out the throttle
    Model errors (401 / 429)the error text; the provider dashboardbad/expired key, or rate/quota limit401 → check the key is present and valid; 429 → back off, do not retry in a tight loop
    Responses very slowhermes status (provider/model); host loadremote provider latency, or a local model too large for the hostconfirm the intended provider/model, then check host memory/CPU
    Command refused by the guardthe refusal text, verbatima 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 nothingthe tool result text; hermes toolstool disabled for the platform, or a missing dependencyenable the tool for that platform; check the dependency named
    Work stuck looking pendingthe board/queue; is the assignee still a live profilethe target profile was retired, so nothing dispatches itroute 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 in launchctl 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 + install once. 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.

  • 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 mode 0600.

    When to use

    • A new box, after box-interview has 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)

    OptionWhen it fitsWhat it needs
    Hosted API (e.g. DeepSeek, OpenAI, Anthropic, OpenRouter)Almost always — fastest to a working agentAn account and one API key
    Self-hosted / local modelPrivacy-first, no per-call cost, or no reliable internetA model runtime serving an OpenAI-compatible endpoint on the machine
    BothHosted for quality, local as a fallbackBoth 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 custom entry with base_url pointing at the runtime on 127.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 .env

    The 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>/.env
    

    Then 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>/.env
    

    config/env.example in this pack lists every variable the template expects, key names only. Never write a value into config.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.yaml carries the provider and model; the .env carries 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 read
    

    Validate the file after editing, then check the resolved model:

    hermes --profile <profile> config check
    hermes --profile <profile> status        # prints the resolved provider and model
    

    Step 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

    1. A live model reply on this box, through the resolved profile — not a curl to the provider:
      hermes --profile <profile> chat "Reply with the single word: ready"
      
      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.
    2. The .env is 0600 — ls -l ~/.hermes/profiles/<profile>/.env prints -rw-------.
    3. The key is not in config.yaml and not in any git repository — grep -c <first 4 chars> over the config, and confirm the profile directory is either untracked or git-ignored.
    4. 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.yaml survives in every copy, backup and diff of that file. The .env is the single place it lives.
    • chmod 600 after 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.0 is 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.
  • 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 — hermes binds 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

    1. A public bind requires authentication. The dashboard's --insecure flag 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".
    2. 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.
    3. 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

    SurfaceCommandDefault
    Web dashboardhermes dashboard127.0.0.1:9119
    Backend server (headless; powers the desktop app and remote backends)hermes serveloopback
    Native desktop apphermes desktoplaunches locally
    State check / stophermes 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 loopback
    

    If the dashboard needs a dashboard-specific auth secret or portal registration, that is a setup step — hermes dashboard register and hermes portal cover 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

    1. 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.
    2. SSH tunnel — no changes to the host's bind at all:
      ssh -N -L <PORT>:127.0.0.1:<PORT> <user>@<host>
      # then browse http://127.0.0.1:<PORT> on the client
      
      This is the least invasive option and the one to reach for first when debugging.
    3. 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 pairing manages 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 peer covers 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:

    1. Is the process up? hermes dashboard --status (or the gateway's status).
    2. Is it listening, and on which address? A service bound to 127.0.0.1 is not reachable from another machine, no matter what the firewall says. This is the most common cause.
    3. Does the host's firewall allow the port (if you bound beyond loopback)?
    4. Is the network path there? From the client: can you reach the host at all (ping/ssh)?
    5. Is the tunnel/overlay up? Check the tunnel process on the client before suspecting the host.
    6. 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.
    • --insecure is 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: --status and 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 updates
    

    Then 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-readable
    

    Every .env is mode 600. Every profile directory is 700. Fix with chmod, and check the parent directory too — a readable parent leaks children that look private.

    Secret hygiene (the rules that matter)

    1. 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-8 proves a value matches without printing it.
    2. Secrets live in exactly one place: the .env file of the profile that uses it. Not in config.yaml (which is committed or copied), not in a skill, not in a script argument.
    3. Never print a provider's whole variable set. railway variables or env dumps every secret at once into the transcript, which is durable. Project only the keys you need: … --json | jq '{KEY_len: ((.KEY // "") | length)}'.
    4. Pass secrets on stdin, not in argv. A key in a command line lands in the shell history and the process table.
    5. 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.deny list (see config/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

    1. Contain: revoke/rotate the credential at the issuer first. A blocked key is worth more than a tidy investigation.
    2. Assess: what could that credential do, and what did it have access to? Check for use.
    3. Clean: remove the copy from wherever it leaked (file, commit, pasted transcript), knowing this does not undo the leak.
    4. 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.
    • .env paths 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 -l on every file that holds a secret, mode 600 (directory 700).
    • 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: 0
    

    An unlocked, unencrypted disk with ~/.hermes/.env on 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 it
    

    Listening surfaces

    lsof -nP -iTCP -sTCP:LISTEN | head -40
    

    Read 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 to 0.0.0.0 that 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 600
    

    Fix 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 hermes
    

    For 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 .env files 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.