← Back to the page
How it's built

The code behind it

Short tours of the real source, for anyone who wants to see how the pieces work.

Polaris + Olympus
Code tour How a tick in the planner becomes progress on the phone: five excerpts and the checks

Polaris is my planning app: a calendar with a scheduling engine that sizes work into sessions and checks that it fits before the deadline. Olympus is my progress app: it turns finished work into elevation, in feet, on the web and on my iPhone. For about two months an AI agent scored my progress against a written rulebook; in August I switched that off: now I set what each finished item was worth myself, and the machine only does the bookkeeping. I designed and directed both; AI coding agents wrote the code. This shows the structure, not my data.

How the parts fit

  1. Polaris, on phone or desktop: I tick a finished item and set what it was worth.
  2. A small server function merges only what changed into one shared store, key by key.
  3. Every 2 minutes, a job on the always-on server makes the record of finished work match the ticks, then runs the build's checks. If a data check fails, it writes nothing.
  4. One data file is published and fetched fresh each time the Olympus app opens, on the web and in the iPhone app.
  5. At 6 a.m. and 8 p.m. New York time the server sends a notification through Apple's push service.

5 short excerpts from the real files

The numbers on the left are the real line numbers.

01
Polaris: "does this fit at all?"

Before any day-by-day schedule, Polaris asks a simpler question. Add up the work sessions needed before a deadline. Compare that with the sessions that exist between now and then. If the need is bigger than the supply, no clever ordering can save it, and the honest thing is to say so.

