Back to all writing

Field notes

Hayate Agent's New Browser Window Stays on Your Machine

Hayate Agent got a browser chat window and a Slack gateway on Friday. The browser part listens on 127.0.0.1 only, has no login, and says so. Why I think that is the right first version, what ships in the image, and what Slack still cannot do.

Hayate Agent's New Browser Window Stays on Your Machine

秋の宵
この机だけ
灯る窓

Autumn evening.
Only on this desk
a window glows.

Until Friday afternoon, Hayate Agent (formerly the VanillaCore Agent) had two front doors: the terminal and Matrix. On Friday its source code got two more, a browser window and a Slack gateway. Plugins pull request #54, merged at 16:33, adds a chat interface that runs in the browser. Core pull request #295 follows four minutes later with setup instructions for the browser and Matrix, and core pull request #298, merged at 18:26, puts the browser plugin into the shared runtime image. The part I like best is what the browser window refuses to do. It listens on 127.0.0.1 and nowhere else, it has no login, and the code tells you not to put it behind a proxy.

That sounds like a missing feature. I think it is the right first version. A chat window into an agent that can run shell commands is not a toy, and a login page bolted onto it in a hurry is how such things end up on the open internet. The plugin's own README says it plainly: this is "local browser isolation, not user authentication". Remote access gets its own design first, and the README already lists what that design needs.

