Skip to content

Web admin panel

The web admin panel is APX’s browser-based UI. It runs entirely in your browser, talks to the daemon over its HTTP API, and requires no public server. Any browser on the same machine — or on the same LAN after pairing — can use it.

APX web panel — home screen showing daemon health and project list

The daemon serves the built panel at http://127.0.0.1:7430/. Start the daemon and open that URL:

Terminal window
apx status # boots the daemon if it isn't running yet
# then open http://127.0.0.1:7430 in your browser

On the first load the panel fetches its auth token automatically from /api/admin/web-token (loopback-only endpoint). You don’t need to copy a token manually when opening from the same machine.

To open the panel on your phone or another computer on the same LAN, use apx pair web. The command prints a QR code and a direct URL:

Terminal window
apx pair web
# Prints:
# http://192.168.1.42:7430/#token=<token>
# [QR code for scanning with a phone camera]

Scan the QR with your phone camera (no app needed) or share the URL. The #token=… fragment carries the bearer token — the panel uses it automatically.

apx
$ apx pair web
  APX pairing (web)  ·  expires in 90s

  █▀▀▀▀▀█ ▀▄█▀▄ █▀▀▀▀▀█
  █ ███ █ ▀█▄▀  █ ███ █
  █ ▀▀▀ █ █▄▀█▀ █ ▀▀▀ █
  ▀▀▀▀▀▀▀ █ ▀ █ ▀▀▀▀▀▀▀
  ██▀▄ ▀▀▄▀█▄▀▄█▀ ▄█▀▄█
  ▀ ▀▀▀▀▀ █▄▀ ▄██▀▀▄ ▄▀
  █▀▀▀▀▀█ ▄▀█▄▀▀█▄▀ █▄█
  █ ███ █ ▀█▄ ▀▄█▀▄▀▀▀▀
  █ ▀▀▀ █ ▀▄█▀█ ▄▀▄█▄ ▄
  ▀▀▀▀▀▀▀ ▀▀ ▀▀▀▀ ▀ ▀▀▀

scan with the phone camera — opens the web already linked
link: http://192.168.1.42:7430/#token=8f3a…d10c
code: pr_7QF2K  (or paste it in the web pairing screen)
apx pair web — QR code and LAN link printed to the terminal

To see all currently paired clients or revoke one:

Terminal window
apx pair list
apx pair revoke <id>

The panel is a Vite + React + TypeScript app under src/interfaces/web/. The built output lands in src/interfaces/web/dist/ and is served by the daemon.

Terminal window
# Build once (from repo root)
node scripts/build-web.js
# Or rebuild manually
cd src/interfaces/web
pnpm install
pnpm build
# Development with hot reload (proxies API calls to :7430)
pnpm dev

The build:web npm script and the prepack hook both call node scripts/build-web.js, so a global npm install -g . always ships a fresh bundle. Set APX_SKIP_WEB_BUILD=1 to skip it during dev flows that don’t touch the UI.

The panel has two navigation levels:

  • Base (/p/0/…) — the global daemon space: workspaces, models, sessions, logs, and global config. The chat in Base talks to the super-agent.
  • Project (/p/:pid/…) — a single project workspace: agents, chat, tasks, routines, MCPs, memories, and config.

A left rail shows all registered projects plus module shortcuts (Voices, Desktop, Deck, Code). Below the rail is a Roby button that opens a floating chat sheet connected to the super-agent.

Two more namespaces sit beside the project ones. The panel’s own modules are top-level paths — /inbox, /code, /desktop — and /m/… belongs entirely to the phone surface: /m/chat, /m/tasks, /m/commitments. The phone’s old address, /mobile, still resolves; it redirects into /m/… so links already out in the world (printed QR codes, the Android shell) keep working.

Left rail — project list plus module shortcuts

Every screen that shows a conversation updates itself. The panel keeps one WebSocket open to the daemon (/api/events/ws, authenticated with the same token as the HTTP API), and the daemon reports on it whenever a conversation moves — whichever surface produced the turn.

So a message that arrives on Telegram appears in the inbox on the laptop and on the phone at the same time, without a reload, and three devices open on the same thread stay in step. The socket carries a signal, never content: it says which thread moved, and each screen re-reads that thread through the API it already uses.

It repairs itself. A phone that suspends the tab, a laptop that sleeps, a tailnet that drops — on coming back the panel reconnects and revalidates once, so nothing is missed while it was away. If the socket cannot be opened at all (a proxy that refuses upgrades), the lists fall back to their 15-second refresh and the panel keeps working, just less immediately.

