Compare commits
13 changed files with 61 additions and 71 deletions
|
|
@ -16,6 +16,6 @@ docs/persistence.md
|
|||
docs/terminal-rendering.md
|
||||
docs/tools/forge.md
|
||||
docs/tools/matrix.md
|
||||
docs/turn-loop/README.md
|
||||
docs/turn-loop.md
|
||||
docs/web-ui/agent.md
|
||||
docs/web-ui/dashboard.md
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ read them à la carte.
|
|||
- **"How does the per-agent terminal classify + colour
|
||||
events?"** → [`docs/terminal-rendering.md`](docs/terminal-rendering.md).
|
||||
- **"How does claude get its prompt and what tools does it have?"** →
|
||||
[`docs/turn-loop/`](docs/turn-loop/README.md) (index: the loop, binary shape,
|
||||
[`docs/turn-loop.md`](docs/turn-loop.md) (index: the loop, binary shape,
|
||||
turn outcomes; sub-pages:
|
||||
[`claude-invocation`](docs/turn-loop/claude-invocation.md),
|
||||
[`config`](docs/turn-loop/config.md), [`mcp`](docs/turn-loop/mcp.md)).
|
||||
|
|
@ -239,7 +239,7 @@ The docs below own the details — this section just points at them.
|
|||
- **NixOS / nspawn quirks** (bind mounts, conf flags, etc.): →
|
||||
[`docs/gotchas.md`](docs/gotchas.md).
|
||||
- **Turn loop, sentinels (rate-limit, auth-failed), context
|
||||
window:** → [`docs/turn-loop/`](docs/turn-loop/README.md).
|
||||
window:** → [`docs/turn-loop.md`](docs/turn-loop.md).
|
||||
- **Two-step spawn, approval flow, flake.lock validation:** →
|
||||
[`docs/approvals.md`](docs/approvals.md).
|
||||
- **Pre-push lint hook** (catches tracker-tag and comment-block failures
|
||||
|
|
|
|||
42
README.md
42
README.md
|
|
@ -46,7 +46,7 @@ Depth lives in [`docs/`](docs/) — pick the one matching your task:
|
|||
| reading path | doc |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| dashboard layout + endpoints | [`docs/web-ui.md`](docs/web-ui.md) ([shape](docs/web-ui/shape.md) · [dashboard](docs/web-ui/dashboard.md) · [agent](docs/web-ui/agent.md)) |
|
||||
| claude turn loop + MCP tools | [`docs/turn-loop/`](docs/turn-loop/README.md) |
|
||||
| claude turn loop + MCP tools | [`docs/turn-loop.md`](docs/turn-loop.md) |
|
||||
| config-edit + approval state machine | [`docs/approvals.md`](docs/approvals.md) |
|
||||
| what survives destroy / purge / restart | [`docs/persistence.md`](docs/persistence.md) |
|
||||
| naming, wire protocol, commit style | [`docs/conventions.md`](docs/conventions.md) |
|
||||
|
|
@ -103,6 +103,46 @@ older/newer channel hits breakage hyperhive's CI doesn't catch.
|
|||
For the full list of host and agent NixOS options see the
|
||||
**[options reference](https://hyperhive.darkest.space/options/)**.
|
||||
|
||||
## Agent configuration
|
||||
|
||||
Per-agent config lives in each agent's `agent.nix` (proposed, operator-approved, deployed as git commits). Key options:
|
||||
|
||||
### Multi-account Matrix support
|
||||
|
||||
`hyperhive.matrixAccounts` declares _additional_ matrix accounts for an agent, beyond the hive-internal one. Each entry is keyed by account name and specifies:
|
||||
|
||||
- `tokenFile` — path to the matrix bearer token (provisioned out-of-band)
|
||||
- `sessionDir` — path to the per-account matrix-sdk sqlite state (crypto keys + cache)
|
||||
- `homeserver` — optional homeserver URL (defaults to `hyperhive.matrix.url`)
|
||||
|
||||
**Example:**
|
||||
|
||||
```nix
|
||||
hyperhive.matrixAccounts = {
|
||||
external-public = {
|
||||
tokenFile = "/agents/myagent/state/matrix-token-external";
|
||||
sessionDir = "/agents/myagent/state/matrix-sdk-state-external";
|
||||
homeserver = "https://matrix.org";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The hive-internal account is always named `main` (synthesized from `hyperhive.matrix.url` + agent state). This option only declares _extras_; the `main` name is reserved and cannot be used here. Requires `hyperhive.matrix.enable = true`.
|
||||
|
||||
For more details see [`docs/matrix.md`](docs/matrix.md).
|
||||
|
||||
### GitHub account
|
||||
|
||||
Every agent gets a managed GitHub identity — a `gh` CLI wrapper and `git push` over HTTPS — on by default (`hyperhive.github.enable`), inert until a PAT is provisioned. There is nothing per-agent to declare: paste an operator-supplied personal access token into the agent's dashboard **credentials** tab (or `hivectl github set-token <agent> --token-stdin`) and it works. The `gh` wrapper + git credential helper read the token live (git auths as `x-access-token` + the PAT; github.com only), so a rotated PAT takes effect with no rebuild.
|
||||
|
||||
Turn the integration off for the whole hive with the host option:
|
||||
|
||||
```nix
|
||||
services.hyperhive.github.enable = false;
|
||||
```
|
||||
|
||||
The PAT value is never in nix — only the enable flag. For more details see [`docs/github.md`](docs/github.md).
|
||||
|
||||
## Operator CLI
|
||||
|
||||
`hivectl` is the operator-facing host CLI for ad-hoc administration that
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ Survives destroy/recreate, gone on `--purge`.
|
|||
Empty marker file. Its presence parks the agent's turn loop: the
|
||||
harness keeps serving its web UI and MCP daemons but drives no turns,
|
||||
and inbox messages queue unacked until it's removed (see
|
||||
[turn loop](turn-loop/README.md#the-loop)).
|
||||
[turn loop](turn-loop.md#the-loop)).
|
||||
|
||||
Unusually, it's read and written from **both** sides of the harness
|
||||
bind-mount, and that's the whole design: the harness stats it
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
# Tools
|
||||
|
||||
`hivectl` is *your* tool — the operator's own host CLI. Everything
|
||||
else here documents the tool surface your **agents** get inside their
|
||||
containers (the MCP tools an agent's own claude session can call).
|
||||
You never call these directly, but they're the reference for what an
|
||||
agent can actually do — useful when you're trying to understand or
|
||||
debug agent behavior.
|
||||
|
||||
## For the operator
|
||||
|
||||
- **[hivectl](hivectl.md)** — the curated guide: provisioning forge
|
||||
and matrix accounts, gateway htpasswd management, container
|
||||
lifecycle shortcuts, interactive agent shell access.
|
||||
- **[hivectl-cli](hivectl-cli.md)** — the exhaustive, auto-generated
|
||||
flag-by-flag reference, kept in lockstep with the binary by CI.
|
||||
|
||||
## What your agents can do
|
||||
|
||||
- **[bash](bash.md)** — background shell execution (`mcp__bash__*`),
|
||||
available on every agent unconditionally.
|
||||
- **[forge](forge.md)** — the `hive-forge` Forgejo CLI every agent has
|
||||
for issues, PRs, and comments. Not an MCP tool — a binary agents
|
||||
shell out to instead of ad-hoc curl.
|
||||
- **[lifecycle](lifecycle.md)** — kill/start/restart/update for an
|
||||
agent's own direct children, plus the approval-gated config-change
|
||||
tools.
|
||||
- **[matrix](matrix.md)** — the matrix MCP tool surface
|
||||
(`mcp__matrix__*`) for agents with a matrix account, multiple
|
||||
accounts per agent, and declaring extra MCP servers generally.
|
||||
- **[scheduling](scheduling.md)** — scheduled prompts (operator
|
||||
approval required) and the diagnostics tools (`get_logs`,
|
||||
`get_host_journal`).
|
||||
|
|
@ -67,23 +67,6 @@ room you haven't read yet.
|
|||
|
||||
- `mark_read(room, event_id)` — advance the read receipt
|
||||
|
||||
## Multiple accounts
|
||||
|
||||
`hyperhive.matrixAccounts` (declared in `agent.nix`) gives an agent
|
||||
*additional* matrix identities beyond the hive-internal one — e.g. an
|
||||
external-facing account alongside the internal one. Each entry is
|
||||
keyed by account name and specifies `tokenFile` (bearer token,
|
||||
provisioned out-of-band; basename must start with `matrix-token`),
|
||||
`sessionDir` (per-account matrix-sdk sqlite state — crypto keys +
|
||||
cache), and an optional `homeserver` (defaults to
|
||||
`hyperhive.matrix.url`). The hive-internal account is always named
|
||||
`main`, synthesized from `hyperhive.matrix.url` + agent state — this
|
||||
option only declares extras, and `main` is a reserved key here.
|
||||
Requires `hyperhive.matrix.enable = true`.
|
||||
|
||||
Every matrix tool above takes an optional `account` parameter (a name
|
||||
from this map) to act as that identity instead of the primary one.
|
||||
|
||||
## Architecture
|
||||
|
||||
`hive-matrix-daemon` is a single long-running process (one per agent
|
||||
|
|
|
|||
|
|
@ -89,6 +89,6 @@ lifecycle events, or another container's boot log.
|
|||
## See also
|
||||
|
||||
- `remind` (no-approval self-wake path) — documented in
|
||||
[`docs/turn-loop/`](../turn-loop/README.md).
|
||||
[`docs/turn-loop.md`](../turn-loop.md).
|
||||
- [`docs/approvals.md`](../approvals.md) — approval flow for
|
||||
`request_schedule_prompt`.
|
||||
|
|
|
|||
171
docs/turn-loop.md
Normal file
171
docs/turn-loop.md
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
# Turn loop + MCP
|
||||
|
||||
How the harness wakes up, what it asks claude to do, and what tools
|
||||
claude has access to in return.
|
||||
|
||||
## The loop
|
||||
|
||||
Each agent harness (`hive-agent` — one serve-loop binary for all
|
||||
agents) runs:
|
||||
|
||||
0. Check the pause marker (`<harness>/paused`). While it exists the
|
||||
loop does nothing but re-stat it every 5 s — no broker poll, no
|
||||
claude process. Because step 1 is never reached, messages stay
|
||||
queued and unacked, so a resume drains the backlog instead of
|
||||
losing it; reminders and todo wakes buffer in their channels. Set
|
||||
it with `hivectl agent <name> pause` or the dashboard toggle; see
|
||||
[persistence](persistence.md#-harnesspaused-per-agent).
|
||||
1. Long-poll `Recv` on its socket. The host-side broker
|
||||
(`broker.rs::recv_blocking_batch`) returns immediately if there's
|
||||
a pending message, otherwise waits up to 30 s for a broker `Sent`
|
||||
event for this recipient.
|
||||
2. Pop one message. Peek the remaining inbox depth with `Status`.
|
||||
3. Emit `LiveEvent::TurnStart { from, body, unread }` onto the SSE
|
||||
bus.
|
||||
4. Spawn claude (one process per turn) and pipe the wake prompt
|
||||
over stdin.
|
||||
5. Stream stdout (JSON lines) into the bus as
|
||||
`LiveEvent::Stream(value)`. Pump stderr as `Note`.
|
||||
6. Wait for claude to exit and classify the turn's outcome from the
|
||||
stream + exit — success, compaction, rate-limit, auth-failure, or
|
||||
hard failure. The outcome drives the post-turn action (see
|
||||
[Turn outcomes](#turn-outcomes)); compaction is handled inside the
|
||||
session (see
|
||||
[Compaction](turn-loop/claude-invocation.md#compaction)). Rate-limit
|
||||
and auth-failure detection is described [below](#failure-detection-and-login).
|
||||
7. Emit `LiveEvent::TurnEnd { ok, note }`. Sleep `poll_ms` to avoid
|
||||
tight loops on transient failures.
|
||||
|
||||
### Failure detection and login
|
||||
|
||||
- **Rate limit** — a `429` / `rate_limit` marker on stderr, or a parsed
|
||||
`{"type":"error"}` rate-limit event on stdout (conversation-text
|
||||
mentions don't count), sets the `rate_limited` sentinel, parks for
|
||||
`HIVE_RATE_LIMIT_SLEEP_SECS` (default 300), then retries. The UI shows
|
||||
a `⊘ rate limited` badge while parked.
|
||||
- **Auth failure (401)** — `drive_turn` retries once (transient
|
||||
token-refresh races clear on retry); a second `AuthFailed` writes
|
||||
`{state_dir}/hyperhive-needs-login`, requeues the message, and parks in
|
||||
`wait_for_login` — the same path as a cold boot with no session. The
|
||||
operator re-auths via the per-agent web UI; the queued message then
|
||||
drives the next turn.
|
||||
- **Login detection** — both boot (`login::has_session`, Online vs
|
||||
NeedsLogin) and `wait_for_login`'s resume check key off the credential
|
||||
files in `login::CRED_FILE_NAMES` (the set `/logout` deletes).
|
||||
`wait_for_login` resumes only when that set changes (a new file or a
|
||||
newer mtime), so stale credentials on disk at the 401 don't trigger an
|
||||
instant false-resume, and leftover session-history files don't read as a
|
||||
live session after a logout + container recreate.
|
||||
|
||||
## Harness binary shape
|
||||
|
||||
Two sibling binaries out of the one `hive-ag3nt` crate, all
|
||||
role-agnostic. (The earlier split into `hive-ag3nt` + `hive-m1nd`
|
||||
was collapsed because the privilege boundary lives server-side at
|
||||
the broker socket (`/run/hive/mcp.sock`): `ManagerRequest` calls are
|
||||
refused by the standard agent socket regardless of who sends them.)
|
||||
|
||||
- `hive-agent` — long-running harness loop (the inbox poll +
|
||||
claude-pump + ack/requeue cycle described above).
|
||||
- `hive-agent-mcp` — MCP server for the built-in `hyperhive` surface.
|
||||
Run with `--http <addr>` as a persistent streamable-HTTP daemon (the
|
||||
`hive-mcp-http` systemd unit, on `hyperhive.mcp.httpPort`, default
|
||||
8790); claude connects to its URL via `--mcp-config`. HTTP is the sole
|
||||
transport — no per-turn stdio child (eliminates the re-registration
|
||||
race).
|
||||
|
||||
### `Surface` trait + zero-sized type tags
|
||||
|
||||
`AgentRequest` / `AgentResponse` (= `ManagerRequest` / `ManagerResponse` —
|
||||
type aliases) are the wire types. There is one role: agent.
|
||||
`bin/hive-agent.rs` factors the turn loop through a `Surface` trait
|
||||
with one zero-sized impl (`AgentSurface`) wrapping:
|
||||
|
||||
- One async method per wire op: `ack_turn`, `requeue_inflight`,
|
||||
`inbox_unread`, `post_turn_counts`, `send_to_parent`, `recv_next`.
|
||||
|
||||
`main()` calls `serve_main::<AgentSurface>` for all roles. The turn
|
||||
loop (`serve_loop` / `handle_turn`) has no per-role branches.
|
||||
|
||||
### Boot wiring
|
||||
|
||||
`serve_main` reads `HIVE_PORT` (default `DEFAULT_WEB_PORT`) +
|
||||
`HIVE_LABEL` (default `"hive"` for standalone runs; the meta
|
||||
flake sets it unconditionally for any container-deployed agent;
|
||||
see `docs/conventions.md::Hive identity` for the env stack),
|
||||
opens turn-stats sqlite, prepares the on-boot files (see
|
||||
[claude-invocation](turn-loop/claude-invocation.md#on-boot-files)),
|
||||
installs claude plugins, spawns `web_ui::serve` + `vacuum::run`,
|
||||
and either drops into `serve_loop` directly (`Online`) or parks on
|
||||
the login flow first (`NeedsLogin`). (The forge notification poller
|
||||
used to be spawned here too; it is its own process now —
|
||||
`hive-forge-notify`, see [`forge.md`](forge.md).)
|
||||
|
||||
`spawn_todo_socket` opens the todos store and, alongside
|
||||
`todo_server::run` (the socket the out-of-process producers dial),
|
||||
spawns `disk_watch::run` — an *in-process* todo producer, so it shares
|
||||
the store + wake `Notify` directly rather than dialling its own socket.
|
||||
It raises a keyed `disk` todo when the filesystem backing the agent's
|
||||
state gets tight, naming the agent's own biggest directories; the
|
||||
summary is bucketed and carries no raw byte counts, so an unchanged
|
||||
situation re-upserts as `changed == false` and never re-wakes.
|
||||
|
||||
Plugin install failures are not fatal: each entry comes back as a
|
||||
human-readable failure string that gets routed via
|
||||
`Surface::send_to_parent` to the agent's topology parent (the
|
||||
broker resolves `<parent>` per `topology::parent_of`; root agents
|
||||
and the manager fall through to operator).
|
||||
|
||||
### Turn outcomes
|
||||
|
||||
`turn::TurnOutcome` (`Result<bool, TurnError>` — `Ok(compacted)` on success,
|
||||
else a `TurnError`) drives the post-claude branch:
|
||||
|
||||
| Outcome | Action |
|
||||
| --- | --- |
|
||||
| `Ok(_)` (`false` normal / `true` compacted) | `ack_turn` |
|
||||
| `Err(PromptTooLong)` | `drive_turn` archived the session (the lib already compacted + retried and it still overflowed); requeue inflight so the message redelivers into a fresh session that fits — no status park |
|
||||
| `Err(RateLimited)` | sleep `HIVE_RATE_LIMIT_SLEEP_SECS` (default 300), requeue inflight, status back to `online` |
|
||||
| `Err(AuthFailed)` | emit `needs_login_idle` sentinel, requeue inflight, park in `wait_for_login` |
|
||||
| `Err(SessionNotFound)` | resume + create self-heal both missed ("shouldn't happen"); requeue inflight so the next turn creates fresh — no status park, message not dropped |
|
||||
| `Err(ApiStall)` | idle watchdog killed claude after `HIVE_TURN_IDLE_SECS` (default 600) of output silence; sleep `HIVE_STALL_SLEEP_SECS` (default 60), requeue inflight, status back to `online` |
|
||||
| `Err(Failed(err))` | route `[system] \`<qualified-label>\` claude turn failed:\n<err>` to `<parent>` via `send_to_parent` |
|
||||
|
||||
`ApiStall` catches an Anthropic API stall — a multi-retry connection storm where
|
||||
the stream goes silent for minutes. The idle watchdog lives in `hive-claude`'s
|
||||
driver (`Config::idle_timeout`, enforced around `child.wait()`): the timer
|
||||
resets on every stdout line, so a large but still-streaming turn is never cut —
|
||||
only complete output silence for the window trips it. The harness sets the
|
||||
window from `HIVE_TURN_IDLE_SECS` (`0` disables) and maps the driver's
|
||||
`Error::IdleTimeout` onto `TurnError::ApiStall`.
|
||||
|
||||
After the outcome handler, the stats sink records a row. `handle_turn`
|
||||
reports the result to `serve_loop` via `TurnControl { auth_failed }` —
|
||||
on auth failure the loop parks in `wait_for_login`; otherwise it loops
|
||||
straight back to the idle wait (step 1). There is no same-turn
|
||||
self-continue mechanism: every multi-step continuation rides an
|
||||
external wake instead — a new inbox message, a `remind`, or an
|
||||
in-container todo wake (bash-task completion, forge notification,
|
||||
matrix activity). Ending the turn and letting one of
|
||||
those drive the next one is strictly better than parking in-process:
|
||||
it checkpoints the session and observes wakes that only reach the
|
||||
harness between turns.
|
||||
|
||||
## Sub-pages
|
||||
|
||||
The rest lives in three topic pages under [`turn-loop/`](turn-loop/):
|
||||
|
||||
- **[claude-invocation.md](turn-loop/claude-invocation.md)** — how the harness
|
||||
spawns `claude --print` each turn, the two-pronged compaction (reactive +
|
||||
proactive), and the on-boot files it materialises (`--mcp-config`,
|
||||
`--system-prompt-file`).
|
||||
- **[config.md](turn-loop/config.md)** — the optional per-agent knobs the meta
|
||||
flake wires in (reference docs, icon, passwordless sudo, dashboard links,
|
||||
custom static files, connectivity overrides, claude plugins, cargo message
|
||||
filtering).
|
||||
- **[mcp.md](turn-loop/mcp.md)** — the MCP tool surface claude sees: core tools,
|
||||
privileged tool groups, self-wake, authoritative state, the tool envelope,
|
||||
and the built-in tool whitelist.
|
||||
|
||||
Per-subsystem impl detail lives in each module's `//!` doc-comment; these pages
|
||||
describe present-state behaviour + wiring, not line-level mechanics.
|
||||
|
|
@ -116,7 +116,7 @@ window with two triggers baked into its `run`:
|
|||
*still* overflows, `run` surfaces `Error::PromptTooLong`; `drive_turn`
|
||||
then archives the session (session lifecycle stays hive-side) and the
|
||||
serve loop requeues the message so it redelivers into a fresh session
|
||||
(see [Turn outcomes](README.md#turn-outcomes) — the wake prompt itself is tiny, so
|
||||
(see [Turn outcomes](../turn-loop.md#turn-outcomes) — the wake prompt itself is tiny, so
|
||||
the overflow was the accumulated context the archive clears).
|
||||
- **Proactive** — a turn finishes cleanly but the last inference's context
|
||||
size crossed the policy watermark. While the session is still healthy it
|
||||
|
|
|
|||
|
|
@ -41,4 +41,4 @@ tempfile = "3"
|
|||
# The sibling MCP server is its own bin crate now (`hive-agent-mcp`).
|
||||
# Privilege boundary is enforced server-side at the socket (tool
|
||||
# groups / manager surface).
|
||||
# See `docs/turn-loop/README.md::Harness binary shape`.
|
||||
# See `docs/turn-loop.md::Harness binary shape`.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ understand or change: what happens between "a message lands in the
|
|||
inbox" and "claude produces a reply", how login/auth is bootstrapped,
|
||||
how the per-agent web UI is served, or how turn/event stats get
|
||||
recorded. Architecture detail lives in
|
||||
[`docs/turn-loop/`](../docs/turn-loop/README.md); this README is just the
|
||||
[`docs/turn-loop.md`](../docs/turn-loop.md); this README is just the
|
||||
map of the module tree.
|
||||
|
||||
## Shape
|
||||
|
|
@ -48,4 +48,4 @@ map of the module tree.
|
|||
|
||||
Sibling: **`hive-agent-mcp`** (the MCP server this loop points claude
|
||||
at every turn). Both are described together in
|
||||
[`docs/turn-loop/::Harness binary shape`](../docs/turn-loop/README.md).
|
||||
[`docs/turn-loop.md::Harness binary shape`](../docs/turn-loop.md).
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ impl LoginState {
|
|||
/// infinite-401 loop a bare-existence check would produce when stale
|
||||
/// credentials are already on disk. Mtime-snapshot resumption rationale
|
||||
/// and `DirSnapshot` two-axis design: see
|
||||
/// [`docs/turn-loop/::The loop`](../../docs/turn-loop/README.md).
|
||||
/// [`docs/turn-loop.md::The loop`](../../docs/turn-loop.md).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! generic and testable. Sibling: `hive-agent-mcp` (the MCP server this
|
||||
//! loop points claude at).
|
||||
//! Architecture lives in
|
||||
//! [`docs/turn-loop/::Harness binary shape`](../../../docs/turn-loop/README.md).
|
||||
//! [`docs/turn-loop.md::Harness binary shape`](../../../docs/turn-loop.md).
|
||||
//!
|
||||
//! Single bin crate: the module tree below (formerly this crate's `lib.rs`,
|
||||
//! before lib + bin were collapsed into one) plus the serve loop.
|
||||
|
|
@ -510,7 +510,7 @@ fn spawn_todo_socket(
|
|||
/// Boot — wires up the web UI, login state, stats, plugins, forge
|
||||
/// notifier, and either drops into `serve_loop` directly (`Online`) or
|
||||
/// parks on the login flow first (`NeedsLogin`). See
|
||||
/// `docs/turn-loop/README.md::Boot wiring`.
|
||||
/// `docs/turn-loop.md::Boot wiring`.
|
||||
async fn serve_main<S: Surface>(socket: &Path, poll_ms: u64) -> Result<()> {
|
||||
let port = std::env::var("HIVE_PORT")
|
||||
.ok()
|
||||
|
|
|
|||
Loading…
Reference in a new issue