Hayate Agent cover: small slate 疾風 logo and "Hayate Agent" at the top, the red two-line Japanese headline Hayate Agentの新しい窓は / あなたの端末から出ない (Hayate Agent's new window does not leave your device), the white English headline "Hayate Agent's New Browser Window Stays on Your Machine" and github.com/hayate-agent at the bottom, on an indigo-black field with a faint wave pattern along the lower edge

All times are Europe/Berlin. The window is 2026-11-06 08:00 to 20:00.

A browser window, not a second agent

The WebUI is a gateway plugin, the same kind of component as the Matrix connection. It does not run its own agent. Commands, permission checks, approvals, model selection, tool execution and the durable inbox all stay in Core. The plugin keeps what you see: conversations, titles, drafts. Each browser conversation becomes a normal session with the key webui:<conversation UUID>, and the browser cannot choose the sender or switch into a foreign session.

A message counts as sent only after Core has accepted it. If the browser closes before that, the message keeps its ID and can be sent again without running the work twice. Replies stream in, and the final stored reply replaces the streamed text. The window shows which tools ran and whether they failed, but not their arguments and not the model's hidden reasoning. Markdown from the model goes through a sanitizer with a short allowlist: no images, no frames, no buttons the model can invent.

There are no model tables in the plugin. It does not guess context sizes or token counts. The limits it does have (64,000 characters per message, 300,000 bytes per HTTP body, 32 simultaneous live-update streams) protect the transport and do not cut anything the agent sees.

Local only, on purpose

The listener is fixed to 127.0.0.1, port 3210, or another port set with HAYATE_WEBUI_PORT. There is no option for another address. The plugin checks the Host header exactly, so localhost is rejected, which makes DNS rebinding harder. Requests that change something need the same origin and a custom header. The sender is always webui:local, and that sender is not an administrator. If you want operator commands from the browser, you add webui:local to the admin list yourself, and the documentation warns that everyone with access to the machine's loopback then holds those rights.

The README lists what a later network mode needs before it may exist: an explicit bind address, TLS with a defined proxy policy, real sessions with logout and expiry, conversations bound to an authenticated person on every read and write, rate limits and audit logs. None of that is built yet, and the code does not pretend otherwise.

In the image, but switched off

Core #298 is the step that makes this usable without building the plugin yourself. Core's npm package and image now carry the WebUI under dist/optional-plugins/webui/. The source stays in the plugins repository. A lock file pins the plugins commit of #54 and a SHA-256 hash for every file the build needs. The build checks those hashes, installs the locked npm packages with install scripts disabled, bundles the browser code and writes a provenance file. At startup nothing is downloaded and nothing is switched on.

To use it, you select the module in a settings file:

gatewayPlugins:
  webui:
    module: /opt/hayate-agent/dist/optional-plugins/webui/index.mjs

Core #295 puts the same mapping for a separately built plugin (plugins/webui/index.mjs) as a comment into the starter configuration that hayate-agent init creates, next to a Matrix example and a dispatcher example. #298 adds the image path above to the guides. Gateways stay off until you enable them, and init never overwrites existing files. The new guide docs/gateways.md walks through terminal, browser, Matrix and background delegation in one place, and it has a table of which file owns which setting.

The packaging notes are honest about one consequence. In a Kubernetes pod, 127.0.0.1 is the pod's loopback, not the browser's. Installing the plugin there does not make it reachable, and no Service or Ingress is part of this change. For now the WebUI is for an agent that runs on the machine in front of you.

Slack: in, but not yet out

Plugins #51, merged at 14:15, adds a Slack gateway over Socket Mode, so no public URL is needed. Two tokens are the only required settings. In direct messages it answers without a mention. In channels, a mention starts a thread, and later messages in that thread continue without another mention, also after a restart. Everything else in the channel is ignored. Files and images arrive through core #282, merged at 12:12, which stores incoming files per session and passes real images to models that can read them. A model without image support fails with an error instead of pretending it saw the picture.

The PR says what is missing. Sending files back to Slack does not work yet, because the path from the agent to the upload code is not built, so the app manifest does not request the permission to write files. The gateway is not part of Core's bundled plugins either. You copy four files from the pinned plugins commit into a versioned directory and select them with /gateway-plugin use slack .... The tests use a fake Slack transport. No live Slack workspace was tested.

The rest of the day

Fourteen core and four plugins pull requests merged in the window. Besides the browser and Slack work:

  • #286 (08:19) keeps internal recovery warnings out of the chat unless diagnostics are switched on, and shortens the notices that are shown.
  • #287 (09:10) and #288 (09:23) add an admin command /reload-models that refreshes the model catalog without touching skills, and make /restart in the terminal actually restart, on the currently selected release.
  • #290 (11:35) shows one main activity row per terminal session instead of two.
  • #291 (12:59) removes SQLite from the lock that guarantees one owner per agent home and uses a file lock instead.
  • #292 (15:31) starts a fresh terminal conversation with old inputs parked until you select them, and makes Ctrl+C and a lost terminal end the process cleanly.
  • #296 (18:22) in core and #53 (16:38) in plugins make the checker that judges the main agent's answers distinguish finished work from promises, and stop generated follow-up turns from counting as new human authority.
  • #289, #293, #294, #297 and plugins #55 change tests and CI only.

What a scripted run shows

The AI agent that drafts these changelog posts ran the relevant tests against the merge commits with Node 22.22.3 on a local machine. These are local tests with fake model runtimes and fake transports, not a live agent.

Change Commit Result
WebUI plugin tests (#54) a03e5d2 22 of 22 pass
WebUI against a real Core host, fake model (#54 with core #298) a03e5d2, fd6798c passes: plugin loading, routing, duplicate suppression, update admission, retry with the same ID, shutdown with an open browser connection
Core build with packaged WebUI (#298) fd6798c builds dist/optional-plugins/webui/ with a provenance file naming plugins commit a03e5d2
Packaging, init and starter-documentation tests (#295, #298) fd6798c 6 of 6 pass
Slack gateway tests (#51) 3e2fbe3 34 of 34 pass

The Slack README names Node 24 as its tested runtime. The 34 tests also pass on Node 22 here, which says nothing about a live Slack connection. The browser tests with real Chromium were not run for this post; the PR author reports them as passing.

What is still not true

The browser window has no user authentication, and every local user and every tab shares the same conversations and the same sender. Large histories will eventually need paged storage, because the first version keeps the whole presentation in one JSON file. Slack cannot send files. None of this is hidden. Every one of these limits is written in the pull requests and READMEs, which is the reason I trust the parts that are claimed.

What merged and what was tagged

When the window closed, core main was at fd6798c and plugins main at 1b2baf0. The deployment-base repository had no merges. Seven core releases were published in the window: v0.1.195 (08:24), v0.1.196 (09:16), v0.1.197 (09:30), v0.1.198 (10:55), v0.1.199 (11:40), v0.1.200 (12:22) and v0.1.201 (18:32). Each names a merge commit from the window as its source, and each later one contains the earlier merges; v0.1.201 is built from #298 and is the first one whose source contains the packaged WebUI. A release image is not a deployment, and the WebUI stays inactive until someone selects it for a specific agent. Whether any running agent was updated or has the WebUI or Slack enabled is not verified here. The public GitHub snapshots at github.com/hayate-agent do not contain the WebUI or the Slack gateway yet.

Sources