HomeDocsArchitecture › 5. `rysh-shared`

5. rysh-shared — The Shared Core

rysh-shared (module github.com/rysh-ai/rysh-shared) is the reusable heart of Rysh, imported by both rysh-cli and rysh-server. It contains the message protocol, the agentic engine, the provider abstraction, the host-agnostic tools, and the NATS↔actor bridge. Most of its content is documented in depth in 3. Messaging & Protocol and 4. Agentic Engine; this chapter is the package map and consumption model.


5.1 Package map

graph TD
    subgraph msg["msg/"]
        conv["conversation.go
unified ConversationMessage"] codec["codec.go
NATSEnvelope + CodecRegistry"] topics["topics.go
subject scheme"] pub["publisher.go
NATSPublisher"] agmsg["agentic_messages.go"] agcodec["agentic_codec.go
RegisterAgenticCodecs"] memmsg["memory.go"] brmsg["browser_messages.go"] cbmsg["chatbot_messages.go"] legacy["messages.go (legacy)"] log["msglog.go"] end subgraph agentic["agentic/"] mgr["llm_prompt_execution_actor.go"] orch["orchestrator.go"] appr["approval.go"] cfg["config.go (AgentConfig)"] suba["sub_agent.go"] perm["permissions.go"] rt["read_tracker.go (stale-edit)"] osh["output_shape.go"] end subgraph provider["provider/"] prov["agentic_provider.go
Claude + Static"] strm["streaming.go (SSE)"] retry["retry.go (backoff/fallback)"] think["thinking.go"] mdef["model_defaults.go"] end subgraph tools["tools/"] reg["registry.go (framework)"] ws["web_search.go"] wf["web_fetch.go"] ba["browser_action.go"] pc["page_context.go"] lt["list_tools.go"] end bridge["bridge/bridge.go
NATSBridge"] agentic --> provider agentic --> tools agentic --> msg bridge --> msg
Package Contents Reference
msg/ unified conversation model, JSON envelope + codec, subject scheme, publisher, agentic/memory/browser/chatbot message catalogs, message logger §3
agentic/ LLMPromptExecutionActor, OrchestratorActor, approval flow, AgentConfig, real sub-agents, permission policy, stale-edit read-tracker, tool-output shaping §4.1–4.3
provider/ Provider/AgenticProvider/StreamingProvider interfaces, ClaudeAgenticProvider (streaming, retry/backoff, model fallback, thinking, per-model token defaults), StaticAgenticProvider §4.4
tools/ ToolExecutor/ToolRegistry framework (incl. Register/Unregister/Clone) + 5 host-agnostic tools §4.5
bridge/ NATSBridge — delivers decoded NATS messages into actor mailboxes §4.6

5.2 AgentConfig (agentic/config.go)

The host-agnostic configuration both CLI and server populate to build the engine:

type AgentConfig struct {
    APIKey        string // Anthropic API key
    APIURL        string // default "https://api.anthropic.com" if empty
    DefaultModel  string // default "claude-sonnet-4-20250514" if empty
    MaxTokens     int    // when ≤0, provider uses DefaultMaxTokensForModel (Sonnet 8192, Opus/Haiku 4096)
    SystemPrompt  string // text or file path
    BraveAPIKey   string // enables web_search
    MaxIterations int    // default 20 if zero (CLI raises per-pane to 50)
}

AgentConfig only documents the defaults; they are actually applied in NewClaudeAgenticProvider (provider) and NewLLMPromptExecutionActor (agentic).

Note — no context-token limit here. AgentConfig has no field for the context-window limit that drives compaction (§4.2). That limit defaults to DefaultContextTokenLimit = 160000 and is set separately on the manager actor via SetContextTokenLimit, which threads it into each spawned orchestrator.


5.3 The host-agnostic tools (detail)

These five tools are the only ones in rysh-shared because they don't touch the host filesystem/shell — making them safe to share between the local CLI and the cloud server.

browser_action (tools/browser_action.go)

Controls the user's Chrome via the Rysh extension over NATS. Input {action, params}. 23 actions: navigate, click, type, select, check, scroll, hover, wait, screenshot, get_text, get_html, get_elements, get_value, get_tabs, switch_tab, new_tab, close_tab, back, forward, reload, execute_js, press_key, drag_drop. Selector formats: CSS, xpath:, text:, aria:, role:, testid:. RequiresApproval is true only for execute_js.

Execution (browser_action.go:185): subscribes (SubscribeSync) to the response subject before publishing the request through a BrowserActionPublisher interface (decoupled to avoid a cross-module import of the publisher), then polls in 500ms quanta up to 60s for a response whose RequestID matches:

timeout := 60 * time.Second
deadline := time.After(timeout)
for {
    select {
    case <-ctx.Done():  return &ToolOutput{Error: "browser action cancelled"}, nil
    case <-deadline:    return &ToolOutput{Error: "... timed out after 60s — the Chrome extension may not be connected"}, nil
    default:
        natsMsg, err := sub.NextMsg(500 * time.Millisecond)
        if err != nil { continue }
        resp, err := decodeBrowserActionResponse(natsMsg.Data)
        if err != nil || resp.RequestID != requestID { continue } // skip non-matching
        // success → Content = string(resp.Result); screenshot → Metadata["screenshot_base64"]
    }
}

Gotcha: the tool decodes a local natsEnvelope whose Payload is []byte (base64-decoded), distinct from msg.NATSEnvelope's json.RawMessage — because the Chrome extension base64-encodes its payloads. decodeBrowserActionResponse rejects any tag ≠ "MsgBrowserActionResponse".

type BrowserActionPublisher interface {
    SendBrowserAction(subject string, requestID, action string, params json.RawMessage) error
}

page_context (tools/page_context.go)

Returns the current tab's {url, title, selected_text, body_text}, which the extension injects into a shared sync.Map keyed by pane ID. Single-use — a LoadAndDelete clears it after read, so the agent gets fresh context each time the user submits:

func (t *PageContextTool) Execute(_ context.Context, _ json.RawMessage) (*ToolOutput, error) {
    val, ok := t.store.LoadAndDelete(t.paneID)   // atomic read+clear
    if !ok { return &ToolOutput{Content: "No page context available."}, nil }
    pc := val.(PageContext)
    data, _ := json.MarshalIndent(pc, "", "  ")
    return &ToolOutput{Content: string(data)}, nil
}

web_search / web_fetch / list_tools

  • web_search — Brave Search API (https://api.search.brave.com/res/v1/web/search, header X-Subscription-Token); input {query, count} (default 5, capped 20); 512KB body cap, 30s timeout; errors returned as ToolOutput.Error (not Go errors).
  • web_fetch — input {url, max_length, extract_mode∈text|html|raw}. Constants (web_fetch.go:14): webFetchMaxBody = 2MB, webFetchDefaultMax = 50000 chars, webFetchTimeout = 30s. UA rysh/1.0; accepts HTTP 200-399; text mode strips scripts/styles/comments/tags and decodes entities; truncation appends [Content truncated at N characters].
  • list_tools — formatted list ("- %s: %s\n" per spec) of all registered tools; holds a registry reference.

5.4 Consumption model

graph LR
    subgraph shared["rysh-shared"]
        Engine["agentic + provider + tools + msg + bridge"]
    end
    subgraph cli["rysh-cli"]
        Alias["type aliases
(shared_aliases.go)"] CLItools["+ bash, file_*, git_*, email_*,
ask_user, pane_*, todo, context_store..."] end subgraph srv["rysh-server"] Direct["direct imports"] SrvTools["+ page_context, browser_action
(or zero tools for chatbot)"] end Alias --> Engine CLItools --> Engine Direct --> Engine SrvTools --> Engine
  • CLI uses Go type aliases (type LLMPromptExecutionActor = sharedagentic.LLMPromptExecutionActor) and var-aliased constructors so its internal packages keep their own import paths while the implementation stays in rysh-shared. This also makes CLI-built messages structurally match the shared actors' type switches.
  • Server imports the shared packages directly and uses a browserActionPublisherAdapter to satisfy the tools.BrowserActionPublisher interface without crossing module boundaries with the concrete publisher.

5.5 Caveats

  • The wire format is JSON, not protobuf, despite the sibling rysh-proto module.
  • The provider supports both streaming (SSE, StreamingProvider.CompleteWithToolsStream) and non-streaming (CompleteWithTools) paths; the orchestrator prefers streaming when the provider implements StreamingProvider and transparently falls back otherwise. Transient API errors are retried inside the provider (5 attempts, exponential backoff, Retry-After-aware, optional model fallback). Anthropic prompt caching is on by default, the response usage is parsed into AgenticResponse.Usage (driving compaction + the context-% status line), and extended thinking is available via SetThinking (see §4.2 and §4.4).
  • rysh-shared deliberately contains no host-specific tools — the rich toolbox is in rysh-cli.