Running turns and queued messages also survive navigation inside the panel. If you leave a chat and come back, its partial reply and completed tool cards are restored in their original order, then streaming continues. Every chat list uses the same compact status marker beside the agent badge: a spinner while the agent is writing, then a blue dot when the reply finishes outside the open chat. Opening the chat clears the dot. Switching chats clears the old transcript immediately; a request still running for that old chat continues in the daemon, but cannot write into the newly opened one.

The dot is shared, not per device. What has been read lives with the daemon (~/.apx/read-marks.json), so reading a conversation on the laptop clears it on the phone within the same second — the other surfaces are told over the same socket. It used to be each browser’s own memory, which meant an afternoon of reading on one device left forty blue rows on the other, every one of them already read. An install that has been running for a while takes its baseline the first time it is asked: everything said before that moment counts as read, and only what arrives afterwards raises a dot.

Notifications, and which channels reach you

Section titled “Notifications, and which channels reach you”

The panel can tell you when an agent writes and you are not looking: one notification per thread, through the service worker, tapping it opens that conversation. Turn it on in Settings → Notifications (or from the phone’s own preferences sheet). It needs https — apx panel tailscale on — and it needs the app to be running, backgrounded included; waking a fully closed app is web push, which APX does not do yet.

What raises one is an agent reply, and only that. Your own message does not, and neither does any of the work in between: a turn can run two dozen tools and each one moves the thread, which is how the same turn used to arrive as a stack of notifications. A streamed answer that lands in pieces rings once and then updates the same banner quietly.

Which channels may ring is answered per device. The phone has Telegram installed on it, so an APX notification about a Telegram reply is the same news twice — there it starts off. On the laptop it is the only way to hear about it, so there everything starts on. Chips under Settings → Notifications switch each channel for the device you are on; the choice lives in that browser (localStorage) and never leaves it.

The same idea applies to the lists. The inbox and /m/chat show every channel a conversation can happen on, and one collapsed picker — “Channels · 6 of 11” — switches each one off and back on for that device, again with Telegram off by default on the phone. A channel that is off keeps its switch (with its count), so the way back is where the way out was, and a list emptied by a filter says which filter emptied it instead of claiming there is nothing there. Both lists are flat and sorted by recency, so every row wears its channel as a tag.

The inbox spans every project at once, which raises a second question the channel cannot answer: two projects can both have an agent called Zoya, and “Zoya · Web” does not say whose. So provenance is its own badge — the project name, next to the channel tag and never folded into it — on the list row and in the header of the conversation it opens, on the desktop and on the phone alike.

Only agents from another project are marked. The default workspace is where the super-agent lives and where every conversation without a project of its own lands, so badging it would label most of the list with the one place that goes without saying.

Beside the channel picker there is a Projects picker of the same shape, with a count per project, so you can read one project’s conversations on their own. Like the channel one it is a per-device choice kept in that browser.

Inside a project’s own Chat tab (/p/:pid/chat) neither the badge nor the filter appears: the answer is the screen you are standing on.

Opening a project lands on its Overview — a “mission control” floor with live stat cards (agents, open tasks, active routines, artifacts), a task-workflow strip broken down by status, the agent roster (orchestrators vs. specialists), and the most recent open tasks. Each tab below maps to a daemon API namespace.

🖥️ SCREENSHOT · web Project Overview — stat cards, task workflow strip, and agent roster apx web, then open any project (its landing tab)
Project Overview — stat cards, task workflow strip, and agent roster

The Chat tab talks to the super-agent (Roby) and to each project agent. The sidebar is channel-first, grouped as Web, Telegram, Desktop, Voice, Agent ↔ Agent, Schedule, Other — in that order. Besides your own web conversations, it also surfaces the super-agent’s day-threads from every other channel (Telegram, Desktop, Deck), so a conversation that happened over Telegram is visible from the web — and can be carried on there, in a session of its own.

The ”+ New” button opens an agent picker (super-agent or any project agent) and starts a fresh session that only materializes under the Web group once you send the first message. Each open conversation has a “New session” action (resets in place) and a “Delete” action (behind a confirmation dialog) that permanently removes the conversation file or the channel thread.

Under the agent’s name, the thread’s title is a switcher: it lists the other sessions of the conversation you are reading — the same channel, and on a channel that carries several people (WhatsApp) the same person, one row per day. Not every thread APX has: opening one contact’s WhatsApp offers their days, not the Telegram day or somebody else’s conversation. A person who has written from two numbers is still one person there, because the roster says so. An agent-to- agent pair and a group room are one conversation each, so neither gets a switcher at all.

