The github.com/goppydae/sharur/sdk package lets you embed a sharur agent in any Go program.
import"github.com/goppydae/sharur/sdk"
See the sub-pages for a quickstart, custom tool implementations, the EventBus API, and in-process extensions.
Subsections of SDK
Quickstart
Import github.com/goppydae/sharur/sdk to embed an agent in any Go program.
import"github.com/goppydae/sharur/sdk"ag,err:=sdk.NewAgent(sdk.Config{Provider:"ollama",Model:"llama3.2",Tools:sdk.DefaultTools(),})iferr!=nil{panic(err)}ag.Subscribe(func(esdk.Event){ife.Type==sdk.EventTextDelta{fmt.Print(e.Content)}})ag.Prompt(context.Background(),"List the Go files in this directory")<-ag.Idle()
Config Fields
typeConfigstruct{Providerstring// "ollama", "openai", "anthropic", "llamacpp", "google"Modelstring// model name or "provider/model"APIKeystring// optional; env vars take priorityBaseURLstring// optional provider endpoint overrideTools[]sdk.Tool// sdk.DefaultTools() or custom listExtensions[]sdk.ExtensionSystemPromptstringThinkingLevelsdk.ThinkingLevelSessionDirstring// where to persist sessionsDryRunbool}
Core API
Call
Description
sdk.NewAgent(cfg)
Create and initialize an agent
ag.Subscribe(fn)
Register an event handler; called for every emitted event
ag.Prompt(ctx, text)
Send a user message and start the agent loop
ag.Idle()
Returns a channel that closes when the agent reaches Idle state
ag.Steer(ctx, text)
Inject a steering message into the running turn
ag.FollowUp(ctx, text)
Queue a message to process after the current turn
ag.Abort(ctx)
Cancel the current running turn
ag.SetExtensions(exts)
Replace the extension list (takes effect on next prompt)
Event Types
Subscribe to events by checking e.Type:
Event type
Payload field
Description
EventAgentStart
—
Agent loop started
EventAgentEnd
—
Agent loop completed
EventTurnStart
—
LLM turn started
EventTurnEnd
—
LLM turn completed
EventTextDelta
e.Content
Incremental response text
EventThinkingDelta
e.Content
Incremental thinking text
EventToolCall
e.ToolCall
Tool invocation started
EventToolDelta
e.Content
Streaming tool output
EventToolOutput
e.ToolOutput
Final tool result
Minimal Example (no tools, no session)
ag,_:=sdk.NewAgent(sdk.Config{Provider:"anthropic",Model:"claude-sonnet-4-6",APIKey:os.Getenv("ANTHROPIC_API_KEY"),})varbufstrings.Builderag.Subscribe(func(esdk.Event){ife.Type==sdk.EventTextDelta{buf.WriteString(e.Content)}})ag.Prompt(context.Background(),"What is 2+2?")<-ag.Idle()fmt.Println(buf.String())
Pass sdk.DefaultTools() in sdk.Config.Tools to get the full set of built-in tools:
Tool
Description
read
Read file contents with offset/limit support
write
Create or overwrite files
edit
Search-and-replace edits within files
bash
Execute shell commands
grep
Search file contents via regex
ls
List directory contents
find
Locate files using glob patterns
bash, write, and edit are destructive. In --dry-run mode they preview what they would do without executing.
Tool Interface
Implement sdk.Tool to create a custom tool:
typeToolinterface{Name()stringDescription()stringSchema()json.RawMessage// JSON Schema for the input parametersExecute(ctxcontext.Context,argsjson.RawMessage,updateToolUpdate)(*ToolResult,error)IsReadOnly()bool// if true, tool is allowed in dry-run mode}
ToolUpdate is a callback for streaming partial output while the tool runs:
typeToolUpdatefunc(contentstring)
Example: Custom Tool
typeCountLinesToolstruct{}func(t*CountLinesTool)Name()string{return"count_lines"}func(t*CountLinesTool)Description()string{return"Count the number of lines in a file"}func(t*CountLinesTool)Schema()json.RawMessage{returnjson.RawMessage(`{
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path to count lines in"}
},
"required": ["path"]
}`)}func(t*CountLinesTool)IsReadOnly()bool{returntrue}func(t*CountLinesTool)Execute(ctxcontext.Context,argsjson.RawMessage,updatesdk.ToolUpdate)(*sdk.ToolResult,error){varinputstruct{Pathstring`json:"path"`}iferr:=json.Unmarshal(args,&input);err!=nil{returnnil,err}data,err:=os.ReadFile(input.Path)iferr!=nil{return&sdk.ToolResult{Content:err.Error(),IsError:true},nil}n:=strings.Count(string(data),"\n")+1return&sdk.ToolResult{Content:fmt.Sprintf("%d lines",n)},nil}
Multiple subscribers are allowed. Each runs in its own goroutine. The EventBus is non-blocking — Publish enqueues to a 4096-item buffered channel per subscriber and returns immediately, so slow subscribers drop events rather than stalling the agent loop.
ag.Idle() returns a channel that closes when the agent returns to Idle. Use it to block until a prompt completes:
ag.Prompt(ctx,"Refactor main.go")<-ag.Idle()// agent is idle, safe to call Prompt again
In-Process Extensions
If your extension is written in Go and you control the build, you can implement sdk.Extension (an alias of agent.Extension) directly — no gRPC, no subprocess, no socket. This is the lowest-overhead extension path.
All types are re-exported from sdk so callers only need to import github.com/goppydae/sharur/sdk.
Key Hook Behaviours
ModifyInput — runs before the user text is added to the transcript. Return an InputResult with:
sdk.InputContinue — pass through unchanged
sdk.InputTransform — replace with result.Text
sdk.InputHandled — consume entirely; no agent turn is started and nothing is appended to the transcript
ModifyContext — receives and returns the message slice that will be sent to the LLM. Changes do not affect the stored session transcript — they are ephemeral per-turn.
BeforeToolCall — return (result, true) to intercept and block the tool; return (nil, false) to allow normal execution.
BeforeCompact — return nil to let the default LLM summarization run, or a *CompactionResult to supply your own summary and skip the LLM call.
typesandboxExtstruct{sdk.NoopExtensionallowedDirstring}func(e*sandboxExt)BeforeToolCall(_context.Context,call*sdk.ToolCall,argsjson.RawMessage)(*sdk.ToolResult,bool){varinputstruct{Pathstring`json:"path"`}_=json.Unmarshal(args,&input)ifinput.Path!=""&&!strings.HasPrefix(input.Path,e.allowedDir){return&sdk.ToolResult{Content:fmt.Sprintf("blocked: %s is outside %s",input.Path,e.allowedDir),IsError:true,},true}returnnil,false}