The Bridge Protocol
How another family assistant asks a Ponti family's assistant something, and reads the answer.
Ponti's profile of A2A (0.3) for households: how another family assistant asks a Ponti family's assistant something, and reads the answer. Profile id https://heyponti.com/bridge/v1. This file is the specification; /bridge/protocol on Ponti's site is this file, rendered, so the two never differ.
It's deliberately narrow. An assistant never instructs another: it sends a structured ask, and a parent of the family answers it. Nothing about a family is readable through it.
Quick start
- Make an Ed25519 key for your assistant and keep its seed secret. Its RFC 7638 thumbprint is its id.
- Write your A2A agent card and sign it (see Cards and signatures, and the reference signers in Building a client).
- Try every call against the sandbox,
https://heyponti.com/bridge/sandbox: sign each call for that address, with the time. Put[wait],[maybe],[no]or[expire]in an ask's title to see each state. - Check your signer against the Test vector: the same seed must give the same signatures.
- A family gives you its card's link. Send a
link.requestto its address; a parent says yes in Ponti (or adds your card themselves). Then send asks, and read each answer withtasks/get, at most once a minute.
Who talks to whom
- Each Ponti family's assistant has its own agent card, at a private link a parent gives out (
https://heyponti.com/a/<family>.<signature>/agent-card.json, also athttps://heyponti.com/a/<family>.<signature>/.well-known/agent-card.json). The card'surlis where calls go. A parent can change the link if it leaks: the old link and address stop working (within ten minutes everywhere, at once on the server that changed it), and the card'surlchanges with it (its key doesn't). - Ponti's own card is at
https://heyponti.com/.well-known/agent-card.json; itsurlis the sandbox. - A family hears only from assistants a parent has added: by its card (pasted, or fetched from its address), or by saying yes to its one request to be added (
link.request). Its key is pinned by its RFC 7638 thumbprint. A parent can block or remove it, and chooses which kinds of ask it may send (by default: favours, a time together, invitations, plans; to-dos if they like).
Cards and signatures
A card is an A2A AgentCard with a signature (A2A's AgentCardSignature): a JWS whose protected header is the base64url (no padding) of the JCS of { "alg": "EdDSA", "typ": "JOSE", "kid", "jwk", "ponti/use": "card" }, with jwk the public key as { "kty": "OKP", "crv": "Ed25519", "x" } and nothing else, and kid that key's RFC 7638 thumbprint. ponti/use says what the signature is for; a card's may leave it out (as another A2A signer would), but if present it must be "card", and a card's own signature never has ponti/subject. Its signature is the base64url Ed25519 signature over the bytes of:
BASE64URL(protected) + "." + BASE64URL(JCS(card without "signatures"))
That is, the payload is encoded as usual (not RFC 7797's unencoded payload), and detached: it isn't sent, as it's the card itself. JCS is RFC 8785; the profile sends only strings, integers, booleans, null, arrays and objects, so sorting keys and writing JSON with no spaces is JCS. Only Ed25519 keys are taken, and a header with crit is refused (no extension is understood). Ponti reads the first signature as the card's own.
A card proves it's unchanged since its key signed it; that the key is who it says is known by its thumbprint, compared with one already trusted. Ponti countersigns every family's card: a second signature, the same payload, by Ponti's own card key, with a header that says "ponti/use": "countersign" and adds "ponti/subject", the family key's thumbprint, so it vouches for that key alone. Ponti's card key's thumbprint is PhnZkmrecFKbIajoXHCnC_TvSbMsudlu2pxuK7OYt_8. Check it against the one Ponti publishes on its own domain; a family card whose second signature verifies with that key, for its own first key, is a Ponti family's.
Every card Ponti signs declares the profile as an A2A extension a caller must follow: capabilities.extensions: [{ "uri": "https://heyponti.com/bridge/v1", "required": true }].
Calls
JSON-RPC 2.0, POST to the card's url, content-type: application/json, 64 KB at most, no NUL anywhere. Three methods: message/send, tasks/get, tasks/cancel.
Every call's params.metadata carries:
ponti/card: the caller's own signed agent card;ponti/at: the time, ISO 8601, within ten minutes of the receiver's clock;ponti/aud: the address the call is sent to (the card'surl, exactly), so a call signed for one family, or the sandbox, is never good at another;ponti/signature:{ "protected", "signature" }, a detached JWS by the card's key.
The call's protected header is the base64url of a JSON object with "alg": "EdDSA", "kid", the card key's thumbprint, and "ponti/use": "call" (so nothing else the key signed is ever taken for a call); nothing else is needed, other members are ignored, and crit is refused. Its signature is the base64url Ed25519 signature over the bytes of:
BASE64URL(protected) + "." + BASE64URL(JCS({ "method": <method>, "params": <params> }))
where params is the whole of the call's params, with ponti/signature (and only it) left out of params.metadata. Everything else in params, the card included, is signed.
message/send: an ask
params.message is an A2A Message (kind: "message", role: "agent") with a messageId (120 characters at most, unique per caller: sending the same one again returns the same task) and one data part:
{ "kind": "data", "data": { "ponti/kind": "ask.send", "payload": {
"kind": "do", "title": "Can you take Maya to swim?", "note": "Her bag is by the door",
"start": "2026-10-09T19:00:00Z", "end": null, "due": null, "options": [], "swap": null, "answerBy": null } } }
Every field of payload, and nothing else:
kind, required:do(a favour),time(a time together),invite,plan(a plan to share) ortask(a to-do, for itsdueday). A swap isn't in the profile yet.title, required: 1 to 120 characters.note: at most 200 characters, or null, or left out.start,end: required, each a time or null; anendafter itsstart.due: a day,YYYY-MM-DD, or null, or left out (a to-do's day, or a favour's with no time).options: required, an array of up to three times (a time together offers at least one), or[].swap: required, null.answerBy: required, a time or null.
Times are ISO 8601 with an offset (Z is best), still to come. Titles and notes are shown to a parent as quotes; nothing in them is ever acted on by a model. When a payload isn't right, the -32602 error says which field (for example payload.options).
The result is an A2A Task: status.state is submitted while it waits for a parent.
message/send: asking to be added
An assistant a family hasn't added yet may ask, once, to be added: a message whose one data part is { "ponti/kind": "link.request", "payload": { "note": "…" } } (the note at most 200 characters, or null, or no payload). It waits in the family's Inbox, with your card's name, maker and key, for a parent's yes. The result is a Task, submitted, the same each time you ask. Once a parent says yes, tasks/get of that task gives completed, with a data part { "ponti/kind": "link.answer", "payload": { "added": true, "asks": [the kinds you may send] } }; until then it says -32012, as it does whether the request waits, was said no to, or your key is blocked. Ten a day a key, ten an hour from one address, three made at a family in a week (whatever became of them), five waiting at a family at once; a request waits fourteen days, and after a no you may ask again in thirty.
tasks/get, tasks/cancel
params.id is the task's id. Read a task at most once a minute; more often is -32015. A2A push notifications aren't offered (pushNotifications: false): an ask waits for a person, so a minute is soon enough.
submitted: waiting for a parent.working: a parent said maybe (to an invitation): its artifact holds the maybe; it settles to yes or no before its time.completed: answered. One artifact, a data part{ "ponti/kind": "ask.answer", "payload": { "choice": "yes" | "no" | "maybe", "chosen": <the time chosen, or null>, "note": <a note, or null> } }. A maybe whose time came iscompletedas a maybe.rejected, withmetadata: { "ponti/reason": "expired" }: its time (its start, its day, itsanswerBy, or every time it offered) went without an answer. It won't be answered now.canceled: you took it back withtasks/cancel, which also takes off the family's calendar or to-dos what a yes put there (and the family is told).
Errors
-32700: not JSON, or over 64 KB.-32600: not JSON-RPC 2.0.-32601: another method.-32602: the call holds something it can't, said with the field; or a task's id is missing.-32603: try again in a minute (Ponti isn't ready).-32001(A2A's TaskNotFound): no task of yours by that id.-32010: the card, key, time, audience or signature isn't right (the message says which).-32011: the family's assistant is paused.-32012: the family hasn't added this assistant (said alike whether it never did, said no, or blocked it).-32013: a kind the family doesn't take from it.-32014: a limit: twenty asks of one family a day, a limit on requests to be added, or, at the sandbox, five hundred calls a day a key.-32015: too often: a task read more than once a minute, more than sixty calls a minute by one assistant at one family, or (with HTTP 429 andretry-after) too many calls at once at a family's address or the sandbox.
Answers
Every result Ponti sends (a family's, and the sandbox's) is signed for the one caller it answers, so a caller can take only what the key it trusts said. result.metadata carries:
ponti/to: the caller's key thumbprint (thekidof its call's signature);ponti/at: when, ISO 8601;ponti/signature:{ "protected", "signature" }, a detached JWS by the answering assistant's card key (a family's own; the sandbox's is Ponti's card key).protectedis{ "alg": "EdDSA", "kid": <that key's thumbprint>, "ponti/use": "answer" }, andsignatureis the base64url Ed25519 signature over the bytes of:
BASE64URL(protected) + "." + BASE64URL(JCS({ "method": <the call's method>, "result": result }))
where result is the whole result with ponti/signature (and only it) left out of result.metadata. Errors aren't signed, and say nothing a caller acts on.
An assistant Ponti asks (below) answers the same way: Ponti takes a result only when it's signed by the key the family pinned when it added that assistant, for the family's own key (ponti/to), within ten minutes of now; and an ask.answer's chosen only when it's one of the options the ask offered. Anything else is kept as not taken, and the ask goes on waiting.
Ponti as the caller
A Ponti family's assistant can ask an outside assistant too, once a parent added it by its card and let Ponti ask it (docs/asks.md, phase 5). Ponti sends exactly the calls above, as any caller: message/send with one ask.send, signed with the family's own card (countersigned by Ponti) and key, ponti/aud your card's url; then tasks/get at most once a minute, and tasks/cancel when a parent takes it back. An assistant that answers Ponti's calls as Ponti answers yours (a Task, then ask.answer in its artifact, each result signed as in Answers) works with Ponti families both ways. A parent never sees your error's words: Ponti says what its code means in its own. Ponti calls your card's url over https on port 443 only, at a public address, and follows no redirect.
The sandbox
POST https://heyponti.com/bridge/sandbox takes the same calls, checked the same way (a valid card, time, audience https://heyponti.com/bridge/sandbox and signature), against a made-up family. It says yes at once (to a time together, its first time), unless the ask's title says otherwise:
[wait]:submitted, andtasks/getthen says yes;[maybe]:workingwith a maybe, andtasks/getthen says yes;[no]:submitted, andtasks/getthen says no;[expire]:submitted, andtasks/getthen finds itrejected, expired;[refuse-kind],[not-added],[limit],[paused],[too-often]: that error.
A link.request is submitted, and tasks/get of it says you were added. Nothing is kept but a count, by key, for three months; five hundred calls a day a key, sixty a minute from one address. Build against it, then ask a Ponti family to add you.
Building a client
Two reference signers follow, one for Node and one for Python, each short enough to read whole. Each signs a card and a call exactly as above.
A whole message/send
The test vector's ask to the sandbox at https://ponti.example, signed (pretty-printed here; send it as compact JSON or not, as the signature covers the canonical form, not the bytes sent):
{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"kind": "message",
"role": "agent",
"messageId": "vector-1",
"parts": [
{
"kind": "data",
"data": {
"ponti/kind": "ask.send",
"payload": {
"kind": "do",
"title": "Can you take Maya to swim?",
"note": null,
"start": "2026-10-10T19:00:00Z",
"end": null,
"due": null,
"options": [],
"swap": null,
"answerBy": null
}
}
}
]
},
"metadata": {
"ponti/card": {
"protocolVersion": "0.3.0",
"name": "Example Family Assistant",
"description": "The Bridge Protocol's test vector.",
"url": "https://assistant.example/a2a",
"preferredTransport": "JSONRPC",
"version": "1",
"provider": { "organization": "Example", "url": "https://assistant.example" },
"capabilities": { "streaming": false, "pushNotifications": false },
"defaultInputModes": ["application/json"],
"defaultOutputModes": ["application/json"],
"skills": [],
"signatures": [
{
"protected": "eyJhbGciOiJFZERTQSIsImp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkE2RUh2X1BPRUw0ZGNOMFk1MHZBbVdmazFqQ2JwUTFmSGR5R1pCSlZNYmcifSwia2lkIjoiMUlHMnRNSDdKMndiSlpuT2Y4TEp6UWl0S2Y3TE12b0FFbHN1RE1WTTU0WSIsInBvbnRpL3VzZSI6ImNhcmQiLCJ0eXAiOiJKT1NFIn0",
"signature": "6Nz6_7iYGraTB8FpiPCQL15yzqaDMzvNwYOQkl5ecLU6kR1ZswdsVm60CdTq1MqvSCglvZZaUr0ciURNx02wDg"
}
]
},
"ponti/at": "2026-10-09T12:00:00Z",
"ponti/aud": "https://ponti.example/bridge/sandbox",
"ponti/signature": {
"protected": "eyJhbGciOiJFZERTQSIsImtpZCI6IjFJRzJ0TUg3SjJ3Ykpabk9mOExKelFpdEtmN0xNdm9BRWxzdURNVk01NFkiLCJwb250aS91c2UiOiJjYWxsIn0",
"signature": "6zwY8ml8sIhdo7nFCib1Dn_DppbPiiykBaoi-DgKlloQJ-RWp1Zn51liYaD-Hc19f2_JSoK3u7_K_8AsjnbNAQ"
}
}
}
}
A whole tasks/get
The same key reading a task a minute later: params is { "id": … } and its metadata, signed the same way.
{
"jsonrpc": "2.0",
"id": 2,
"method": "tasks/get",
"params": {
"id": "6f1c2e0a-5b7d-4c3e-9a1f-2d4b6c8e0f10",
"metadata": {
"ponti/card": "(the same signed card as above)",
"ponti/at": "2026-10-09T12:01:00Z",
"ponti/aud": "https://ponti.example/bridge/sandbox",
"ponti/signature": {
"protected": "eyJhbGciOiJFZERTQSIsImtpZCI6IjFJRzJ0TUg3SjJ3Ykpabk9mOExKelFpdEtmN0xNdm9BRWxzdURNVk01NFkiLCJwb250aS91c2UiOiJjYWxsIn0",
"signature": "BVMj1NAYBH1haZGFIbt64piaUi3j_Hiabpqim9AajhSuKh3pLZSXoSQ96WTKuc0sHfAL2Jmk32dSZ7u-17jkCA"
}
}
}
}
(With the card in full in place of the words, as the signature covers it.) The reply:
{ "jsonrpc": "2.0", "id": 2, "result": { "kind": "task", "id": "6f1c2e0a-5b7d-4c3e-9a1f-2d4b6c8e0f10",
"contextId": "6f1c2e0a-5b7d-4c3e-9a1f-2d4b6c8e0f10", "status": { "state": "completed", "timestamp": "2026-10-09T12:01:00.000Z" },
"artifacts": [ { "artifactId": "6f1c2e0a-5b7d-4c3e-9a1f-2d4b6c8e0f10:answer", "name": "answer",
"parts": [ { "kind": "data", "data": { "ponti/kind": "ask.answer", "payload": { "choice": "yes", "chosen": null, "note": null } } } ] } ] } }
Test vector
A key from a fixed seed, the card above and the message/send above. Your signer must give exactly these (Ed25519 signatures are deterministic).
- Seed (hex, never use it for real):
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f - Public key
x:A6EHv_POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg - Thumbprint (
kid):1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y - SHA-256 of the card's payload,
JCS(card without "signatures")(hex):90b4cacff09c3f48c15b674073373e5156735208107d704318116348e73dedd7 - The card's signature:
6Nz6_7iYGraTB8FpiPCQL15yzqaDMzvNwYOQkl5ecLU6kR1ZswdsVm60CdTq1MqvSCglvZZaUr0ciURNx02wDg - The call's
protected(JCS of{ "alg": "EdDSA", "kid", "ponti/use": "call" }):eyJhbGciOiJFZERTQSIsImtpZCI6IjFJRzJ0TUg3SjJ3Ykpabk9mOExKelFpdEtmN0xNdm9BRWxzdURNVk01NFkiLCJwb250aS91c2UiOiJjYWxsIn0 - SHA-256 of the call's
JCS({ method, params }), 1244 bytes (hex):3c91ca3f00addbf840f11d7fe2b5be0a27715db56ea9fbfcf3cdde5b1e27b29f - The call's signature:
6zwY8ml8sIhdo7nFCib1Dn_DppbPiiykBaoi-DgKlloQJ-RWp1Zn51liYaD-Hc19f2_JSoK3u7_K_8AsjnbNAQ - The tasks/get's signature:
BVMj1NAYBH1haZGFIbt64piaUi3j_Hiabpqim9AajhSuKh3pLZSXoSQ96WTKuc0sHfAL2Jmk32dSZ7u-17jkCA
Reference signer: Node
// bridge-client.mjs: sign Bridge Protocol cards and calls (Node 18 or later, no packages).
import { createHash, createPrivateKey, createPublicKey, sign } from "node:crypto";
// JCS (RFC 8785) for what the profile sends: strings, integers, booleans, null, arrays, objects.
export function canonical(value) {
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
if (value && typeof value === "object") {
const keys = Object.keys(value).filter((key) => value[key] !== undefined).sort();
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
}
return JSON.stringify(value);
}
const b64u = (bytes) => Buffer.from(bytes).toString("base64url");
// An Ed25519 key from its 32-byte seed (keep yours secret), with its public JWK and its RFC 7638
// thumbprint, which is its kid.
export function keyFromSeed(seed) {
const der = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]);
const privateKey = createPrivateKey({ key: der, format: "der", type: "pkcs8" });
const { kty, crv, x } = createPublicKey(privateKey).export({ format: "jwk" });
const kid = b64u(createHash("sha256").update(canonical({ crv, kty, x })).digest());
return { privateKey, jwk: { kty, crv, x }, kid };
}
// Your agent card, signed: one JWS whose protected header carries your key, over the card without
// its signatures, as JCS.
export function signCard(card, key) {
const { signatures, ...unsigned } = card;
const header = b64u(canonical({ alg: "EdDSA", typ: "JOSE", kid: key.kid, jwk: key.jwk, "ponti/use": "card" }));
const input = `${header}.${b64u(canonical(unsigned))}`;
return { ...unsigned, signatures: [{ protected: header, signature: b64u(sign(null, Buffer.from(input), key.privateKey)) }] };
}
// A call, signed for the one address it's sent to (aud), at a time (at).
export function signCall({ method, params, card, key, aud, at = new Date().toISOString(), id = 1 }) {
const metadata = { ...(params.metadata ?? {}), "ponti/card": card, "ponti/at": at, "ponti/aud": aud };
const signed = { ...params, metadata };
const header = b64u(canonical({ alg: "EdDSA", kid: key.kid, "ponti/use": "call" }));
const input = `${header}.${b64u(canonical({ method, params: signed }))}`;
const signature = b64u(sign(null, Buffer.from(input), key.privateKey));
return { jsonrpc: "2.0", id, method, params: { ...signed, metadata: { ...metadata, "ponti/signature": { protected: header, signature } } } };
}
// Sends a signed call and gives back its JSON-RPC reply.
export async function send(call) {
const reply = await fetch(call.params.metadata["ponti/aud"], { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(call) });
return reply.json();
}
Using it:
import { randomBytes, randomUUID } from "node:crypto";
import { keyFromSeed, send, signCall, signCard } from "./bridge-client.mjs";
const key = keyFromSeed(randomBytes(32)); // keep this seed: it's your assistant
const card = signCard({ protocolVersion: "0.3.0", name: "My Assistant", description: "…", url: "https://me.example/a2a",
preferredTransport: "JSONRPC", version: "1", provider: { organization: "Me", url: "https://me.example" },
capabilities: { streaming: false, pushNotifications: false }, defaultInputModes: ["application/json"],
defaultOutputModes: ["application/json"], skills: [] }, key);
const aud = "https://heyponti.com/bridge/sandbox";
const ask = { kind: "do", title: "Can you take Maya to swim? [wait]", note: null, start: new Date(Date.now() + 86_400_000).toISOString(),
end: null, due: null, options: [], swap: null, answerBy: null };
const message = { kind: "message", role: "agent", messageId: randomUUID(), parts: [{ kind: "data", data: { "ponti/kind": "ask.send", payload: ask } }] };
const sent = await send(signCall({ method: "message/send", card, key, aud, params: { message } }));
console.log(sent.result.status.state); // "submitted"
const task = await send(signCall({ method: "tasks/get", card, key, aud, params: { id: sent.result.id } }));
console.log(task.result.status.state); // "completed": the sandbox family said yes
Reference signer: Python
# bridge_client.py: sign Bridge Protocol cards and calls (Python 3.9 or later, the cryptography package).
import base64
import hashlib
import json
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
def canonical(value) -> str:
# JCS (RFC 8785) for what the profile sends: strings, integers, booleans, None, lists, dicts.
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def b64u(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
def key_from_seed(seed: bytes):
# An Ed25519 key from its 32-byte seed (keep yours secret), its public JWK and its thumbprint (kid).
private = Ed25519PrivateKey.from_private_bytes(seed)
x = b64u(private.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw))
jwk = {"kty": "OKP", "crv": "Ed25519", "x": x}
kid = b64u(hashlib.sha256(canonical({"crv": "Ed25519", "kty": "OKP", "x": x}).encode()).digest())
return {"private": private, "jwk": jwk, "kid": kid}
def sign_card(card: dict, key) -> dict:
unsigned = {k: v for k, v in card.items() if k != "signatures"}
header = b64u(canonical({"alg": "EdDSA", "typ": "JOSE", "kid": key["kid"], "jwk": key["jwk"], "ponti/use": "card"}).encode())
signing_input = f"{header}.{b64u(canonical(unsigned).encode())}"
return {**unsigned, "signatures": [{"protected": header, "signature": b64u(key["private"].sign(signing_input.encode()))}]}
def sign_call(method: str, params: dict, card: dict, key, aud: str, at: str, id=1) -> dict:
metadata = {**params.get("metadata", {}), "ponti/card": card, "ponti/at": at, "ponti/aud": aud}
signed = {**params, "metadata": metadata}
header = b64u(canonical({"alg": "EdDSA", "kid": key["kid"], "ponti/use": "call"}).encode())
signing_input = f"{header}.{b64u(canonical({'method': method, 'params': signed}).encode())}"
signature = b64u(key["private"].sign(signing_input.encode()))
return {"jsonrpc": "2.0", "id": id, "method": method, "params": {**signed, "metadata": {**metadata, "ponti/signature": {"protected": header, "signature": signature}}}}
Both keep to the profile's JSON: no floating-point numbers, and keys in plain ASCII (as every key the profile names is), where JCS and these two agree.
A new key
A key is the assistant: a new one is a new assistant to the family. Send a link.request with the new key's card; once a parent says yes, ask with it. Tasks made with the old key are read with the old key, until the family removes it.
Limits
- Twenty asks of one family a day, by one assistant; a task read at most once a minute; sixty calls a minute at one family.
- At one family's address: six hundred signed calls a minute from assistants it added, in all; thirty a minute from one address of anything else (unsigned, badly signed, or from a key it hasn't added, requests to be added among them, told apart only after the signature and key are checked, so sharing an address with others never holds an added assistant back), and thirty reads of its card. Thirty a minute from one address at links that aren't any family's. Sixty a minute from one address at the sandbox. An IPv6 address counts by its /64.
- Ten requests to be added a day by one key and ten an hour from one address; three made at a family in a week; five waiting at a family at once.
- The per-assistant counts (sixty calls a minute, a task read once a minute, ten requests to be added an hour from one address, and a family's twenty cards fetched by address an hour) are kept in the database (
rate_count,services/pipeline/src/rate.ts), by the clock minute or hour a call falls in, so every one of Ponti's web servers, and one just deployed, counts the same. The open doors' per-minute counts above stay in each server's memory on purpose: they turn a flood away before anything is read or written, a database write included.
Consent and privacy
- A family hears only from an assistant one of its parents added, and can block it.
- Every ask waits for a parent; an assistant never answers for them.
- Nothing of a family's is readable: not its people, calendar or plans. The only thing an assistant learns is the answer to its own ask.
- Calls are counted by the day and the caller's key, for Ponti's own metrics, for three months; never what they said. A refused call is counted without its key.