A turn that takes several steps does not print as a running log. Every tool the agent ran collapses into one N actions row — open while it works, closed once it is done, one click for the arguments and results, and any failure named in the row itself. What stays in the open is the closing message: the answer the turn was for. Reopening the conversation later shows the same thing: the steps are recorded with the turn, not just streamed and forgotten. Progress prose stays visible too, but is not replayed into the next model prompt; only the final answer becomes conversation history.

Inviting someone turns the chat into a room

Section titled “Inviting someone turns the chat into a room”

A conversation is never left behind to start another one. Adding an agent to a 1:1 converts that chat: the transcript moves onto a group room, the agent that walks in can read everything said before it arrived, and the 1:1 is archived with a pointer to where it continues.

An agent-to-agent pair is a room too — two agents talking — so it is drawn like one, with each speaker named above their bubble instead of tagged under it. It has no seat for you until you want one: the moment you write in a pair it converts as well, seating whichever of the two are agents of this project (the super-agent’s seat becomes yours), and your line is the first thing said in the new room.

An agent-to-agent turn is a full tool loop that can run for minutes. However it was started — over HTTP, or by one agent calling send_to_agent / call_agent, including work left running in the background — the pair shows the same live narration as any other chat: it says somebody is answering before the first token, the tools appear as they run, and Stop reaches the work. Cancelling a background job stops its peer for the same reason.

A chat you start inside a project is listed in that project. The Base workspace still shows every thread from every channel; a project shows the ones opened from it, plus channel threads (Telegram, Desktop) that belong to no project in particular.

Chat tab — channel-first sidebar with super-agent and per-agent threads

Any message in any conversation — yours or an agent’s — has a Forward action next to Copy. It asks two things: who should see it, and where in their history it should land. The most recent session of whoever you pick comes preselected, so continuing a conversation you were already having is one click; New session is always the first option for the other case. Whatever you type goes under the quote, and may be left empty: “read this” is a complete message, and the agent answers the quote.

The list is not limited to this project. An install is one set of agents as far as you are concerned, so the picker reaches across every registered project, grouped under each project’s name, with a search that matches a person’s name, their slug, or the project they are in (GET /api/agents backs it). Forwarding into another project navigates there and continues in that project’s chat — the quote records which project it came FROM, so the card’s link back still opens the right conversation.

On the other side the message arrives as a card that says where it came from, who said it and when, with a link back to that session — so the forward reads as a citation rather than as pasted text with no author. The agent reads the same three facts: the quote travels in the prompt, marked as somebody else’s words.

Some conversations cannot receive a forward, and are left out of the list rather than offered and then refused: Telegram and WhatsApp threads (the message is on somebody’s phone; a turn written here goes out on web instead), agent-to-agent transcripts, group rooms, and any super-agent thread but today’s on this surface — the super-agent has no conversation file, so its turns are written with the clock, and a message dropped into yesterday would be shown in one thread and recorded in another.

Replying inside a Telegram or WhatsApp thread

Section titled “Replying inside a Telegram or WhatsApp thread”

Those threads are readable here and cannot be written back into: the message was delivered on somebody else’s platform, and APX cannot take it back or add to it. The composer says so above the field, and then does the honest thing — what you write continues in a new session with the super-agent, with the thread’s last message quoted at the head of yours, and the panel moves there as it sends. Before, the reply appeared to land in the Telegram thread and was gone on the next reload.

The Tasks tab is the project’s backlog. Besides the classic id/title/tags/due date/assigned-agent fields, tasks now carry a workflow status — pending, running, in_review, or blocked while open, and done/dropped once closed — shown with a status icon and color. Opening a task shows a detail panel with an editable status and body, and a “View thread” link when the task is linked to a chat thread. The list is server-paginated (see below) and can be filtered by state (open / done / dropped).

Tasks tab — open tasks with workflow status, tags, due dates, and assigned agents

The Routines tab schedules recurring work — a super-agent prompt, a shell command, a Telegram message, or a heartbeat — on a cron expression.

Routines tab — scheduled super-agent, shell, Telegram, and heartbeat routines

The MCPs tab lists the Model Context Protocol servers available to the project across all three scopes (shared, runtime, global), with a live log pane. Header/config values in the MCP editor support the variable token picker — see Variables below.

MCP servers — filesystem, GitHub, Postgres, and an HTTP server across scopes

