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.
AgentConfighas no field for the context-window limit that drives compaction (§4.2). That limit defaults toDefaultContextTokenLimit = 160000and is set separately on the manager actor viaSetContextTokenLimit, 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
natsEnvelopewhosePayloadis[]byte(base64-decoded), distinct frommsg.NATSEnvelope'sjson.RawMessage— because the Chrome extension base64-encodes its payloads.decodeBrowserActionResponserejects 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, headerX-Subscription-Token); input{query, count}(default 5, capped 20); 512KB body cap, 30s timeout; errors returned asToolOutput.Error(not Go errors).web_fetch— input{url, max_length, extract_mode∈text|html|raw}. Constants (web_fetch.go:14):webFetchMaxBody = 2MB,webFetchDefaultMax = 50000chars,webFetchTimeout = 30s. UArysh/1.0; accepts HTTP 200-399;textmode 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 inrysh-shared. This also makes CLI-built messages structurally match the shared actors' type switches. - Server imports the shared packages directly and uses a
browserActionPublisherAdapterto satisfy thetools.BrowserActionPublisherinterface without crossing module boundaries with the concrete publisher.
5.5 Caveats
- The wire format is JSON, not protobuf, despite the sibling
rysh-protomodule. - The provider supports both streaming (SSE,
StreamingProvider.CompleteWithToolsStream) and non-streaming (CompleteWithTools) paths; the orchestrator prefers streaming when the provider implementsStreamingProviderand 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 responseusageis parsed intoAgenticResponse.Usage(driving compaction + the context-% status line), and extended thinking is available viaSetThinking(see §4.2 and §4.4). rysh-shareddeliberately contains no host-specific tools — the rich toolbox is inrysh-cli.