A macOS GUI for driving Claude Code agent sessions — and an experiment in handing Claude a local image generator.
gazzo is two things at once:
- A showcase of the concentric, message-driven architecture built on
swift-kernelee: the same ring
layout as the reference app
ConcentricArch, deliberately
exercising the two kernel primitives the reference app doesn't touch —
.divert(theclaude -pstream-json read loop is one self-diverting pipe shared by start and resume) and.fork(parallel session-file reads when browsing history). - An experiment: what happens when you give Claude the ability to generate images? A local stable-diffusion.cpp engine (Metal-backed) is exposed to headless Claude sessions as an MCP tool, so an agent can decide to draw as naturally as it decides to read a file.
- Browse native history — read-only view over
~/.claude/projects(project-scoped or all projects), with import into gazzo's own managed store under~/.gazzo. - Start / resume agents — headless
claude -psessions over stream-json, with a live conversation view (thoughts, tool use, older-turn paging), stop, and rewind (edit an earlier prompt and branch the session from there). - Composer — model picker, working-directory picker,
@file mentions with search, per-session tool configuration, usage gauge. - Split panes — a splittable pane tree; every pane hosts its own session or a fresh composer.
- Subagent watching — live transcripts of the subagents a session spawns.
- Interactive tools for headless sessions — spawned sessions get
ask_permissionandask_choiceMCP tools, so aclaude -pchild can raise a native GUI prompt instead of failing silently. - Search, settings, artifacts — cross-catalog search, a Settings scene (models / tools / prompt / display / image generation), and an artifact pane that shows every generated image with QuickLook.
The GazzoMCP stdio executable exposes three tools to the Claude sessions
gazzo spawns:
| Tool | What it does |
|---|---|
generate_image |
Generate a PNG from a prompt with the local engine; the image is returned to the model and saved under ~/.gazzo/images. |
ask_permission |
Ask the gazzo user to allow or deny an action, via a GUI prompt. |
ask_choice |
Ask the gazzo user to pick one of several options. |
Design points:
- The engine runs inside the MCP process, not the GUI: model weights stay resident between calls (with idle unload), a cross-process lock serializes generation, and everything keeps working with the GUI closed.
- Models are managed from Settings — a FLUX.1-schnell preset can be
downloaded in one click into
~/.gazzo/models; single-file gguf / safetensors SD-family models are picked up from the same directory. - The GUI never generates anything itself; it just watches
~/.gazzo/imagesand surfaces whatever the agent made in the artifact pane.
The interesting part is not the plumbing but the behavior: given the tool,
Claude uses it — illustrating an explanation, iterating on a prompt after
seeing its own output, or asking (via ask_choice) which of several
compositions you prefer.
Control is data: every cross-device call is a phantom-typed Symbol
dispatched through a central Kernel, pipelines flow forward-only, and the
UI observes a Redux-style shared buffer. The full write-up lives in
ConcentricArch's README; the
framework itself is swift-kernelee.
Sources/
├── Contract/ # shared vocabulary: ports (@callable), model, buffer states, errors
├── Compute/ # pure logic — jsonl / stream-json decoding, no I/O, no kernel
├── Infrastructure/ # I/O devices: history store (RO), session store (flock), claude CLI process
├── Circuit/ # orchestration sagas — rules, not logic (the divert stream loop lives here)
├── Driver/ # the single manifest binding ports to concrete devices
├── Presentation/ # SwiftUI — reads the buffer, dispatches, never writes
└── App/ # @main composition root — the only target that sees everything
Invariants worth calling out:
- Only ids cross the bus. The
claudeProcessand its pipes stay inside one Infrastructure actor; sagas passSendablehandles. ~/.claude/projectsis read-only. gazzo never mutates native Claude Code history; its own state lives in~/.gazzo, flock-guarded so the GUI and the MCP process can share it.- Streams are pipes, not recursion. The stream read loop is a pipe that
keeps
.diverting into itself — O(1) stack, one wiring diagram.
Debug builds mount swift-kernelee's tooling: the kernel monitor (⌥⌘M —
live message trace, buffer snapshots, time-travel) and the wiring graph
(⌥⌘W — every pipeline rendered as a node graph). The repo also builds a
kernel-introspect MCP server so coding agents can query the app's real
wiring instead of grepping.
Requires macOS 15+, Swift 6, and the claude CLI on your PATH.
swift build # all targets, including GazzoMCP
.build/debug/gazzo # run the app
swift test # wiring exhaustiveness + decoder/fixture testsRun the binary directly rather than swift run gazzo — the app expects its
sibling GazzoMCP executable next to it (override with GAZZO_MCP_PATH).
Image generation needs one extra step (Apple Silicon recommended):
scripts/build-sdcpp.sh # fetch + build stable-diffusion.cpp with the Metal backend
swift build # now links the real engineWithout it everything else works; generate_image reports the engine as
unavailable. Then download a model from Settings → Image Generation.
MIT © s-age