The Memories tab shows the long-lived context the agents read, one file per row. A project has two. Internal memory (~/.apx/projects/<id>/memory.md) comes first and is what opens: the super-agent’s remember tool writes it and git never sees it, so a fact picked up mid-conversation cannot put a pasted credential in your history. Project memory (.apc/memory.md) is the curated one that travels with the repo. Promoting a line from internal to committed is a manual edit here. Below them sits each agent’s own memory. The super-agent’s notebook (~/.apx/memory.md) is global rather than per-project, so it is listed only in Base.

Memories tab — the project memory.md editor

The Skills tab embeds the same skills manager used in Settings (see Skills), locked to this project’s scope — enable, disable, add, and inspect skills without leaving the project.

Structure Company projects

Section titled “Structure ”

For projects with kind: "company", a Structure tab appears to manage the org model behind agent assignment: Areas (e.g. “Engineering”, “Sales”) and Roles within or outside an area. Areas and roles can be created, edited, and deleted here, and are backed by .apc/organization.json. The same quick-create dialogs are reachable from the agent editor’s Area/Role pickers.

🖥️ SCREENSHOT · web Structure tab — Areas and Roles for a company-kind project apx web, then open a company project → Structure
Structure tab — Areas and Roles for a company-kind project

Two tabs browse the project’s files with the same viewer component:

  • Docs — editable. Rooted at the project’s configured docs folder (docs.root, default docs/). Supports creating, editing (a dependency-free markdown split editor with live preview), and deleting files.
  • Files — read-only. Browses the whole project tree. Type-aware: Markdown renders, code shows with line numbers, images preview inline.
🖥️ SCREENSHOT · web Docs tab — markdown split editor with live preview apx web, then open a project → Docs → create or open a file
Docs tab — markdown split editor with live preview

The Artifacts tab lists the artifacts stored under <project>/artifacts/ — the same list used by the Code module’s side panel, now reachable from the project’s own navigation. Run and Edit hand off to the Code module so you can pass arguments (e.g. a URL) in the terminal or edit the file directly, instead of running headlessly in place.

The Config tab shows the effective project configuration merged from the project’s .apc/ files and the global defaults.

Config tab — effective project configuration and its sources

The Variables tab (vars) manages named, masked secret/config values at project or global scope (e.g. MY_API_KEY). Values are hidden by default with a per-row and a global reveal toggle. Anywhere a value can reference a variable — for example an MCP server’s headers — a token picker (the ”+” button next to the field) lets you search and insert ${var.NAME}, rendered inline as a $NAME badge so the reference reads as part of the line instead of a raw template string.

🖥️ SCREENSHOT · web Variables tab — masked values with the token picker used to reference them elsewhere apx web, then open a project → Variables, and MCPs → header field
Variables tab — masked values with the token picker used to reference them elsewhere

The Projects tab (Base → Workspaces) lists all registered projects as cards. Click a card to enter that project. The “New project” button opens an inline dialog (?action=add-project).

Workspaces tab — project cards with name, kind, and path

Each project has an Agents tab. You can create, edit, and delete agents; view and edit each agent’s memory.md; and assign skills. In the Base space the Agents tab shows the super-agent configuration (read-only summary).

Agents tab — agent list with memory and skill counts

Open an agent to inspect its memory, skills, tools, sub-agents, and brain graph. The editor also sets an emoji (shown next to the agent everywhere in the UI, including the Overview roster), an autonomy level — a three-way toggle for the same permission modes as the CLI/config (total, automatico, permiso, labeled Total / Auto / Permission) — and, for company-kind projects, an Area and Role pulled from Structure, with quick-create buttons next to each picker.

Agent detail — memory, skills, sub-agents, and the brain graph tabs

The Base Sessions tab (/p/0/sessions) lists every recorded session across engines. A search box (title match by default, with a “Deep” toggle to scan transcript content) reuses the same matching logic as apx session find, plus an engine filter and a Clear button. Each row exposes four actions: copy the apx session resume <id> --continue command, ask the assistant to continue that session from a quick-chat bubble, open the session’s working folder, and copy its path.

Sessions tab — search, engine filter, and per-row actions

The Voices module (/m/voice) configures TTS and STT globally. It shows the live status of every configured TTS engine (Piper, ElevenLabs, OpenAI, Gemini, mock), lets you pick a default or chain-with-fallback mode, reorder the chain, configure per-engine credentials, and run a live test playback. The STT section configures the Whisper model and language for mic input.

See Voice for the full CLI reference.

Voices module — TTS provider list with status badges and default picker

The Desktop module (/desktop) shows whether the floating Electron window is running (connected WebSocket clients), lets you edit the global shortcut, and toggle whether the daemon responds to desktop messages. You cannot start or stop the Electron process from the web panel — use apx desktop start / stop.

See Desktop for the full CLI reference.

