# WebSocket Model Calls and Scheduled Tool Calls: Octomind 0.67.0

> Octomind 0.67.0 sends octohub and ChatGPT model calls over a persistent WebSocket, schedules tool calls, queues specialist prompts and keeps agent cost history.

![Animated Octomind icon — purple pixel-art octopus](/octomind-icon-animated.gif)

An agent turn is rarely one model call. It is twenty, fifty, a hundred: read a file, call the model, run a command, call the model again. Until now every one of those calls through octohub opened its own HTTP request.

**Octomind 0.67.0 keeps one WebSocket open per session and sends every octohub and ChatGPT model call over it.** It also lets the agent schedule a tool call instead of a message, queue follow-up prompts for a specialist that is still working, and keep a cost history of every delegated run. There was no 0.65.x or 0.66.x post, so this one covers everything since [the 0.64.0 post](/blog/octomind-0-64-0-release). The open-source update is available today.

## Model calls over one WebSocket

A new key in `[model]` picks how requests travel:

```toml
[model]
# Persistent WebSocket for octohub/chatgpt requests ("http" opts out); other providers ignore it.
request_transport = "websocket"
```

- **`"websocket"` is the default** for `octohub:` and `chatgpt:` models. Every other provider ignores the key.
- **It is set per model.** Compression and supervisor models take it in their own tables, and override profiles inherit it from the profile they extend.
- **There is no separate URL.** The WebSocket address comes from the octohub base URL you already have, private hosts and ports included.
- **Nothing falls back silently.** If the WebSocket fails, the call fails with the error. Octomind does not quietly switch to HTTP, and it does not replay a request whose reply was lost, because the model may already have done that work and billed it.

What keeps the socket healthy (octolib 0.48.1):

- A session reuses one socket across its calls. Sockets are pooled, up to 64 at a time.
- A ping goes out every 30 seconds and the pong must come back within 10. An idle socket that died is replaced before the next request, without spending a retry.
- A connection that fails before the request is sent is retried as usual: nothing reached the model, so nothing can run twice. Retries also cover an explicit retryable status from the server.

Calls on one session's socket run one at a time, in order. Parallel sessions get their own sockets.

The `chatgpt` provider follows the same key, so ChatGPT-subscription sessions get the persistent socket too.

### Self-hosted octohub: update the hub first

WebSocket completions arrived in **octohub 0.15.0**, which ships alongside this release. An older hub has no WebSocket route, and every call fails with HTTP 404. Update the hub first. If you can't do that yet, opt out:

```toml
[model]
request_transport = "http"
```

Set the same line in `[compression.model]` and the supervisor model table if they name their own models. The hosted hub at `hub.octomind.run` already runs 0.15.0.

## Two timeouts, one for silence

Model timeouts now come in two kinds, set per model in the same tables:

| Key                       | Default   | What it limits                                                                     |
| ------------------------- | --------- | ---------------------------------------------------------------------------------- |
| `request_timeout_seconds` | `300`     | Total time for one request attempt, over HTTP or WebSocket. `0` turns it off.      |
| `stream_timeout_seconds`  | `0` (off) | How long a streamed response may go silent. It never stretches the total deadline. |

The split lets a slow reasoning model think for minutes inside the total deadline, while a stream that has truly stalled is cut on its own clock.

## The agent can schedule a tool call

The agent's `schedule` tool used to deliver a message later. It now takes either a `message` or a `tool_call`, exactly one:

```json
{
	"command": "add",
	"when": "in 5m",
	"tool_call": {
		"name": "tap",
		"arguments": { "action": "run", "role": "developer:general", "prompt": "Review auth" }
	}
}
```

When it fires, the tool runs directly, with no model turn in between. Its result, or its error, lands in the session inbox.

- **Permissions apply when it fires.** The tool must be enabled both when the agent schedules it and when it runs, and guardrails check the call at fire time.
- **The working directory is the one from scheduling time.**
- **A repeating call skips a tick while the previous one is still busy**, a specialist it launched included, so runs don't pile up.
- **`remove` stops future firings only.** To cancel a run a schedule already launched, use `tap(action="stop", session="<run-id>")`.
- **`edit` replaces the whole payload.** Arguments are not merged.

Schedules belong to the session; they are not system cron. For jobs that run while you are away, see [routines](/blog/routines-scheduled-agents). The `/schedule` command you type stays message-only. The [MCP tools guide](/docs/usage/07-mcp-tools) has the full reference.

## Send a specialist more work while it runs

A specialist launched with `tap` used to take one prompt. A second `run` on a busy specialist failed. Now the agent can queue more:

