# Rekan.Ai Workload Packs
A workload pack is the declarative template for one business-function agent box. It answers a single question: if a customer says "give me a finance coworker", what box do we ship? The pack fixes the persona, the business tools the box may call, the actions a human must approve, the skills preloaded into the box, and the resource envelope it is deployed with.
Packs live in control-plane/rekan/catalog/workloads/ — one YAML file per pack, filename stem equal to the pack slug. Nothing in a pack is a secret; see Secrets below.
- Catalog:
control-plane/rekan/catalog/workloads/ - Deploy-time box config (not this repo):
/etc/rekan/box.yaml+/etc/rekan/box.env - Applied on the box by:
rekan-box-apply
# The catalogue
| slug | name | vertical | gateway | skills | notable guardrails |
|---|---|---|---|---|---|
general | General Coworker | general | telegram | 3 | no privileged tools at all: shell.exec, ssh.connect, secrets.read forbidden; external sends need approval |
finance-cfo | Finance Copilot | finance | slack | 4 | read-only on money: ledger.write, bank.transfer forbidden, payments.create / journal.post / price.update gated; financial data class |
finance-accounting | Accounting Clerk | finance | 4 | bank.transfer + ledger.delete forbidden; journal.post, invoice.approve, tax.filing.submit gated; closed-period posts refused | |
supplychain-logistics | Logistics Coordinator | supplychain | telegram | 4 | customs.declare forbidden; shipment.reroute, carrier.book, freight.cost_approve gated |
supplychain-procurement | Procurement Officer | supplychain | 4 | vendor.bank_detail_change forbidden; po.commit, vendor.onboard, contract.sign, price.update gated | |
legal-counsel | Legal Counsel Aide | legal | teams | 4 | legal_privileged data class; privilege.waive forbidden; contract.sign, nda.execute, legal.filing.submit, advice.finalize gated |
hr-people-ops | People Ops Coordinator | hr | slack | 4 | pii class, pii.export forbidden; offer.send, employee.terminate, payroll.adjust gated |
sales-revenue-ops | Revenue Ops Copilot | sales | telegram | 4 | forecast.publish_external forbidden; quote.send, discount.approve, contract.sign, crm.bulk_update gated |
customer-support | Support Desk Agent | support | lark | 3 | customer.record_delete forbidden; refund.issue, account.credit, data.subject_erasure gated |
engineering-devops | Platform Engineer | engineering | discord | 4 | only pack with terminal; shell.exec_root, secrets.read_plaintext, db.drop, publish.public_release forbidden; deploy.production, infra.destroy, iam.grant gated |
marketing-growth | Growth Marketer | marketing | lark | 4 | brand.impersonate forbidden; campaign.launch, publish.public, social.post_external, budget.adjust gated |
data-analyst | Data Analyst | data | slack | 4 | prod.write forbidden, writes only to scratch; dashboard.publish_public, data.export_external, pii.unmask gated |
Reading the table: skills is the count of preloaded Hermes skills, not capabilities. Approval-gated actions are the ones that stop and ask a human; forbidden actions are refused outright regardless of who asks.
# How to add a new pack
- Copy the closest existing pack as a starting point:
cp control-plane/rekan/catalog/workloads/data-analyst.yaml .../catalog/workloads/<slug>.yaml - Set
slugto exactly the filename stem. The loader keys on the stem and will reject a mismatch. - Fill every field described below. Keep the pack differentiated — a new vertical is defined by its capabilities, its guardrails and its skills, not by a renamed copy of an existing pack.
- Validate before committing:
``bash python3 -c "import yaml,glob;[yaml.safe_load(open(f)) for f in glob.glob('control-plane/rekan/catalog/workloads/*.yaml')]" \ && grep -rnE 'sk-[A-Za-z0-9]{8,}|api_key: [A-Za-z0-9]' control-plane/rekan/catalog/workloads/ ; ls -1 control-plane/rekan/catalog/workloads/*.yaml | wc -l ``
- Add a row to the table above and open a PR.
# Schema, field by field
Every pack has exactly these top-level keys.
| field | type | rules |
|---|---|---|
slug | str | Must equal the filename stem. Lowercase, hyphenated, family-detail (finance-accounting) or bare family (general). |
name | str | Human label shown in the catalog and the box's SOUL.md, e.g. Finance Copilot. |
vertical | str | Family key shared by related packs (finance, supplychain, legal, hr, sales, support, engineering, marketing, data, general). Drives grouping in the UI. |
tagline | str | One line of business value, ≤90 characters. No trailing period needed. Goes in the catalog card. |
summary | str | 2–4 sentences: what the box does day to day, written for a buyer, not for an engineer. |
persona.role | str | The job title the box plays (Chief Financial Officer analyst). Becomes part of the box identity. |
persona.language | str | Working language. id unless the vertical is inherently English (legal-international, etc.). |
persona.tone | str | How it sounds. One clause, not a personality essay. |
persona.directives | list[str] | 3–6 short imperatives that change behaviour. This is where domain judgment lives — cite the source, state the currency, escalate churn risk. If a directive could apply to any agent, cut it. |
capabilities | list[str] | Dotted business-tool names the box may call: knowledge.search, sheets.read, docs.write, email.draft, erp.read, ledger.read, contract.clause_extract, po.draft, shipment.track, invoice.extract, ticket.update, crm.read, calendar.propose, … Use read/write/draft suffixes; a draft capability never commits. |
guardrails.human_approval_required | list[str] | Dotted actions that must be approved by a human before executing. Every irreversible or money-moving action belongs here — payments.create, contract.sign, po.commit, journal.post, price.update, message.send_external, deploy.production, refund.issue. An action must not appear here and in forbidden. |
guardrails.forbidden | list[str] | 3–6 dotted actions or behaviours the box must never do, regardless of approval: shell.exec, ssh.connect, secrets.read, publish.public, data.export_bulk, bank.transfer, priv.waive. |
guardrails.data_classes | list[str] | Subset of public, internal, confidential, pii, financial, legal_privileged. Declares the highest classification the box handles; the deploy tool uses it to pick egress and logging policy. |
guardrails.egress_allow | list[str] | host:port the box may reach — internal systems it needs plus its gateway host. The model endpoint goes in as the literal token model-endpoint, which the control plane expands to the configured REKAN_MODEL_BASE_URL host:port at render time; never hardcode an address here or the pack breaks the moment the deployment moves. Default-deny: anything not listed is blocked. |
skills | list[str] | Hermes skill slugs preloaded into the box. Match the workload: numbers-heavy → xlsx/powerpoint; document-heavy → pdf/docx/grounded-citations; comms-heavy → email-inbox-triage/meeting-action-items. |
toolsets | list[str] | Subset of web, terminal, files, browser, memory, cron, delegation, vision, tts. Keep narrow. terminal is never granted to finance, legal, hr or support packs. |
model.provider / api_key_env | str | Inference target. provider: custom, api_key_env: NETRARUNTIME_API_KEY. The endpoint and the model name are deployment settings, not pack data (REKAN_MODEL_BASE_URL, REKAN_MODEL_DEFAULT) — a pack that pins base_url/default ships a box that cannot reach its model when the deployment moves, and tests/test_control_plane.py fails it. api_key_env names a variable only — never a value. |
resources.cpu | int | 2–4 vCPU. 2 for coordination-heavy boxes, 4 for spreadsheet/document/data work. |
resources.memory | str | GiB string, "4GiB" or "8GiB". |
resources.disk | str | GiB string, "20GiB" or "40GiB". |
gateway.platform | str | Exactly one of telegram, slack, lark, discord, teams, email — where the coworker talks to its owner. |
gateway.enabled | bool | true for shipped packs. false keeps the box headless (cron/API only). |
version | int | Schema/pack version, currently 1. Bump only when the pack's meaning changes in a way that alters an already-deployed box. |
# Secrets
Packs are secret-free by construction. No token, password, API key or credential of any kind is written into a pack. At deploy time the operator places credentials in /etc/rekan/box.env (mode 0600) on the box itself, and rekan-box-apply installs them into the Hermes home as .env. Packs reference credentials only by variable name via api_key_env.
CI enforces this with a blocking grep for secret-shaped literals (sk-…, xox…, ghp_…, AKIA…, token=, long base64 blobs) across the catalog.
# Container safety checklist
A pack is only mergeable if all of these hold:
- parses with
yaml.safe_loadand carries every top-level key above; slugequals the filename stem,tagline≤90 chars;toolsetsnever containsterminalfor a finance, legal, hr or support pack;- every irreversible or money-moving capability appears in
human_approval_required; - no action appears in both
human_approval_requiredandforbidden; forbiddenhas 3–6 entries and includes the relevant subset ofshell.exec,ssh.connect,secrets.read,data.export_bulk,publish.public;- no literal secret anywhere in the file.
# From pack to running box
catalog/workloads/<slug>.yaml declarative template (this doc)
│ deploy tool renders it, merges operator inputs
▼
/etc/rekan/box.yaml per-box non-secret config (name, role, owner, gateway)
/etc/rekan/box.env per-box secrets, 0600, written by the operator
│ rekan-box-apply (first boot + on demand, idempotent)
▼
~/.hermes/config.yaml + SOUL.md live Hermes box, gateway service installed
rekan-box-apply is idempotent: re-running it on a live box rotates a key or renames the coworker without a rebuild. Config mutations go through hermes config set, never by hand-editing config.yaml.