HEART Hub site#
Device health pages at https://heartcortex.ai/device/<device ID>, or on your own site. Open code, Apache-2.0. Template: heart/embeddedstandard/monitor-site/v1.2611 · SPDX-License-Identifier: Apache-2.0
The rules are in spec/HEART-Monitor-1.0.md. In short: a device makes a device ID and a claim code, its owner registers it with them, and from then on it reports its public health here. Pages are private until the owner shares them. If a device goes silent, its owner gets an email. Owners sign in with a code sent by email.
Small devices may send a plain JSON light report instead of envelopes, with up to 64 figures, and may say how long they will sleep, so they are not taken for silent (Sections 5.4 and 7). While an owner is changing a setting, the site answers that device with nextSec: 2 (the faster channel, Section 5.5). Devices on MQTT reach the site through a bridge (Section 5.6, iot-examples/heart_mqtt_bridge.py). See HEART for IoT.
Files#
| File | What it is |
|---|---|
worker/worker.js | The one file to deploy. Generated: do not edit. |
src/worker.src.js | The source |
tools/build-worker.mjs | Builds worker/worker.js, adding the plain-language meaning of every fault code from Part 3 |
test/monitor.test.mjs | 55 tests of the Worker (npm test): devices, light reports, sleeping, the faster channel, approvals, sign-in codes and their limits, addresses, sessions, the menu, and the Command Center |
test/e2e.py | The Python and ESP32 templates report to a local copy of the site, and are registered through its pages |
test/serve.mjs, test/dynamo-fake.mjs | Run the Worker on Node, with an in-memory stand-in for DynamoDB, for testing only |
Signing in#
No passwords: the site emails a 6-digit code. Every account has a random ID that never changes, and 1 to 10 email addresses that open it; one is the primary, where emails go. The hamburger menu at the top right of every page has Sign in, or, once signed in, My devices, Register a device, Account (addresses and signed-in browsers) and Sign out. The rules and limits are in Section 4.1 of the profile.
Deploying#
- DynamoDB tables. Run
python3 CreateALLTables.pyin the project's top folder (--checkonly reports). It makesheartaispec-userandheartaispec-monitorwith their indexes, on-demand, with TTL clean-up, and is safe to run again. It reads the AWS keys from the environment or~/.config/heart/b2_etc.env. - An AWS key for the Worker. In IAM, make a user whose only permission is this, and make an access key for it:
``json { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:ConditionCheckItem"], "Resource": ["arn:aws:dynamodb:us-east-1:*:table/heartaispec-*", "arn:aws:dynamodb:us-east-1:*:table/heartaispec-*/index/*"] }] } ``
Never give the Worker your root or admin keys.
- Turnstile (recommended). Cloudflare dashboard → Turnstile → Add widget for
heartcortex.ai(Managed). Keep the site key and secret. - Worker. Workers & Pages → Create → Worker. Paste
worker/worker.jsand deploy. - Settings → Variables and Secrets:
| Name | Type | Needed? | Value |
|---|---|---|---|
AWS_ACCESS_KEY_ID | Secret | Yes | The key from step 2 |
AWS_SECRET_ACCESS_KEY | Secret | Yes | The key from step 2 |
SESSION_SECRET | Secret | Yes | 32 or more random characters, such as from openssl rand -base64 33. Changing it signs everyone out. |
RESEND_API_KEY | Secret | Yes | Your Resend key: sign-in codes are sent by email |
RESEND_FROM | Text | Yes | For example HEART Hub <monitor@heartcortex.ai> (a domain verified in Resend) |
AWS_REGION | Text | No | Default us-east-1 |
ADMIN_EMAILS | Text | No | Addresses whose accounts are administrators, separated by commas |
SITE_URL | Text | No | Default: the address the site is visited on. The "gone quiet" emails use https://heartcortex.ai unless this is set. |
DYNAMODB_TABLE_PREFIX | Text | No | Default heartaispec- |
SITE_SIGNING_KEY | Secret | For approvals | An Ed25519 key, as base64url of 32 random bytes: node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))". It signs every approval decision, and devices pin its public key when they say hello. Without it, owners cannot turn approvals on. Changing it means every device must be set up again to accept approvals. |
LLM_KEYS_SECRET | Secret | For AI models | base64url of 32 random bytes, like SITE_SIGNING_KEY. It encrypts the API keys owners connect at /account/ai. Without it, owners cannot connect models. Changing it makes every saved key unreadable: owners must add them again. |
COMMAND_CENTER | Text | No | 1 turns on the Command Center (see below). Off by default. |
CC_MODELS | Text | For the Command Center | The blessed models, as JSON: [{"id": "provider/model", "version": "2026-10-01", "uses": ["explain", "propose"]}]. A model on the device itself adds "local": true and its file's "sha256". |
CC_PROVIDER_URL, CC_PROVIDER_TOKEN | Text, Secret | For the Command Center | The upstream Reasoning Gateway that holds the model provider keys: answers come from CC_PROVIDER_URL/heart/v1/reason |
CC_KEY_HOLDERS | Text | For the Command Center | The five key holders' email addresses, separated by commas. Cutting a model or a use everywhere needs 2 of them; the global switch, and every restore of those, needs 3. |
TURNSTILE_SITE_KEY, TURNSTILE_SECRET | Text, Secret | Recommended | From step 3. Without them the "are you a person" check is skipped; the per-address and per-network limits still apply. |
The whole-site build (FrontEnd/worker.js) also needs B2_KEY_ID, B2_APP_KEY and B2_BUCKET_NAME: downloads and the paper come from B2 (see website/README.md). DEEPINFRA_API_KEY, DEEPSEEK_PLATFORM_API_KEY and HF_TOKEN are not used; they can stay set. Never set DEV_LOG_LINKS, DYNAMODB_ENDPOINT or TEST_ANTHROPIC_URL: they are for local tests only.
- Settings → Triggers → Cron Triggers: every 5 minutes (
*/5 * * * *). This sends the "gone quiet" emails. DynamoDB deletes old rows itself. - Settings → Domains & Routes. With
heartcortex.aion Cloudflare, add these routes to this Worker:
`` heartcortex.ai/device/* heartcortex.ai/devices heartcortex.ai/register heartcortex.ai/login* heartcortex.ai/logout heartcortex.ai/account* heartcortex.ai/api/* heartcortex.ai/v1/devices/* heartcortex.ai/commandcenter* heartcortex.ai/cc/v1/* (only with COMMAND_CENTER=1) ``
On heartcortex.ai itself, deploy FrontEnd/worker.js instead (built by website/build.mjs): it is this Worker plus the whole site, so it takes the single route heartcortex.ai/*. See website/README.md.
Running only the Monitor? The rest of a site can come from anywhere, such as a B2 bucket. The HEART-Rating Worker can take heartcortex.ai/v1/*: Cloudflare uses the most specific route, so /v1/devices/* still reaches this Worker.
Testing on your own computer#
npm test # the Worker: 55 tests, against an in-memory DynamoDB
python3 test/e2e.py # templates report to a local copy, and are approved remotely: 10 checks
HEART_E2E_ASAN=1 python3 test/e2e.py # the same, with the C core under AddressSanitizer
The Command Center#
With COMMAND_CENTER=1 the same Worker is also a Command Center (HEART Command Center 1.0). It gives registered devices their AI brain on a lease, signs every answer with SITE_SIGNING_KEY (the key devices already pin at hello), and can take the brain away at once.
| Who | Where | What |
|---|---|---|
| Devices | POST /cc/v1/devices/{id}/lease, /reason, /countersign, /offline-log | Renew a lease, ask for a signed answer, have a local model's answer signed, upload an auto-mode log |
| Owners | /commandcenter, /commandcenter/device/{id} | Choose models, uses, lease length and auto mode; cut one device or all their devices at once; restore with an emailed code |
| Key holders and operators | /commandcenter (Switches) | Cut or restore a model or use everywhere, or everything (the global switch, 3 of 5), each approval with an emailed code |
| Anyone | /commandcenter/log, /cc/v1/log, /cc/v1/models | The public, hash-chained log, and the blessed models |
Tripwires cut automatically, at the narrowest scope, and email the owner: too many refused Proposals or rejected answers from one device, a known prompt-injection pattern, a jump in its call rate, or the same proposal reaching 5 devices within 10 minutes (that model, for everyone).
Running your own site#
Deploy the same file under your own domain, and point devices at it with HEART_MONITOR_URL (Python) or CONFIG_HEART_MONITOR_URL (ESP32). Or write a different site that follows the profile.