
Security News
PodRocket Podcast: Inside the Recent npm Supply Chain Attacks
Socket CEO Feross Aboukhadijeh discusses the recent npm supply chain attacks on PodRocket, covering novel attack vectors and how developers can protect themselves.
github.com/itsneelabh/gomind/core
Welcome to the foundation of intelligent agent systems! This guide will walk you through everything step-by-step, like a friendly mentor sitting right next to you. β
Let me explain this in the simplest way possible.
Imagine you're running a coffee shop. You have different workers:
Now, imagine if these workers could:
That's exactly what the GoMind Core module does for your code! It helps you build intelligent components that can work independently or together.
In GoMind, we have two fundamental building blocks:
Think of Tools like the appliances in your kitchen:
Tools in GoMind:
ls
, grep
, sort
)// Tools are created with NewTool()
calculator := core.NewTool("calculator")
Think of Agents like the human workers who USE the tools:
Agents in GoMind:
// Agents are created with NewBaseAgent()
orchestrator := core.NewBaseAgent("orchestrator")
β οΈ IMPORTANT β οΈ
Tools are passive - they NEVER call or discover other components.
Agents are active - they CAN discover and orchestrate everything.
This separation ensures:
β WRONG: Tool trying to discover:
// Tools should NEVER do this!
func (t *BadTool) Handler(w http.ResponseWriter, r *http.Request) {
// Compile error! Tools don't have Discovery
other, _ := t.Discovery.FindByCapability("something") // WON'T COMPILE!
}
β RIGHT: Tool just does its job:
func (t *GoodTool) Handler(w http.ResponseWriter, r *http.Request) {
// Process input, return output
result := t.calculate(input)
json.NewEncoder(w).Encode(result)
}
β RIGHT: Agent orchestrating tools:
func (a *SmartAgent) Orchestrate(ctx context.Context) {
// Agents CAN discover
tools, _ := a.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
})
// ... coordinate the tools
}
How do components find each other? Through a registry - think of it like a company directory.
Redis is our registry. It keeps track of:
But here's the key difference:
With the rise of AI and Large Language Models (LLMs), we need:
ai
moduleorchestration
moduletelemetry
moduleresilience
moduleWant AI-powered components? It's easy!
// AI-enhanced Tool (passive, but uses AI internally)
translator := ai.NewAITool("translator", apiKey)
// AI-powered Agent (can orchestrate AND use AI)
assistant := ai.NewAIAgent("assistant", apiKey)
Before we build our first components, let's understand the Framework - it's the conductor that orchestrates everything!
The Framework is like the stage manager in a theater - it:
package main
import (
"context"
"github.com/itsneelabh/gomind/core"
)
func main() {
// Create your component (Tool or Agent)
tool := core.NewTool("my-tool")
// Wrap it with Framework and configure
framework, err := core.NewFramework(tool,
core.WithPort(8080),
core.WithRedisURL("redis://localhost:6379"),
core.WithDiscovery(true),
core.WithCORS([]string{"https://app.example.com"}, true),
)
if err != nil {
log.Fatal(err)
}
// Run (initializes and starts the component)
ctx := context.Background()
if err := framework.Run(ctx); err != nil {
log.Fatal(err)
}
}
The Framework accepts configuration options that apply to your component:
// Core options
core.WithName("my-service") // Component name
core.WithPort(8080) // HTTP port
core.WithNamespace("production") // Namespace for grouping
// Discovery options
core.WithRedisURL("redis://localhost:6379") // Redis connection
core.WithDiscovery(true, "redis") // Enable service discovery with provider
core.WithDiscoveryCacheEnabled(true) // Enable discovery caching
// CORS options
core.WithCORS([]string{"https://app.example.com"}, true)
// Development options
core.WithDevelopmentMode(true) // Enable development features
core.WithMockDiscovery(true) // Use in-memory discovery (testing)
The Framework automatically handles dependency injection for your components:
// Tools get Registry automatically when discovery is enabled
tool := core.NewTool("calculator")
framework, _ := core.NewFramework(tool,
core.WithDiscovery(true, "redis"),
core.WithRedisURL("redis://localhost:6379"),
)
// After framework.Run(), tool.Registry is automatically set!
// No manual setup required - the framework handles it
// Agents get Discovery automatically when discovery is enabled
agent := core.NewBaseAgent("orchestrator")
framework, _ := core.NewFramework(agent,
core.WithDiscovery(true, "redis"),
core.WithRedisURL("redis://localhost:6379"),
)
// After framework.Run(), agent.Discovery is automatically set!
// The agent can immediately start discovering other components
// Manual setup still works (for custom scenarios)
tool := core.NewTool("custom-tool")
registry, _ := core.NewRedisRegistry("redis://localhost:6379")
tool.Registry = registry // Manual assignment works
// Framework respects manual setup and won't override it
framework, _ := core.NewFramework(tool, ...)
// 1. Create component
component := core.NewTool("example")
// 2. Create framework with configuration
framework, _ := core.NewFramework(component, options...)
// 3. Run (initializes and starts - blocking)
framework.Run(ctx) // Initializes, connects to Redis, registers, starts server
// The framework handles:
// - Graceful shutdown on SIGINT/SIGTERM
// - Deregistration from discovery
// - Connection cleanup
go get github.com/itsneelabh/gomind/core
package main
import (
"context"
"encoding/json"
"net/http"
"github.com/itsneelabh/gomind/core"
)
func main() {
// Create a tool (passive component)
calculator := core.NewTool("calculator")
// Register what it can do (see "Registering Capabilities" section for details)
calculator.RegisterCapability(core.Capability{
Name: "add",
Description: "Adds two numbers",
Endpoint: "/add",
Handler: func(w http.ResponseWriter, r *http.Request) {
// Parse input
var input struct {
A float64 `json:"a"`
B float64 `json:"b"`
}
json.NewDecoder(r.Body).Decode(&input)
// Calculate (tools just do their job)
result := input.A + input.B
// Return result
json.NewEncoder(w).Encode(map[string]float64{
"result": result,
})
},
})
// Initialize and start
ctx := context.Background()
calculator.Initialize(ctx)
calculator.Start(ctx, 8080)
// Tool is now running at http://localhost:8080
// It CANNOT discover or call other components
select {} // Keep running
}
package main
import (
"context"
"encoding/json"
"net/http"
"github.com/itsneelabh/gomind/core"
)
func main() {
// Create an agent (active orchestrator)
orchestrator := core.NewBaseAgent("orchestrator")
// Configure with discovery capability
framework, _ := core.NewFramework(orchestrator,
core.WithRedisURL("redis://localhost:6379"),
core.WithDiscovery(true),
)
ctx := context.Background()
// Add a discovery capability to the agent
orchestrator.RegisterCapability(core.Capability{
Name: "discover_components",
Description: "Discovers and lists available components",
Handler: func(w http.ResponseWriter, r *http.Request) {
// Find all available tools
tools, _ := orchestrator.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
})
// Find other agents
agents, _ := orchestrator.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeAgent,
})
// Return discovery results
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"tools": tools,
"agents": agents,
})
},
})
// The framework handles initialization and starts the agent
// Once running, you can call: GET /api/capabilities/discover_components
framework.Run(ctx)
}
Now that you know how to create Tools and Agents, let's learn the most important part: how to make them actually DO something! This is where capabilities come in.
Think of capabilities like the menu at a restaurant:
When you register a capability, you're telling the world: "Hey, I can do this thing!"
type Capability struct {
Name string `json:"name"` // Unique identifier
Description string `json:"description"` // What it does
Endpoint string `json:"endpoint"` // Where to call it (optional)
InputTypes []string `json:"input_types"` // Expected input formats
OutputTypes []string `json:"output_types"`// Output formats
Handler http.HandlerFunc `json:"-"` // The actual function (optional)
}
Both Tools and Agents use RegisterCapability()
to define what they can do:
// For any component (Tool or Agent)
component.RegisterCapability(core.Capability{
Name: "capability_name",
Description: "What this capability does",
// That's it! Everything else is optional
})
Tools register capabilities to define their functions. Here are two ways to do it:
Great News for Tools: Just like Agents, Tools also support auto-endpoint generation and generic handlers! If you don't specify an Endpoint
, it will auto-generate as /api/capabilities/{name}
. If you don't provide a Handler
, a generic one is provided automatically. This means Tools and Agents use the exact same capability registration pattern!
package main
import (
"encoding/json"
"net/http"
"github.com/itsneelabh/gomind/core"
)
func main() {
calculator := core.NewTool("calculator")
// Simple capability - endpoint auto-generates as /api/capabilities/add
calculator.RegisterCapability(core.Capability{
Name: "add",
Description: "Adds two numbers",
// No Endpoint specified - auto-generates!
InputTypes: []string{"json"},
OutputTypes: []string{"json"},
Handler: func(w http.ResponseWriter, r *http.Request) {
// Parse input
var input struct {
A float64 `json:"a"`
B float64 `json:"b"`
}
json.NewDecoder(r.Body).Decode(&input)
// Do the calculation
result := input.A + input.B
// Return result
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]float64{
"result": result,
})
},
})
// Accessible at: http://localhost:8080/api/capabilities/add
}
func main() {
weatherTool := core.NewTool("weather-service")
// Register with a custom endpoint path
weatherTool.RegisterCapability(core.Capability{
Name: "current_weather",
Description: "Gets current weather for a city",
Endpoint: "/weather/current", // Custom endpoint (overrides auto-generation)
Handler: func(w http.ResponseWriter, r *http.Request) {
city := r.URL.Query().Get("city")
// Fetch weather data...
weather := getWeather(city)
json.NewEncoder(w).Encode(weather)
},
})
// Accessible at: http://localhost:8080/weather/current?city=London
}
func main() {
simpleTool := core.NewTool("simple-tool")
// Just name and description - everything else is auto-generated!
simpleTool.RegisterCapability(core.Capability{
Name: "ping",
Description: "Simple ping capability for testing",
// No Endpoint - auto-generates as /api/capabilities/ping
// No Handler - uses generic handler that returns capability info
})
// The generic handler will return:
// {"capability": "ping", "description": "Simple ping capability for testing"}
// Accessible at: http://localhost:8080/api/capabilities/ping
}
Agents register capabilities using the exact same pattern as Tools:
Endpoint
, it auto-generates as /api/capabilities/{name}
(same as Tools)Handler
, a generic one is provided (same as Tools)package main
import (
"context"
"encoding/json"
"net/http"
"github.com/itsneelabh/gomind/core"
)
func main() {
// Create an agent
orchestrator := core.NewBaseAgent("orchestrator")
// Simple capability - endpoint auto-generates as /api/capabilities/status
orchestrator.RegisterCapability(core.Capability{
Name: "status",
Description: "Returns agent status",
// No Endpoint specified - auto-generates!
// No Handler specified - uses generic handler!
})
// Capability with custom handler but auto-generated endpoint
orchestrator.RegisterCapability(core.Capability{
Name: "coordinate",
Description: "Coordinates multiple tools",
// No Endpoint - auto-generates as /api/capabilities/coordinate
Handler: func(w http.ResponseWriter, r *http.Request) {
// Custom orchestration logic
w.Write([]byte("Coordinating tools..."))
},
})
}
func main() {
// Create an agent
dataProcessor := core.NewBaseAgent("data-processor")
// Register a capability that orchestrates multiple tools
dataProcessor.RegisterCapability(core.Capability{
Name: "process_report",
Description: "Fetches data, analyzes it, and generates a report",
InputTypes: []string{"json"},
OutputTypes: []string{"pdf", "json"},
Handler: func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Agents can discover other components!
dataTools, _ := dataProcessor.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
Capabilities: []string{"fetch_data"},
})
analyticTools, _ := dataProcessor.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
Capabilities: []string{"analyze"},
})
// Orchestrate the workflow
// 1. Fetch data using data tool
// 2. Analyze using analytics tool
// 3. Generate report
// Return the final report
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(report)
},
})
}
IMPORTANT: All registered capabilities are automatically exposed at a special endpoint!
# Every component automatically provides this endpoint:
GET http://localhost:8080/api/capabilities
# Returns:
[
{
"name": "add",
"description": "Adds two numbers",
"endpoint": "/api/capabilities/add",
"input_types": ["json"],
"output_types": ["json"]
},
{
"name": "multiply",
"description": "Multiplies two numbers",
"endpoint": "/api/capabilities/multiply",
"input_types": ["json"],
"output_types": ["json"]
}
]
This is how other agents discover what your component can do!
Let's build a complete tool with multiple capabilities:
package main
import (
"context"
"encoding/json"
"net/http"
"strings"
"github.com/itsneelabh/gomind/core"
)
type TranslationTool struct {
*core.BaseTool
// Add any tool-specific fields
}
func NewTranslationTool() *TranslationTool {
tool := &TranslationTool{
BaseTool: core.NewTool("translator"),
}
// Register capability 1: Translate text
tool.RegisterCapability(core.Capability{
Name: "translate",
Description: "Translates text between languages",
InputTypes: []string{"json"},
OutputTypes: []string{"json"},
Handler: tool.handleTranslate,
})
// Register capability 2: Detect language
tool.RegisterCapability(core.Capability{
Name: "detect_language",
Description: "Detects the language of input text",
InputTypes: []string{"text", "json"},
OutputTypes: []string{"json"},
Handler: tool.handleDetectLanguage,
})
// Register capability 3: List supported languages
tool.RegisterCapability(core.Capability{
Name: "list_languages",
Description: "Lists all supported languages",
Endpoint: "/languages", // Custom endpoint
Handler: tool.handleListLanguages,
})
return tool
}
func (t *TranslationTool) handleTranslate(w http.ResponseWriter, r *http.Request) {
var req struct {
Text string `json:"text"`
From string `json:"from"`
To string `json:"to"`
}
json.NewDecoder(r.Body).Decode(&req)
// Perform translation (simplified)
translated := t.translate(req.Text, req.From, req.To)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"translated": translated,
"from": req.From,
"to": req.To,
})
}
func (t *TranslationTool) handleDetectLanguage(w http.ResponseWriter, r *http.Request) {
var req struct {
Text string `json:"text"`
}
json.NewDecoder(r.Body).Decode(&req)
// Detect language (simplified example)
language := "en"
if strings.Contains(req.Text, "hola") {
language = "es"
} else if strings.Contains(req.Text, "bonjour") {
language = "fr"
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"language": language,
"confidence": 0.95,
})
}
func (t *TranslationTool) handleListLanguages(w http.ResponseWriter, r *http.Request) {
languages := []map[string]string{
{"code": "en", "name": "English"},
{"code": "es", "name": "Spanish"},
{"code": "fr", "name": "French"},
{"code": "de", "name": "German"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(languages)
}
func (t *TranslationTool) translate(text, from, to string) string {
// Your translation logic here
return "translated: " + text
}
func main() {
// Create and configure the tool
translator := NewTranslationTool()
// Initialize with framework
framework, _ := core.NewFramework(translator,
core.WithRedisURL("redis://localhost:6379"),
core.WithDiscovery(true),
)
ctx := context.Background()
framework.Initialize(ctx)
// The tool is now running with three endpoints:
// - POST /api/capabilities/translate
// - POST /api/capabilities/detect_language
// - GET /languages
// - GET /api/capabilities (lists all capabilities)
framework.Run(ctx)
}
Use Descriptive Names:
translate_text
, calculate_tax
, fetch_weather
process
, handle
, do_stuff
Provide Clear Descriptions:
Specify Input/Output Types:
InputTypes: []string{"json", "text"}, // Accept both
OutputTypes: []string{"json"}, // Always return JSON
Tools Should Have Focused Capabilities:
// β
GOOD: Each capability does one thing
calculator.RegisterCapability(core.Capability{Name: "add"})
calculator.RegisterCapability(core.Capability{Name: "subtract"})
// β BAD: One capability doing everything
calculator.RegisterCapability(core.Capability{Name: "calculate_anything"})
Agents Can Have Orchestration Capabilities:
// β
GOOD: Agent orchestrates a workflow
agent.RegisterCapability(core.Capability{
Name: "generate_monthly_report",
Description: "Fetches data, analyzes trends, creates visualizations, generates PDF",
})
Once registered, test your capabilities:
# List all capabilities
curl http://localhost:8080/api/capabilities
# Call a specific capability (auto-generated endpoint)
curl -X POST http://localhost:8080/api/capabilities/translate \
-H "Content-Type: application/json" \
-d '{"text":"Hello","from":"en","to":"es"}'
# Call a custom endpoint
curl http://localhost:8080/languages
/api/capabilities
Now that you understand capabilities, let's explore the powerful features that make your components production-ready. Think of these as the professional-grade tools in your workshop!
Components often need to remember things - user preferences, cached data, conversation context. The Memory interface provides a simple key-value store with TTL (Time To Live) support.
// Memory interface - your component's notepad
type Memory interface {
Get(ctx context.Context, key string) (string, error)
Set(ctx context.Context, key string, value string, ttl time.Duration) error
Delete(ctx context.Context, key string) error
Exists(ctx context.Context, key string) (bool, error)
}
package main
import (
"context"
"time"
"github.com/itsneelabh/gomind/core"
)
func main() {
// Create a tool with memory
calculator := core.NewTool("smart-calculator")
// Use the default in-memory store
// Note: The current NewInMemoryStore() implementation ignores TTL
// For production with TTL support, configure Redis via framework options
calculator.Memory = core.NewInMemoryStore()
// Register a capability that uses memory
calculator.RegisterCapability(core.Capability{
Name: "calculate_with_memory",
Description: "Calculator that remembers previous results",
Endpoint: "/calculate",
Handler: func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Store the last result
result := performCalculation()
calculator.Memory.Set(ctx, "last_result",
strconv.FormatFloat(result, 'f', 2, 64),
5*time.Minute) // Remember for 5 minutes
// Retrieve previous result
lastResult, _ := calculator.Memory.Get(ctx, "last_result")
json.NewEncoder(w).Encode(map[string]interface{}{
"result": result,
"last_result": lastResult,
})
},
})
}
// When using Redis, memory is automatically shared across instances
framework, _ := core.NewFramework(tool,
core.WithRedisURL("redis://localhost:6379"),
// Memory automatically uses Redis when available
)
When building web-accessible components, you need Cross-Origin Resource Sharing (CORS) support. GoMind provides powerful CORS middleware with wildcard support.
func main() {
agent := core.NewBaseAgent("web-api")
// Configure CORS
agent.Config.HTTP.CORS = core.CORSConfig{
Enabled: true,
AllowedOrigins: []string{"https://myapp.com"},
AllowCredentials: true,
}
// That's it! CORS headers are automatically added
}
// Allow multiple origins with wildcards
agent.Config.HTTP.CORS = core.CORSConfig{
Enabled: true,
AllowedOrigins: []string{
"https://app.example.com",
"https://*.example.com", // Wildcard subdomains
"http://localhost:*", // Any localhost port
},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization", "X-Custom-Header"},
AllowCredentials: true,
MaxAge: 86400, // Cache preflight for 24 hours
}
// You can also apply CORS to any HTTP handler
mux := http.NewServeMux()
corsConfig := &core.CORSConfig{
Enabled: true,
AllowedOrigins: []string{"*"}, // Allow all origins (use carefully!)
}
// Wrap your handler with CORS
handler := core.CORSMiddleware(corsConfig)(mux)
http.ListenAndServe(":8080", handler)
Every component gets a structured logger automatically. It's like having a flight recorder for your code!
type Logger interface {
Info(msg string, fields map[string]interface{})
Error(msg string, fields map[string]interface{})
Warn(msg string, fields map[string]interface{})
Debug(msg string, fields map[string]interface{})
}
func main() {
tool := core.NewTool("data-processor")
// Logger is automatically available
tool.Logger.Info("Starting data processor", map[string]interface{}{
"version": "1.0.0",
"mode": "production",
})
// In your handlers
tool.RegisterCapability(core.Capability{
Name: "process",
Endpoint: "/process",
Handler: func(w http.ResponseWriter, r *http.Request) {
startTime := time.Now()
// Log the request
tool.Logger.Info("Processing request", map[string]interface{}{
"method": r.Method,
"path": r.URL.Path,
"user_agent": r.UserAgent(),
})
// Process...
if err := processData(); err != nil {
tool.Logger.Error("Processing failed", map[string]interface{}{
"error": err.Error(),
"duration": time.Since(startTime),
})
http.Error(w, "Processing failed", 500)
return
}
tool.Logger.Info("Processing complete", map[string]interface{}{
"duration": time.Since(startTime),
})
},
})
}
Want to add distributed tracing and metrics? The Telemetry interface makes it easy!
type Telemetry interface {
StartSpan(ctx context.Context, name string) (context.Context, Span)
RecordMetric(name string, value float64, labels map[string]string)
}
func main() {
agent := core.NewBaseAgent("analytics")
// Telemetry is optional - add it when needed
// (Usually added by the telemetry module)
agent.RegisterCapability(core.Capability{
Name: "analyze",
Endpoint: "/analyze",
Handler: func(w http.ResponseWriter, r *http.Request) {
// Start a span for tracing
ctx, span := agent.Telemetry.StartSpan(r.Context(), "analyze_data")
defer span.End()
// Add attributes to the span
span.SetAttribute("data.size", len(data))
span.SetAttribute("user.id", userID)
// Record metrics
agent.Telemetry.RecordMetric("analysis.count", 1, map[string]string{
"type": "full",
})
// Process with context
result, err := analyzeWithContext(ctx, data)
if err != nil {
span.RecordError(err)
return
}
// Record performance metric
agent.Telemetry.RecordMetric("analysis.duration",
time.Since(start).Seconds(),
map[string]string{"status": "success"})
},
})
}
GoMind provides a comprehensive error system with standard errors and helper functions.
// Use standard errors for consistency
if service == nil {
return fmt.Errorf("failed to find service %s: %w",
serviceName, core.ErrServiceNotFound)
}
// Check error types
if errors.Is(err, core.ErrTimeout) {
// Handle timeout specifically
return retryWithBackoff()
}
// Check categories of errors
if core.IsRetryable(err) {
// This error might succeed if we try again
return retry()
}
if core.IsNotFound(err) {
// Resource doesn't exist
return createResource()
}
func (t *DataTool) fetchData(ctx context.Context, id string) error {
// Check initialization
if t.client == nil {
return core.ErrNotInitialized
}
// Set timeout
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
// Fetch with retry logic
var lastErr error
for i := 0; i < 3; i++ {
data, err := t.client.Get(ctx, id)
if err == nil {
return nil // Success!
}
lastErr = err
// Check if we should retry
if !core.IsRetryable(err) {
// No point retrying
return fmt.Errorf("fetch failed: %w", err)
}
// Log retry attempt
t.Logger.Warn("Retrying fetch", map[string]interface{}{
"attempt": i + 1,
"error": err.Error(),
})
time.Sleep(time.Second * time.Duration(i+1)) // Exponential backoff
}
return fmt.Errorf("max retries exceeded: %w", core.ErrMaxRetriesExceeded)
}
The configuration system uses a three-layer priority system:
// Layer 1: Defaults
cfg := core.DefaultConfig() // Port: 8080 (default)
// Layer 2: Environment variables override defaults
// If GOMIND_PORT=9090 is set, port becomes 9090
// Layer 3: Functional options override everything
cfg, _ = core.NewConfig(
core.WithPort(3000), // This wins! Port is now 3000
)
When starting components, port configuration follows a specific precedence:
tool := core.NewTool("example")
tool.Config = &core.Config{Port: 8080} // Config port
// Explicit parameter overrides config
err := tool.Start(ctx, 9090) // 9090 wins over config port 8080
// If no explicit parameter (port < 0), config is used
err := tool.Start(ctx, -1) // Uses config port 8080
// Framework also respects this precedence
framework, _ := core.NewFramework(tool, core.WithPort(7070))
// Framework port 7070 overrides tool.Config.Port
Port Precedence Rules:
tool.Start(ctx, 9090)
) - Highest prioritytool.Config.Port = 8080
) - Medium priority8080
for most components) - Lowest priorityfunc main() {
// Configure with functional options
cfg, err := core.NewConfig(
core.WithName("my-agent"),
core.WithPort(8080),
// CORS configuration
core.WithCORS([]string{"https://app.example.com"}, true),
// Discovery configuration
core.WithRedisURL("redis://localhost:6379"),
core.WithDiscoveryCacheEnabled(true),
// Development mode
core.WithDevelopmentMode(true),
)
if err != nil {
log.Fatal(err)
}
// Create agent with config
agent := core.NewBaseAgentWithConfig(cfg)
}
All configuration can be set via environment variables:
# Core configuration
export GOMIND_AGENT_NAME="my-agent"
export GOMIND_PORT=8080
export GOMIND_NAMESPACE="production"
# HTTP configuration
export GOMIND_HTTP_READ_TIMEOUT="30s"
export GOMIND_HTTP_HEALTH_CHECK=true
# CORS configuration
export GOMIND_CORS_ENABLED=true
export GOMIND_CORS_ORIGINS="https://app.example.com,https://*.example.com"
# Redis configuration
export GOMIND_REDIS_URL="redis://localhost:6379"
export GOMIND_REDIS_PASSWORD="secret"
# Development mode
export GOMIND_DEV_MODE=true
Every component automatically gets a health check endpoint. It's like a heartbeat for your services!
// Every component automatically provides:
// GET /health (or configured path)
// Returns:
{
"status": "healthy",
"component": "calculator-tool",
"timestamp": "2024-01-01T12:00:00Z"
}
agent := core.NewBaseAgent("database-agent")
// Configure health check path
agent.Config.HTTP.HealthCheckPath = "/healthz"
// Add custom health logic
agent.RegisterCapability(core.Capability{
Name: "health",
Endpoint: "/healthz",
Handler: func(w http.ResponseWriter, r *http.Request) {
// Check database connection
if err := checkDatabase(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "unhealthy",
"error": err.Error(),
})
return
}
// Check Redis connection
if err := checkRedis(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "degraded",
"warning": "Redis unavailable",
})
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "healthy",
"checks": map[string]string{
"database": "ok",
"redis": "ok",
},
})
},
})
The Circuit Breaker pattern prevents cascading failures. When a service is struggling, the circuit breaker "opens" to give it time to recover.
CLOSED β (failures exceed threshold) β OPEN β (timeout) β HALF-OPEN β (success) β CLOSED
β β
βββ (failure) ββββββββ
// The CircuitBreaker interface (implementations in resilience module)
type CircuitBreaker interface {
Execute(ctx context.Context, fn func() error) error
ExecuteWithTimeout(ctx context.Context, timeout time.Duration, fn func() error) error
GetState() string // "closed", "open", "half-open"
Reset()
}
// Example usage (with resilience module)
func (a *Agent) callExternalService(ctx context.Context) error {
return a.CircuitBreaker.Execute(ctx, func() error {
// This function is protected by the circuit breaker
resp, err := http.Get("https://api.example.com/data")
if err != nil {
return err // Circuit breaker counts this failure
}
if resp.StatusCode >= 500 {
return fmt.Errorf("server error: %d", resp.StatusCode)
}
return nil // Success!
})
}
Always configure timeouts:
// Set timeouts via config
config.HTTP.ReadTimeout = 30 * time.Second
config.HTTP.WriteTimeout = 30 * time.Second
Use structured logging:
logger.Info("Operation completed", map[string]interface{}{
"duration": time.Since(start),
"records": count,
})
Handle errors properly:
if core.IsRetryable(err) {
return retryWithExponentialBackoff()
}
Set up health checks:
// Components get /health automatically
// Add custom checks for dependencies
Configure CORS carefully:
// Production: specific origins
AllowedOrigins: []string{"https://app.example.com"}
// Development: localhost
AllowedOrigins: []string{"http://localhost:*"}
Understand TTL and Heartbeat Behavior:
// When using Redis discovery, components auto-register with a 30-second TTL
// Framework automatically starts heartbeat every 15 seconds to keep registration alive
// β
GOOD: Framework-managed components (automatic heartbeat)
framework, _ := core.NewFramework(tool,
core.WithDiscovery(true, "redis"),
core.WithRedisURL("redis://localhost:6379"),
)
framework.Run(ctx) // Auto-starts heartbeat, keeps component alive
// β οΈ CAREFUL: Manual registration (no automatic heartbeat)
tool.Registry.Register(ctx, serviceInfo) // Expires after 30 seconds without heartbeat
// π For monitoring: Components without heartbeat disappear after 30 seconds
// This helps automatically clean up crashed or stopped components
Tools announce their existence but can't look for others:
tool := core.NewTool("weather-tool")
// Framework automatically initializes Registry for tools when discovery is enabled
framework, _ := core.NewFramework(tool,
core.WithRedisURL("redis://localhost:6379"),
core.WithDiscovery(true, "redis"), // Enables registration
)
// After framework.Run(), tool.Registry is automatically set!
// The tool registers itself and starts heartbeat automatically
// Other agents can find it, but it can't find others
// Manual setup still works (for advanced use cases)
// registry, _ := core.NewRedisRegistry("redis://localhost:6379")
// tool.Registry = registry // Manual assignment (framework respects this)
Agents can find both tools and other agents:
agent := core.NewBaseAgent("coordinator")
// Framework automatically initializes Discovery for agents when discovery is enabled
framework, _ := core.NewFramework(agent,
core.WithRedisURL("redis://localhost:6379"),
core.WithDiscovery(true, "redis"), // Enables discovery
)
// After framework.Run(), agent.Discovery is automatically set!
// The agent can immediately start discovering other components
// Find tools for specific tasks
weatherTools, _ := agent.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
Capabilities: []string{"weather_forecast"},
})
// Find other agents for delegation
aiAgents, _ := agent.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeAgent,
Capabilities: []string{"natural_language_processing"},
})
// Manual setup still works (for advanced use cases)
// discovery, _ := core.NewRedisDiscovery("redis://localhost:6379")
// agent.Discovery = discovery // Manual assignment (framework respects this)
βββββββββββββββββββββββββββββββββββββββββββ
β Orchestrator Agent β
β (Discovers & Coordinates) β
ββββββββββββββββββ¬βββββββββββββββββββββββββ
β Discovers
ββββββββββββββΌβββββββββββββ¬ββββββββββββ
βΌ βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββ βββββββββββ
βCalculatorββ Weather ββDatabase ββTranslatorβ
β Tool ββ Tool ββ Tool ββ Tool β
βββββββββββ βββββββββββ βββββββββββ βββββββββββ
βββββββββββββββββββββββββββββββββββββββββββ
β Master Agent (AI-Powered) β
ββββββββββββββββββ¬βββββββββββββββββββββββββ
β Discovers & Delegates
ββββββββββββββΌβββββββββββββ
βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββ
β Data ββAnalytics ββ Report β
β Agent ββ Agent ββ Agent β
ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ
β β β
[Tools] [Tools] [Tools]
Both Tools and Agents work seamlessly in Kubernetes:
// Configuration automatically detects Kubernetes
config := core.DefaultConfig()
config.Kubernetes.ServiceName = "calculator-tool"
config.Kubernetes.ServicePort = 80
tool := core.NewToolWithConfig(config)
// Registers as: calculator-tool.namespace.svc.cluster.local:80
Agents can filter discoveries by multiple criteria:
// Find specific tools
mathTools, _ := agent.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
Capabilities: []string{"add", "multiply"},
Name: "calculator",
})
// Find agents in specific namespace
productionAgents, _ := agent.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeAgent,
Metadata: map[string]interface{}{
"environment": "production",
},
})
Use a Tool when:
Use an Agent when:
// β
GOOD: Tool does one thing
type CalculatorTool struct {
*core.BaseTool
}
func (c *CalculatorTool) Add(a, b float64) float64 {
return a + b
}
// β BAD: Tool trying to do too much
type BadTool struct {
*core.BaseTool
}
func (b *BadTool) ProcessOrder() {
// Trying to calculate, save to DB, send email...
// This should be an Agent orchestrating multiple tools!
}
// β
GOOD: Agent orchestrating tools
type OrderAgent struct {
*core.BaseAgent
}
func (o *OrderAgent) ProcessOrder(ctx context.Context, order Order) error {
// Find calculator tool
calc, _ := o.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
Name: "calculator",
})
// Call it for tax calculation
// Find database tool
db, _ := o.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
Name: "database",
})
// Call it to save order
// Find email tool
email, _ := o.Discover(ctx, core.DiscoveryFilter{
Type: core.ComponentTypeTool,
Name: "email",
})
// Call it to send confirmation
return nil
}
// A tool that uses AI internally but doesn't orchestrate
translator := ai.NewAITool("translator", apiKey)
translator.RegisterCapability(core.Capability{
Name: "translate",
Description: "Translates text using AI",
Handler: translateHandler,
})
// Can use AI but can't discover other components
// An agent that orchestrates a complex workflow
workflow := core.NewBaseAgent("workflow-engine")
// Can discover and coordinate multiple tools and agents
// An agent that acts as a gateway to multiple tools
gateway := core.NewBaseAgent("api-gateway")
// Discovers available tools and routes requests
# See what's registered in Redis
redis-cli KEYS "gomind:*"
# Check specific component
redis-cli GET "gomind:services:calculator-tool-abc123"
All components automatically provide health endpoints:
http://localhost:8080/health
http://localhost:8090/health
The GoMind Core module provides two fundamental building blocks:
This clear separation enables:
Remember: Tools work, Agents think!
Happy building! π
FAQs
Unknown package
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Socket CEO Feross Aboukhadijeh discusses the recent npm supply chain attacks on PodRocket, covering novel attack vectors and how developers can protect themselves.
Security News
Maintainers back GitHubβs npm security overhaul but raise concerns about CI/CD workflows, enterprise support, and token management.
Product
Socket Firewall is a free tool that blocks malicious packages at install time, giving developers proactive protection against rising supply chain attacks.