carafe / always-on Macs for AI agents
Declare your Macs. Kubernetes keeps them.
The carafe Fleet operator runs Fleet, the open-source device manager from FleetDM, inside your cluster. Through Fleet it checks that every Mac station is awake, logged in and running its AI agent (Claude Code, Codex or your own), and runs the configured fix when a check fails.
- 51 s to notice a stopped agent page
- 142 s until it is running again
- 0 people touching the Mac
[lab-tested, not yet in production] Measured 2026-10-28 in a kind cluster against a macOS 26.5 VM, checks every minute.
kubectl -n fleet-a get macstations -w — illustrativeNAME ONLINE CELLAR-DOOR CHROME SIGNED-IN STATUS station-01 true true true true ok # 0 s the agent's page is stopped on the Mac station-01 true false true true attention # 51 s policy failing → remedy restart-cellar-door.sh # script exit 0, output "ok"; host refetched station-01 true true true true ok # 142 s back, nobody at the screen
Where it sits
Five layers of AI. carafe is layer 3, the infrastructure.
Jensen Huang describes AI as a five-layer stack: energy, chips, infrastructure, models and applications. Infrastructure includes power delivery, networking and the systems that run many processors as one machine. carafe is building that layer for AI agents: a Mac mini cloud that keeps each agent's machine powered, connected and running.
Framework from Jensen Huang, "AI Is a 5-Layer Cake", NVIDIA blog, 2026-03-10. Placing carafe on it is our reading; carafe is not affiliated with or endorsed by NVIDIA.
| 5 | Applications | where value is created: assistants and agents people use every day |
| 4 | Models | Claude, GPT and open models, reached over the network |
| 3 | Infrastructure [we build this] | carafe: power delivery, networking, remote hands and orchestration for always-on agent machines |
| 2 | Chips | the processors that turn power into computation |
| 1 | Energy | the power every token starts from |
The problem
An AI colleague only works while its computer is on.
Lid closes
The agent stops.
Claude Code, Codex and other agents run on someone's laptop. Remote access helps, but only while that laptop is awake. Not everyone can keep a computer on, or set one up.
Real work is in a browser
A container has no browser.
Permit applications on government portals and design proposals happen in web pages. Claude in Chrome drives a real Chrome on a real desktop, so the plan is a Mac mini for each of them.
Now there are N Macs
Someone has to keep them alive.
Every station must stay awake, logged in, with the agent and its browser running, and be fixed from afar when it is not. Doing that by hand in a device console adds one more place for settings to drift.
First use / carafe, for Claude Code
One always-on Claude workspace per colleague, reachable from the phone.
A carafe is one person's own container with Claude Code, their work folder and the company's skills. A Mac station runs the same setup natively on a Mac mini, with a real Chrome. Each has one web page, the cellar door, where the person signs in and turns on phone access. Then they work from the Claude app.
- A session started from claude.ai/code, the same path the phone uses, drove Chrome on a Mac station. After a reboot the station came back by itself in about two minutes. A test on a real phone is still to come.
- The Fleet operator reports every station's checks and runs the configured fix when one fails.
Two rules from the start
- Everyone signs in themselves.
- The sign-in happens in the person's own browser, through Anthropic's flow, and the code goes straight into their own carafe. carafe keeps no copy of anyone's Claude credentials.
- Claude's questions are the person's to answer.
- "Trust this folder?" and "Enable Remote Control?" are shown verbatim and answered by the person. carafe never answers them for anyone.
- The operator respects that line.
- The
signed-incheck has no remedy. Signing in again needs the person, so the operator reports it and stops there.
How it works
One operator. Three resources. One writer.
The operator owns Fleet: it deploys it, sets it up, configures it and upgrades it. Fleet in turn manages the Macs. The operator is meant to be the only thing that writes to Fleet.
FleetServer— one per company- Fleet, MySQL and Valkey from the official Helm chart:
spec.versionpicks Fleet,spec.chartVersionthe chart. Then TLS, the first admin, an API user, Secrets, NodePort and NetworkPolicies. FleetConfig— one per company- Policies, scripts and labels, kept matching the spec: removed from the spec means removed from Fleet. Built-in and person labels are left alone.
MacStation— one per person- An enrolled Mac, found by serial number: labelled with its person, every check in
status, and the remedy script run when a check fails.
What you write
A health check and its fix, declared in YAML.
Each policy is an osquery check that runs on every Mac. Give it a remedy and the operator runs that script when the check fails and the Mac is online. Give it none and the operator only reports.
- Five station checks ship in the example: never sleeps, logs in by itself, the agent page answers, Chrome is running, Claude is signed in.
kubectl get macstationsshows every person's Mac, with the five station checks as columns.- Hand edits are reverted at the next drift check.
kind: FleetConfig
spec:
serverRef: fleet
policies:
- key: cellar-door
platform: darwin
query: SELECT 1 FROM curl WHERE url =
'http://127.0.0.1:7681/healthz' AND response_code = 200;
remedy: restart-cellar-door.sh # run when it fails
- key: signed-in
query: SELECT 1 FROM curl WHERE url =
'http://127.0.0.1:7681/signedin' AND result = 'yes';
# no remedy: signing in again is the person's job
---
kind: MacStation
spec:
serverRef: fleet
person: ana
serial: C02ABCDEF123
remedyBackoff: 120 # seconds between fixes
Any agent
Claude Code, Codex or your own agent.
If it runs on macOS, the operator can watch it. A check is an osquery query, a fix is a shell script run as root, and both live in your spec. Anything osquery can see counts: a process, a launchd job, a port, a heartbeat file.
| agent on the station | what to check | status |
|---|---|---|
| Claude Code, in a carafe Mac station | agent page answers, Chrome running, Claude signed in | [lab-tested] |
| OpenAI Codex CLI | process running, its LaunchAgent loaded | [same pattern] |
| your own agent or bot | a process, a port, a heartbeat file | [same pattern] |
Lab-tested: carafe's checks and its agent page's recovery. Codex and custom agents are examples, not yet tested; their checks show in status.checks.
kind: FleetConfig
spec:
serverRef: fleet
scripts:
- name: restart-codex.sh
content: |
#!/bin/sh
# your own LaunchAgent that runs Codex
l=com.example.codex
u=$(stat -f %Su /dev/console); uid=$(id -u "$u")
launchctl kickstart -k "gui/$uid/$l" ||
launchctl bootstrap "gui/$uid" \
"/Users/$u/Library/LaunchAgents/$l.plist"
policies:
- name: "station: Codex is running"
key: codex
platform: darwin
query: SELECT 1 FROM processes WHERE name = 'codex';
remedy: restart-codex.sh # run when it fails
Configuration drift
The spec is the only truth. Hand edits are put back.
No edits in Fleet's web page, no separate GitOps run, no tug of war between two tools. The operator reads Fleet back every five minutes and makes it match.
| what happened | what the operator did | measured |
|---|---|---|
| a policy edited in the spec | applied to Fleet | under 1 s |
| a policy added by hand in Fleet | removed | at the next check |
| a script changed by hand in Fleet | put back | 287 s |
Self-healing
A failed check triggers its fix, carefully.
The operator runs a remedy only when four things hold. That keeps a broken script from hammering a Mac, and skips Macs that are off.
- The policy is failing, per Fleet's own result.
- The spec names a remedy for it.
- The Mac is online. Nothing new is sent while Fleet reports it offline.
- The backoff has passed since the last run.
After a reboot every check came back with nobody touching the Mac. The operator works out boot time from uptime, because in the lab a quick reboot never showed as offline in Fleet; it landed within one second of the kernel's own boot time.
Fleet Free
Runs on Fleet's free tier.
Fleet's own "run a script when a policy fails" is a Premium feature. The operator does it itself with what the free tier already has: scripts, policies, labels and an API-only user.
When we looked in October 2026 we found no Kubernetes operator for Fleet, from its maintainers or the community. The options were the Helm chart, a Terraform provider and fleetctl gitops. The operator uses the official chart rather than replacing it.
Fleet tiers per fleetdm.com/pricing, read 2026-10-27.
| capability | Fleet Free | with the operator |
|---|---|---|
| policies, scripts, labels, API-only users | [yes] | declared in YAML |
| run a fix when a policy fails | [Premium] | [yes] with backoff |
| install, set up, upgrade Fleet | separate tools: Helm, Terraform | [yes] from spec.version |
| keep Fleet matching a spec | when fleetctl gitops runs | [yes] every 5 min |
| several companies apart | one org per install | [yes] one per namespace |
Security
The token that can run root on every Mac stays in one namespace.
- Least privilege in Fleet. The operator's API user defaults to maintainer, the lowest role that can run scripts, not admin.
- Least privilege in Kubernetes. A namespaced Role, never a ClusterRole. The operator watches one namespace only.
- NetworkPolicies by default. MySQL and Valkey only from inside the namespace; from outside the cluster, only Fleet's port.
- The AI agents' pods cannot reach Fleet. The lab found that a pod going to a node's NodePort keeps its pod address, so the rule for the Macs excludes the pod network. In the kind lab, pods in other namespaces timed out on the Service, the NodePort and MySQL. Nothing else runs in Fleet's namespace.
Multi-company
One Fleet per company, tested apart.
Each company gets its own namespace, operator, Fleet, database and port, and later its own Apple push certificate. Fleet's free tier has one organization per install, so this is also how each company sees only its own name and its own Macs.
- <1 GB memory per company, 690–930 MB observed
- ~20 MB the operator itself
Two companies side by side in the kind lab, 2026-10-28; the first had one Mac enrolled, the second none.
| # | company A against company B | result |
|---|---|---|
| 1 | second Fleet comes up beside the first | [71 s] first untouched |
| 2 | each sees only its own hosts | [apart] |
| 3 | one company's API token on the other's Fleet | [401] both ways |
| 4 | one company's enroll secret on the other's Fleet | [401] both ways |
| 5 | one company's CA on the other's Fleet | [TLS refused] |
| 6 | a pod to the other's Fleet, MySQL, Valkey, NodePort | [blocked] both ways |
| 7 | one operator's account on the other's namespace | [denied] both ways |
| 8 | A's Mac declared as a station of B | [not enrolled] nothing shown |
Proof / six checks, two implementations
Built twice, measured twice.
A Python proof of concept first, then a Go rewrite with kubebuilder and controller-runtime that runs inside the cluster, calling Helm's Go SDK in-process. Both passed the same six checks against a real macOS VM. The Python code was then removed.
| # | check | Go operator, in the cluster | Python POC |
|---|---|---|---|
| A | empty namespace to a working Fleet, users and Secrets | [pass] Ready in 71 s | 97 s |
| B | spec applied; hand edits undone | [pass] < 1 s; 287 s | 2 s; 71 s |
| C | a Mac enrolls and shows its checks | [pass] listed after 10 s | pass |
| D | the agent page stopped, fixed by the operator | [pass] 51 s / 142 s | 82 s / 171 s |
| E | reboot, every check back untouched | [pass] boot time ±1 s | pass |
| F | other namespaces cannot reach Fleet | [pass] timeouts on all paths | pass |
Fleet 4.93.2, Helm chart 7.0.30, kind 0.33 with Kubernetes 1.37, macOS 26.5 VM. Lab checks ran every minute; about every five minutes is proposed for stations. Single runs, one macOS VM.
Status
Proven in the lab. Next: MDM and a first real office.
Nothing here runs in production yet. Lab-tested; MDM, backups and a first office come next.
Proven [lab]
- Go operator: install, configure, drift, remedy, reboot, isolation (A–F)
- Two companies side by side, eight isolation checks
- Unit tests against a fake Fleet and a fake API server, no cluster needed
- Trusting the company CA on a Mac as root, with nobody at the screen
Next [planned]
- MDM: Apple push certificate in a Secret, expiry in
status, enrollment profile from the API - First profile: Chrome's managed policy that installs Claude in Chrome
- Nightly MySQL dump, with a restore tested before stations depend on it
- The NetworkPolicy checks again on k3s; a per-company install overlay
Later [open]
- Zero-touch enrollment through Apple Business Manager
- Several fleets per install with Fleet Premium
- OS updates, lock and wipe through MDM
- A company-owned carafe box for small offices, with an engineer for setup and workflows
Where it goes / a small office [direction, not built yet]
Next: an agent-ready office for a ten-person company.
A small company has no IT team and no Kubernetes. The plan: it buys a carafe box that becomes its control plane, with no data centre to rent and nothing hosted by us. An engineer sets it up, then works on the team's workflows.
carafe box— the control plane- A Raspberry Pi 5 running k3s and the operator. The company owns it and decides who is admin; support would get in only with its approval, each visit logged.
carafe— one per person- Claude Code, a work folder and the company's skills, on a host in the office, reached from the phone.
Mac station— one per boundary- A Mac mini for each role that must stay apart: front desk, finance, back office, audit, the owner. Each checked and fixed by the operator through Fleet.
network— the office's own- Claude Code's Remote Control opens no inbound ports, so phones need no public address; Fleet stays on the office LAN. A recommended UniFi kit would add a VLAN for the agents, PoE and a 5G backup line.
Setup
The box goes in.
An engineer, ours or a partner's, installs the box, sets up the network and sign-in, and gives everyone a carafe.
The engagement
Work, not plumbing.
The setup is the same in every office, so the engineer's time goes to one or two workflows for each team.
After
Skills travel.
Those workflows become skills that the next company of the same kind can start with. A subscription per person would cover monitoring, upgrades and skill updates.
Licensing
Source available. Free for noncommercial use. Licensed for companies.
For IT teams that already run Kubernetes, for service providers who look after Macs for several small companies, for consultancies placing AI Mac stations with their clients, and, next, for small companies and the engineers who set them up.
ccheng@ccheng.us | | Write to us
Write for a commercial license or a scoped pilot on your own Mac stations.
Independent software. Not affiliated with or endorsed by Fleet Device Management, Anthropic, OpenAI, NVIDIA, Apple, Raspberry Pi or Ubiquiti.
Noncommercial
Free for the uses the PolyForm Noncommercial License 1.0.0 permits.
Commercial
Commercial use, including running carafes for an organization's own staff or its clients, needs a separate written license signed by Cheng Cheng.
Yours stays yours
The licensor claims nothing in what a person keeps or makes in their carafe: workspace files, sessions, transcripts, the configuration carafe writes.
Third parties
Fleet, Claude Code, Codex and macOS keep their own terms. carafe's license covers carafe's own code only.