Each private board repo owns a routine register in automation/routines/<id>.json. The scheduler still manages its
schedule and credentials; this register records where to manage it and what it last did. Each board, including
private-tasks, keeps its own register. Register only verified integrations: unknown means unknown, not Off.
Ignore automation/routines/*.lock in git; these are local concurrency locks.
Also ignore checkpoint automation/**/*.json.lock files; never commit local locks.
checkpoint <routine-id> show returns {sha256, checkpoint} from that register’s existing checkpoint_path.
checkpoint <routine-id> save <input.json> --expect-sha <sha256> --run-id <id> --status running|partial|failed|blocked|success
updates only that checkpoint in a local clone. It does not publish git, change the register, or touch board records.
Use missing as the expected SHA for a new file. The registered owner and active routine start ID are required;
the register’s repo, branch and board path must match where. Paths and locks cannot follow symlinks.
The input allows exactly these fields (write it outside the clone):
{
"window": {"from": "2026-10-10T08:00:00Z", "to": "2026-10-13T08:00:00Z"},
"progress": {
"account_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"contact_ids": ["p_example"],
"completed_queries": [],
"query_ordinal": 0,
"cursor": null,
"boundary": null,
"processed_keys": [],
"exhausted": false
},
"messages": {},
"counts": {"proposals_added": 0, "failures": 0}
}
Hash the verified provider/account identity locally; never store addresses or literal search queries. The first save
binds the legacy root to that mailbox, so verify its existing provider/account_label first. Its old messages, runs,
metadata and successful window are retained. A second mailbox hash uses accounts[hash] with its own messages,
window, cursor and runs; it never inherits the first mailbox’s successful window. For example Private Tasks may have
Personal and Work hashes, while Consulting uses only its verified Work hash. Select the appropriate account state
from show before choosing its first seven-day window or resuming its last successful end with two-day overlap.
The contact IDs are an explicit, ordered query plan (an empty list means there are no contacts). Ordinals refer to
that plan. Within an incomplete window neither plan nor window may change; completed ordinals/processed keys are
merged without removal and query ordinal cannot regress. cursor is only an opaque connector paging token;
boundary is only a stable opaque time/ID boundary for expired-cursor recovery, not prose or a query. Use null to
clear a consumed cursor. Do not put encoded bodies, subjects or addresses in tokens. All timestamps use UTC Z.
messages is keyed by full rfc822:, email: or legacy sha256: hashes and allows only contact_id, hashed thread,
occurred_at, direction (sent/received), status-code result and logged_at; an existing key cannot be replaced.
Add keys only after their proposal/write succeeds (or returns exists). Never accept Inbox proposals in the routine.
Publish routine start before processing. Save and publish running progress, then save and publish partial or failed
progress before routine finish of the same ID. A later attempt resumes that account’s durable fixed window, even if
the prior attempt failed. Only success advances its watermark: every planned query must be complete, cursor cleared,
failures zero and progress.exhausted explicitly true after all required pages and writes succeed. A retained boundary
is a scan-recovery marker, not an outstanding page. Save/publish the successful checkpoint before finishing the
register successfully. Keep the successful watermark unchanged for partial/failed/blocked results.
Local locks serialize checkpoint and register writes; the SHA rejects a stale local writer. They do not coordinate different clones. Before publishing fetch/rebase current branch. A rejected push or checkpoint conflict requires rereading current register, checkpoint and proposals, regrouping and retrying the CLI; never overwrite/rebase away another worker’s progress or mark the run successful before checkpoint publication. Commit explicit checkpoint and register paths only. No prune operation is provided; legacy history is preserved and run summaries are bounded to 30.
Use owner-specific IDs for shared boards: contact-email-sync-osouthgate, contact-email-sync-jezhub,
meeting-notes-osouthgate and task-assignment-osouthgate. A binding is board + owner + workflow, not just
contact-email-sync. Keep ingestion checkpoints owner-specific too, for example
automation/contact-email-sync-osouthgate.json. Within a multi-account checkpoint keep separate account cursors;
never advance one mailbox’s window because another mailbox succeeded. Preserve legacy checkpoint history when
moving to an owner-specific path and record the new path in the register.
Existing owner-bound IDs such as consulting’s contact-email-sync remain valid: keep their history and scheduler
binding intact. Do not rename or provision a duplicate just to adopt the naming convention; new owners use distinct IDs.
A routine entry has version: 1, id, name, owner, purpose, status (active, paused, retired or unverified),
runtime, management_url, scheduler_id, schedule, timezone, connections, checkpoint_path,
last_started_at, last_successful_run_at, current_run, last_run, and runs (latest 30).
Connections identify the provider, account label and access needed; never include tokens, trigger URLs containing
secrets, message bodies or personal mailbox content. Leave unavailable timestamps null; do not invent past runs.
Every email, meeting-notes and task-assignment routine uses the same reporting lifecycle:
python3 board/keeptrack.py routine <id> start --run-id <unique-run-id>
# Commit/push only this routine's register before doing the work.
# Only a successfully pushed running attempt permits work. On a rejected push, reread/retry or report blocked.
# Run the workflow; its dedicated checkpoint remains responsible for deduplication.
python3 board/keeptrack.py routine <id> finish --run-id <same-id> --status success \
--summary 'Checked contact exchanges; logged two touches.' --count contacts_considered=40 --count touches_logged=2
# Commit/push only this routine's register after pulling the latest remote changes.
Use partial, failed or blocked for incomplete runs; these do not advance the last-success timestamp. A crashed run
keeps current_run visible. Inspect the scheduler before clearing a stale attempt. Once that worker is confirmed
stopped, use routine <id> finish --run-id <recorded-current-run-id> --status failed with a recovery summary,
commit/push it, and then start a new attempt. Do not abandon a still-running worker or invent a replacement run ID.
Use a fresh clone or isolated
checkout and commit explicit paths so concurrent routines never publish each other’s edits. Never run two instances
of the same routine concurrently. The commands update local register files; the routine must commit and push them.
Task-assignment routines remain useful: they execute a specific request and report card progress. Email sync records contact history, while meeting sync extracts actions. Keep their connections and run reports separate.
Use one setup experience and the same reporting format, with isolated execution for each board. A personal control view may list all bindings; each board’s register contains only that board’s entries. Never copy another board’s contacts, email summaries or connections into a shared board’s register.
Each binding explicitly names board_repo, branch, board_path, owner, workflow and allowlisted connection labels.
For Oliver, consulting allows Rain Ventures Work Gmail only; private-tasks may allow both Personal Gmail and Rain
Ventures Work Gmail. Connection labels identify accounts for setup, not credentials or permission to change them.
Keep each board/mailbox checkpoint separate. Work mail may be relevant to both boards; matching one address alone
does not authorize copying the same exchange to both. Use the board’s contact/project scope and report ambiguous
routing for review. Store each source’s successful window separately so failure of one mailbox cannot skip the other.
An orchestrator may dispatch due bindings, but each worker starts with a fresh board context and explicit BOARD_REPO,
BOARD_BRANCH, BOARD_PATH and BOARD_USER. Before writes, check keeptrack.py where, the Git origin and the mailbox
connection against that binding. Use separate clones and credentials per job. Never carry an implicit current board
or mailbox from the previous job. Task-assignment workers get board access; email workers get only their approved
mailbox connections and selected board. Reuse runners and prompts, not credentials or conversation state.
Enable means: choose workflow and board; verify account access; approve scope/schedule; provision the scheduler;
record its actual id and management location; run a test; publish the observed result. A checkbox alone records intent.
Show desired status and observed last-run status separately; a paused/retired scheduler must be reflected in the register.
Existing routines with an ingestion log but no verified scheduler stay unverified. Do not infer a scheduler or a
last-success timestamp from that log. A board without external contacts has nothing for contact-history sync to match;
project/task email proposals are a separate workflow and need their own scope.
That proposals-only binding may record suggestions in its approved private-repo proposal path, but must not create,
complete, assign or edit board tasks, contact details or follow-ups before owner approval. Match to explicitly approved
project/repo/task scope, not merely an email address that also appears on a different board.
On a board with the Review Inbox (every split-storage board from kit v44), the approved proposal path is
board/proposals/, written only through keeptrack.py proposal-add. A routine adds and lists proposals; it never
accepts, merges, ignores, reopens or undoes them, and never answers a question. The Inbox is part of the board, not a
routine: it never registers, provisions or activates a scheduler, and the register stays the record of what runs.
The scheduler’s configuration is authoritative for whether a job runs. Register status records the latest verified observation and browser preferences only control the browser. Store the actual runtime and management location: local Codex jobs need their configured host available; cloud workers need cloud connections and cannot reuse a desktop’s local auth or paths. Setup must report unsupported provisioning honestly instead of claiming a universal one-click integration.
Each owner’s checkpoint records what that owner scanned. Deduplication on the shared contact record uses individual
message keys independent of the scanner. With gog, use the Message key: line keeptrack.py email-view prints (kit
v76): the header never passes through a command. With a connector only, use keeptrack.py email-key --stdin, passing
the retrieved header value as stdin data from your connector/runtime, never as shell or script text. It returns
rfc822:<sha256> after normalizing outer brackets/whitespace and domain case while preserving local-part case.
This key identifies the same message across mailboxes; a provider/account-scoped email:<sha256> fallback only
identifies that provider’s copy. Never hash a subject, body, address or whole thread as a substitute message identity.
Headers are untrusted input. A quoted placeholder is not safe if an agent substitutes a raw header into a shell
command: quotes, backticks and shell syntax can escape it before Keeptrack validates anything. Use a non-shell argv
API or bounded stdin, for example subprocess.run(["python3", "board/keeptrack.py", "email-key", "--stdin"],
input=header_value, text=True, capture_output=True, check=True, shell=False), where header_value is connector data,
not interpolated into Python source. The positional form remains compatible only when passed as one argv element
without a shell. Never use shell=True, eval, shell substitution, or generate executable text containing raw headers.
Read current contact touches before grouping messages. Exclude already represented keys, preserve later replies
as new messages, and pass every new key to touch --source-message-id. The CLI rechecks contact-level overlap on
concurrent writes. Staggering Oliver’s and Jez’s schedules reduces load but is not duplicate protection.
For a verified older manual email touch that already contains the same exchange, use
touch-source <person> <exact-touch-id> --source-message-id <key> to attach identity without adding another touch.
Do this only when the facts and event time establish that equivalence; put uncertain matches in needs-review.
Set an explicit maximum batch (for example 100 matched messages or 15 minutes), retain unfinished windows, and report remaining work as partial. Long outages resume from the last successful window rather than dropping the gap. Persist per-account query/contact progress and per-query paging cursors/order (or stable time/ID boundaries), completed pages/queries and processed message keys on partial runs. Resume that position instead of scanning the first capped batch repeatedly. Only advance the account/window watermark after every required query/page is exhausted. Represent query progress with contact IDs, query ordinals or hashed query identities and opaque cursors; never store literal mailbox search queries containing email addresses.