Everything an implementer (human or AI agent) needs: verified data sources with exact table and field names, cycle algorithms, thresholds, model routing, and prerequisites. Verified live on 2026-07-07 against the NetSuite warehouse, the Odoo 19 CRM, and the vps2 infrastructure. The plain-language overview stays the front door — this page is the workshop.
| Stage type | Model |
|---|---|
| Harness code, verifier skills, judgment calls, high-stakes alert wording, anything customer-facing | Opus |
Data pulls, SQL, diffing, digest assembly, scraping, log digging — output prefixed [codex] | GPT-5.5 via Codex (effectively free) |
| Glue stages in Workflow scripts | Sonnet |
| — | Haiku: never |
Standing escalation: judge output, not price — redo weak cheap-model output with a smarter model without asking. Effort scale used below: S ≤1 agent-day · M 2–4 days · L needs new data plumbing or app changes first.
workspace/assistant/*-baseline.json + .jsonl ledgers). Loops needing SQL-diffable history get a small table in shared Postgres reporting, schema loops (e.g. loops.inv1_snapshots). Never store state only in agent context.jobs.json, use the CLI). Judgment-heavy / Claude-MCP-dependent → Claude Code /schedule cloud routines, piloted as local /loop.bizops (Codex provider, Telegram gateway) hosts the CRM/INV/FIN/OPS digests — own HERMES_HOME, .env with Telegram bot token + Fizzy vars (FIZZY_ACCOUNT + FIZZY_API_URL + FIZZY_TOKEN — legacy FIZZY_PROFILE alone fails), a hermes-gateway-bizops.service systemd user unit, restart-notice autoclean on. DB reads via the hermes_ro Postgres role (exists; no live client config yet — wire during build).The previously-undocumented gap is closed. Everything below was read from the running system.
Creating a job — use the CLI, never hand-edit jobs.json:
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main \
--profile bizops cron create '0 7 * * *' 'Self-contained job prompt…' \
--name crm-hygiene-watchdog --deliver telegram \
--skill some-skill --script pull-data.sh --workdir /opt/some-project
# schedule accepts '30m', 'every 2h', or a 5-field cron expr (Asia/Dubai)
# other subcommands: list · edit · pause · resume · run (fire next tick) · remove · status · tick
Job entry schema (<profile>/cron/jobs.json, verified fields):
{ "jobs": [ {
"id": "…", "name": "health-probe",
"prompt": "…" , // agent instruction (or "" for script-only jobs)
"skills": ["…"], "skill": "…",
"model": null, "provider": null, "base_url": null, // PER-JOB model/provider override
"script": "foo.sh", // under <profile>/scripts/; default: stdout injected into prompt
"no_agent": true, // script IS the job; stdout delivered verbatim
"schedule": {"kind":"interval","minutes":240} | {"kind":"cron","expr":"0 8 * * 1"},
"repeat": {"times": null, "completed": 335},
"enabled": true, "state": "scheduled",
"deliver": "telegram" | "origin" | "local" | "platform:chat_id",
"origin": {"platform":"telegram","chat_id":"…"}, // deliver:"origin" replies into the creating chat
"workdir": "/abs/path", // injects that dir's AGENTS.md/CLAUDE.md, cwd for tools
"next_run_at": "…", "last_run_at": "…", "last_status": "ok", "last_error": null
} ] }
--no-agent design note: documented behavior is "empty stdout = silent" — that conflicts with our never-silent promise. Loop scripts must ALWAYS emit (including the all-green line); the Healthchecks ping still catches outright death.model/provider override means one bizops profile can run jobs on different providers — no profile sprawl needed for model routing.~hermes/.config/systemd/user/hermes-gateway-<p>.service, verified): ExecStart=<venv>/python -m hermes_cli.main --profile <p> gateway run · WorkingDirectory=/home/hermes/.hermes/profiles/<p> · Environment="HERMES_HOME=…/profiles/<p>" · Restart=always, RestartSec=5, RestartForceExitStatus=75, KillMode=mixed, ExecReload=kill -USR1, ExecStopPost=gateway.cgroup_cleanup, TimeoutStopSec=210, WantedBy=default.target.config.yaml (keys: model, providers, toolsets, agent, terminal, web, browser, checkpoints, compression, prompt_caching…), .env, cron/, scripts/, skills/, workspace/, SOUL.md, state.db. The default profile IS /home/hermes/.hermes/; named profiles live under …/profiles/<p>/..env keys for a Telegram+Fizzy profile (names verified on huxberrypm): TELEGRAM_BOT_TOKEN, TELEGRAM_ALLOWED_USERS, TELEGRAM_HOME_CHANNEL, TELEGRAM_HOME_CHANNEL_NAME, FIZZY_API_URL, FIZZY_TOKEN, FIZZY_ACCOUNT (+ legacy FIZZY_PROFILE).1c473bc6, 2026-07-07; from a 45-day / ~907-feature changelog scan)pre_tool_call plugin action that deterministically escalates any tool call to the human gate; per-tool MCP gating. "Never autopilot" becomes enforced code: Odoo reads open, write-tools gated, deny rules under every loop profile. pre_tool_call is also the upgrade path to deterministic (non-LLM-turn) Telegram approvals./api/cron/fire webhook + per-profile /p/<profile>/ inbound routing + at-most-once fire semantics — DATA-0 can fire on ETL completion instead of a clock; HUX-L5's fatigue→creative trigger gets a clean mechanism.hermes -z --usage-file emits per-run token/cost JSON; /usage per-category breakdowns; per-profile observability. No per-cron-job rollup exists — IT-3 builds that aggregation itself..env shims.deliver: local.bizops setup; /goal completion contracts + /goal wait for INV-2; web_extract truncate-and-store + free Parallel web search cut scraping costs (HUX-L2, CRM-3, INV-4).deliver: local — Hermes has no native SMTP cron delivery (verified v0.18.0).deliver: local, the script sends its own message; raw-stdout deliver: telegram is audit-only.PullEntry in config/metabase-pull.ts (repo ~/Documents/Projects/company-feed): questionId, feed slug, title, mode: "upsert"|"on-change", metricColumn, unit, optional tiles[]/chart/sourceUrl. Metabase question must exist first (author via metabase-admin skill, [Card] <Area> — <Metric> naming). Deploy: branch off feat/m1-vertical-slice, typecheck+test, PR, pull + docker compose build app pull + up -d app on vps2, run run-pull.sh, verify status:"published" http:202. Composition is all-or-nothing.systems-handbook/wiki/processes.md — table Process | Coordinated via | Status | What it does, name Process - Hermes - <Description>./ping/<uuid>; cron-type with the loop's schedule + 1h grace). Ping on success only, at the END of the cycle./usage for Claude runtimes; token logs for Hermes).Universal guardrails: read-only vault-managed credentials by default · any system-of-record write or outward message = human approval · "nothing found" is a reported result · every digest ends with a data-freshness stamp (source + as-of date).
Status pills: Ready data verified, build now · Needs prep small investigation/setup first · Needs plumbing new data connection required.
raw.items → duplicate key … items_pkey, 11 consecutive failures, no table synced after the abort) — every inventory dashboard silently ~12 days stale. Fix session run same day; sentinel makes recurrence impossible to miss.vpsops extension.netsuite_warehouse.raw.sync_log (table_name, started_at, finished_at, rows_loaded, status, error_message); freshness via max(synced_at) on raw.items/raw.transactions; Company Feed publisher_runs + logs/pull.log; GBrain source timers (netsuite-to-brain was failing in the June audit, odoo-crm-to-brain, calendar-to-brain); gbrain-dream.timer via /opt/gbrain/logs/dream.log; Healthchecks API all-checks status.status != success or rows_loaded = 0; (2) freshness: warehouse synced_at > 26h ⇒ RED, feed pull missing today's published ⇒ RED; (3) timers failed/stale > 2 days ⇒ AMBER; (4) one status line per pipeline (✅/⚠️/❌ + as-of + error excerpt); (5) on RED, diagnosis paragraph (only judgment step); (6) ping own Healthchecks check.source_id/medium_id/campaign_id 100% unset; 634 active missing both email+phone.crm.lead: activity_date_deadline (false = none), activity_state (overdue|today|planned|false), stage_id, team_id, user_id, expected_revenue, won_status, lost_reason_id, active, email_from, phone, country_id, date_last_stage_update, duration_tracking (JSON seconds-per-stage), duplicate_lead_count, rotting_days/is_rotting (native but thresholds unconfigured — 0 records match). Stages (global; crm.stage.team_id does not exist): 1 New · 2 Qualified · 3 Proposition · 5 High probability · 4 Won. Teams: 1 BR (406) · 4 Construction (94) · 5 Hospitality (644) · 6 OEM (0) · 2 Website (0). ⚠️ Default search excludes active=false — lost-lead queries need explicit ["active","=",false].stage_id != 4, active) with activity_date_deadline = false — per team + top 10 by expected_revenue (daily).activity_state = 'overdue' — list all (daily).date_last_stage_update older than New>14d, Qualified>30d, Proposition>21d, High-prob>14d (daily; tune with Rajiv after week 1).duplicate_lead_count > 0 (weekly). 6. Contact gaps (weekly). 7. Dead teams OEM/Website at 0 (weekly line).is_rotting works natively; add Activity Plans (Odoo CRM NG A5). ~1 hour.loops.crm2_snapshots(run_date, team_id, stage_id, count, value) → WoW movement per team (new/advanced/won/lost/value deltas; Odoo keeps no aggregate history — we accumulate our own) → biggest 5 moves narrated → per-team one-screen brief. duration_tracking gives stage velocity free.sales-* feed cards (headline: open pipeline value; tiles: WoW delta, won-this-week; stacked-bars chart by stage) + per-lead digest.crm.lead (team_id 5) via odoo-write, per-record approval, source stamped. Construction clone for Yasar. Qualified signals feed B2B-L4.raw.item_inventory, raw.items (averagecost, displayname, parent_id), raw.transactions + raw.transaction_lines. Verified base query:WITH last_txn AS (SELECT tl.item_id, max(t.trandate) AS last_txn_date
FROM raw.transaction_lines tl JOIN raw.transactions t ON t.id=tl.transaction_id GROUP BY 1)
SELECT i.id, i.displayname, inv.quantityonhand, i.averagecost,
inv.quantityonhand*coalesce(i.averagecost,0) AS value, lt.last_txn_date
FROM raw.item_inventory inv JOIN raw.items i ON i.id=inv.item_id
LEFT JOIN last_txn lt ON lt.item_id=inv.item_id
WHERE inv.quantityonhand > 0
AND (lt.last_txn_date IS NULL OR lt.last_txn_date < now() - interval '6 months')
loops.inv1_snapshots → diff (newly dead, revived) → top 20 by value grouped by parent_id family → Codex drafts / Opus reviews liquidation shortlist (proposals only) → freshness stamp from DATA-0.inventory feed card (headline: dead-stock value; tiles: item count, % of stock value, WoW delta).transaction_lines until re-synced).raw.items has NO lead-time / reorder-point / safety-stock / preferred-vendor columns — not extracted from NetSuite (where only 9% of items have lead times, 0% reorder points, 35% missing vendor). Fix inside the same ETL extension as FIN-1.mart.purchase_orders + lines, vendor from last POs, demand variability from WO/SO lines. Output = xlsx review batch for Lerris; approved values load into Odoo during migration data-prep (no NetSuite write path — values would die with NetSuite anyway). Coverage dashboard: % items with approved lead time / reorder point.quantityavailable + quantityonorder vs approved lead time + demand rate → "order by <date>" for long-lead items (fabric 40–95 days first); open-PO netting via mart.purchase_orders. Recipient Lerris/Ajeesh. Codex. Effort S after INV-2.nhc supplier job (deleted 2026-05-29; only 3 of 7 jobs documented, all prompt text lost — write fresh). Cadence 1st + 15th. Scope: standing watchlist (Wise Wool, Lenzing/Tencel, Yatas foam CertiPUR, Gobi cashmere) + strong-wool commodity price. Delivery: report into the wiki's raw/inbox/ → 07:00 Wiki Router auto-ingest (pattern verified live) + Telegram summary. Codex scrapes/drafts, Opus reviews before inbox drop. Effort S.duedate, no amount-remaining column anywhere (checked information_schema).CustInvc (+ CustPymt) to /opt/NetsuiteReporting with duedate, amount-remaining, entity — ~1 Codex day incl. sync_log wiring; also unblocks FIN-2 v2 + the finance feed. (b) Direct SuiteQL per run — no ETL work, slower, invisible to Metabase.finance feed card. Codex ETL + queries; Opus chase-email tone (outward text = ships). Effort M.loops.fin2_attestations (the audit trail banks like) → escalate to Rajiv if a month goes unanswered → flag new contracts with assigned debtors for correct routing.google-ads-pp-cli — needs developer token + OAuth in vault; Meta via Marketing API/vault-curl or the ads-skills repo scripts) vs agency/platform invoices (Gmail/Drive search) vs bank-out lines (manual statement extract from Shahbaz's team in v1 — don't over-automate). Output: reconciliation table, unexplained gaps flagged, running total vs the AED 720K precedent. Effort M. Prereq: ads API credentials (same unlock as HUX-L3).expenseapptest (container-local). Documented API is only /api/health + /api/extract-receipt — read the queue directly from Postgres (read-only role via SSH tunnel). States: pending review → approved / rejected / correction-requested (→ back to pending).billing.db at /opt/joinery-billing/data/ (container mig-joinery-billing): contract value, advance %, retention %, MIR/WIR/TOC/DLP milestones. ⚠️ Schema undocumented; whether DLP dates are computed/populated is unconfirmed. NetSuite holds retention accounting (fields undocumented); joinery reporting mart and Traxx have none of it (verified absent).finance feed card ("retention receivable due next 90 days").billing.db may hold milestone timestamps (OPS-1's step-0 dump answers this). If timestamps exist → weekly stage-duration report (median days Delivered→MIR, Installed→WIR, per project) vs targets set with Yasar. If not → this becomes a feature request on the Joinery Billing app (add milestone dates) — flag and stop, don't pretend. Effort M–L.raw.transactions.entity_id/entity_name (type='SalesOrd'), item family via raw.items.parent_id hierarchy, qty = transaction_lines.quantity. ⚠️ SO line quantities are negative (NetSuite sign convention — use abs()); SO history thin pre-2025 (~1.5 usable years).gbrain-dream.timer already runs a ~20-phase self-maintenance pass every 4h (lint, backlinks, sync, orphans — logs /opt/gbrain/logs/dream.log). IT-1 supervises and fills gaps, not duplicates.git log --since), run gbrain sync --source <id> via the docker-exec wrapper — the hourly wikis-sync timer only git-pulls, it does NOT re-embed; (4) /lint <wiki> --fix on 2 highest-churn wikis, rotating (auto-fix frontmatter/index drift only); (5) first run: fix the live merge-conflict artifact in vps-architecture/index.md frontmatter (being indexed into GBrain as-is).gam print tokens) diffed vs last month, suspended/never-logged-in accounts vs offboarding checklist, forwarding rules, root-OU population. Deltas only. Codex over the google-workspace-cli skill. Effort S.cron list + recent logs per Hermes profile; token/cost per profile and Claude routine; each loop's last-3-cycles outcomes (state files). Scorecard: healthy / stalled / noisy / over-budget / kill-candidate. Killing a useless loop is a success. Opus. Effort S.Reference implementation — bookmark, don't clone yet: ivangfalco/ads-skills (ColdIQ; $300K/mo managed spend from Claude Code; ~40 strategy files + 39 Python API scripts; MIT + Commons Clause — free to use/adapt internally, cannot be resold). Clone at build time and reuse the API scripts (drops HUX-L3 effort M→S) and the audit-checklist structure — but NOT its strategy content for D2C (it's B2B demand-gen; build a hux-paid-ads knowledge skill with our GCC/D2C thresholds instead; its B2B files transfer to §05 nearly as-is).
Credential gotchas: Meta long-lived tokens die ~2 months → wire into the assetops cred_expiry_check loop from day one; Google needs a developer token via an MCC manager account (request early); LinkedIn Advertising API approval takes days.
directus-pp-cli (the ONE sanctioned write, scoped to the review folder, "AI Agent" role) → Sherif approves in DAM. Never touches Meta/Google. Post-Fable: add one extra Sherif review cycle in month 1. Effort S–M.ahrefs-pp-cli/semrush-pp-cli (vault keys) + google-search-console-pp-cli after L0 (CLI only, no MCP); Codex scrape-diff of Heveya + 3 competitors' PDPs/prices/promos; position state file; digest to Sherif + ecommerce feed card. Effort S.google-ads-pp-cli + Meta Marketing API via vault-curl. Effort S with repo scripts (M from scratch). Prereqs: L0; Meta token renewal wired into cred_expiry_check.Story and market case: the go-to-market page. This section is the technical residue of the absorbed "Auto Go To Market" design (July 2026, incl. its 64-claim fact-check) — the original folder is superseded.
crm.lead via odoo-write, per-record BU-lead approval, source/medium/campaign stamped at creation (today 100% of deals have no source recorded). Reply-only sync principle: contacts enter the CRM only on real interaction — never raw list imports. Shares CRM-3's qualification harness. Effort S once B2B-L1 exists.| Tool | Verdict | Why |
|---|---|---|
| Clay (Growth $495/mo, $446 annual) | ✅ v2 only, after Phase-1 conversion | Only tier with HTTP API + webhooks (Odoo sync); the multi-provider waterfall is the real MENA find-rate edge |
| Apollo | ✅ free tier for v1 | ~10k email credits/mo free; NO native Odoo integration — use Captivea cap_apollo_connector (Odoo v19, $29.87) or Balane apollo_connector (v16–19, CSV any plan) |
| Evaboot ($9–99/mo) | ✅ replaces Scrupp | $0.02–0.09/contact, credits roll over, clean billing record |
| Sales Navigator (~$100/seat/mo) | ✅ base layer | Gold standard for GCC decision-maker identity |
| Albato/Make | ✅ v2 middleware | Live Clay↔Odoo integration exists on Albato (trigger → Create/Update Lead) |
| TOPHOTELPROJECTS | ✅ own seat (~€85–250/mo) | ~8,500 hotel projects + ~20,000 decision-maker contacts included; per-user license — never share logins |
| BNC Network | ✅ pay-as-you-go entry | Project signal only (no contacts included; CRM sync add-on top tier only) — people-finding stays with Apollo/Clay/Evaboot |
| Scrupp | ❌ dropped | Pricing was misstated; 2026 reviews report broken cancellations + silent auto-renewals |
| Cognism | ❌ dropped | MENA-coverage claim unsupported (real strength EU/UK; $15–50k/yr) |
| ZoomInfo | ❌ avoid | $15k–45k+/yr; stale MENA data |
| Expandi / HeyReach / Waalaxy | ❌ not in v1 | All violate LinkedIn's UA; active enforcement. Return only via explicit accepted-risk sign-off — human-send likely makes them unnecessary |
| Phase | Gate | New paid tools | Monthly |
|---|---|---|---|
| 0 — prove demand | none (already Wave 3) | None — CRM-3 Tier-0 public sweeps | AED 0 |
| 1 — validate | Phase-0 shortlists convert (or conviction) | Sales Nav ×2 (~$200) · TOPHOTELPROJECTS Essential (~€85–90) · BNC PAYG ($50–100) · Evaboot ($29–49) · Apollo free · Odoo connector ($29.87 one-off) | ~$450–650 |
| 2 — scale finding | Phase-1 reply/meeting metrics | + Clay Growth ($446 annual) + Albato. Sending stays human by default | ~$1,100–1,400 all-in |