AIObox × AkiMCP
How a window is named
Handle, chatId and targetId: how AIs in many windows reach each other through AkiMCP, with every branch drawn.
Many AIs run side by side in many windows, on several providers. For them to work together, every window needs a lasting name: one that does not change when the chat changes, when the tab changes or when Chrome restarts.
A window is called by its handle P#·W#. The chatId is only the chat open in that window right now. The targetId is only the Chrome tab of this run. AkiMCP is the only one that turns a name into a real tab.
1. The layers
The user and the AIs speak in handles. AIObox keeps the name book; AkiMCP looks it up and touches the right tab.
- AIObox desktop (the Rust core) opens and runs Chrome, puts the handle in each page title and writes the name book.
~/.aki/aiobox/cdp/windows.jsonis the name book: profiles → windows → tabs, withretired[]. AIObox writes it atomically; AkiMCP reads it.windows.refresh: AkiMCP writes one id line to ask for a fresh read, and AIObox answers within 1 s.handles.jsonholds the W counter; only AIObox reads and writes it.
2. Three kinds of id
Each id has one job. Using one for another's job sends a message to the wrong chat. The ids below are made-up examples.
- Handle, the lasting name (
P2·W1). What: P is the profile number, W the window number from that profile's counter. Lives: never given again; kept through an AIObox restart and through session restore. Used for: naming a window in every op (window=). - chatId, the chat open now (
1a2b3c4d…9f0e). What: the chat open in the window: Notion?t=, ChatGPT and Grok/c/, Claude/chat/, Gemini/app/. Lives: changes with a new chat, a workspace switch or a handoff. Used for:from=(yourself) andexpect=(to be sure it is still that chat). - targetId, the Chrome tab (
0D1E2F3A…). What: the CDP tab of this Chrome run. Lives: one run. Used for: internal only; AkiMCP sends CDP to this tab.
An example: P2·W1 moves to a new workspace to get more quota. Its chatId changes; its handle is still P2·W1, so every other window still reaches it.
3. What a window knows
No file reading, no guessing from titles: a window asks AkiMCP.
- Itself:
op=whoamigives its handle, chatId,workspace({ id, label, status }) andusage(session,weekly%,readAt). Every window: the same fields inop=state. - Not known yet:
usageorworkspaceisnullandusageWhysays why. Never infer it from the title. - Where to go:
op=stateworkspaces, per profileId: each workspace'slabel,status,session,weekly. - Quota: read
usageinop=whoami; from 82% keep your state; at 87% AIObox moves the chat itself. - Chat cutoffs, read mode, busy:
op=state. - Automation runs:
aki__aiobox op=runslists AIObox's automation runs (usage reads,connect-akimcp) with their outcome; filter byautomation,since,last.
aki__aiobox op=whoami
quote="20+ characters of the user's message"
→ you: {
handle: "P2·W1", chatId: "1a2b3c4d…9f0e",
workspace: { id, label: "Team", status: null },
usage: { session: 41, weekly: 63, readAt }
}
4. Where one window=X command goes
X can be a handle, a chatId or a targetId. Each step has one right way out; every other way stops with a named error code.
command inright pathwait, queue or reconnectstop with an error
Step by step, with real commands
- Look it up. Find X in
windows.jsonby handle, chatId or targetId. A tab whose title carries another handle:stale_map. - Not there: ask for a refresh. Write
windows.refresh, wait for a newansweredorgeneration(at most 5 s), look again. Nobody holds X:no_window. - A retired handle leads to its successor. Follow
retired[]to the window still open; the result namesresolvedFrom. A live handle always wins. A loop A→B→A:retired_loop. - Check
expect. The window must still show the chat expected. Another one:wrong_window. - Never to yourself. A
fromequal to the target:self_target. - Never over a draft. A draft in the target's box is never touched. Send v2 (
capabilities.send2) queues the message:queued: truewith itsposition. Send v1 refuses withdraft; retry in the same turn withwait=<s>. - Send the way the provider reads.
liveandqueued: send now, mid-answer too.blocked:wait=, then send; without it the target isbusy. - Delivered only once read back.
op=read last=2shows the message: only then is it reported as delivered.
aki__aiobox_write op=send
window=P2·W2
from=1a2b3c4d…9f0e
text="[P2·W1 → P2·W2] …"
→ sent, delivered, midAnswer
aki__aiobox op=read
window=P2·W2 last=2
→ message seen → "delivered"
5. Each provider takes a message its own way
One rule does not block every send. The sender knows how the target reads.
| Provider | Read mode | Send while it answers | The target AI sees it |
|---|---|---|---|
| Notion, ChatGPT, Grok | live | Yes | At once, mid-answer |
| Claude | queued | Yes | After its turn ends |
| Gemini | blocked | No | Once it is no longer busy (the sender uses wait=) |
Notion only: one profile is one account with several workspaces, and quota is counted per workspace. From 82% keep your state; at 87% AIObox moves the chat itself.
Choosing the Notion account and workspace
Chat cutoffs ("Resting")
A chat cutoff, shown as "Resting" in AIObox, is a normal, temporary state: an account or workspace takes a short break from new AI chats (its quota is full, two replies in a row were interrupted, or the provider paused its usage for now). It clears by itself at until, or once the allowance is back; meanwhile AIObox starts new AI chat work on another account. Joining the workspace, reconnecting AkiMCP, reading usage and account admin go ahead as usual. op=profiles shows canTakeChat and chatPause.
Tell AIObox what you saw: aki__aiobox_write op=pause_chat reason=<what the chat showed>, plus:
- Interrupted replies on two or more windows of one account:
account=<label> profile=<profileId> hours=4. - Full quota or a pause notice on one workspace:
workspace=<label>. The account rests too once this happens on two or more of its workspaces. op=resume_chatends one.
AIObox then moves every window there to another account at once.
6. A handoff keeps the name
Two kinds of handoff. Both end on the same invariant: whoever calls the old name still reaches whoever holds the role now.
To another window: P8·W13 → P4·W10
place_like window=P4·W10 like=P8·W13: the new window takes the old one's place.close_window window=P8·W13 successor=P4·W10: AIObox recordsretired {P8·W13 → P4·W10}. Withoutsuccessornothing is recorded. Refused while the window answers or holds a draft; a dead window (no composer, nothing answering) closes.- Whoever calls
P8·W13reachesP4·W10; the result carriesresolvedFrom.
Same window: P2·W1, chatId 5e6f7a8b… → 1a2b3c4d…
usageinop=whoamireaches 82%: keep your state; at 87% AIObox moves the chat itself.- Once
op=readshows an empty chat,op=send window=<own targetId>withoutfrom: the role and the old chatId. - The successor confirms its chatId in
op=state. The handle stays; nothing to place or close.
7. Five rules that never bend
- Windows call each other by handle. A chatId is never the key to follow.
- A live handle always wins over an old
retirededge; an edge that makes a loop is refused. - An AI acts only through AkiMCP ops: no scripts, no raw CDP, no closing a tab through devtools.
- AkiMCP writes only two things into AIObox's folder:
windows.refreshand the request files inrequests/.flags.jsonis only read. - A message not yet seen by
op=readis not delivered. Someone else's draft is never touched.