Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

**A 3D office your team shares with its coding agents.**

Sit **Claude Code**, **Codex**, **OpenCode**, **Grok**, **Muse**, **DeepSeek Harness** and **Cursor** workers at desks, watch each one's terminal on the laptop in front of it,
Sit **Claude Code**, **Codex**, **OpenCode**, **Grok**, **Muse**, **DeepSeek Harness**, **Hermes Agent** and **Cursor** workers at desks, watch each one's terminal on the laptop in front of it,
and jump into any of them together. Every GitHub repo is a floor of the building.

[![Release](https://img.shields.io/github/v/release/AgentSystemLabs/agent-office?style=flat-square&color=e8c547&label=release)](https://github.com/AgentSystemLabs/agent-office/releases)
Expand All @@ -33,7 +33,7 @@ curl -fsSL https://raw.githubusercontent.com/AgentSystemLabs/agent-office/main/i
## What it is

- **A floor per project.** Ride the elevator, pick one of your GitHub repos, and the office clones it (showing how far along it is) and opens a floor for it. Every worker, board and queue on that floor works in that checkout.
- **Workers at desks.** Walk up to an empty desk, press **E**, and pick Claude Code, Codex, OpenCode, Grok, Muse, DeepSeek Harness, Pi or Cursor, each with its model and reasoning effort. The agent's live terminal shows on its laptop, and anyone can open it and type.
- **Workers at desks.** Walk up to an empty desk, press **E**, and pick Claude Code, Codex, OpenCode, Grok, Muse, DeepSeek Harness, Pi, Hermes Agent or Cursor, each with its model and reasoning effort. The agent's live terminal shows on its laptop, and anyone can open it and type.
- **Talk instead of typing.** Hold **Ctrl+Space** (or the **🎤**) in a worker's terminal or a prompt box and say what you want: it's typed in for you to send. Your browser does the listening, so there's nothing to install.
- **You can't miss who needs you.** A worker that stops to ask you something lights a red beacon over its desk, puts a banner on your screen saying who and what for, and sounds an alarm. One that has finished jumps up and down and dings. Press **N** to go straight to whoever is waiting.
- **From your phone, too.** `/lite` is the office in 2D: every worker and what it's waiting on, its terminal with the keys a phone keyboard lacks, and the boards. The 3D office offers it on a phone or a slow computer.
Expand Down Expand Up @@ -378,7 +378,7 @@ deploy/coolify.sh ssh 'node /opt/agent-office/bin/agent-office.js accounts invit
| M | Mute / unmute in voice |
| Ctrl + Space | Dictate into a terminal or a prompt box: hold it and talk (or hold the **🎤**) |
| Tab | The ☰ menu: every window |
| Esc | Close any window. In a terminal, **Hide**, **✕** and Esc only hide the terminal; the worker keeps running |
| Esc | Close any window |
| Ctrl + [ | Send Esc to a terminal, to close a menu like Claude's `/skills` or interrupt Claude (or **⎋ Esc** in its header) |

The full list is in [docs/controls.md](docs/controls.md).
Expand Down
4 changes: 2 additions & 2 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Everything in the office, room by room. Back to the [README](../README.md).
- **Take a seat.** Press **E** at the lounge couch, a beanbag, the balcony bench or a stool at its bistro table, or the couch or the boss's chair up in the boss office to sit down. Walk off, jump or press **E** again to get up. Everyone on your floor sees you sitting. Sit on the couch facing the TV while someone's sharing their screen and it opens full screen for you.
- **Minesweeper on the boss's monitor.** In the boss's chair, **E** plays Minesweeper on the monitor: the camera moves in close and the board sits right on the screen. Click to dig, right-click (or Shift-click) to flag, and click a number to dig around it once its mines are flagged. The first click is never a mine. **Stop playing** leaves your game up on the monitor, with its clock paused, until you come back. Walk off to get up.
- **Pick your character.** The first time you join, a character select screen lets you choose your name, skin tone, hair style, hair color and shirt, with a spinning preview. No name in mind? Leave it blank (or skip the screen) and you go by the made-up one shown in the box, like *Sunny Otter*, or press **🎲** for another. The office remembers your name and look in that browser, so you only pick once. Change it any time from **⚙️** or by clicking your name under *In the office* (turn on **👥 People** in the **☰** menu).
- **Hire workers.** Walk up to an empty desk and press **E** to choose Claude Code, OpenCode, Codex, Grok, Muse, DeepSeek Harness, Pi or Cursor, or press **P** to write a task first. A little worker sits down, a laptop opens, and the agent's live screen appears on it. Choose the agent for each queue task too.
- **Hire workers.** Walk up to an empty desk and press **E** to choose Claude Code, OpenCode, Codex, Grok, Muse, DeepSeek Harness, Pi, Hermes Agent or Cursor, or press **P** to write a task first. A little worker sits down, a laptop opens, and the agent's live screen appears on it. Choose the agent for each queue task too.
- **Signs over the desks.** Press **L** at any desk, empty or not, to hang a big sign from the ceiling over it: *Operations*, *Code cleanup*, *Frontend*, whatever that desk is for. Type up to 32 characters (or pick one of the ideas), choose one of seven colors and press **Enter**. It hangs over the far edge of the desk, facing the chair, so it reads over the shoulder of whoever sits there; while the desk back to back with it has no sign of its own, it says the same on the back, so you can read it from across the room either way. Everyone on the floor sees it, the hint at the desk names it, and it stays up across restarts. **L** again changes it or takes it down. Each floor has its own.
- **Room to grow.** When a floor needs more desks, the north wall past the gong comes down. Walk up to the yellow **🚧 Room to grow** sign on it and press **E**, then **🔨 Knock through**: the wall goes, and a back office opens behind it with a pair of desks, a rug, a lamp and a window, in a puff of confetti. Knock through once more and it goes back another row, for two more desks (two rows is as far as it goes before the street behind the building). New workers, and the task queue, take its desks once the room's are full, before any bean bag comes out. The sign moves to the back wall, where **E** can also wall the last row back up, once nobody's working there. Each floor is built out on its own, and from outside you see it: the back office sticks out of that storey, on posts down to the lawn.
- **Bean bags for the overflow.** There are 16 desks, and up to 4 more in the back office. Once they're all taken, a bean bag comes out on the floor with a low lap desk in front of it. Hire a worker there the same way as at a desk, and the next bean bag comes out, up to 12 around the room. The task queue seats workers on them too. A bean bag stays out while a worker sits on it, and the empty one goes away again as soon as a desk frees up.
Expand All @@ -40,7 +40,7 @@ Everything in the office, room by room. Back to the [README](../README.md).
- **Notifications.** You don't have to be in the office tab. The tab title counts the workers waiting on someone, like `(2) my-project · Agent Office`. Allow desktop notifications (the office asks when you first hire a worker, or turn them on under **⚙️**) and a worker that needs you or finishes while you're in another tab or app pops one up (*🙋 Byte needs you*, with what it's asking; that one stays up until it's dealt with). Click it to go straight to that worker: the office comes to the front with you at its desk and its terminal open. For the whole team, paste a Slack or Discord incoming webhook under **⚙️** → *Team notifications* (or start with `--webhook <url>`), and the office posts to that channel when a worker has needed input or been done for a few seconds with nobody at its terminal.
- **The 2D view, for your phone.** Open `/lite` (or **📱 2D view** in the **☰** menu) for the office without the 3D. It lists every worker on the floor, the ones waiting on someone first, each with what it's asking (*🙋 Wants permission: Bash: npm test*) or doing, its branch and PR, and who typed to it last. Tap one for its terminal, with a row of the keys a phone keyboard hasn't got (**1** **2** **3** to answer a question or a permission prompt, the arrows, Enter, Tab, Esc and Ctrl+C) and a box to send it a prompt. **✍️** on a card sends one without opening the terminal. **📌 Issues**, **🔀 PRs**, **📋 Queue** and **✨ New task** open the same windows as in the office, and the floor at the top switches projects, with a button straight to any floor where someone's waiting. The tab title counts who's waiting, and once you allow notifications (**🔔**) a worker that needs you pops one up and buzzes the phone. The 3D office offers the 2D view as it loads on a phone, and on a computer where it draws fewer than 20 frames a second for ten seconds. **Stay in 3D** stops it asking in that browser. A browser that can't draw the 3D office at all (no WebGL) goes straight to the 2D view, and signing in from `/lite` brings you back there. Teammates see you under *In the office* as *📱 on the 2D view*, not standing anywhere in it.
- **Task cards.** A card over each worker's head names what it's on ("Fix Login Redirect") and says in one line what it's doing right now. For Claude workers, Claude Haiku writes it from prompts and recent tools through the `claude` CLI. OpenCode, Codex, Grok, Muse, DeepSeek Harness and Cursor use local task summaries and never invoke Claude to name their tasks.
- **Survives restarts.** Workers are saved to disk with their agent choice and session ID, and their terminals run in a small process of their own. When the server restarts (a dev-server reload, a `kill` of its process), the agents keep working through it, and the new server picks every terminal back up, scrollback and all. Open terminals reconnect by themselves. Ctrl+C stops the workers; the next start wakes them back up in their sessions, and **R** resumes the Claude Code, OpenCode, Codex, Grok, Muse, DeepSeek Harness, Pi or Cursor session of one that exited. DeepSeek Harness workers are the exception to keeping working through a restart: their ACP session is a child of the office process, so it stops with the office and comes back *offline*, resumable with **R**. When a restart does take a worker's terminal with it (the machine rebooted, the terminal process died or was replaced by a newer one), a worker that was in the middle of something is resumed with *continue*, so it carries on by itself. The chat and each worker's last 3,000 lines of terminal are kept too, so after a restart (or a crash) you can still scroll back through what the workers printed and what everyone said.
- **Survives restarts.** Workers are saved to disk with their agent choice and session ID, and their terminals run in a small process of their own. When the server restarts (a dev-server reload, a `kill` of its process), the agents keep working through it, and the new server picks every terminal back up, scrollback and all. Open terminals reconnect by themselves. Ctrl+C stops the workers; the next start wakes them back up in their sessions, and **R** resumes the Claude Code, OpenCode, Codex, Grok, Muse, DeepSeek Harness, Pi, Hermes Agent or Cursor session of one that exited. DeepSeek Harness workers are the exception to keeping working through a restart: their ACP session is a child of the office process, so it stops with the office and comes back *offline*, resumable with **R**. When a restart does take a worker's terminal with it (the machine rebooted, the terminal process died or was replaced by a newer one), a worker that was in the middle of something is resumed with *continue*, so it carries on by itself. The chat and each worker's last 3,000 lines of terminal are kept too, so after a restart (or a crash) you can still scroll back through what the workers printed and what everyone said.
- **Search.** Press **/** (or **🔎 Search** in the **☰** menu) to search the chat and every worker's terminal at once, including what they showed before the office restarted. Click a terminal line to open that terminal scrolled to it, with the line lit up.
- **Command palette.** Press **Ctrl+K** (**⌘K** on a Mac) anywhere in the office to find anything on your floor by typing a few letters: workers (by name, task card or desk), issues, pull requests, services, the boards, teammates, and actions (hire a worker, open the task queue, settings, invite, hang a picture). Words, word starts and scattered letters all match, best first, so "login" finds the worker whose card says *Fix Login Redirect*. **↑ ↓** choose and **Enter** opens it the way clicking it in the office does: a worker's terminal, a PR's window, a service in a new tab. **Shift+Enter** walks your character over to it first and opens it when you get there (take a step yourself to call it off). **Esc** or **Ctrl+K** again puts it away. It doesn't open while you're typing in a terminal or a text box, where Ctrl+K is the terminal's own (cut to the end of the line).
- **Cost per worker.** Claude Code workers show tracked session cost and tokens. OpenCode workers show tokens and model/provider-reported cost estimates, including child-session usage, with a cache/reasoning breakdown in tooltips. These estimates are not billing; missing cost is shown as unavailable. Codex workers show the root session’s recorded tokens, including cache and reasoning breakdowns; cost and API-call count are unavailable. DeepSeek Harness workers show what is in each session's context window (and the window's size) from ACP's usage updates, plus a cost only when the harness reports one. Today's and all-time totals, `--budget` warnings and `--budget-pause` use tracked Claude Code spend only; they cannot cap OpenCode, Codex or DeepSeek Harness spending. Once the tracked budget is spent, the hiring pause applies to all new workers. OpenCode and Codex session snapshots and Claude totals survive restarts. Current-desk totals disappear when their workers are sent home.
Expand Down
2 changes: 1 addition & 1 deletion docs/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ browser ──HTTPS/WSS──▶ agent-office (Node)

## Security notes

Anyone who can sign in can drive Claude Code, OpenCode, Codex, Grok, Muse, DeepSeek Harness, Pi or Cursor in that directory, and through it run commands as the user that runs the office. Treat the password, the accounts and the invite links like SSH access:
Anyone who can sign in can drive Claude Code, OpenCode, Codex, Grok, Muse, DeepSeek Harness, Pi, Hermes Agent or Cursor in that directory, and through it run commands as the user that runs the office. Treat the password, the accounts and the invite links like SSH access:

- The office listens on `127.0.0.1` unless you pass `--host`, and `deploy/provision.sh` keeps it there, behind SSH, Caddy or Tailscale Serve. The sign-in link the terminal prints works once, and the office keeps only a hash of it, in memory.
- Use a strong password and HTTPS. With `--trust-proxy`, cookies are `Secure` once the proxy says the request came over https.
Expand Down
16 changes: 10 additions & 6 deletions src/client/features/hud/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import type { Parts } from '../../core/parts';
import { waitingInOrder, waitingLabel } from '../../nextup';
import { saveSettings, store } from '../../state';
import { openAccounts } from '../../ui/accounts';
import { openAlerts } from '../../ui/alerts';
import { openBoard } from '../../ui/boards';
import { openCharacter } from '../../ui/character';
import { $ } from '../../ui/dom';
import { toggleFloorMenu } from '../../ui/floormenu';
import { openHelp } from '../../ui/hud';
import { openLobby } from '../../ui/lobby';
import { mountHud } from '../../ui/menu';
import { openServices } from '../../ui/services';
import { openSettings, type SettingsPane } from '../../ui/settings';
Expand Down Expand Up @@ -49,20 +51,22 @@ export function installHud(ctx: Ctx, core: CoreState, parts: HudParts) {
const noMedia = () => (window.isSecureContext ? undefined : 'Voice and screen sharing need HTTPS or localhost — use a TLS proxy, --self-signed, or an SSH tunnel');
const hud = mountHud(
[
{ id: 'lobby', icon: '🏠', label: 'Dewa Alpha lobby', section: 'Open', title: () => 'Today’s brief and the fastest way into every workspace', run: () => openLobby({ council: parts.meeting.showMeeting, canvas: () => openWhiteboard(net), alerts: openAlerts }) },
{ id: 'issues', icon: '📌', label: 'Issues', section: 'Open', count: () => store.issues.items.filter((i) => i.state === 'OPEN').length, run: () => openBoard('issues', net, actions.boardActions()) },
{ id: 'pulls', icon: '🔀', label: 'Pull requests', section: 'Open', count: () => store.pulls.items.filter((p) => p.state === 'OPEN').length, run: () => openBoard('pulls', net, actions.boardActions()) },
{ id: 'queue', icon: '📋', label: 'Task queue', section: 'Open', count: () => store.queue.tasks.filter((t) => t.status !== 'done').length, title: () => 'Issues and tasks waiting for a worker', run: waiting.showQueue },
{ id: 'alerts', icon: '📡', label: 'Dewa Alpha alerts', section: 'Open', count: () => store.alerts.cards.length, title: () => 'WHY NOW cards with evidence, freshness, coverage, and invalidation', run: openAlerts },
{ id: 'services', icon: '🌐', label: 'Services', section: 'Open', count: () => store.services.items.length, title: () => 'Web servers the workers are running', run: () => openServices() },
{ id: 'whiteboard', icon: '📝', label: 'Whiteboard', section: 'Open', title: () => 'Draw together, live', run: () => openWhiteboard(net) },
// Up on the top bar while a meeting is on: what's being worked through in the meeting room.
{ id: 'whiteboard', icon: '🧠', label: 'Research canvas', section: 'Open', title: () => 'Map narratives, wallet evidence, hypotheses, and next actions together', run: () => openWhiteboard(net) },
// Up on the top bar while a council session is on: the decision being worked through in the war room.
{
id: 'meeting',
icon: '🤝',
label: 'Meeting room',
icon: '🏛️',
label: 'Council war room',
section: 'Open',
status: () => store.meeting.current?.status === 'running',
chip: () => 'In a meeting',
title: () => 'Call a meeting: workers work through a question or a task together',
chip: () => 'Council active',
title: () => 'Call LokiDewa Council to research, challenge evidence, decide, and assign next actions',
run: () => parts.meeting.showMeeting(),
},
{ id: 'search', icon: '🔎', label: 'Search', section: 'Open', key: '/', title: () => 'Search the chat and every terminal', run: waiting.showSearch },
Expand Down
5 changes: 5 additions & 0 deletions src/client/lite.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ body.lite { min-height: 100dvh; background: var(--paper); -webkit-tap-highlight-
.lite-go { justify-content: space-between; min-height: 44px; background: #ffd6e0; white-space: normal; text-align: left; }
.lite-go:hover { background: #ffc2d1; }
.lite-h { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; margin: 4px 0 10px; font-size: 14px; font-weight: 900; text-transform: uppercase; letter-spacing: .05em; }
.lite-brief { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:8px; margin:10px 0 16px; }
.lite-brief div { display:flex; flex-direction:column; padding:10px; border:2px solid var(--ink); border-radius:10px; background:#fff; box-shadow:0 2px 0 var(--ink); }
.lite-brief b { font-size:18px; overflow:hidden; text-overflow:ellipsis; }
.lite-brief small { color:var(--muted); font-weight:800; }
.lite-waiting { font-size: 13px; text-transform: none; letter-spacing: 0; color: #c9184a; }

/* A card per worker, the ones waiting on someone first (see byUrgency). */
Expand Down Expand Up @@ -65,6 +69,7 @@ body.lite { min-height: 100dvh; background: var(--paper); -webkit-tap-highlight-

/* On a phone, the big windows take the whole screen; the small ones stay dialogs. */
@media (max-width: 640px) {
.lite-brief { grid-template-columns:repeat(2,minmax(0,1fr)); }
.backdrop { padding: 8px; }
.backdrop:has(> .modal.term, > .modal.board, > .modal.gh-window, > .modal.desk-changes, > .modal.meeting-window, > .modal[aria-label="Task queue"]) { padding: 0; }
/* body.lite outranks the windows' own sizes and padding, whatever order the sheets load in. */
Expand Down
Loading