```json
{ "action": "send", "session": "RUN_ID", "prompt": "Also cover the logout path" }
```

`send` returns at once; the reply only confirms the prompt is queued. Queued prompts run in order in the same child session, so the specialist keeps everything it already read. A run holds up to 32 queued prompts. A finished run is continued with `run`, as before, and workflows don't take prompts.

## See what every specialist cost

Delegated runs now leave a history that survives checkpoints, compaction and `--resume`:

- **`/status agents`** lists saved runs: completed, failed, cancelled, and unattached (a saved run with no live process behind it).
- **`/info`** splits cost into `$X total · $Y main`, so you can see what the main model spent versus the specialists. Sessions saved before 0.67.0 show `unknown` for the main part.
- **Runs without usage data** are counted as `N unpriced` next to the known cost, instead of being shown as zero.

Costs are printed in yellow since 0.66.4, so they stand out in a long session.

## Your turn goes first

- **A message you type jumps ahead of background work (0.67.0).** Monitor output and other inbox items wait until your turn is handled.
- **WebSocket clients wait instead of bouncing (0.67.0).** A frame that arrives while a background turn runs now queues behind it. Two foreground requests racing each other still get `Session '<id>' is busy processing another request. Please wait.`

## Faster read-only tools

- **Consecutive read-only tool calls run together (0.66.2).** When the model asks for five file reads in a row, they run at the same time. A call that may write acts as a barrier: everything before it finishes first.
- **Writes run one at a time, in the order the model emitted them (0.66.0).** A tool counts as read-only only when it says so. MCP servers declare it with the `readOnlyHint` annotation, which Octomind now keeps. A tool with no hint is treated as one that writes.

## Other changes since 0.64.0

- **File lines carry a content hash (0.66.0).** `@file` expansions and `<context>` blocks render lines as `12:8b|text` instead of `12: text`: the line number, a two-character hash of the line's content, and the text.
- **The installer verifies checksums (0.65.1).** Every release archive has a `.sha256` file, and `install.sh` aborts on a missing or mismatched checksum. Thanks to Oscar-Williams for the contribution.
- **Resume brings back your model and role (0.66.4).** A `/model` switch is now saved to the session; your config file stays unchanged.
- **Retries say which call they are for (0.66.4).** The WebSocket server's `retry` frame now names the purpose (main, supervisor or compression), provider, model, attempt and delay.
- **WebSocket clients get the command list (0.65.0).** Bind with the `commands_v1` capability and the acknowledgement lists the session commands the client can offer. ACP clients get the routed commands too.
- **Loops are caught in cycles, not only repeats (0.66.1).** `max_identical_rounds` (default 10) now also stops a turn that cycles through the same rounds, and the stall count resets when the working tree changes.
- **The verify-gate can flag what it couldn't settle (0.66.3).** New `[supervisor.gate]` keys: `unsettled_advisory = true` and `unsettled_confidence_threshold = 0.8`.
- **No more internal notes in answers (0.66.2).** Supervisor self-reports no longer leak into what you read.
- **Hindsight scoring runs in a shared local worker (0.67.0)**, in its own process group.
- **Smaller fixes:** `untap` completes installed tap names in bash, zsh and fish (0.65.0); empty MCP server names are rejected (0.65.0); session times show in local time (0.65.0); MCP servers no longer reconnect after a session closes (0.67.0).

## Before you update

- **Your config migrates to version 26 on first start.** Octomind adds `request_transport = "websocket"` and `stream_timeout_seconds = 0` where they're missing, so octohub and ChatGPT calls switch to WebSocket. A config at version 26 won't load in an older Octomind.
- **A self-hosted octohub older than 0.15.0 needs `request_transport = "http"`** until you update it, or every call fails with 404.
- **The `@file` line format changed** to `N:hh|text`. A script that parses the old `N: text` form needs updating.
- **No keys were removed.**

## Update to Octomind 0.67.0

On macOS and Linux:

```bash
curl -fsSL https://octomind.run/install.sh | bash -s -- --version 0.67.0
octomind --version
```

On Windows, in PowerShell:

```powershell
$env:OCTOMIND_VERSION = "0.67.0"
irm https://raw.githubusercontent.com/muvon/octomind/master/install.ps1 | iex
```

Or run `scoop update octomind`. See the [installation guide](/docs/usage/01-installation) for other methods, the [full changelog](https://github.com/muvon/octomind/blob/0.67.0/CHANGELOG.md), and [the 0.64.0 notes](/blog/octomind-0-64-0-release) if you're coming from 0.63 or earlier.

Then try it: start a long task on an `octohub:` model, run `/info` at the end, and see how the cost splits between the main model and the specialists it called.

— DK
