HEART Embedded Systems logoHEART

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#

FileWhat it is
worker/worker.jsThe one file to deploy. Generated: do not edit.
src/worker.src.jsThe source
tools/build-worker.mjsBuilds worker/worker.js, adding the plain-language meaning of every fault code from Part 3
test/monitor.test.mjs55 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.pyThe Python and ESP32 templates report to a local copy of the site, and are registered through its pages
test/serve.mjs, test/dynamo-fake.mjsRun 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#

  1. DynamoDB tables. Run python3 CreateALLTables.py in the project's top folder (--check only reports). It makes heartaispec-user and heartaispec-monitor with 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.
  2. 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.

  1. Turnstile (recommended). Cloudflare dashboard → Turnstile → Add widget for heartcortex.ai (Managed). Keep the site key and secret.
  2. Worker. Workers & Pages → Create → Worker. Paste worker/worker.js and deploy.
  3. Settings → Variables and Secrets:
NameTypeNeeded?Value
AWS_ACCESS_KEY_IDSecretYesThe key from step 2
AWS_SECRET_ACCESS_KEYSecretYesThe key from step 2
SESSION_SECRETSecretYes32 or more random characters, such as from openssl rand -base64 33. Changing it signs everyone out.
RESEND_API_KEYSecretYesYour Resend key: sign-in codes are sent by email
RESEND_FROMTextYesFor example HEART Hub <monitor@heartcortex.ai> (a domain verified in Resend)
AWS_REGIONTextNoDefault us-east-1
ADMIN_EMAILSTextNoAddresses whose accounts are administrators, separated by commas
SITE_URLTextNoDefault: the address the site is visited on. The "gone quiet" emails use https://heartcortex.ai unless this is set.
DYNAMODB_TABLE_PREFIXTextNoDefault heartaispec-
SITE_SIGNING_KEYSecretFor approvalsAn 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_SECRETSecretFor AI modelsbase64url 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_CENTERTextNo1 turns on the Command Center (see below). Off by default.
CC_MODELSTextFor the Command CenterThe 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_TOKENText, SecretFor the Command CenterThe upstream Reasoning Gateway that holds the model provider keys: answers come from CC_PROVIDER_URL/heart/v1/reason
CC_KEY_HOLDERSTextFor the Command CenterThe 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_SECRETText, SecretRecommendedFrom 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.

  1. Settings → Triggers → Cron Triggers: every 5 minutes (*/5 * * * *). This sends the "gone quiet" emails. DynamoDB deletes old rows itself.
  2. Settings → Domains & Routes. With heartcortex.ai on 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.

WhoWhereWhat
DevicesPOST /cc/v1/devices/{id}/lease, /reason, /countersign, /offline-logRenew 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/modelsThe 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.