Skip to main content

Module gopher

Module gopher 

Source
Expand description

Gopher (RFC 1436) client.

§One connection per request, deliberately

Gopher has no session. The client sends one selector line, the server answers and closes — the close is the framing, because the reply carries no length and no content type. So there is no persistent socket for this client to hold, and “browsing” means opening a new connection per item.

That is exactly what happens here: GopherClient::connect_with_llm_actions opens one connection to prove the hole is reachable and to obtain the local address the Client trait must return, then closes it. Every fetch afterwards — the model’s, or one injected from the dashboard — opens its own connection to whatever host and port the action names, which is what lets a menu item point at a different server and still be followable.

Nothing in a Gopher reply says which it is. The item type in the request is the only signal, so send_gopher_request carries an item_type and this module reads the reply according to it: 1 and 7 are parsed as menus, everything else as a document. There is exactly one exception, and it is not a heuristic about content: a reply whose first line is a type-3 item is reported as an error whatever was asked for, because type 3 is the only error channel the protocol has and a server uses it to refuse any request.

When the guess is wrong the reply is not thrown away. A menu request whose reply contains no parseable menu line at all is reported as gopher_document_received with requested_item_type still set, so the model can see the mismatch; a menu request whose reply is mostly menu lines keeps the odd ones in malformed_lines. The reverse is not attempted — a document may legitimately be full of tabs, so a document reply is never re-read as a menu.

§The follow-up chain

Browsing is iterative by nature: fetch a menu, pick an item, fetch that, repeat. A follow-up fetch that raised no event would give the model exactly one turn and then go deaf, which is the elasticsearch/http2 defect the root CLAUDE.md catalogues. So every fetch raises its event and asks the model again, and the recursion is bounded by MAX_FOLLOWUP_DEPTH rather than by silence. The recursive call is boxed (an async fn that awaits itself has an infinitely-sized future) and the boxed type names + Send explicitly, because it is awaited inside a tokio::spawn.

Re-exports§

pub use actions::GopherClientProtocol;

Modules§

actions
Gopher (RFC 1436) client protocol actions.

Structs§

GopherClient
Gopher client that browses a gopher hole under LLM control.
GopherMenuItem
One parsed menu line.

Enums§

GopherReply
What a reply turned out to be.

Constants§

DEFAULT_GOPHER_PORT
The default port when a remote address names no port. RFC 1436 §2.
MAX_FOLLOWUP_DEPTH
How many model round-trips one automatic browse may chain.
MAX_MENU_ITEMS
Menu items kept from one reply. A menu longer than this is a server misbehaving.
MAX_REPLY_BYTES
Largest reply this client will hold. Gopher sends no length, so without a cap any server can make the client allocate until it dies.

Functions§

item_type_name
What an RFC 1436 item type character means, for the model’s benefit.
parse_gopher_reply
Read a reply according to the item type that was asked for.
request_line
Build the request line. A query makes it a type-7 search.