Give your Codex agent its own Inkbox identity:
a mailbox, iMessage, a phone number for calls and SMS, and an internet address.
Step away from the keyboard and keep working with it from anywhere.
Email · Calls · SMS / MMS · iMessage · Tunnel
- Codex installed and logged in. The bridge drives a real Codex session, so the
codexCLI has to be on the machine and authenticated — install it (developers.openai.com/codex), then either sign in with a ChatGPT/Codex login or setOPENAI_API_KEY.inkbox-codex doctorchecks for it. - Python 3.11+. The installer finds one and builds the bridge its own venv.
- macOS or Linux. Boot persistence uses a systemd user unit on Linux and a launchd agent on macOS.
- An Inkbox agent — nothing to set up in advance; the setup wizard self-signs up for you (or takes an existing API key).
This finds a Python 3.11+, installs the bridge in its own venv, puts inkbox-codex on your PATH, and runs the setup wizard:
curl -fsSL https://raw.githubusercontent.com/inkbox-ai/codex-plugin/main/install.sh | bashThat's the whole setup. The wizard creates a fresh Inkbox agent for you (or takes an existing API key), provisions a phone number, connects iMessage, mints a webhook signing key, picks the project directory Codex works in, and offers to keep the bridge running on every boot. When it finishes, text/email/call your agent and it answers from a real Codex session.
The one thing to have ready: be logged into Codex — a ChatGPT/Codex login (via the Codex app/CLI) or OPENAI_API_KEY set. The installer checks this and warns if it's missing.
Flags: --start (launch the background gateway when done), --no-setup (install only). From a local checkout, run ./install.sh. Re-running is safe.
For unattended agent setup, install without opening the wizard and pass the API key through the environment (or standard input), never a command-line argument:
curl -fsSL https://raw.githubusercontent.com/inkbox-ai/codex-plugin/main/install.sh | bash -s -- --no-setup
export INKBOX_API_KEY="ApiKey_..."
inkbox-codex bootstrap --identity my-agent --project-dir "$PWD" \
--voice-ai --rotate-signing-key --start-gateway
unset INKBOX_API_KEYbootstrap validates that the key can access exactly the requested identity, scopes down an admin key before saving it, preserves existing Voice AI settings, enables native Inkbox tool approvals, and starts or restarts the detached gateway. Signing-key replacement is opt-in because it transfers verified webhook delivery away from any gateway using the previous key. The command prints a secret-redacted JSON result and is safe to resume.
Check it any time:
inkbox-codex doctor # config, codex CLI/auth, identity reachability
inkbox-codex status # is the background gateway up? where are the logs?you (phone) ── SMS / iMessage / email / call ──▶ Inkbox ──▶ tunnel ──▶ bridge
│
▼
Codex session
(full tool access in
your project dir)
-
Text, iMessage, email, or call your agent's Inkbox number. Each remote party gets one Codex session spanning every channel — text it on the walk home, then email it details, same conversation.
-
Codex runs with full tool access in
CODEX_PROJECT_DIR. It reads, searches, and browses freely; anything risky (running commands, editing files) is escalated to you as a text:Codex wants to run the command: npm test
Reply 1 (or YES) to allow once, 2 (or ALWAYS) to allow this kind of action for the rest of the session, 3 (or NO) to block it.
-
When Codex needs you to pick between options (the
AskUserQuestiontool), you get a numbered poll on whatever channel you're on, and your reply is fed back as the answer. -
Each message you send is tagged with its channel, so Codex knows whether it's on SMS, iMessage, email, or a call.
-
A channel prompt is appended to Codex's system prompt so replies fit a phone: plain text, no markdown, short, jargon kept to a minimum ("saved and published the change", not "pushed to origin/main").
-
Codex also gets Inkbox tools (
inkbox_send_email,inkbox_send_sms,inkbox_send_imessage, …) so it can proactively reach you — "email me the full report" works.
If you'd rather not run the installer (any Python 3.11+ environment):
pip install -e .
inkbox-codex setup # interactive wizard — writes .env for you
set -a; source .env; set +a
inkbox-codex doctor
inkbox-codex runinkbox-codex setup walks you through everything and writes .env: create a fresh Inkbox agent via self-signup (or bring an existing API key), pick or create the identity, attach the Codex avatar, provision a phone number, wait for your START opt-in, choose a phone-call voice stack, connect iMessage, mint a webhook signing key, choose the project directory, choose whether to trust Inkbox MCP tools without repeated allow prompts, and set up autostart. Realtime keys are validated before selection is saved. An admin key used to change Voice AI authority is held only for that setup run and is never written to .env. Rerun setup anytime to reconfigure.
The repository includes a manual-test image with Codex, the plugin, and the Inkbox SDK preinstalled. It reuses the host's native Codex login and keeps Inkbox plugin state in a named volume; neither is copied into the image:
docker build -t inkbox-codex-local .
docker run -dit --name inkbox-codex-local \
-v "${CODEX_HOME:-$HOME/.codex}:/root/.codex" \
-v "$PWD:/workspace" \
-v inkbox-codex-state:/root/.inkbox-codex \
inkbox-codex-local
docker exec -it inkbox-codex-local bash
# inside the container
inkbox-codex setup
inkbox-codex doctor
inkbox-codex run
# after leaving the container
docker rm -f inkbox-codex-localThe image contains no credentials. If you use API-key authentication instead
of codex login, add -e OPENAI_API_KEY="$OPENAI_API_KEY" to docker run.
Inkbox credentials are entered into setup or supplied only at runtime.
On startup the bridge opens an Inkbox tunnel, wires mail/text/iMessage webhook subscriptions and the incoming-call channel to it, and routes everything into Codex sessions.
inkbox-codex run # foreground (Ctrl+C to stop) — good for first runs and debuggingOr run it as a background daemon (PID + log under ~/.inkbox-codex/):
inkbox-codex start # detach and run in the background
inkbox-codex status # is it running? where are the logs?
inkbox-codex restart # restart it
inkbox-codex stop # graceful stop (SIGTERM, then SIGKILL after 5s)
tail -f ~/.inkbox-codex/gateway.logstart auto-loads .env from the current directory, so you don't have to source it first. run is the foreground version a service manager (systemd, Docker) should supervise; start/stop are the self-contained background option.
The setup wizard offers to keep the bridge running for you — either just in the background for this session, or as a service that starts on every boot. On Linux it installs a systemd user unit (~/.config/systemd/user/inkbox-codex.service) and enables it; on macOS it installs a launchd agent. To keep a Linux service alive while you're logged out, enable lingering once:
sudo loginctl enable-linger "$USER"
systemctl --user status inkbox-codex # restart | stop | statusinkbox-codex uninstall # stop it, remove the boot service + launcher; keep config
inkbox-codex uninstall --purge # also delete ~/.inkbox-codex (config, logs, sessions)This is local-only — webhook subscriptions on the Inkbox side are left as-is; remove them in the Inkbox Console if you want.
Then, from your phone:
- Text
STARTto the agent's number (first time only, carrier opt-in). - Text it something like "clean up the TODOs in the auth module".
- Approve the permission texts as they arrive. Get the result as a text.
Codex never silently runs anything destructive. The bridge starts codex app-server and answers its approval requests over your active Inkbox channel:
- Commands, file changes, permission-profile changes, and request-user-input prompts block the agent mid-turn while the bridge texts you a one-line plain-language summary.
- Your next message answers the escalation instead of starting a new turn — reply
1/yes,2/always(session-scoped grant), or3/no. - Request-user-input prompts are formatted as numbered options; reply with the number or free text.
- No reply within
INKBOX_PERMISSION_TIMEOUT_S(default 10 min) → the request is denied or answered empty and Codex carries on as best it can.
Direct-message sessions are keyed by Inkbox contact, so one person = one conversation across channels. Group SMS messages share a session keyed by the group conversation, separate from direct messages and other groups, while retaining each sender's contact details. Codex session ids are persisted in ~/.inkbox-codex/sessions.json and resumed across bridge restarts — your conversation picks up where it left off. Replies go out on the channel you last used. If a voice call ends before Codex finishes a voice reply, that late voice reply is dropped instead of silently switching to SMS or email.
Group replies. The setup wizard offers Automatic (default) or Mention required for group SMS and iMessage, saved as INKBOX_GROUP_REPLY_MODE=auto|mention. Automatic keeps the existing behavior: the agent decides whether to answer. Mention mode starts a reply only when the new message itself includes @agent or @<agent-handle> as a whole mention, case-insensitively; older messages, links, and email addresses do not count. Other messages and group reactions are added to Codex's context without generating a reply or a typing indicator. While a turn is running, background messages wait until it finishes before being appended. Appended context persists with the Codex thread; messages still waiting in the bridge's queue are not persisted across a restart. Direct messages are unchanged. Commands such as /stop, and answers to the agent's pending questions from the sender it asked, do not require a mention.
Verified SMS/MMS, iMessage and email webhooks may include a top-level
companion object. Webhooks without it (or with null) retain normal routing.
The bridge loads the full authorized initialization snapshot using the Inkbox
SDK, including every page, and submits one combined input. The sponsor trigger
is included once; historical messages, attachment descriptors, and history notices remain
conversation data, never individual turns or slash commands.
The setup wizard offers two independent settings, including when rerun for an existing identity:
- Companion responses:
INKBOX_COMPANION_RESPONSE_MODE=safe|relaxed. Safe (default) allows only messages withsender_access="direct"to wake the agent. Sponsored messages, or messages with missing/unrecognized access, are appended to context without model generation, typing, or a reply. Relaxed allows any delivered message to wake the agent, including sponsored and unknown-access messages. - Group replies:
INKBOX_GROUP_REPLY_MODE=auto|mention, also applied to Companion email. Automatic lets an eligible message start a turn; the agent decides whether a reply is warranted. Mention required additionally requires@agentor@<agent-handle>in the current message's own text. For Companion email, putting the agent's mailbox in the current message's To recipients also counts as a mention. Address matching is case-insensitive and supports display names; Cc/Bcc alone do not count. Quoted headers, historical mentions, notices, and attachment metadata do not count. Reply-all may retain the agent in To and therefore satisfy this gate again.
The signed webhook supplies sender_access on data.text_message for SMS/MMS
or data.message for iMessage/email. direct means contact rules permitted the
message without sponsorship, including allowed-by-default senders; sponsored
means delivery was authorized through sponsorship. Neither value grants command
permissions or establishes permanent trust. Access is not inferred from the
sender's contact record, sponsor identity, or Companion phase.
Wake below means context plus a model turn; Context means context only. These rules apply equally to SMS/MMS, iMessage, and email Companion inputs.
| Companion mode | Group replies | Direct, no mention | Direct, mention | Sponsored, no mention | Sponsored, mention |
|---|---|---|---|---|---|
| Safe (default) | Auto | Wake | Wake | Context | Context |
| Safe (default) | Mention | Context | Wake | Context | Context |
| Relaxed | Auto | Wake | Wake | Wake | Wake |
| Relaxed | Mention | Context | Wake | Context | Wake |
For email, the table's mention includes the agent being in To. This does not override sender access: sponsored and unknown-access messages remain context-only in Safe mode even when addressed To the agent. Auto is unchanged.
Context-only messages are retained for later eligible turns. For initialization, the entire snapshot is appended together; only the current received message can trigger generation. Webhooks without Companion metadata retain normal routing and are unaffected by the Companion response setting.
- Sessions are isolated by API environment, identity, channel, server scope, and activation, separate from ordinary contact sessions. A new activation starts a fresh session with its authorized snapshot. Each new released snapshot must have a distinct activation ID; replaying an immutable activation is not a new batch.
- Initialization completes before queued live messages run. A first-seen live
event loads history as context, without a separate historical sponsor turn;
only the live message can wake the agent. If that message is already in the
snapshot, the combined input uses its access and mention instead of repeating it.
Pending events run in sequence order; numeric
gaps do not stall the conversation. Late unseen events older than an already
submitted input require reconciliation instead of running out of order.
phase="ordinary"uses a separate scoped session and never loads hidden history. - Replies use the original group conversation ID, or the SDK-approved email reply context's stored message UUID with canonical reply-all. They never fall back to privately messaging the latest author. Local sponsor restrictions remain in force. Live turns and replies use the signed conversation scope and saved sponsor message without additional activation lookups. Replies reuse the identity loaded at startup.
- Only a live reply by the prompted, locally allowed sender that passes both
response gates can answer an approval request. Historical or context-only
approval-looking text cannot. Sponsor slash controls also require both gates.
In Mention mode, address email To the agent or prefix answers and controls with
@agent, for example@agent allowor@agent /stop; escalation prompts remind you of this.
SDK requirement: The bridge requires Inkbox SDK 0.7.3 or newer, including
client.companion.load_initialization and activation_messages. Installation
resolves the published SDK; CI tests the released inkbox==0.7.3 minimum across
unit, real-host, and live-channel lanes. No preview source checkout is needed.
An unsupported SDK produces an explicit webhook error; the bridge never falls
back to submitting only the trigger.
Wake decisions use the signed current-message access and do not require extra
SDK lookups. Older SDKs may omit per-entry access labels from rendered history;
those historical entries remain context, never independent triggers. Safe mode
on webhooks that omit sender_access is context-only; no missing field is treated
as direct access.
Delivery and recovery: receipts and initialization checkpoints live under
$INKBOX_CODEX_HOME/companion/ (default ~/.inkbox-codex/companion/), with private
permissions and one receiver owner per environment/identity. Transient reads before
submission retry with capped backoff while the gateway runs; other pre-submission
failures retry up to five times. Pending inputs resume after restart or webhook
redelivery. A retry may refresh its delivery timestamp or inline history preview
without creating another input. Stable event IDs and acknowledged snapshot/live source-message IDs
are deduplicated across restarts within their scope and activation. Completed answers
are saved before delivery.
Transient read failures while preparing delivery retry with capped backoff, including
after restart, without rerunning the model. An interrupted host
submission or uncertain reply send pauses that scope rather than risking another
model turn or duplicate send. The log identifies the retained receipt. Inspect the
scoped Codex thread and channel delivery before operator recovery; do not delete
receipts or blindly replay uncertain events. This is at-most-once automatic
retry behavior at ambiguous boundaries, not an exactly-once transport claim.
Initialization above 8 MiB fails explicitly rather than truncating. The preview
uses POSIX file locking (Linux/macOS).
Run inkbox-codex setup to change the choice without reconfiguring your identity, or edit .env, then restart the bridge to apply it. For background mode, use inkbox-codex restart; for a systemd installation, use systemctl --user restart inkbox-codex.service. Mention mode requires a Codex version supporting thread/inject_items.
Typing indicator. While Codex works on a turn, the bridge keeps a typing indicator alive on your iMessage thread (refreshed every few seconds, since it expires) so you can see it's busy. SMS, email, and voice have no typing indicator, so this is iMessage-only.
Delivery failures. Outbound messages can silently fail — a carrier filters an SMS, an iMessage is declined, an email bounces. Inkbox reports these asynchronously (text.delivery_failed, imessage.delivery_failed, message.bounced/message.failed). The bridge catches them and wakes the affected contact's session to tell Codex which message didn't land and why, so it can retry or reach you another way (a different channel, or a call) using its Inkbox tools. The notice runs as a side-effect turn — Codex acts via tools rather than replying on the channel that just failed — and repeat webhooks for the same message are de-duplicated so it can't loop. text.delivery_unconfirmed is different: it only means the carrier couldn't confirm delivery (the message usually landed), so it's logged for debugging without waking Codex — waking there would resend a message that was likely delivered.
Interrupt by texting again. Messaging the agent again while it's mid-turn works like pressing Esc in Codex and typing a new message: the running turn is interrupted, its partial answer is dropped, and Codex picks up your new message instead. (A reply while it's waiting on a permission/poll still answers that escalation — interrupting only applies while it's actively working.)
Control commands. A handful of slash-commands steer the conversation itself and are handled by the bridge instead of being sent to Codex (works on any channel):
/clear(or/new) — start a fresh conversation: forgets the resumed session, tears down the client, and clears session-scoped permission grants./stop(or/cancel) — interrupt the current turn and drop anything queued, keeping your conversation context intact./resume— texts you back a numbered list of recent Codex conversations (each with a short summary and timestamp); reply with a number to reopen that one. Like/resumein the Codex CLI./status— reports what the bridge is doing for you right now (working, waiting on a reply, or idle) and whether you're in a fresh or ongoing conversation. Read-only; doesn't disturb a running turn./usage— reports Codex rate-limit windows and token summary from app-server account endpoints./health— reports bridge health: whether Inkbox is reachable (live identity check + which channels are live), the inbound tunnel is connected, and Codex is ready to run (CLI present, authenticated).
These match only when the whole message is exactly the command, so "please /clear the cache" is still a normal turn.
Errors. If a turn fails, you get a short plain-language heads-up ("I hit an error while working on that and had to stop") rather than silence.
The setup wizard has a Phone call voice stack section with three choices:
-
Inkbox Voice AI: Inkbox handles the audio and conversation on Codex's behalf. Choose contact-scoped or YOLO authority during setup. Hosted outbound calls carry a task reason, inherit that saved authority by omitting a per-call override, and notify Codex through a signed
call.endedevent. Codex then fetches the authoritative transcript and executes any remaining post-call commitments in a side-effect-only turn; its plain model prose is never sent after hangup. -
OpenAI Realtime (when configured): the bridge pre-opens an OpenAI Realtime session and accepts the call in raw-media mode, so a natural, low-latency voice handles the conversation. It runs the call itself and has these tools:
consult_agent— do real work now in the project; runs in the same contact-keyed session as your SMS/iMessage and its answer is spoken back.register_post_call_action/edit_post_call_action/delete_post_call_action— queue, change, or cancel work to run after you hang up.hang_up_call— two-step (say goodbye, then end the call).
When the call ends, queued actions run in your session (and any plain "reflect on the call" follow-up if none were queued) — so "after we hang up, open a PR and text me" actually happens. Enable it in
inkbox-codex setup(it validates your OpenAI key live) or via theINKBOX_REALTIME_*env vars below. -
Inkbox STT/TTS (default): Inkbox auto-accepts the call and opens a WebSocket to the bridge; finalized transcripts become turns in your same session and Codex's replies are spoken back. Realtime may fall back to this if OpenAI cannot be reached (unless
INKBOX_REALTIME_FALLBACK_TO_INKBOX_STT_TTS=false).
Calls — inbound and outbound — can run over either of two lines, and the agent picks the one that matches the channel it's talking on:
- The dedicated phone number. The agent's own number (the same line SMS uses). Outbound calls present this number; inbound calls to it ring the agent.
- The shared Inkbox iMessage line. The agent can also place and receive voice calls with a person it's connected to over iMessage, over the same shared line that person already messages. The underlying number is never surfaced — Inkbox resolves it from the iMessage connection — and it only works for people already connected over iMessage (an unknown caller is rejected; an outbound call with no connection is refused).
Inbound answering is configured once per identity: Voice AI uses hosted_agent; local Realtime and TTS/STT use auto_accept plus the bridge WebSocket. Outbound, the agent sets origination on inkbox_place_call (dedicated_number / shared_imessage_number), or omits it: the bridge then uses the only available line, or — when both exist — the line matching the current conversation's channel.
Besides Inkbox's own events, the webhook endpoint can inject events from outside systems (e.g. a CI failure) to wake the agent on its own external:<source> thread. Routing is by verified source, never by the body's claimed event type:
- Registered providers (e.g. GitHub via
X-Hub-Signature-256) are verified with their own secret fromINKBOX_WEBHOOK_SECRET_<NAME>; registering the provider + setting its secret is the opt-in, and forged signatures are rejected outright. - Mock provider accepts a shared secret directly in
X-Inkbox-Mock-Secret, matched againstINKBOX_WEBHOOK_SECRET_MOCK. It is intended for manualcurlprobes and test systems that cannot calculate a body signature. A valid secret makes the event verified and wakes the agent even when generic external events are disabled. - Everything else (unknown sources, or Inkbox-signed payloads with no handler) is delivered only when
INKBOX_EXTERNAL_EVENTS_ENABLED=true, and unverified events carry a cautious directive that forbids irreversible action on their say-so.
No human reads an external thread, so the agent is told to act via its tools rather than reply. Adding a source is drop-in: a new module in inkbox_codex/webhook_providers/ with a @register_provider class.
Send a mock event with:
curl --fail-with-body --request POST 'https://your-agent-host.example/webhook' \
--header 'Content-Type: application/json' \
--header "X-Inkbox-Mock-Secret: $INKBOX_WEBHOOK_SECRET_MOCK" \
--data '{
"id": "mock-run-123",
"source": "mock-ci",
"event": "workflow.failed",
"title": "Mock workflow failed",
"summary": "Inspect the repository and decide what action is appropriate.",
"requested_action": "Investigate the failure and take any safe corrective action."
}'Automatic email replies use reply-all on the original message. The reply goes
to its Reply-To address (or sender), with the other visible To/CC recipients
included in CC. The agent's own mailbox and BCC recipients are excluded, and the
original email thread is preserved. No mention is required for email replies.
Queued replies, approval prompts, and send-failure recovery retain the original
message's reply target even if another message arrives in the same session.
If an inbound event lacks the original message ID, the bridge cannot send an automatic reply-all; it does not fall back to a sender-only email.
Live reply-all CI uses the existing CODEX_INKBOX_API_KEY and
REMOTE_INKBOX_API_KEY to verify real reply delivery, sender deduplication,
self-exclusion, and reply-thread headers. An optional REPLY_ALL_INKBOX_API_KEY
for a third, non-auto-replying dedicated inbox enables separate CC delivery
checks (including additional original To recipients). Those two additional
cases are explicitly skipped when the third credential is absent; two-identity
checks do not prove independent CC delivery.
The live channel workflow's email_reply_all_only input runs just these checks,
with both deterministic and real-model gateway runs, without SMS reset traffic.
Inbound. When someone sends an MMS image, an iMessage attachment, or an email with files, the gateway downloads them to ~/.inkbox-codex/media/ (override with INKBOX_CODEX_MEDIA_DIR) and appends the local paths to the message, so Codex can open them with its Read tool — including viewing images. Media-only messages (no text) still wake the agent.
Outbound. Codex sends media with a single tool call per channel — it just passes local file paths, and the tool handles any upload-then-send round trip internally:
- Email —
inkbox_send_email(..., attachment_paths=[...])(base64 inline, ~25 MB total). - iMessage —
inkbox_send_imessage(..., media_path=...)(uploaded + sent, ≤10 MB). - SMS/MMS —
inkbox_send_sms(..., media_paths=[...])(uploaded + sent;media_urlsalso accepts already-hosted URLs).
| Env var | Required | Default | Description |
|---|---|---|---|
INKBOX_API_KEY |
yes | - | Agent-scoped Inkbox API key. |
INKBOX_IDENTITY |
yes | - | Inkbox agent identity handle. |
INKBOX_SIGNING_KEY |
inbound | - | Webhook HMAC secret for signed inbound events. |
CODEX_PROJECT_DIR |
yes | cwd | Directory Codex works in. |
CODEX_MODEL |
no | CLI default | Model override for bridged sessions. |
INKBOX_REQUIRE_SIGNATURE |
no | true |
Refuse unsigned inbound webhooks unless false. |
INKBOX_SKIP_WEBHOOK_RECONCILE |
no | false |
Leave webhook subscriptions untouched on start. For deployments that provision them ahead of time, where the destination is fixed or this API key may not change it. They must already point at this bridge's webhook URL, or nothing arrives. |
INKBOX_CONTACT_MEMORIES_ENABLED |
no | true |
Add memories supplied with the matched webhook contact as background context. |
INKBOX_BASE_URL |
no | SDK default | Override the Inkbox API base URL. |
INKBOX_PUBLIC_URL |
no | - | Public bridge URL. Omit to use an Inkbox tunnel. |
INKBOX_TUNNEL_NAME |
no | identity handle | Tunnel name override. |
INKBOX_ALLOWED_USERS |
no | - | Local allowlist (emails / E.164 numbers). Usually leave empty and use Inkbox contact rules. |
INKBOX_ALLOW_ALL_USERS |
no | false |
Allow all senders admitted by Inkbox contact rules. |
INKBOX_BRIDGE_PORT |
no | 8767 |
Local webhook server port. |
INKBOX_PERMISSION_TIMEOUT_S |
no | 600 |
Seconds to wait for a permission/poll reply. |
INKBOX_GROUP_REPLY_MODE |
no | auto |
Group SMS/iMessage and Companion email replies: auto lets the agent decide; mention requires @agent or @<agent-handle> in the new message. Other messages become context without starting a turn. Also configurable in setup. |
INKBOX_COMPANION_RESPONSE_MODE |
no | safe |
Companion SMS/MMS, iMessage, and email: safe wakes only for direct access; relaxed permits any delivered sender. Both honor Auto/Mention. Sponsored and unknown access stays context-only in Safe mode. Also configurable in setup. |
INKBOX_CODEX_AUTO_APPROVE_INKBOX_TOOLS |
no | false |
Auto-accept Codex MCP prompts for Inkbox tools only. The setup wizard writes true when you trust the agent to send through Inkbox without per-call approval. |
INKBOX_A2A_PROGRESS_INTERVAL_SECONDS |
no | 180 |
Seconds between progress updates for active inbound A2A tasks. Set to 0 to disable periodic updates. |
INKBOX_VOICE_STACK |
no | inkbox_tts_stt |
inkbox_voice_ai, openai_realtime, or inkbox_tts_stt. When absent, legacy Realtime settings remain compatible. |
INKBOX_VOICE_AI_AUTHORITY_MODE |
Voice AI | contact_scoped |
Saved Voice AI authority selected during setup: contact_scoped or yolo. |
INKBOX_VOICEMAIL_DETECTION |
no | enabled |
Outbound-call voicemail policy: enabled or disabled. Live CI uses disabled. |
CODEX_BIN |
no | codex |
Codex CLI executable to run. |
CODEX_SANDBOX |
no | workspace-write |
App-server thread sandbox (read-only, workspace-write, danger-full-access). |
CODEX_APPROVAL_POLICY |
no | on-request |
Codex approval policy for bridged turns. |
INKBOX_REALTIME_ENABLED |
no | false |
Use OpenAI Realtime for calls. Needs a key; off → Inkbox STT/TTS. |
INKBOX_REALTIME_API_KEY |
realtime | OPENAI_API_KEY |
OpenAI key with /v1/realtime access. |
INKBOX_REALTIME_MODEL |
no | gpt-realtime-2 |
Realtime model id. |
INKBOX_REALTIME_VOICE |
no | cedar |
Realtime voice name. |
INKBOX_REALTIME_FALLBACK_TO_INKBOX_STT_TTS |
no | true |
Fall back to Inkbox STT/TTS if OpenAI connect fails. |
INKBOX_EXTERNAL_EVENTS_ENABLED |
no | false |
Wake the agent on unrecognised (external) webhooks — see External events. |
INKBOX_WEBHOOK_SECRET_<NAME> |
per provider | - | Verification secret for a registered third-party webhook provider (e.g. INKBOX_WEBHOOK_SECRET_GITHUB). |
INKBOX_WEBHOOK_SECRET_MOCK |
mock provider | - | Expected plaintext value of the X-Inkbox-Mock-Secret header. Use a random secret. |
The agent reaches you (or third parties) through an in-process MCP server:
inkbox_whoami— its own identity: handle, mailbox, iMessage status, and its two calling lines (dedicated number vs shared iMessage line).inkbox_place_call— place an outbound voice call over either line (origination:dedicated_number/shared_imessage_number) — see Two calling lines.inkbox_list_calls·inkbox_get_call_transcript— browse call history and transcripts.inkbox_send_email— send email; attach local files withattachment_paths.inkbox_send_sms— send SMS/MMS; attach local files withmedia_paths(or hostedmedia_urls).inkbox_send_imessage— send into an iMessage conversation; attach a local file withmedia_path.inkbox_list_text_conversations·inkbox_get_text_conversation— browse SMS threads and history.inkbox_list_imessage_conversations·inkbox_get_imessage_conversation— browse iMessage threads and history (find theconversation_idto send into).inkbox_lookup_contact·inkbox_list_contacts·inkbox_get_contact— resolve and read address-book contacts (reverse-lookup by email/phone, free-text search, or full record by id).inkbox_create_contact·inkbox_update_contact·inkbox_delete_contact— save, edit, and remove organization-wide contacts. Changes affect the shared address book. vCard export/import is not exposed.inkbox_a2a_call·inkbox_a2a_check·inkbox_a2a_reply— delegate work to another agent and follow its task.inkbox_list_a2a_tasks·inkbox_list_a2a_messages— page and search this identity's inbound and outbound A2A history, with participant, task, context, role, state, and timestamp filters.inkbox_a2a_complete·inkbox_a2a_ask_caller·inkbox_a2a_fail— commit the outcome of a verified inbound A2A task. These tools are rejected outside that task's isolated session.
Inbound A2A tasks acknowledge pickup immediately. While a task remains active, the worker sends a short progress update about every three minutes by default; these updates are visible in task history without starting a requester turn.
The bridge requires Inkbox SDK 0.7.3 or newer.
On a live call, the OpenAI Realtime voice agent additionally gets consult_agent, register_post_call_action / edit_post_call_action / delete_post_call_action, and hang_up_call — see Voice.
inkbox-codex doctor— everything green.- Text
START, then text the agent; verify it replies in the same thread. - Ask it to do something requiring a command (e.g. "run the tests") and verify you get a permission text; reply
1and verify the result comes back. - Ask it something open-ended enough to trigger a poll; reply with a number.
- Email the agent; verify the reply lands as an email on the same thread.
- Call the number, ask what it's working on, hang up mid-answer, and verify the late voice tail is not silently sent as SMS or email.
python -m pytest- Tunnel-first inbound: with a signing key, the gateway opens an Inkbox tunnel, reconciles mail/text/iMessage plus
call.endedsubscriptions, and sets the identity's incoming-call action from the selected stack —hosted_agentfor Voice AI orauto_acceptplus the call WebSocket for local stacks. - Session routing: direct messages use the resolved contact id across channels, falling back to the channel conversation or raw address/number. Group SMS uses the group conversation id regardless of sender.
- Escalation over the active channel: a pending permission/poll captures the contact's next inbound message as its answer, on whichever text channel they're using.
- Codex app-server: each contact session owns one
codex app-serversubprocess, one Codex thread, app-server approval request handling over Inkbox, and a local stdio MCP server for the Inkbox tools.
Realtime calls request 16 kHz mono PCM16 call audio. The bridge continuously resamples to and from the realtime session's 24 kHz PCM format, preserving audio across WebSocket frame boundaries. Older call streams that advertise 8 kHz μ-law (or omit their audio descriptor) remain supported. Call audio quality also depends on the remote connection. Hosted voice and managed speech modes are unchanged.