Console — the yard board
The product is the crane: ai-gantry. This page is the operator walk for this repo — the board that appears when you run more than one. Pitch and why the harness is worth operating live in the root readme. Install: install.md. Headless host + attach: headless.md. Login, profile, settings: operators.md. Door: security.md.
Chat is the pendant (Telegram, Discord, Slack if you already have that bot). Nothing here sits in a chat turn. Gantree reads Docker and files after the fact. It writes the same files the harness already understands.
browser → gantree (localhost | Tailscale | tunnel) → Docker + files
gantry gantry gantry
phone → gantry-pendant (Google) ←dials— those same gantries
Agents open zero inbound ports. Bind 127.0.0.1 by default. If you
expose the console, you expose it to yourself.
First boot
Need Node 22. npm start (or compose) on the Docker host.
- Open
http://127.0.0.1:3000— first boot is/setup(one operator). - After that,
/login. - Sessions live in yard
gantree.db— not a crane’sdata/gantry.db.
Forgot the passphrase: stop, delete that sqlite file, start, run setup again. No email reset. Click your name for Profile; the cog is Settings. Roles, add/remove, and the stuck table: operators.md. What the door checks: security.md.
Screenshot yard (no daemon)
The board looks empty or unknown when Docker is missing — common on
SteamOS / a laptop without the socket. Seed a photographable house, then
paint it:
npm run seed
# .env (loopback only — never compose):
# GANTREE_DEV=1
# GANTREE_DEV_OPERATOR=bob
# GANTREE_DEV_PASSPHRASE=bob-dev-ok
# GANTREE_SHOT=1
npm run dev
node scripts/shot.mjs http://127.0.0.1:3000 yard boards boards-page host crane crane-metrics metrics profile settings
That writes five named cranes (kit, ada, jules, moss, piper), five operators
with photos and Telegram ids, host/token series in gantree.db, and a mock
corkboard (./boards: kit/Bob, ada/Sam, jules/Nia, an open 100k-steps
challenge, a settled 10k, and a PR pin).
GANTREE_SHOT does not start containers. A real daemon on Arch/SteamOS is
often $XDG_RUNTIME_DIR/docker.sock — set DOCKER_SOCKET and unset
GANTREE_SHOT.
Board
A handful of named pets, not a Kubernetes dashboard. Each card: name, alive
or not, model, channel, published vs skipped MCP. Nags (dead process,
skipped grant, needs-auth) sit on the card — you do not have to open Tools.
Click through. Kit’s page is Kit — graphs and log, not a mixed fleet dump.
The host card (CPU bubble) is the Mini itself: CPU, RAM, and Docker network
(rx/tx), gantree.toml, yard sqlite, and how this process was started. People
(roles) stay on the cog.
Boards is a card on this grid (next to Host) and a Tools grant
(boards-mcp). Click the card for /boards: full roster with author
slugs, ranked scores, pins, and closed contests. Read-only — agents pin
and check in. The card is the teaser (a few open, latest pins). Empty
dir → empty card. Toggle grants the MCP; recreate fetches the bin and
binds the yard corkboard ./boards into each crane at /boards
(container path, not the HTTP route). BOARDS_AUTHOR is the crane slug
(clone rewrites it). Not a default profile grant. Messages stay private.
Do not watch notices_list.
Build a crane from the board (yard type first: home Mini or cloud VM).
Default channel is pendant. Settings → Pendant must be ready
(Google + Cloudflare token — access.md); Build ticks
operators (email is enough), the yard mints the bearer, recreate. The
yard cookie never goes to that Worker. Upload a photo on the crane —
it lands in persona/avatar.jpg, shows on the board, and the yard POSTs
that same JPEG to the Worker mailbox (JPEG, 5MB, avatar.jpg). The phone
user can replace the face from the pendant header. Grant image and
pendant, then recreate, so the agent can draw a picture and wear it:
photo_generate (1:1 face, 9:16 wallpaper) then
pendant__avatar_update / backdrop_update, plus theme_list then
theme_update (catalog id, no invented hex). Sending a PNG in the thread
is the harness, not this board. The room blob can drift from
persona/avatar.jpg. Pendant ticks operators (email until the Google
sub is learned) onto PENDANT_ALLOWED_USERS, nags recreate, and offers
to store an unseen sub on the matching profile.
Telegram is still a mouth: Create with BotFather copies /newbot,
opens the chat, and suggests {slug}_bot — paste the token it replies
with. On a Telegram crane the bot’s face updates too (setMyProfilePhoto).
Telegram (below the photo) can getMe the token, push name / about /
the / command menu (including /new), ask an allowlisted chatter to tap
/new (distill, then drop that thread — the yard cannot send as her), and
edit TELEGRAM_ALLOWED_USERS from numeric ids (slog user_id after
someone talks — not @username). Telegram crane → pendant (same memory):
channel_migration_doc.md. Grant a tool,
recreate, watch that crane’s doctor. Message it on the pendant.
/tools is the crane’s mouth; this page is the operator’s.
Pin: shotah/ai-gantry:latest (Hub). Nested repos/ai-gantry is dev
only — do not copy .env or data/ from a private checkout.
Grant is how it stays personal
Long-horizon is useless if the container is “healthy” with zero tools and a
dead token. MCP is the grant. The UI is a structured editor of that
crane’s mcp.toml, not a second inventory.
- Toggle on → write
[[server]]. Recreate fetches bins into/data/binand reloads until/toolsshows the prefix. - Toggle off → omit from the manifest.
- “Needs auth” is a button (laptop hop or paste a code from
/authin chat).
Hand-editing mcp.toml still works. Your own binary:
custom-mcp.md.
Two files, not a catalog
| File | Who writes it |
|---|---|
PERSONA.md |
You — who it should be, who you are |
persona/avatar.jpg |
You — face on the board and the Photo-fold push; the agent's pendant MCP paint is a separate room blob |
SELF.md |
The agent — voice, rituals, north-star aims that survive /new |
Gantree edits those files (and .env, and mcp.toml). It does not merge
memories across cranes. Isolation is the feature: one human, one bot, one
directory, one data/. Delete a tryout = delete that directory.
The seed on disk is lib/yard/crane/templates/PERSONA.example.md — keep it
identical to ai-gantry examples/persona/PERSONA.example.md. Replace from
template loads that file (agent Identity name from the slug). Inject
user (admin) copies selected profile fields into About you only; Save
still writes PERSONA.md. Recreate / rebuild never replace an existing
persona file.
Pull, don’t punch
The crane does not grow a /metrics port. The yard pulls (docker inspect, docker logs, sampled stats, JSON slog). Parse what the harness
already emits. Do not tax parallel tool calls so a chart looks nicer.
Samples and turns land in yard sqlite (7-day cap) so bouncing npm start
does not wipe Kit’s graphs. Mutations (grant, recreate, env, operators)
show who on a small events strip — not a SIEM. Admin also sees logins
and logouts there; other operators do not.
Doctor says why a tool is skipped (no binary / no key / no OAuth). Recreate
keeps the host user that owns data/ (never Distroless 65532),
network_mode, and extra binds.
Operator loop
npm start(or compose) on the Docker host- Board shows every gantry in
gantree.toml— alive or not - Click a card: per-instance graphs + visual logs. Host card: Mini CPU/RAM/net +
gantree.toml. Boards card: corkboard - Build a crane (yard type → slug → model → channel → profile)
- Grant / revoke MCP; files update; container recreates
- Watch that agent’s log and metrics until the grant is real
- Doctor says why something is skipped
The board has to work. The speed the human feels is still the crane.