The Deck module (/m/deck) will surface the GET /api/deck/manifest payload: daemon health, active project, registered plugins, and the widget/desktop grid, and let you enable or disable individual widgets from this view.

See Deck for the pairing CLI reference.

The Code module (/code) is the admin surface for the APX coding assistant. It opens on a code session: a persistent transcript with its own git baseline, plan/build mode, and optional project agent.

It opens on every project’s sessions, newest first — the project picker at the top of the rail narrows the list rather than defining it. That matters because a session belongs to the project its working directory resolved to, so a run started elsewhere would otherwise be invisible here. Each row names its project and, when one is set, its agent.

New session asks the two questions that are decisions: which project it runs in (files, terminal and diff are scoped to it) and which agent answers in it — the super-agent, or any agent of that project. Both are visible afterwards in the Context panel, where the agent can still be changed for that session.

The terminal shares the same sessions:

Terminal window
apx exec --code "refactor the auth middleware"

runs one turn in a fresh session titled from your prompt, prints the reply on stdout, and prints the session id on stderr so you can open it in the panel. Pass --session <id> to continue an existing one. Run from a folder that is not a registered project, the session works in that folder.

In build mode the assistant reads the project’s AGENTS.md, keeps a checklist for multi-step work, edits with patches (apply_patch) or exact replacements, runs the project’s own tests or checks before calling the change done, and ends with the diff. It starts with the coding tools only; anything else (browser, calendar, messaging) it activates on demand. Each new turn in a session knows which files the previous turns read and changed.

Global config lives under Settings (/settings/*), grouped into sections. Sub-sections:

PathSectionWhat it edits
/settings/identityAccountAssistant name, persona, timezone
/settings/super-agentAgentsName, avatar, personality and extra prompt; own model, permission mode, guards (risk grading, loop detection, spend breaker) and step limits per channel
/settings/enginesAgentsModel providers (add, edit, toggle)
/settings/memoryAgentsMemory (RAG) — embeddings + retrieval config
/settings/skillsAgentsSkills manager + RAG (Skill Inspector) — see Skills
/settings/telegramChannelsTelegram bot token and channels
/settings/devicesChannelsPaired devices list
/settings/voiceModulesTTS/STT — same as the Voices rail module
/settings/desktopModulesDesktop window settings
/settings/deckModulesDeck companion settings
/settings/webModulesWeb — this panel’s own theme, language, and (via Identity) timezone
/settings/advancedAdvancedRaw ~/.apx/config.json editor

The old /settings/appearance route still works — it now opens /settings/web.

Settings — model providers panel

The Super-agent panel sets Roby’s permission mode, personality, and system prompt; the active model and fallback chain are configured in the model router.

Settings — super-agent behavior, permission mode, and system prompt

The Web panel controls the panel’s own UX rather than the agent’s identity: pick Light, Dark, or System (follows the OS color scheme) for theme, and switch the UI language — the page reloads once so every rendered string picks up the new locale.

🖥️ SCREENSHOT · web Settings → Web — theme (light/dark/system) and language pickers apx web, then Settings → Modules → Web
Settings → Web — theme (light/dark/system) and language pickers

The Telegram panel manages the bot’s channels, contacts, and per-role tool permissions.

Settings — Telegram channels, contacts, and roles

The Devices panel lists every paired browser or phone and lets you revoke a token.

Settings — paired devices with last-seen and revoke

List views: pagination and full-height layout

Section titled “List views: pagination and full-height layout”

The Sessions, global Tasks, and per-project Tasks lists use real server-side pagination: the daemon pages by ?limit & ?offset and returns the total row count in an X-Total-Count header, so the panel only fetches the current page and shows a true range (“1–20 of 485”) and page count instead of being capped at a fixed fetch size. Page size is selectable (10/20/50/100, default 20). These list tabs also use a full-height layout: the list scrolls internally while the pager stays pinned at the bottom, so the whole view fits one screen.

The panel is a thin client: it has no local state beyond the current session. Every read is a GET against the daemon’s HTTP API; every write is a PATCH or POST. The daemon is the single source of truth.

Browser → GET/POST → http://127.0.0.1:7430/<route>
↓
APX daemon (host/daemon/)
↓
core/ (logic, memory, agents…)

The web bundle is served by src/host/daemon/api/web.js. API routes always take priority over the SPA catch-all, so a request to /api/projects/1/agents hits the real endpoint, not index.html.

  • Desktop — the Electron floating window
  • Voice — TTS/STT CLI and providers
  • Deck — companion app pairing
  • Skills — the skills manager, scopes, and the Skill Inspector (RAG)