engine.js
/* FEASIBILITY — the aggregate question, asked before any per-item detail.    */
/* "Does this fit at all?" is answered by arithmetic, not by a schedule: sum  */
/* the sessions required before a deadline, and compare with the sessions     */
/* that exist between now and then. If the sum exceeds the supply, no amount  */
/* of clever ordering rescues it, and the honest thing is to say so.          */
/* ------------------------------------------------------------------------ */
function feasibility(ITEMS, LANES, today, deadline, capacity = {}, effortOverride = {}) {
  const weeks = Math.max(0, diff(deadline, today) / 7);
  const need = {}, supply = {};
  for (const [k, v] of Object.entries(LANES)) {
    if (v.floor || v.agentPool) continue;
    supply[k] = (capacity[k] ?? v.cap) * weeks;
    need[k] = 0;
  }
  for (const it of ITEMS) {
    /* Count EVERY declared owner session, not only `task` rows. Agent reviews,
       the rehearsal, the reserve and the milestones carry real hours; counting
       only tasks left roughly a third of the Osiris lane's supply out of the
       headline "does it fit" answer. A shared sitting is counted once. */
    if (!it.due || diff(deadline, it.due) < 0) continue;
    if (it.sharesSitting) continue;
    const lane = ownerLane(it);
    if (!(lane in need)) continue;
    need[lane] += (effortOverride[it.id] ?? it.effort) || 0;
  }

Source: Teams/Odin - Research & Strategy Team Lead/BKM/in-flight/osiris-study/_polaris/engine.js, lines 459–483

02
Polaris: two devices must never erase each other

If each device sent everything it had, the last one to save would silently erase the other's work while both said "saved". So devices send only what changed, and the server merges. It first did this with a list of fields that were safe to merge. Three times, a new field was added to the app but not to the list, and real marks were wiped while the app said "Saved". So the rule was flipped: any object merges by default, and deleting needs its own explicit message.

marks.mjs
/* ⚠️ 2026-08-10 — IT HAPPENED A THIRD TIME, AND THE ALLOWLIST IS WHY.
   `edits`, `lanes` and `tags` were added to the client's MARK_MAPS and never
   here. Restoring his marks after the functions outage posted `tags` with one
   new tag; this list did not recognise the key, the `else` branch replaced the
   whole map, and 15 tags were destroyed in one call — which returned
   `ok: true`. They came back only because the export being restored happened
   to be a superset. It would not always be.

   So the rule is inverted below: ANY PLAIN OBJECT MERGES, and only scalars and
   arrays replace. An allowlist has to be updated in two repositories at once to
   stay safe, and three times running it was not — the list itself is the defect
   generator, not the people forgetting it. Removal has always been the explicit
   `clear` channel, so merging by default cannot strand a deletion. MAPS is kept
   below only as documentation of what the client sends. */
const isMap = v => v && typeof v === "object" && !Array.isArray(v);

Source: Teams/Odin - Research & Strategy Team Lead/BKM/in-flight/osiris-study/_polaris/functions/marks.mjs, lines 56–70

03
Olympus: the build refuses to publish wrong numbers

The data build runs its checks before it publishes, and each check is in one of two classes. A failed integrity check means a number on screen would be wrong, so nothing is written. A failed freshness check means the record is only behind while everything shown is still true, so it publishes with a loud warning. Every check must be given a class, or the build refuses to run. Today there are 15 integrity checks and 1 freshness check.

build.mjs
  //  ⚖️ TWO CLASSES — owner ruling 2026-08-01 (the decoupling).
  //  Every gate is classified EXPLICITLY below. There is no default class: a new
  //  gate must be added to GATE_CLASS or the build refuses to run (see the
  //  completeness check after the gates). That is deliberate — a gate that
  //  silently inherits "freshness" would be a hole in the ironclad design.
  //
  //   • INTEGRITY — failing proves THE DATA WOULD BE WRONG. Stays FATAL: the
  //     build writes NOTHING and exits 1. Unchanged from the original design.
  //   • FRESHNESS — failing proves only THE RECORD IS BEHIND, while everything
  //     that would be displayed is still TRUE. Prints a loud warning, writes
  //     normally, exits 0, and the warning is carried to the publish-status line
  //     so it can never be silent.

Source: Teams/Odin - Research & Strategy Team Lead/Research & Strategy Team/6 - Zeus the Guardian of Olympus/Olympus/dashboard-v2/build.mjs, lines 2010–2021

build.mjs
  let ok = true;              // INTEGRITY only — this alone decides the write
  const freshness = [];       // FRESHNESS warnings — loud, but never withhold

Source: Teams/Odin - Research & Strategy Team Lead/Research & Strategy Team/6 - Zeus the Guardian of Olympus/Olympus/dashboard-v2/build.mjs, lines 2055–2056

04
Olympus: the app can never go blank

The app ships with a built-in copy of the data, and also fetches the newest data file when it opens. A stored copy wins only if it is strictly newer and has the right shape. Any error leaves the built-in copy in place, so a bad network or broken storage can never blank the screen.

livedata.js
  try {
    const baked = window.OLYMPUS_DATA;
    const raw = localStorage.getItem(window.OLYMPUS_LIVE_KEY);
    if (!raw) return;
    const stored = JSON.parse(raw);
    // minimal shape check — a corrupt payload must never boot the app
    if (!stored || !stored.meta || !stored.meta.built_at
        || !Array.isArray(stored.ticks) || !Array.isArray(stored.lanes)) {
      localStorage.removeItem(window.OLYMPUS_LIVE_KEY);
      return;
    }
    // STRICTLY NEWER wins (ISO stamps compare lexically). A deploy that bakes
    // fresher data beats the stored copy — which is then junk; drop it.
    if (baked && baked.meta && baked.meta.built_at
        && stored.meta.built_at <= baked.meta.built_at) {
      localStorage.removeItem(window.OLYMPUS_LIVE_KEY);
      return;
    }
    window.OLYMPUS_DATA = stored;
  } catch { /* storage/parse trouble → the baked data stands */ }

Source: Teams/Odin - Research & Strategy Team Lead/Research & Strategy Team/6 - Zeus the Guardian of Olympus/Olympus/dashboard-v5-sonnet/js/livedata.js, lines 21–40

05
Olympus: a notification is sent once, on the right day

A timer that missed its time (say, after a restart) runs late, which could send yesterday's message or send it twice. So the sender checks, in order: is the off switch on, was this one already sent, is it still inside its time window? Then it records the send before sending, so a crash halfway can never lead to a second copy.

olympus-push.js
  if (off) { log(`${stamp} ${slot}: OFF switch present (${OFF_FILE()}) — sent nothing`); return 0; }
  if (test) return sendTest(slot, x, payload, stamp, log);
  if (sent[sentKey]) { log(`${stamp} ${slot}: ${sentKey} already sent at ${sent[sentKey].at} — skipped (never twice)`); return 0; }
  if (!slotWindowOK(slot, now())) { log(`${stamp} ${slot}: outside its send window — skipped (a late timer must not send a wrong-day message)`); return 0; }

Source: Teams/Odin - Research & Strategy Team Lead/Research & Strategy Team/6 - Zeus the Guardian of Olympus/Olympus/native-app/push/olympus-push.js, lines 250–253

olympus-push.js
  // record BEFORE sending: a crash mid-send must never lead to a second copy
  sent[sentKey] = { at: new Date().toISOString(), feet: x.feet, overall: x.overall, results: 'sending' };
  saveJSON(SENT_FILE(), sent);

Source: Teams/Odin - Research & Strategy Team Lead/Research & Strategy Team/6 - Zeus the Guardian of Olympus/Olympus/native-app/push/olympus-push.js, lines 258–260

What broke in real use, and the fix

A "last safe start" plan is always red

ProblemThe first plan scheduled back from each due date. That leaves zero slack at every moment, so any ordinary bad week turns items red, and soon the whole board is red and ignored.

FixSchedule forward from today, keep the backward date only as an alarm line, and show the slack as the number that matters.

One late record blocked every unrelated fix

ProblemThe build was all-or-nothing. One record that was only behind held back data that was entirely correct, plus unrelated fixes, for hours.

FixThe two classes of check in excerpt 3: wrong data still blocks; late data warns.

How it is checked

The build's own checks (15 integrity, 1 freshness) run on every build. A live check reads what the deployed app actually serves and compares it with the expected numbers, field by field. Redesigns: both apps were redesigned by AI models in head-to-head builds, and I picked the winners. Before Polaris switched to its new version on 9 October 2026, the new one was checked against an inventory of 208 features read from the original, so nothing was quietly dropped, and the original was kept so switching back is one setting. Olympus has been on iPhone through TestFlight since 3 October 2026.

My part, and the agents' part

My part
  • The scoring idea: progress as elevation in feet, summits to cross, and only provable work counts.
  • The planning rules the engine follows, for example: if a month overflows, light items slip and the most important ones never do.
  • "Un-ticking must look like it never happened."
  • Turning off AI scoring in favour of pricing my own work.
The AI coding agents' part
  • Both web apps, the scheduling engine, the sync function, the build and its checks, the iPhone shell and the notification sender.
  • Turning my rules into working algorithms.
  • Recording each defect in the code's comments, with the date and the cause, so every rule traces to a real failure.
Code tour The demo link: how the two demo copies share one browser
My agent system, and Vocalis
Code tour How the agent system is put together: rules, skills, guards, backups and the worker loop

This is how I run my own work with AI agents, every day since 15 August 2026. It is small, but it has the parts an AI rollout inside a company needs: clear roles, written rules, reusable procedures, guards that enforce the rules, cost limits, backups, and proof before work counts as done, with an independent check on the riskiest work. I designed and directed it; AI coding agents wrote the code.

How the parts fit

  1. I type an agent's name on my PC or my iPhone. That opens its own session on an always-on Linux server, which keeps working after I close the laptop.
  2. Each agent has one main job, for example a research-strategy agent, a builder, a tutor, and an agent that keeps two apps working.
  3. Each agent reads its rulebook first. A shared CLAUDE.md file holds the rules every agent follows, and each team has its own on top.
  4. Skills are written procedures any agent can run by name, such as "write a goal" or "turn a review into a checklist". There are 43.
  5. Hooks are small scripts Claude Code runs before a command. They turn a written rule into a wall the agent cannot walk through.
  6. Bigger work goes through a worker loop: a written goal with a "done when" list, a fresh worker agent that must show proof for each item, a separate checker agent on a cheaper model for the riskiest work, then a report to me.
  7. Every 10 minutes everything is committed and pushed to GitHub, so there is normally a recent copy off the server.

5 short excerpts from the real files

The numbers on the left are the real line numbers.

01
A rule written once, read by every agent

Agents were publishing websites live just to look at their own changes. A written rule in the shared rulebook says what to do instead: preview locally for free, use a draft release to check, and ship once, at the end, through one gate.

CLAUDE.md
**The rule, in four lines:**
- **Verification NEVER uses a production deploy.** To look at a change, use a **local preview** (`_build/agentos/deploy/preview.sh <site-dir>` — free, serves the exact file bytes) or a **draft deploy** (`netlify deploy` with **no** `--prod`), which gets its own URL and does not touch the live site.
- **One production deploy per finished piece of work, at the end**, once the work is already proven. Never one per iteration, never mid-task to have a look.
- **In a driven queue, production deploys wait for the END OF THE QUEUE** — not the end of each goal.
- **Production goes through `_build/agentos/deploy/safe-deploy.sh`**, which is the only sanctioned way to deploy. It refuses a production deploy without a written `--why` and without a site id that actually resolves, and it enforces three limits from `deploy-budgets.conf`: a **minimum gap between production deploys to the same site** (the one that stops a read-back loop), a **24-hour cap**, and a **30-day cap** as a backstop. Every attempt gets a row in `deploy-log.tsv`. Drafts and previews are never blocked, because they cost nothing.

Source: CLAUDE.md, lines 106–110

02
…and a guard that enforces it

A rule in a file does not stop an agent that is not re-reading it. So a hook checks every command before it runs. If a command is a live release that skips the gate, it is blocked with a message that explains why and what to do instead. The file's own header admits its known limit (it reads command text, so scripts are handled separately), because a guard you overtrust is worse than none.

deploy-guard.sh
  # Is a netlify deploy in this segment? Any leading path, any npx form, whole word only.
  printf '%s' "$SEG" | grep -qE '(^| )([^ ]*/)?netlify(-cli)?(@[^ ]*)? +deploy( |$)' || continue

  # Is it PRODUCTION? -p is netlify's own short flag for --prod, and --prod-if-unlocked
  # publishes too. Missing -p is how an earlier version of the WRAPPER called a production
  # deploy a draft and shipped it with no reason, no site check, no budget and no log row.
  printf '%s' "$SEG" | grep -qE '(^| )(--prod(uction)?|--prod-if-unlocked|--prodIfUnlocked|-p)( |=|$)' || continue

  # Allowed only if THIS SEGMENT invokes the wrapper before the netlify token.
  BEFORE="$(printf '%s' "$SEG" | sed -E 's/(^| )([^ ]*\/)?netlify(-cli)?(@[^ ]*)? +deploy.*/\1/')"
  printf '%s' "$BEFORE" | grep -q 'safe-deploy\.sh' && continue

  VERDICT=2
done
[ "$VERDICT" -eq 2 ] || exit 0

Source: ~/.claude/hooks/deploy-guard.sh, lines 47–61

03
A skill: a procedure any agent can reuse

When I find myself explaining the same process twice, it becomes a skill: a written procedure with a name and a one-line description of when to use it. Any agent can then run it the same way. This one turns a conversation into a goal a worker agent can finish and prove.

SKILL.md
---
name: make-goal-prompt
description: Write the owner a paste-ready /goal prompt for whatever we're working on — self-contained, verifiable DONE-WHEN conditions, actually finishable Fires on "/make-goal-prompt", which takes: <what the goal is about> — or leave empty to use whatever we're already discussing, or by intent when the owner asks for this in their own words.
---

Source: ~/.claude/skills/make-goal-prompt/SKILL.md, lines 1–4

04
Always on: one session per agent, a backup every ten minutes

Each agent gets its own named session on the server, so closing my laptop never stops the work. Every ten minutes a scheduled job commits everything and pushes it to GitHub. It only ever does a plain push: if GitHub refuses, it logs that a human is needed rather than forcing it.

agent-open.sh
    n="$NAME"; i=2
    while have "$n"; do n="$NAME~$i"; i=$((i+1)); done
    # No -A. That flag is the whole bug this file exists to remove.
    exec tmux new-session -s "$n" -c "$DIR" bash -lic "$CMD"

Source: _build/agentos/agent-open.sh, lines 70–73

auto-push.sh
# 3. PUSH only when actually ahead. Nothing to say otherwise — a log line every
#    10 minutes saying "nothing happened" is a log nobody reads.
if [ -n "$(git log origin/main..HEAD --oneline 2>/dev/null)" ]; then
  # plain push only. never -f, never --force-with-lease. A rejection is
  # information, not something to overpower.
  if git push --quiet origin main; then
    echo "$(date '+%F %T')  pushed $(git rev-parse --short HEAD)" >> "$LOG"
  else
    echo "$(date '+%F %T')  PUSH REJECTED — human needed" >> "$LOG"
  fi

Source: _build/agentos/auto-push.sh, lines 157–166

05
The worker loop, and its budget

Bigger work is written as a goal with a checkable "done when" list, then typed into a fresh worker session by a small script. This guard in that script refuses a file that does not start with a slash command such as /goal, because that mistake fails silently: the worker would run the text as an ordinary prompt with no "done when" check, and nothing would look wrong. The checking rules are written down too: one review, one attempt to break the work, at most two re-checks of what was fixed, and every checker on a cheaper model. That budget came from measuring what had drained my AI plan.

arm-goal.sh
# Guard the one mistake this design cannot otherwise catch: a caller passing a
# bare condition with no command would paste prose into the box and submit it as
# an ordinary prompt — the goal would never arm, and nothing would look wrong.
head -c 40 "$LINEFILE" | grep -q '^/' || {
  echo "refusing: $LINEFILE does not begin with a slash command." >&2
  echo "pass the full line from: queue-read.mjs <page> --index N" >&2
  exit 1
}

Source: _build/agentos/drive/arm-goal.sh, lines 41–48

worker-usage-budget.md
3. **Light checks, run on Sonnet.**
- One plan challenge.
- `/code-review` at **medium**.
- **One** break-it round.
- If break-it fails: fix, then run a **targeted re-check of the fixed findings only**, not a whole new round. At most 2 re-checks. Then rate what is left and stop.
- **Every checker agent is dispatched with `model: "sonnet"`.** Checkers follow a script, and Sonnet does that at a fraction of the usage.

Source: Company BKM/worker-usage-budget.md, lines 18–23

What broke in real use, and the fix

Live releases used as a test step

ProblemIn under a month, 392 of 396 website releases were full live releases, mostly agents checking their own edits. They used about 95% of the hosting credits and paused every site.

FixMeasured it first, then the rule (excerpt 1), a guard that blocks it (excerpt 2), a single release gate with limits, and a log row for every release.

My AI plan drained in about 30 hours

ProblemAbout 3 billion tokens were re-read in roughly 30 hours. About 65% came from one worker that ran five goals in one huge session.

FixA written budget every agent reads: at most four goals per queue, normal-size sessions cleared between goals, cheaper models for checking, one worker at a time.

"It keeps putting me back in the old session"

ProblemTwo fixes for this looked right and both failed, because the real cause was one flag in a command on my PC, and the fixes were made on the server.

FixAll of the session logic moved into one file on the server (excerpt 4), so a fix now lands in the one place that actually runs.

My part, and the agents' part

My part
  • The shape: one always-on server, one main job per agent, a rulebook each, and skills for anything repeated.
  • The rules and budgets, each written from a measured failure: the release rule, the usage budget, and "on the riskiest work, the agent that did the work does not sign it off".
  • Spotting the problems from the numbers and the logs, and deciding the fix.
  • Which work gets automated, and which calls stay with me.
The AI coding agents' part
  • All of the code: the session launcher, the backup job, the deploy guard and gate, the worker scripts.
  • Many of the technical choices, such as how the guard normalises a command before matching it.
  • A written report at the end of every goal saying what was proven and what was not.
Code tour How Vocalis works: five excerpts, what broke in real use, and how it is tested

Claude Code agents live in a terminal, so you have to sit at a keyboard to use them. Vocalis lets me talk to the same agent sessions by voice, from my iPhone or my PC, and hear them answer: same files, tools and history, so I can switch between typing and talking mid-task. It was built in about 34 numbered goals from 26 September 2026; each goal was a written brief an AI coding agent carried out, tested and reported on. I designed and directed it; AI coding agents wrote the code.

How the parts fit

  1. iPhone app or Windows app: turns my speech into text and plays the spoken reply.
  2. Encrypted private network only. Each device must be one of mine and gets its own key the first time it opens the app.
  3. The Vocalis host on the always-on server holds the chat history and settings, so phone and PC share one state. It is the only part that touches the agent sessions.
  4. The agent sessions: the host types into a session and reads the reply from its transcript. It only does what I could do at the keyboard.

5 short excerpts from the real files

The numbers on the left are the real line numbers.

01
Never type on top of anything

The server types my words into a live terminal. That is risky: the agent might be busy, or I might be typing there myself. So before it pastes anything, it checks. If the session is busy it refuses. If there is text in the input box, it waits a moment and then gives up, rather than mixing my words into someone else's. The rest of this function then proves the message arrived by finding it in the session's own transcript.

vbox.py
def cmd_send(d, text, wait_typing=1.5, now=False, v5=False):   # short: the core retries and tells him it is waiting
    text = text.strip()
    if not text:
        out({"ok": False, "why": "empty"}); return
    # Typing guard: never paste on top of anything. Wait (up to wait_typing s) for an empty box.
    # now=True (v4 "Send now"): he chose to send while the session works. The CLI takes typed input
    # while busy and holds it in its OWN queue, so the busy check is skipped (the typing guard stays).
    t0 = time.time()
    while True:
        s = state(d)
        if s["busy"] and not now:
            out({"ok": False, "why": "busy"}); return
        if not s["typing"] and not s["picker"]:
            break
        if time.time() - t0 > wait_typing:
            out({"ok": False, "why": "typing", "typed": s["typed"]}); return
        time.sleep(0.5)

Source: _build/agentos/voice/vocalis/box/vbox.py, lines 643–659

02
Only my voice messages change how the agent answers

Spoken answers need a different shape: no tables, no code, no file paths. I did not want to edit any agent's instructions. So every message Vocalis sends starts with a 🎙 mark, and a hook (a small script Claude Code runs before each message) adds the "write for the ear" instruction only when it sees that mark. Anything I type by hand is untouched.

talk-hook.py
if prefs.get("read", "full") == "full":
    HINT = FULL
HINT += ASK.get(prefs.get("ask", "risky"), ASK["risky"])
# Batch 2 (DRIVING MODE, owner): while it is on, he is at the wheel and listening by ear only.
# 29 Sep (owner): driving mode is a SCREEN setting only, used for driving AND for plain hands-free talk,
# so it no longer changes how the agent answers. Kept here, switched off, in case he wants it back.
if False and prefs.get("driving"):
    HINT += (" HE IS DRIVING right now and can only listen, not read. Keep every reply short and easy to follow by ear:"
             " the answer first, a few plain sentences, no lists or detail he would need to look at."
             " If there is more, say so in one sentence and offer it for later.")
# The mark is looked for near the start, not only AT the start: a long paste can arrive
# wrapped in the CLI's <pasted_content> tags.
if "🎙" in str(p.get("prompt", ""))[:300]:
    print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": HINT}}))

Source: _build/agentos/voice/vocalis/box/talk-hook.py, lines 55–68

03
The echo guard

One day, with the phone on speaker, it heard the agent's own reply and sent it back as my message, word for word. The fix: remember every line spoken in the last three minutes. If most of the word pairs the microphone heard match something just spoken, it is the echo, not me, and it is not sent. Short commands like "stop" are never judged, so they always work.

vocalis_core.py
# Every line that is spoken (or sent to the phone to speak) is remembered for 3 minutes; a transcript whose word pairs are
# mostly (>= 60 %) word pairs of those lines is the reply coming back through the mic, and is NOT sent. Short phrases
# (under 4 words: "stop", "yes", "go ahead") are never judged, so commands always work. "that was me" still sends it.
ECHO_SECS, ECHO_LINE = 180, 0.6
_spoken = []                                   # [(time, line)]
def note_spoken(line):
    now = time.time()
    _spoken.append((now, str(line or "")))
    while _spoken and (now - _spoken[0][0] > ECHO_SECS or len(_spoken) > 80): _spoken.pop(0)
def _echo_words(t): return re.findall(r"[a-z0-9']+", str(t or "").lower())
def echo_score(text, spoken=None):
    """Share of the transcript's word pairs that were in a recently spoken reply (0..1), or None if too short to judge."""
    w = _echo_words(text)
    if len(w) < 4: return None
    now = time.time()
    said = [x for at, ln in (spoken if spoken is not None else _spoken) if now - at <= ECHO_SECS for x in _echo_words(ln) + ["\x00"]]
    if not said: return 0.0
    pairs, spk = list(zip(w, w[1:])), set(zip(said, said[1:]))
    return round(sum(1 for p in pairs if p in spk) / len(pairs), 3)

Source: _build/agentos/voice/vocalis/core/vocalis_core.py, lines 868–886

04
When the iPhone takes the microphone away

iOS can take the audio away at any moment: a call, another app, or its audio system restarting. One restart sent four notices in 33 milliseconds. This file decides what to do, in one place: handle a restart once, ignore stale notices, and never re-activate the audio more than 6 times in 10 seconds. It is pure logic with no audio code, so it can be tested on its own.

VocalisMicRecovery.swift
    // the loop guard: true = this activation may go ahead (and is counted)
    func mayActivate(_ now: Date) -> Bool {
        activations = activations.filter { now.timeIntervalSince($0) < Self.activationWindow }
        if activations.count >= Self.maxActivations { refused += 1; return false }
        activations.append(now)
        return true
    }
    var recentActivations: Int { activations.count }

    func mediaReset(_ s: TalkSnap, now: Date) -> [TalkStep] {
        if now.timeIntervalSince(lastReset) < Self.resetWindow { resetsMerged += 1; return [.ignore("the same reset again (merged)")] }
        lastReset = now; resetsHandled += 1
        var steps: [TalkStep] = [.prepareCategory, .dropDeadEngine]
        guard s.on else { return steps + [.note("reset handled: Talk not running in the background")] }
        steps += [.activate("media services reset"), .startEngine("media services reset")]
        steps.append(s.busy ? .note("mic rebuilt, gated: a reply is still coming") : .listen)
        return steps
    }

Source: _build/agentos/voice/vocalis/ios/native/App/VocalisMicRecovery.swift, lines 53–70

05
Trust the server by its name, reach it by its address

Some days the app said the server was unreachable while the network was fine: an iPhone privacy setting sent the phone to the wrong place. So the phone tries the server's private address first, with a fallback to its name, and either way checks the server's security certificate against its name by hand. A certificate for any other name is refused.

VocalisNet.swift
    // A TLS challenge from the box's address → accept only if the certificate is valid for the box's name.
    // Anything else (another host, another kind of challenge) → nil = iOS's normal handling.
    static func trust(_ challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?)? {
        let ps = challenge.protectionSpace
        guard ps.authenticationMethod == NSURLAuthenticationMethodServerTrust, ps.host == boxIP || ps.host == boxName, let t = ps.serverTrust else { return nil }
        SecTrustSetPolicies(t, SecPolicyCreateSSL(true, boxName as CFString))
        // NEXT-FIXES-17 item 8: the SAME certificate that passed the full check in the last hour is accepted at once — the full
        // check (SecTrustEvaluateWithError) can wait on the network (revocation), and it ran for every new connection.
        let leaf = (SecTrustCopyCertificateChain(t) as? [SecCertificate])?.first.map { SecCertificateCopyData($0) as Data }
        if let leaf, cache.withLock({ $0.leaf == leaf && Date() < $0.until }) { return (.useCredential, URLCredential(trust: t)) }
        var err: CFError?
        if SecTrustEvaluateWithError(t, &err) {
            if let leaf { cache.withLock { $0 = (leaf, Date().addingTimeInterval(3600)) } }
            if !trustOKLogged { trustOKLogged = true; report(["ev": "net-trust", "ok": true, "host": ps.host, "chain": SecTrustGetCertificateCount(t)]) }
            return (.useCredential, URLCredential(trust: t))
        }
        lastTrustFail = Date()
        report(["ev": "net-trust", "ok": false, "host": ps.host, "chain": SecTrustGetCertificateCount(t), "why": err.map { String(String(describing: $0).prefix(300)) } ?? "?"])
        return (.cancelAuthenticationChallenge, nil)
    }

Source: _build/agentos/voice/vocalis/ios/native/App/VocalisNet.swift, lines 44–63

What broke in real use, and the fix

Fixing one thing kept breaking another

ProblemBug fixing became whack-a-mole: each fix was tested only on the bug it fixed, and something else broke.

FixI asked for one set of tests that checks the whole app after any change: the robot user (below). Every bug I report becomes a check first, and only then gets fixed.

A rebuild that tested well but felt worse

ProblemA rebuilt, fully native iPhone version passed its tests on a simulator, but felt worse in my hands on the real phone.

FixI turned it down and kept improving the original screens. New versions of a daily tool now need my look, my features and a real-phone trial before they replace anything.

How it is checked

The robot user opens the real phone screens in a browser with no window, at my iPhone's size, with a fake microphone, and talks to a test copy of the server using throwaway sessions only. It runs 289 checks, including rules that must always hold (for example: nothing I said is ever lost), a seeded random walk that can be replayed exactly, and a named check for every past bug. It caught 19 of 19 deliberately broken copies of the app, which proves the tests can fail. In a test on a simulated slow 4G connection, opening the app went from 9.5 seconds to 0.3 seconds. A lesson I paid for: in October the robot went unrun for over three weeks, and a voice outage went unnoticed for two of them. The rule now: every change, however small, runs the full set before it ships.

My part, and the agents' part

My part
  • What it is: the command line by voice, in the same session, never a separate chatbot.
  • How it behaves: it starts off and never connects on its own; one session in talk mode at a time; replies read in full by default.
  • Turning down the native rebuild after trying it on the real phone.
  • Asking for one test set that checks the whole app, and the rule that every change runs it.
The AI coding agents' part
  • The Windows app, the server host, the iPhone app and keyboard, and every test.
  • Most technical choices, such as using a hook to shape spoken answers.
  • Finding causes from logs, and a close report for each goal saying what was proven and what was not.