Go SDK Guide
Active Development
The SDK is under active development. Pin to a specific release for production use.
The Go SDK lets you build high-performance Aether agents using Go's idiomatic interfaces, goroutines, and channels. It communicates with the Hub over gRPC.
Installation
go get github.com/amansrivastava/hacky-automation/sdks/goRequires: Go 1.21+
Configuration
Environment Variables
AETHER_HUB_ADDRESS=localhost:50051
AETHER_AGENT_ID=my-agent-001
AETHER_AGENT_NAME="My Agent"
AETHER_AGENT_SECRET=sk-secretConfig Reference
| Field | Type | Default | Description |
|---|---|---|---|
AgentID | string | Required | Unique identifier (e.g. pm_sarah) |
AgentName | string | Required | Human-readable name |
Version | string | "0.1.0" | Agent version |
HubAddress | string | "localhost:50051" | Hub gRPC endpoint |
Secret | string | "" | Authentication secret |
Capabilities | map[string]string | {} | Key-value capability flags |
Metadata | map[string]string | {} | Arbitrary key-value metadata |
HeartbeatInterval | time.Duration | 30s | Time between heartbeats |
ReconnectDelay | time.Duration | 5s | Delay before reconnect attempt |
MaxReconnectAttempts | int | -1 | Max attempts (-1 = infinite) |
import sdk "github.com/amansrivastava/hacky-automation/sdks/go"
import "os"
import "time"
config := sdk.NewConfig("my-agent-001", "My Agent")
config.HubAddress = "hub.example.com:50051"
config.Secret = os.Getenv("AGENT_SECRET")
config.Capabilities["task_types"] = "analysis"
config.HeartbeatInterval = 60 * time.Second
config.MaxReconnectAttempts = 10Quickstart
Create Your Agent
Implement the TaskHandler interface and wire it with sdk.NewAgent:
package main
import (
"context"
"fmt"
"log"
sdk "github.com/amansrivastava/hacky-automation/sdks/go"
)
// MyAgent implements sdk.TaskHandler
type MyAgent struct {
hub *sdk.Hub
}
func (a *MyAgent) HandleTask(ctx context.Context, task *sdk.Task) (*sdk.TaskResult, error) {
log.Printf("Processing task %s: %s", task.TaskID, task.Description)
result := fmt.Sprintf("Processed: %s", task.Description)
return &sdk.TaskResult{
TaskID: task.TaskID,
Success: true,
Result: []byte(result),
Metrics: map[string]string{"processing_time": "0.5s"},
}, nil
}
func main() {
config := sdk.NewConfig("my-agent-001", "My Example Agent")
config.HubAddress = "localhost:50051"
config.Capabilities = map[string]string{
"task_types": "analysis,processing",
"max_concurrent": "5",
}
handler := &MyAgent{}
agent, err := sdk.NewAgent(config, handler)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
handler.hub = sdk.NewHub(agent.Hub())
ctx := context.Background()
if err := agent.Run(ctx); err != nil {
log.Fatalf("Agent error: %v", err)
}
}Start, Stop, Run
// Start: connect + register + begin heartbeat/task loops (non-blocking)
err = agent.Start(ctx)
// Stop: unregister + close loops + disconnect
err = agent.Stop(ctx)
// Run: Start + block until ctx is cancelled or signal received
err = agent.Run(ctx)Hub Services
Access Hub-proxied services through a *sdk.Hub created from the agent's client:
hub := sdk.NewHub(agent.Hub())Pass hub to your handler (e.g. in a struct field) before calling Run.
LLM
response, err := hub.LLM().Chat(ctx, sdk.ChatRequest{
Messages: []sdk.Message{
{Role: "system", Content: "You are a helpful assistant."},
{Role: "user", Content: string(task.Payload)},
},
Model: "gpt-4o-mini", // Hub may override
Temperature: 0.3,
MaxTokens: 1000,
})
if err != nil {
return nil, fmt.Errorf("LLM call failed: %w", err)
}
fmt.Println(response.Content, response.Model)Tools
// Execute a tool
result, err := hub.Tools().Execute(ctx, sdk.ExecuteRequest{
ToolID: "add_comment",
Input: map[string]interface{}{"issue_id": "123", "comment": "Done"},
})Memory
// Store
err = hub.Memory().Store(ctx, sdk.StoreRequest{
Key: "my-config",
Value: []byte(`{"theme":"dark"}`),
Type: "agent", // "short_term" | "long_term" | "agent" | "task"
})
// Retrieve
resp, err := hub.Memory().Retrieve(ctx, sdk.RetrieveRequest{
Key: "my-config",
Type: "agent",
})
fmt.Println(string(resp.Value))Messaging
// Publish
err = hub.Messaging().Send(ctx, sdk.SendRequest{
To: "workflow.status",
Message: []byte(`{"status":"processing"}`),
})
// Request-reply
resp, err := hub.Messaging().Request(ctx, sdk.RequestMsg{
To: "workflow.status",
Message: []byte(`{"workflow_id":"123"}`),
Timeout: 10,
})Example: Code Reviewer Agent
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
sdk "github.com/amansrivastava/hacky-automation/sdks/go"
)
type CodeReviewer struct {
hub *sdk.Hub
}
func (a *CodeReviewer) HandleTask(ctx context.Context, task *sdk.Task) (*sdk.TaskResult, error) {
log.Printf("Code review task: %s", task.Description)
response, err := a.hub.LLM().Chat(ctx, sdk.ChatRequest{
Messages: []sdk.Message{
{Role: "system", Content: "You are an expert code reviewer. Be concise."},
{Role: "user", Content: fmt.Sprintf("Review this diff:\n\n%s", string(task.Payload))},
},
Temperature: 0.2,
})
if err != nil {
return nil, fmt.Errorf("LLM call failed: %w", err)
}
return &sdk.TaskResult{
TaskID: task.TaskID,
Success: true,
Result: []byte(response.Content),
Metrics: map[string]string{
"review_type": "automated",
"model": response.Model,
},
}, nil
}
func main() {
config := sdk.NewConfig("dev_go_reviewer", "Go Code Reviewer")
config.HubAddress = getEnv("HUB_ADDRESS", "localhost:50051")
config.Secret = getEnv("AGENT_SECRET", "")
config.Capabilities = map[string]string{
"code_review": "true",
"go": "true",
}
handler := &CodeReviewer{}
agent, err := sdk.NewAgent(config, handler)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
handler.hub = sdk.NewHub(agent.Hub())
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
go func() { <-sigs; cancel() }()
if err := agent.Run(ctx); err != nil {
log.Fatalf("Agent error: %v", err)
}
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}Example: Custom Data Processor
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
sdk "github.com/amansrivastava/hacky-automation/sdks/go"
)
type DataProcessor struct {
hub *sdk.Hub
}
func (a *DataProcessor) HandleTask(ctx context.Context, task *sdk.Task) (*sdk.TaskResult, error) {
response, err := a.hub.LLM().Chat(ctx, sdk.ChatRequest{
Messages: []sdk.Message{
{Role: "system", Content: "Classify and summarize this data as JSON."},
{Role: "user", Content: string(task.Payload)},
},
Temperature: 0.0,
})
if err != nil {
return nil, fmt.Errorf("LLM failed: %w", err)
}
// Persist result
value, _ := json.Marshal(map[string]string{"result": response.Content})
if err := a.hub.Memory().Store(ctx, sdk.StoreRequest{
Key: fmt.Sprintf("analysis-%s", task.TaskID),
Value: value,
Type: "task",
}); err != nil {
log.Printf("Memory store warning: %v", err)
}
return &sdk.TaskResult{
TaskID: task.TaskID,
Success: true,
Result: []byte(response.Content),
}, nil
}
func main() {
config := sdk.NewConfig("data_processor", "Data Processor")
config.HubAddress = os.Getenv("HUB_ADDRESS")
if config.HubAddress == "" {
config.HubAddress = "localhost:50051"
}
processor := &DataProcessor{}
agent, err := sdk.NewAgent(config, processor)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
processor.hub = sdk.NewHub(agent.Hub())
if err := agent.Run(context.Background()); err != nil {
log.Fatalf("Agent error: %v", err)
}
}Error Handling
The SDK exposes typed errors for structured handling:
if sdkErr, ok := err.(*sdk.SDKError); ok {
switch sdkErr.Code {
case sdk.ErrCodeNotConnected:
// Not connected to Hub
case sdk.ErrCodeRegistration:
// Registration failure
case sdk.ErrCodeMaxReconnectAttempts:
// Gave up reconnecting
}
}Error Codes
| Code | Meaning |
|---|---|
INVALID_CONFIG | Missing required field in config |
NOT_CONNECTED | gRPC connection not established |
NOT_REGISTERED | Agent not registered with Hub |
ALREADY_RUNNING | Run/Start called while already running |
REGISTRATION_FAILED | Hub rejected the registration |
CONNECTION_FAILED | Could not establish gRPC connection |
TASK_PROCESSING_FAILED | HandleTask returned an error |
MAX_RECONNECT_ATTEMPTS | Reconnect limit reached |
Deployment
Binary
cd sdks/go
make example # Build example binary
HUB_ADDRESS=localhost:50051 AGENT_SECRET=sk-xxx ./examples/code-reviewer/code-reviewerDocker
FROM golang:1.21-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o agent ./cmd/agent
FROM alpine:3.18
COPY --from=builder /build/agent /agent
CMD ["/agent"]docker run -e HUB_ADDRESS=hub:50051 -e AGENT_SECRET=sk-xxx my-go-agentDocker Compose (with Aether)
services:
my-agent:
build: ./my-agent
environment:
HUB_ADDRESS: goway:9090
AGENT_SECRET: ${AGENT_SECRET}
depends_on:
- goway
restart: unless-stoppedKubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-go-agent
spec:
replicas: 2 # Go agents can scale horizontally
selector:
matchLabels:
app: my-go-agent
template:
metadata:
labels:
app: my-go-agent
spec:
containers:
- name: agent
image: my-go-agent:latest
env:
- name: HUB_ADDRESS
value: "aether-hub:9090"
- name: AGENT_SECRET
valueFrom:
secretKeyRef:
name: aether-secrets
key: agent-secretAPI Reference
TaskHandler Interface
type TaskHandler interface {
HandleTask(ctx context.Context, task *Task) (*TaskResult, error)
}Implement this interface to define your agent's task logic.
Agent Interface
type Agent interface {
TaskHandler
Start(ctx context.Context) error
Stop(ctx context.Context) error
Run(ctx context.Context) error
Hub() *Client
}NewAgent
agent, err := sdk.NewAgent(config *Config, handler TaskHandler) (Agent, error)Hub Services
| Method | Description |
|---|---|
hub.LLM() | LLM service proxy |
hub.Tools() | Tools service proxy |
hub.Memory() | Memory service proxy |
hub.Messaging() | Messaging service proxy |
Client (low-level)
| Method | Description |
|---|---|
Connect(ctx) | Establish gRPC connection |
Disconnect(ctx) | Close connection |
Register(ctx) | Register with Hub |
Unregister(ctx) | Unregister from Hub |
SendHeartbeat(ctx) | Send heartbeat |
StreamTasks(ctx) | Open bidirectional task stream |
SendTaskResult(ctx, result) | Submit task result |
Development
cd sdks/go
make deps # Download dependencies
make test # Run all tests
make test-cov # Tests with coverage
make fmt # gofmt
make lint # golangci-lint
make example # Build example binary
make run-example # Run the code-reviewer exampleTroubleshooting
connection refused on startup
Check that the Hub gRPC port (default 9090) is reachable and the HubAddress in config is correct.
rpc error: code = Unauthenticated
The Secret in your config does not match the agent record in the Hub database.
Agent stops unexpectedly after N reconnect attempts
MaxReconnectAttempts defaults to -1 (infinite). If you set it to a positive number, the agent will exit after that many failed reconnects. Set to -1 for production services.
gRPC stubs are outdated
Proto definitions live in goway/proto/. Run make proto-gen from the goway directory to regenerate stubs, then update the SDK.
