Parallel coding agents on one MacBook with T3 Code
Run T3 Code on one MacBook with two Claude accounts and four models, reach it from your phone over Tailscale, and cut each agent's background processes so more of them fit in memory.
Updated
Tags: ai · agents · tools
On this page
On a busy day I have more than a dozen agent threads going in T3 Code, some on Claude Code and some on Codex. I start most of them at my desk and keep an eye on them from my phone, where I can reply to one or start another. A few start on their own, on a schedule.
My setup spreads that across a Raspberry Pi, a Mac mini and a MacBook, but one MacBook and a phone get you most of the way. The way of working carries over; how many agents run at once depends on the Mac's memory. This guide builds that smaller version in five stages:
- Install the agents and share two Claude Ultra accounts between them.
- Set up T3 Code with four models: Opus 5.5, Fable 5.1, Astra 6 and Sol 6.1.
- Reach the Mac from your phone over Tailscale.
- Keep the Mac awake and each agent light, then run many threads at once.
- Let agents work without you, with Slack and scheduled tasks.
You'll need an Apple silicon MacBook with Homebrew, two Claude Ultra subscriptions, a ChatGPT plan for Codex, and a free Tailscale account.
Install the agents and share your Claude accounts
T3 Code has no models of its own. It runs the Claude Code and Codex CLIs on your Mac, with their settings, so start with those:
brew install --cask claude-code@latest codex
codex loginCodex keeps its own ChatGPT login. There's only one ChatGPT account, so there's nothing to share.
Claude is different. Claude Code signs in to one account at a time, and with two you'd be logging in and out. CLIProxyAPI holds both logins and spreads conversations across them, keeping each conversation on one account so its prompt cache keeps working. My CLIProxyAPI post explains the account rules, the cache problem and how to check it all works. Read its rules first: Anthropic hasn't approved running subscriptions through a proxy, and you do it at your own risk. What follows is that post's single-Mac setup, minus the Codex steps and plus one setting for T3 Code.
One difference from that post: there, the hub runs at home, so every request leaves from your home broadband. On a MacBook, requests leave through whatever connection the Mac is on. Tailscale doesn't change that, because it only connects the phone to the Mac. Keep them off commercial VPNs all the same.
Install the hub and generate two keys: the first is the access key Claude Code sends, the second the management key for the hub's panel. Save both in your password manager.
brew install cliproxyapi
chmod 600 "$(brew --prefix)/etc/cliproxyapi.conf"
install -d -m 700 ~/.cli-proxy-api
openssl rand -hex 32
openssl rand -hex 24In $(brew --prefix)/etc/cliproxyapi.conf, set these keys inside the sections that already exist, add any section that's missing, and leave everything else alone:
server:
host: "127.0.0.1"
port: 8317
management:
allow-remote: false
secret-key: "<management key>"
access:
api-keys:
- "<access key>"
routing:
session-affinity: true
session-affinity-rebind-on: "quota"
requests:
proxy-url: "direct"
observability:
usage:
usage-statistics-enabled: true
upstream:
claude:
header-defaults:
native-entrypoints: ["t3-code"]The usage statistics let the hub's panel show which account served each request. proxy-url: "direct" stops the hub picking up proxy settings from its environment, though it can't route around a VPN running on the Mac itself.
The last block lets the hub recognise T3 Code, which starts Claude Code with the entrypoint t3-code. Without it, the hub adds its own disguise to each request, about 250 extra tokens in my test. It needs CLIProxyAPI 8.0.53 or later.
Start the hub and sign in to the first Claude account. The browser that approves the login has to be signed in to claude.ai as that account:
brew services start cliproxyapi
umask 077
cliproxyapi -claude-loginSwitch claude.ai to the second account and run cliproxyapi -claude-login again, then make the login files private with chmod 600 ~/.cli-proxy-api/*.json.
Point Claude Code at the hub in ~/.claude/settings.json. Create the folder with mkdir -p ~/.claude if it doesn't exist, and keep any settings already in the file:
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317",
"ANTHROPIC_AUTH_TOKEN": "<access key>"
},
"promptCacheTtl": "1h",
"subagentPromptCacheTtl": "1h"
}The file now holds your access key, so run chmod 600 ~/.claude/settings.json. The two cache settings matter: behind a proxy, Claude Code drops its prompt cache from an hour to five minutes, so any longer pause rewrites the conversation's cache. They bring the hour back for sessions and their subagents, and need Claude Code 2.1.242 or later.
Check it by running claude -p "Reply with just ok" twice. Each run is a new conversation, and the hub hands new conversations to the accounts in turn. Then open http://127.0.0.1:8317/management.html, enter the management key, and check that each account shows a recent request. The earlier post has a check for the one-hour cache too. If the hub ever stops working, every Claude session stops with it. Remove the two env lines and new sessions fall back to Claude Code's own login; run /login in Claude Code if you've never signed in directly.
Set up T3 Code and its models
Download T3 Code from t3.codes and open it. In Settings → Providers, Claude and Codex should both show as ready. Add a project (any repository will do), start a thread on each provider and ask for something small, such as a summary of the README. If both answer, the hub and the Codex login work.
Two more things in Settings → Providers. Under Usage providers, choose Add a CLIProxyAPI hub and enter http://127.0.0.1:8317 with the management key; T3 Code then shows the 5-hour and weekly limits for both Claude accounts. And hide the older models in each provider's list, so the picker only shows the four I use:
| Model | Effort | What I use it for |
|---|---|---|
| Claude Opus 5.5 | High | The default for almost every thread |
| Claude Fable 5.1 | High | Design and visual reviews, specs |
| GPT-6 Astra | Medium | Adversarial code reviews |
| GPT-6.1 Sol | Medium | Second-opinion reviews, commit text, fallback |
In Settings → Threads, set the Default model to Opus 5.5 at High, the Fallback model to Sol 6.1 at Medium, and the Text generation model to Sol 6.1 at Medium. The fallback takes over for new threads when the default model's provider is out of usage. The text generation model writes thread titles, branch names and commit messages, which don't need your strongest model. For Fable and Astra, pick the model and effort in the picker under the message box when you start a thread; the choice stays with that thread.
I mix providers deliberately. When Opus writes the code, a review from Astra or Sol comes from a model trained differently, and it tends to catch different mistakes.
Reach the Mac from your phone
Tailscale puts your devices on a private network, called a tailnet, so the phone can reach the Mac from anywhere without opening a port on your router.
- Install the standalone Tailscale app on the Mac from tailscale.com/download and sign in. T3 Code drives Tailscale through its command-line tool, so open CLI integration in Tailscale's settings, install it, and check that
tailscale statusworks in a terminal. - Install Tailscale on your phone and sign in with the same account. On Android, set it as an always-on VPN so it reconnects by itself. Only tailnet traffic goes through it.
- In the admin console's DNS settings, turn on MagicDNS and HTTPS certificates, which T3 Code needs to give the Mac an HTTPS address.
- On the Machines page, turn off key expiry for the Mac so it doesn't drop off the tailnet after six months.
Then install T3 Code on your phone from the App Store or Google Play. It offers to sign in to a T3 account, which you need for T3 Connect, T3's managed tunnel. I skip that and pair by QR code instead: Tailscale already connects the phone to the Mac directly, so I don't need a second route through T3's servers.
On the Mac, open Settings → Sharing & access in T3 Code:
- Choose Enable Tailscale HTTPS, keep the suggested port 443 and confirm the restart. T3 Code publishes its server at
https://<your-mac>.<tailnet>.ts.net(with:portadded if you chose another), reachable only by devices your tailnet allows. If you share your tailnet with anyone, use its access rules to keep them off this machine. - Choose Create link, then pick the Tailscale address as the endpoint in the Create pairing link dialog.
- On the phone, with Tailscale connected, open T3 Code without signing in, choose to pair with a QR code, and scan it.
Each link works once and expires, so make a new one for each device. Paired devices are listed on the same page, where you can revoke them. To check it works away from home, turn off Wi-Fi, leave Tailscale on, and send a message to a test thread over mobile data.
Staying signed out costs you push notifications and Live Activities. Those need a T3 account but not the tunnel. If you want them later, sign in on the phone and in the desktop app and turn on Publish agent activity under Settings → Sharing & access, leaving T3 Connect off.
Keep the Mac awake
The phone only reaches the Mac while it's awake with T3 Code open, and agents and schedules stop when it sleeps. The simplest arrangement is plugged in with the lid open. Closing the lid is fine with an external display, keyboard and mouse or trackpad connected, the clamshell setup macOS supports. Either way, tell macOS never to sleep on power:
sudo pmset -c sleep 0That's the same as System Settings → Battery → Options → Prevent automatic sleeping on power adapter when the display is off. The display can still turn off.
Without the clamshell setup, closing the lid sends the Mac to sleep regardless. There's an override, but treat it as an exception: it stops the Mac sleeping at all, on battery too, until you undo it, and a Mac that can't sleep gets hot in a bag. To keep agents running with the lid shut:
sudo pmset -a disablesleep 1pmset -g then shows SleepDisabled 1. Before you pack the Mac away, restore normal sleep, which leaves the "never sleep on power" setting in place:
sudo pmset -a disablesleep 0For a long run on battery with the lid open, caffeinate -i stops idle sleep for as long as it runs, though not lid-close sleep.
For restarts and updates, add T3 Code to System Settings → General → Login Items and turn on Continue threads after restarts in T3 Code's Settings → Maintenance. Interrupted threads then resume once T3 Code is running again. Both wait for you to log in, so a Mac that restarts overnight sits at the login screen until you unlock it.
To check, start a long task, walk away for longer than the Mac's usual sleep time, and message the thread from your phone over mobile data.
Keep each agent light
MCP servers give agents extra tools, such as a database client or a Slack connection, and most run as local programs. Each T3 Code thread is its own Claude Code or Codex session, and each session starts its own copy of every enabled local server. The cost multiplies with every thread.
On a fresh setup, keep it small from the start. Reach for a CLI or a skill before a server, connect to remote servers over HTTP directly, and add a local server only to the projects that need it, with claude mcp add -s project … in Claude Code.
Skills cost something else. A skill is a folder of instructions and scripts an agent reads when a task needs it, so it keeps no process running, but every new session loads each skill's name and description into the model's context. Move skills you never use out of ~/.claude/skills (Claude Code) and ~/.agents/skills (Codex) to keep that list short.
There's no number of threads that suits every Mac; it depends on your memory and what the agents build and run. Add threads gradually and keep Activity Monitor's Memory tab open. If memory pressure turns yellow or red, or swap keeps growing, you've found your limit for now.
If you already have MCP servers
My MacBook (an M4 Pro with 24 GB of memory) slowed to a crawl with 19 Claude sessions open. It wasn't heat; macOS hadn't recorded any thermal warnings. The MCP servers in those sessions came to 371 processes, and swap had grown to 11 GB. I started each of my servers on its own and measured its resident memory just after startup, including any launcher process. These are figures from my configuration, not benchmarks:
| MCP server | Memory per agent session |
|---|---|
BigQuery (US and EU servers combined, via npx) | 345 MB |
| n8n | 216 MB |
| Kubernetes | 171 MB |
| Slack | 161 MB |
| Grafana, PlanetScale, GitHub (Go binaries) | 11–14 MB each |
The heavy ones all started through npx or uvx. npx keeps an npm process running as the server's parent for the whole session, using 74–134 MB and doing nothing after startup. Removing eight servers took about 870 MB and 14 processes off each new session.
Measure before you change anything. This command adds up the resident memory of matching processes. It misses servers with other names, leaves out swapped memory and can count shared memory twice, so treat it as an estimate:
ps -axo rss=,command= | grep -iE 'mcp|npm exec|uvx' | grep -v grep \
| awk '{s+=$1} END {printf "%.0f MB in %d processes\n", s/1024, NR}'Open sessions keep their servers and the command counts them, so let running work finish, quit T3 Code and run it once to confirm the old servers have gone. Then reopen T3 Code, start one fresh thread and run it again. That's your baseline. Then trim:
- Use a CLI where one exists.
gh,bqandkubectlstart when an agent runs a command and exit afterwards. - Connect to remote servers over HTTP directly, like Linear's, rather than through a local bridge such as
mcp-remote. A direct connection starts no server process on your Mac. - Load servers only where they're needed. In Claude Code,
claude mcp listshows what's configured andclaude mcp get <name>shows a server's settings, so save those before removing anything.claude mcp remove <name> -s userstops a server loading in new sessions; check the project and local scopes too, since one server can be configured in several.claude mcp add -s project …adds a server to one repository instead, and/plugindisables plugins that bring their own servers. In Codex,codex mcp listandcodex mcp remove <name>manage the servers in~/.codex/config.toml, which apply everywhere. - If you keep a Node server, install it globally and point the config at the installed command instead of
npx. - Replace the rest with skills that call the service's CLI or API, like the Slack skill below.
Measure again with a fresh thread on the same provider and project to see what you saved.
Run many threads at once
These settings make a sidebar of parallel threads easier to live with:
| Setting | Where | What I choose |
|---|---|---|
| Working section | General | On. Running threads fold into a Working section and return to the top of the inbox when they need you |
| Follow-up behavior | Chat | Steer, so a message sent mid-run corrects the current run instead of waiting in a queue |
| Snooze until limits reset | Threads | On, so a thread that hits a usage limit gets out of the way |
| Resume after usage limits | Threads | On, so it carries on at the reset time |
| New threads | Threads | Worktree |
| New worktrees start from remote | Worktrees | On |
| Compact idle threads | Providers → Advanced | When you return. Applies to Claude threads over 200,000 tokens |
A worktree is a separate checkout of the same repository. With each thread in its own worktree, starting from the latest remote branch, agents don't overwrite each other's work in progress, though their changes can still conflict when you merge them. This needs a repository with a remote, and a new worktree only contains what you've pushed, so commit and push work you want an agent to build on.
Not every parallel job needs its own thread. An agent in T3 Code can start subagents, including ones on the other provider, and they appear under the parent thread, where you can open or stop each one. Ask in plain words:
Have Astra and Sol review this diff in parallel at medium effort. Give each
only the diff and the task, not your own conclusions, and tell them to read
the code without editing files. Then check every finding against the code
and fix the ones that hold up.The parent waits for both, compares their findings and carries on. Because the reviewers are told only to read, they can share the parent's checkout. Nothing enforces that, so keep the line in the brief. Most of my reviews work this way: Opus writes the change, Astra and Sol review it, and Fable checks anything visual. Leaving the author's conclusions out of the brief lets each reviewer judge the change on its own terms.
A subagent on another provider runs as its own Claude Code or Codex session, so it starts its own local MCP servers too. Claude Code's built-in subagents run inside the parent's session.
Let agents work without you
Scheduled tasks let agents start work while you're away from the Mac. Slack is optional, but many useful jobs read or post there, and my scheduled status update below uses it.
Connect Slack
I connect agents to Slack with a user token and a skill. The Slack MCP server cost 161 MB in every session; the skill runs nothing until an agent uses it, works the same in Claude Code and Codex, and posts appear under your name.
-
At api.slack.com/apps, choose Create New App → From scratch and pick your workspace.
-
Under OAuth & Permissions → User Token Scopes, add
search:read,channels:history,groups:history,im:history,mpim:history,channels:read,users:readandchat:write. -
Install the app to your workspace (an admin may need to approve it) and copy the User OAuth Token, which starts
xoxp-. -
Store the token in the Keychain. This prompts for it, so it never lands in your shell history:
security add-generic-password -U -s slack -a SLACK_USER_TOKEN -w -
Create the skill folders with
mkdir -p ~/.claude/skills/slack ~/.agents/skills/slack, then save this asSKILL.mdin both. Claude Code reads the first and Codex the second:--- name: slack description: Search, read and post in Slack with my user token. Use for anything in Slack. --- Call the Slack Web API with the token from the Keychain. Send it as a header on stdin so it never appears in a command line, and URL-encode parameters: ```sh T=$(security find-generic-password -s slack -a SLACK_USER_TOKEN -w) printf 'Authorization: Bearer %s\n' "$T" | curl -s -H @- --get \ --data-urlencode 'query=<search terms>' --data 'count=20' \ https://slack.com/api/search.messages ``` Slack returns HTTP 200 even when a call fails, so check that the JSON has "ok": true. Useful methods: search.messages, conversations.history, conversations.replies, chat.postMessage. Rules: reading needs no approval. Posts appear as me, so only post text I have approved word for word in this conversation. Never print the token.
Reading is safe to leave to the agent. Posting needs your approval because the message goes out under your name and notifies people.
Before relying on it, start a fresh thread on each provider and ask for a harmless Slack search. Check the reply came back with "ok": true, and answer any Keychain or command approval prompts while you're at the Mac, so an unattended run doesn't stall on one later.
Schedule tasks
T3 Code can run a prompt every so many minutes, at a set time on chosen days, or when a webhook is called. You can add one in Settings → Scheduled tasks → New task, with a title, the prompt, a project, a model and where it runs (the project checkout, a new worktree or a specific checkout). I find it easier to ask from a thread, because the runs then come back to that thread. Try a harmless one first, such as "in five minutes, reply here with the time", and check it arrives.
My weekday status update works this way. It uses the Slack skill and the GitHub CLI (brew install gh, then gh auth login). Fill in your own repositories, username and channel:
Every weekday at 07:15 London time, run in this thread. Gather what I did on
the previous working day (Friday on Mondays): my merged and open PRs in
<owner/repo> as <github-username>, and my messages in Slack. Check whether
I've already posted today's update in #<channel>, and if not, draft one for
me to review here. At 09:00 and 09:45, remind me here if it still isn't
posted. Never post to Slack until I approve the exact text in this thread.Afterwards, open Settings → Scheduled tasks and check the agent created all three runs, with the right days, times and project. Then use Run now on the 07:15 task while you're at the Mac. A scheduled run uses the same permission mode as any thread, so if it stops to ask before running gh or calling Slack, it'll stop the same way at 07:15. Either allow those commands for good or choose a mode that lets the task run them, without giving it more access than the job needs.
Between runs I click Settle on the thread, which moves it out of the sidebar's inbox without stopping its schedules. When the next run starts, it comes back to the top with a draft waiting for me.
Share this post
Related reading
CLIProxyAPI: sharing Claude Max across machines
I run CLIProxyAPI on a Raspberry Pi at home so Claude Code and Codex on every machine can use my accounts without logging in and out. Here are the account rules, the cache fix it needs and the setup over Tailscale.
Reasoning effort in Claude Code and Codex: where max stops paying
On Artificial Analysis's Terminal-Bench runs, Opus 5.5 scores the same at xhigh and max but takes five times as long to start answering. My levels, and why.