ConnectOnion design journal
Engineering notes from the messy middle.
Concrete decisions, failed assumptions, and verified lessons from building Python AI agents, remote browsers, permissions, and release systems.
Latest
Start with what changed recently
Gmail kept the body and changed the ID
A live Gmail test exposed rewritten Message-ID headers. A preserved attempt marker recovers send receipts without risking a duplicate email.
Read noteThe command that blamed the wrong password
On 27 August, four emails were queued in Outlook for the next morning. An hour later the wording had to change.
Read noteThe Command That Had to Run on a Broken File
Draft for 1.8.4; publish after release acceptance.
Read noteArchive
Find the decision behind the code
119 notes
- 3 min read
Gmail kept the body and changed the ID
A live Gmail test exposed rewritten Message-ID headers. A preserved attempt marker recovers send receipts without risking a duplicate email.
Remote BrowserReleaseTesting - 3 min read
The command that blamed the wrong password
On 27 August, four emails were queued in Outlook for the next morning. An hour later the wording had to change.
Remote BrowserPermissionsRelease - 4 min read
The Command That Had to Run on a Broken File
Draft for 1.8.4; publish after release acceptance.
Remote BrowserReleaseTesting - 2 min read
The suite was green and a thread was spinning
The test named in the timeout was a scroll test. Running it alone did not explain why the complete Linux suite kept stopping there.
Remote BrowserTestingDeployment - 6 min read
The request ended. The copy did not.
The copy test lost its connection just after the NAS returned a task ID. The client had reached its waiting limit. The NAS still had a job to do.
Remote BrowserReleaseTesting - 2 min read
The draft that was still number one
The agent had just listed Gmail drafts. Its next command used a number, as the CLI invited it to do.
Remote BrowserTesting - 3 min read
The refresh token we already had
“Why does YouTube need a database migration?”
Remote BrowserPermissionsRelease - 2 min read
A browser on a server, using your address
Use co proxy share to authorize one remote agent to browse through your computer's internet connection; review latency and network limits.
Remote BrowserPermissionsRelease - 2 min read
A reason that nothing read
Check terminal_reason before browser operations so expired, revoked, or unpaid sessions cannot keep serving pages.
Remote BrowserTestingRuntime - 2 min read
Bind first, then prepare
Acquire the daemon socket lock before preparing shared credentials so a losing startup cannot delete the winner's files.
Remote BrowserTestingRuntime - 3 min read
The suffix list, and the table that checked itself
Audit special-use hostname suffixes and test destination policies against independent fixtures and deliberate mutations.
Remote BrowserTesting - 3 min read
The wall was the hard part, not the pipe
Route a server browser through a home connection while refusing private-network destinations such as 192.168.0.1.
Remote BrowserReleaseTesting - 4 min read
Every test was green and the feature had never worked once
Run native egress preflight through the real browser driver and verify that forbidden destinations actually reach the denying gateway.
Remote BrowserReleaseTesting - 3 min read
The check and the browser read different names
Align URL policy checks with browser parsing and test generator-based deny ranges inside IPv4-mapped IPv6 addresses.
Remote BrowserReleaseTesting - 4 min read
The gateway held, but the answer leaked
Inspect client-facing proxy responses as well as outbound destination checks to expose response-smuggling gaps in egress tests.
Remote BrowserReleaseTesting - 2 min read
One Daemon, Many Conversations
Remove daemon-wide request serialization so a long browser task does not block independent sessions; preserve ordering within each session.
Remote BrowserTestingRuntime - 2 min read
127.0.0.1 did not look like an IP address
Normalize numeric hostnames such as 2130706433 before applying private-address policy so the browser and validator agree.
Remote BrowserSecurityChromiumSsrf - 3 min read
A Private Tab Is Not a Private Browser
Give remote browsing exclusive process, profile, proxy, and socket ownership; owner-bound tabs alone cannot isolate network authority.
Remote BrowserTestingRuntime - 3 min read
A Session ID Is Not Browser Authority
Bind browser operations to the authenticated owner and lifecycle so a session identifier alone cannot grant access after reconnect.
Remote BrowserTestingRuntime - 3 min read
A Thread Is Not an Async Runtime
Replace a shared blocking worker with owned async execution so separate browser sessions can make progress concurrently.
Remote BrowserReleaseTesting - 2 min read
Focus Before Delete
Check that focus reached the intended editor before select-all and delete, preventing successful commands from clearing the wrong content.
Remote BrowserTestingRuntime - 3 min read
Input Was the Uncancellable Part
Move cancellation to the terminal-input boundary so stopping wait_for_manual_login does not leave a worker waiting for input.
Remote BrowserReleaseTesting - 2 min read
Sleep Was the Global Lock
Make humanized typing and pointer timing awaitable so one tab's simulated input does not freeze another tab.
Remote BrowserReleaseTesting - 3 min read
The Browser Opened Before We Knew It Was Safe
Require a browser preflight after launch to prove proxy refusal and network isolation before declaring a private browser ready.
Remote BrowserPermissionsRelease - 3 min read
The browser worked until the caller already had a loop
Keep synchronous Browser methods over one owned event loop, with explicit thread ownership and shutdown joining.
Remote BrowserReleaseTesting - 3 min read
The command we had to send before the command
Check the running daemon's engine before sending commands so a paid Onion browser request cannot silently use another engine.
Remote BrowserReleaseTesting - 2 min read
The Matcher Left the Loop
Move synchronous model-backed element matching off the browser event loop so unrelated tabs respond while a model is pending.
Remote BrowserReleaseTesting - 2 min read
The paid launcher was on the wrong loop
Expose an async Onionwright paid launcher for the Patchright event loop while preserving charge, renewal, expiry, and release ownership.
Remote BrowserReleaseTesting - 2 min read
The proxy replied, but the 429 disappeared
A proxy that closes with unread peer bytes can reset the socket on macOS; test that the client receives the overload response before closure.
Remote BrowserProxySecurityTesting - 3 min read
The second tab was not concurrent
Time simultaneous operations to distinguish tab isolation from concurrency, then move daemon and driver work onto one async runtime.
Remote BrowserReleaseTesting - 2 min read
The Upload Button Was in Another Document
Preserve frame routing and listener timing during async migration so file inputs in another document remain reachable.
Remote BrowserReleaseTesting - 4 min read
The URL Passed, but the Socket Was Still Unknown
Resolve, validate, and dial approved numeric addresses in the gateway; a URL check before page.goto cannot control later connections.
Remote BrowserReleaseTesting - 6 min read
A Patch Must Move Forward
Require a forward-port issue for each stable fix and resolve its applicability to every active newer release before publication.
ReleaseCI - 2 min read
A Stable Candidate Cannot Ignore Late Evidence
Stop stable promotion when unattended browser acceptance reveals a new failure; publish new immutable candidate bytes and repeat the gate.
Remote BrowserPermissionsRelease - 6 min read
An Image Prefix Is Not an Image
Validate complete data URLs and image payloads before upload so base64-looking text cannot enter the image path.
ReliabilityTesting - 6 min read
co ai Delegates to Codex and Claude Code — and You Watch It Work
ConnectOnion 1.7 adds resumable native coding Work Rooms, explicit permission modes, and live tool activity through co ai.
ReleaseWork Rooms - 6 min read
Headless Does Not Mean Unconfigured
Honor explicit command grants in unattended Auto while rejecting broad legacy wildcards that would authorize unlimited actions.
PermissionsAutomation - 2 min read
One Fix, Every Newer Line
The release contract checks Git ancestry and applicable behavioral fixes across supported branches, excluding old version metadata from forward-ports.
Remote BrowserReleaseTesting - 2 min read
A Durable Downgrade Must Also Be Live
Publish the narrowed provider lifecycle revision when Host permissions change so open Work Rooms immediately reflect stored authority.
Remote BrowserPermissionsRelease - 2 min read
A new candidate needs a new name
Use a new candidate version whenever published code changes so install commands, evidence, checksums, and rollback identify the same bytes.
Remote BrowserReleaseTesting - 2 min read
A Permission Change Has One Public Truth
Emit the final permission state after a downgrade so stopping and reopening native provider work cannot expose an intermediate authority.
Remote BrowserPermissionsRelease - 2 min read
A permission is a complete state
Copy both the validated mode and positive turns_left budget into native continuations; missing budgets degrade to Auto without renewing authority.
Remote BrowserPermissionsRelease - 2 min read
A tool result is not a final answer
Distinguish a successful Claude Code tool result from parent-turn completion so an empty model answer cannot strand the chat composer.
Remote BrowserReleaseTesting - 3 min read
Arm the deadline before the tool returns
Arm the parent model's 90-second settlement limit before provider work starts and recover the boundary from durable codex or claude_code traces.
Remote BrowserPermissionsRelease - 2 min read
Auto must mean the same promise
Translate Host Auto intent into each native provider's workspace controls while preserving the separately confirmed Full Access ceiling.
PermissionsReleaseTesting - 2 min read
One Approval Boundary Is Enough
Define one explicit handoff from Host policy to native provider approval so a managed delegation does not stall behind nested dialogs.
PermissionsReleaseTesting - 2 min read
The daemon was not the slow part
Measure the command path before blaming the daemon queue; avoid importing the entire persistent service for every state query.
Remote BrowserReleaseTesting - 2 min read
The First Correct Snapshot Was Still Wrong
Publish one provider snapshot at the final authority ceiling after Stop and downgrade, then verify reopened native sessions.
Remote BrowserPermissionsRelease - 3 min read
The last change is the name
Change package version and Production/Stable metadata together, run twine check, and switch docs only after PyPI serves the final package.
Remote BrowserPermissionsRelease - 2 min read
The snapshot that arrived thirty-nine times
Separate replacement snapshots from append-only events so repeated provider state cannot flood the chat and hide an approval.
Remote BrowserPermissionsRelease - 2 min read
The tool finished but the turn did not
Bound parent model settlement, retry once after a timeout, and end with an explicit error if the retry also expires.
ReleaseTestingWork Rooms - 2 min read
The workspace default was already known
Use the Host-owned workspace when a native coding call omits a directory, while rejecting requests outside the authorized boundary.
PermissionsReleaseTesting - 3 min read
A Release Candidate Is a Testable Artifact
Test a clean public wheel through real browser and provider journeys, and keep its bytes unchanged during release acceptance.
Remote BrowserPermissionsRelease - 2 min read
The Work Room Needed Its Own Permissions
Keep Host authority, provider-session modes, and individual approvals distinct and visible in the native coding client.
Remote BrowserPermissionsRelease - 6 min read
What Makes an Agent 10x
Design agents around verifiable work, durable time, learned experience, bounded delegation, and access to real tools.
VisionReliability - 2 min read
A Snapshot Is Not Live State
Update Control Center readiness from live running and approval events so a stored snapshot cannot label a busy agent available.
Remote BrowserPermissionsRelease - 2 min read
A Transcript Is Not a Client
Send new messages into the same native provider conversation so users can continue work from the remote client.
Remote BrowserPermissionsRelease - 2 min read
A Workspace Boundary Is Not a Path
Reject out-of-workspace provider requests without exposing private roots, missing paths, file names, or symlink targets in public errors.
Remote BrowserPermissionsRelease - 2 min read
An Action Is Not a Reason
Describe the operation being performed above a tool call and leave supporting evidence available without inventing model rationale.
ReleaseTestingAPI Design - 2 min read
An Invite Is Not an Environment
Pass co ai --invite-code for a local run or --invite-code-file for automation while keeping temporary credentials out of persistent configuration.
Remote BrowserPermissionsRelease - 2 min read
The Approval Belonged to the Wrong Room
Place native session decisions in the Work Room that can answer them so Host-level approval does not silently stall provider follow-ups.
Remote BrowserPermissionsTesting - 2 min read
The Beta Caught the Wrong Question
Change tool summaries from inferred reasons to observable actions so type-correct UI contracts do not encourage invented explanations.
ReleaseTestingAPI Design - 2 min read
The Bill and the Counter Disagreed
Use one source of truth for new, cached, and output-token accounting so the chat can explain the charge on a managed request.
Remote BrowserTestingDeployment - 2 min read
The Timeout That Blocked Recovery
Allow recovery actions after DOMContentLoaded times out instead of repeating the same blocked readiness check before every command.
Remote BrowserReleaseTesting - 2 min read
The Workroom Lost the Conversation
Carry provider requests and answers through the shared protocol so live status and tool summaries do not replace the conversation.
Remote BrowserReleaseWork Rooms - 2 min read
A Mode Is Not a Compatibility Shim
Reject legacy authority aliases rather than translating them into Auto, Read only, or Full Access permissions.
Remote BrowserPermissionsWork Rooms - 2 min read
A URL Is One Piece of Data
Render long co auth google URLs without inserted line breaks so headless users can copy the exact credential handoff value.
Remote BrowserTestingDeployment - 2 min read
No Dialog Is Not Approval
Apply the same outside-workspace and unknown-tool policy when no approval UI exists; missing dialogs must not grant authority.
Remote BrowserPermissions - 2 min read
The Log Sent Us to the Wrong File
Pass configuration provenance through deployment layers so invite diagnostics identify the actual source without exposing secrets.
DeploymentRuntimeAPI Design - 2 min read
The pipeline that succeeded without doing the work
Use a nonzero command outcome for unfinished agent work so shell pipelines cannot treat an exhausted iteration budget as success.
TestingRuntime - 2 min read
Three Labels Were One Decision
Map Host Auto, Read only, and Full Access to native provider settings so legacy Default and workspace labels cannot imply different authority.
Remote BrowserPermissionsRelease - 2 min read
A Server Quota Is Not a Ceiling
Expose server region choice from the backend catalog so regional GCE address quotas do not become a global provisioning ceiling.
DeploymentAPI Design - 3 min read
Authorise in Advance, So Nobody Signs at 3am
Separate connection-sharing authorization from tunnel transport and reject attempts by the proxy to authorize its own egress.
Remote BrowserPermissionsTesting - 2 min read
New Feature Work Needs a New Number
Move new remote-browser and server-region work to the next feature line while a frozen release accepts stabilization fixes only.
Remote BrowserPermissionsRelease - 2 min read
Sharing a Mailbox Is Not the Same as Giving It Away
Authorize another agent against an existing mailbox identity so outreach and CRM history stay attached to the original address.
Remote BrowserPermissionsEmail - 2 min read
The Browser Gate Was Only Windows
Run clean-wheel browser acceptance on each supported platform so Windows-only coverage cannot hide macOS daemon failures.
Remote BrowserPermissionsRelease - 2 min read
The Model Was Holding the Browser
Separate model waiting from the browser execution lane so spending-cap retries cannot freeze unrelated browser sessions.
Remote BrowserReleaseTesting - 6 min read
Your Servers, Your Regions, Your Shared Mailboxes
Release notes for server-region choice, shared mailboxes, collision-safe Outlook downloads, and bounded provider requests.
ReleaseEmail - 2 min read
A Message Is Not Accepted Until Codex Accepts It
Keep a follow-up draft until successful steering or native turn start; placing text in a Host queue is not delivery.
Remote BrowserTestingWork Rooms - 2 min read
A Stop Request Is Not a Stop
Distinguish Stop intent, delivery, and terminal outcome so the client reports cancellation only for the targeted provider invocation.
Remote BrowserTestingWork Rooms - 2 min read
A Thumbnail Is Evidence, Not a Placeholder
Render verified workspace or inline PNG evidence and reject outside paths, malformed bytes, and stale revisions.
Remote BrowserPermissionsRelease - 3 min read
A Work Room Has One Writer
Let the provider-owning layer emit lifecycle revisions and retain artifacts while browser consumers reject stale or malformed state.
Remote BrowserPermissionsRelease - 2 min read
Auto Is a Boundary, Not a Bypass
Allow narrow local workspace work without repeated approval while leaving ambiguous, destructive, and broader actions under review.
Remote BrowserPermissionsRelease - 6 min read
The Work Room Is a Client, Not a Status Panel
Keep provider conversation, follow-up input, lifecycle, and controls in one Work Room while the native adapter owns execution.
Work RoomsOIP - 3 min read
A Name Is Not an Adapter
Require a Codex tool card and an openable Work Room from the native provider path; pwd and which codex do not prove a Codex session started.
Remote BrowserPermissionsRelease - 3 min read
A Session ID Is Not a Work Room
Emit provider-owned identity, status, authority, continuation, and replay so a UUID inside a tool result becomes a usable Work Room.
Remote BrowserPermissionsRelease - 6 min read
A Tool Transaction Is Not a Work Room
Send live provider activity and approval events through OIP while keeping raw terminal detail out of the default chat surface.
Work RoomsOIP - 2 min read
A User Message Is Not Always a String
Extract text from structured user content without discarding image parts or calling string methods on a list.
Remote BrowserRuntimeAPI Design - 6 min read
A Work Room Is a Summary, Not a Transcript
Use a finite OIP Work Room summary with verified decisions and evidence instead of replaying raw terminal transcripts by default.
Work RoomsOIP - 6 min read
An Approval Is Not Execution Time
Exclude time spent waiting for an operator's approval from the active provider execution budget.
PermissionsCodex - 2 min read
Stop Is a Process-Tree Operation
Launch shell work in a process group, send SIGTERM on Stop or timeout, then SIGKILL after a grace period; revoke the IO lease to ignore late results.
Remote BrowserTestingRuntime - 3 min read
The Long Tail of a Default
Centralize DEFAULT_MODEL and audit examples, templates, and docs so an old model string does not survive as misleading guidance.
Remote BrowserReleaseTesting - 3 min read
The Screenshot Was Not the Test
Test screenshots alongside provider state, reconnect behavior, and operating-system resources to prove Stop and resume actually work.
Remote BrowserPermissionsRelease - 4 min read
The Second Connection Was Still the First
Preserve relay and session continuity across browser reloads while checking that identity and authority have not changed.
Remote BrowserPermissionsRelease - 2 min read
A label is useful only when the work has somewhere to ship
Classify contributions and require real release metadata so unlabeled work and placeholder milestones cannot disappear into the merge queue.
Release - 6 min read
A Page Should Not Become a Wall
Retrieve later mailbox pages with get_emails(last=1000, offset=2000) or co email inbox --last 1000 --offset 2000.
EmailAPI Design - 6 min read
Changing the Default Model Is a Backend Decision First
Verify backend support before clients request Gemini 3.7, and preserve explicit user model choices.
ModelsRelease - 3 min read
Codex Was Ready. Codex Had Never Opened.
Enforce requested provider identity across the browser protocol and validate the installed wheel so base-model fallback cannot fake a handoff.
Remote BrowserPermissionsTesting - 2 min read
Evaluation Is a Choice
Enable optional judging with co ai --eval; retain the newest 500 generated session records while keeping authored evaluation suites.
TestingAPI Design - 2 min read
Every PR tells its story
Require a docs/blog post through the blog-gate CI check so implemented capabilities ship with CLI help, user documentation, and engineering lessons.
TestingAPI Design - 6 min read
One Browser Protocol, Native Coding Adapters
Use one browser-facing protocol while native backend adapters retain provider sessions, approval behavior, and execution ownership.
OIPArchitecture - 4 min read
One Default, Five Answers
Replace repeated model strings with one DEFAULT_MODEL while preserving explicit user configuration and a documented rollback.
Remote BrowserReleaseTesting - 2 min read
Ten minutes was not a timeout policy
Stop silently capping caller timeouts at ten minutes and propagate real timeout errors through direct APIs and Agent traces.
TestingRuntimeAPI Design - 6 min read
The Agent That Was Itself, and Billed Someone Else
Control which credentials a deployed process inherits so operator configuration cannot silently replace agent identity or billing authority.
SecurityDeployment - 2 min read
The cache was invisible
Surface provider cached-token counts in local accounting and terminal output so repeated requests show their actual charge.
Remote BrowserReleaseTesting - 4 min read
The CLI is the product
Expose API features in CLI help and route email ownership failures to co email addresses so users can find a valid next command.
ReleaseTestingEmail - 6 min read
The Owner Needs a Door
Provide a private owner recovery path that permits initial agent access without leaking invite credentials into logs.
SecurityOnboarding - 2 min read
The Third Argument Was Already Taken
Preserve the reply method's existing positional arguments while adding attachment validation and regression tests for legacy callers.
Remote BrowserReleaseTesting - 2 min read
When “Download” Quietly Meant “Replace”
Allocate unique download paths for duplicate attachment names and existing local files, then return the paths actually written.
Remote BrowserReleaseTesting - 2 min read
When logging looked like authoring
Distinguish source authoring from command-output logging so append redirects and tee can write audit records without retry loops.
Remote BrowserTestingRuntime - 6 min read
ConnectOnion 1.6.0
Review the release's remote-agent controls and the separation of deployed agent credentials from operator identity.
ReleaseSecurity - 6 min read
Auto-Debug Design Evolution
Compare five debugging designs that reduced mode complexity and focused the workflow on inspecting live agent execution.
DebuggingUX - 6 min read
Progressive Disclosure in CLI Design
Scaffold a working agent project first, then reveal configuration choices when users need them instead of requiring a setup questionnaire.
CLIUX - 6 min read
Why “Address” Beats “Identity”
Use address for network routing and keep reputation, authorization, and broader identity concepts separate in the public API.
API DesignNaming - 6 min read
Why Agent Addresses Use Hex-Encoded Ed25519 Keys
Represent the full Ed25519 public key in hexadecimal with a 0x prefix so an address directly carries the agent's public verification key.
SecurityNetwork - 6 min read
Why We Chose input() Over run()
Send a task through Agent.input() so the primary method names the caller's input rather than exposing a generic execution verb.
API DesignUX - 6 min read
Why We Chose llm_do() Over llm()
Use llm_do() for a single LLM task without creating an Agent; the function name states an action rather than naming a model object.
API DesignNaming - 6 min read
Why We Chose “Trust”
Use trust as the public API concept for both accepting incoming agents and deciding which remote agents may be called.
API DesignSecurity - 6 min read
Designing the ConnectOnion Network Protocol
Compare agent addressing, messages, and direct connections with the earlier identity and session machinery.
ArchitectureNetwork
ConnectOnion