Back to Docs
Go

Go SDK

v1.2.0 · MIT License · Go 1.22+ · Zero external dependencies

Zero external dependencies for the core. Uses only stdlib crypto/ed25519, net/http, and encoding/json. Idiomatic Go interfaces throughout.

Installation

go get github.com/projectmed99/aroha/sdks/go
The module path is github.com/projectmed99/aroha/sdks/go. All packages live under the aroha/ subdirectory within this module.

ArohaServer

Minimal agent

package main

import (
    "context"
    "crypto/ed25519"
    "crypto/rand"
    "log"

    "github.com/projectmed99/aroha/sdks/go/aroha/transport"
    "github.com/projectmed99/aroha/sdks/go/aroha/identity"
    "github.com/projectmed99/aroha/sdks/go/aroha/messages"
)

func main() {
    pub, priv, _ := ed25519.GenerateKey(rand.Reader)
    myDID := identity.GenerateDID(pub)

    server := transport.NewArohaServer(transport.ArohaServerOptions{
        AgentDID: myDID,
        Port:     3000,
        DevMode:  true, // skip crypto in development

        OnMessage: func(ctx context.Context, env messages.ArohaEnvelope, respond transport.RespondFn, stream transport.StreamFn) error {
            if env.Type != "ArohaRequest" {
                return nil
            }
            body := env.Body.(map[string]any)
            capability := body["capability"].(string)

            switch capability {
            case "greet":
                params := body["params"].(map[string]any)
                return respond(messages.ArohaResponse{
                    Capability: capability,
                    Result:     map[string]any{"message": "Hello, " + params["name"].(string) + "!"},
                })
            }
            return nil
        },

        ResolvePublicKey: func(ctx context.Context, did string) (ed25519.PublicKey, error) {
            // return nil, nil if unknown
            return nil, nil
        },
    })

    log.Printf("Agent %s listening on :3000", myDID)
    log.Fatal(server.Start(context.Background()))
}

Production server

server := transport.NewArohaServer(transport.ArohaServerOptions{
    AgentDID:       myDID,
    PrivateKey:     priv,
    Port:           3000,
    ClockToleranceMs: 5000,
    MaxBodyBytes:   1_048_576,
    MinClientVersion: "1.0",

    ResolvePublicKey: func(ctx context.Context, did string) (ed25519.PublicKey, error) {
        return registry.LookupPublicKey(ctx, did)
    },

    Middleware: []transport.Middleware{
        rbac.NewMiddleware(rbac.Options{RequiredTrustLevel: 2}),
    },

    OnMessage: handleMessage,
})

ArohaClient

package main

import (
    "context"
    "fmt"

    "github.com/projectmed99/aroha/sdks/go/aroha/transport"
    "github.com/projectmed99/aroha/sdks/go/aroha/messages"
)

func main() {
    client := transport.NewArohaClient()

    envelope, err := messages.BuildEnvelope(messages.BuildEnvelopeParams{
        Type:          "ArohaRequest",
        From:          myDID,
        To:            "did:aroha:travel-agent",
        Body: map[string]any{
            "capability": "search-flights",
            "params": map[string]any{
                "from": "JFK",
                "to":   "LHR",
                "date": "2026-08-01",
            },
        },
        CorrelationID: messages.NewCorrelationID(),
        PrivateKey:    priv,
    })
    if err != nil {
        panic(err)
    }

    resp, err := client.Send(context.Background(), "http://travel-agent.example.com", envelope)
    if err != nil {
        panic(err)
    }
    if resp != nil && resp.Type == "ArohaResponse" {
        fmt.Println(resp.Body)
    }
}

Identity & DIDs

import (
    "crypto/ed25519"
    "crypto/rand"

    "github.com/projectmed99/aroha/sdks/go/aroha/identity"
)

// Self-sovereign DID
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
did := identity.GenerateDID(pub)
// → "did:aroha:base58encodedPublicKey"

// Domain-anchored DID
webDID := identity.BuildWebDID("myco.ai", "agents", "travel")
// → "did:aroha-web:myco.ai:agents:travel"

// Resolve a DID
doc, err := identity.ResolveDID(ctx, "did:aroha:someagent")
// doc.VerificationMethod[0].PublicKeyMultibase → key bytes

Envelopes

import "github.com/projectmed99/aroha/sdks/go/aroha/messages"

// Build a typed envelope — all fields (id, created, expires, nonce, proof) set automatically
env, err := messages.BuildEnvelope(messages.BuildEnvelopeParams{
    Type:          "ArohaReserve",
    From:          orchestratorDID,
    To:            providerDID,
    Body: map[string]any{
        "capability": "book-flight",
        "params":     map[string]any{"from": "JFK", "to": "LHR"},
        "budgetUsd":  500,
    },
    CorrelationID: messages.NewCorrelationID(),
    PrivateKey:    priv,
    TTL:           5 * time.Minute, // optional, defaults to 5m
})

// Validate an inbound envelope
result, err := messages.ValidateEnvelope(env, senderPubKey, myDID, nonceRegistry, messages.ValidateOptions{
    ClockToleranceMs: 5000,
})

Spending Mandates

import (
    "time"
    "github.com/projectmed99/aroha/sdks/go/aroha/mandate"
)

constraints := mandate.SpendingConstraints{
    SpendLimitUSD:        500,
    AllowedCapabilities:  []string{"search-flights", "reserve-flight"},
    ExpiresAt:            time.Now().Add(time.Hour),
}

// User authorises the orchestrator
m, err := mandate.IssueIntent(
    userDID,
    orchestratorDID,
    constraints,
    userPrivKey,
)

// Orchestrator sub-delegates to a flight agent (limits can only narrow)
subM, err := mandate.Attenuate(m, mandate.AttenuateParams{
    GranteeDID: flightAgentDID,
    Constraints: mandate.SpendingConstraints{
        SpendLimitUSD:       300,
        AllowedCapabilities: []string{"reserve-flight"},
    },
    PrivateKey: orchestratorPrivKey,
})

// Verify on the receiving server
valid, reason, err := mandate.Verify(subM, flightAgentDID)
fmt.Println(valid, reason) // true, "ok"

Saga Orchestration

import "github.com/projectmed99/aroha/sdks/go/aroha/orchestrator"

engine := orchestrator.NewSagaEngine(orchestrator.SagaEngineOptions{
    Client:          client,
    OrchestratorDID: myDID,
    PrivateKey:      priv,
})

result, err := engine.Run(ctx, []orchestrator.SagaStep{
    {
        AgentDID:   "did:aroha:flight-agent",
        Endpoint:   "http://flights.example.com",
        Capability: "reserve-flight",
        Params:     map[string]any{"from": "JFK", "to": "LHR"},
        BudgetUSD:  300,
    },
    {
        AgentDID:   "did:aroha:hotel-agent",
        Endpoint:   "http://hotels.example.com",
        Capability: "reserve-hotel",
        Params:     map[string]any{"city": "London", "nights": 3},
        BudgetUSD:  200,
    },
})

fmt.Println(result.Status) // "committed" or "compensated"
for _, step := range result.Steps {
    fmt.Println(step.CommitToken) // use for billing
}

Reputation Engine

import "github.com/projectmed99/aroha/sdks/go/aroha/reputation"

engine := reputation.NewBayesianEngine()

// Record an outcome after a capability call
engine.RecordOutcome(reputation.Outcome{
    AgentDID:     "did:aroha:flight-agent",
    Capability:   "reserve-flight",
    Satisfaction: reputation.Satisfied, // or Neutral, Dissatisfied
})

// Get the current score (0–1) for routing decisions
score := engine.Score("did:aroha:flight-agent", "reserve-flight")
fmt.Printf("Score: %.3f
", score)

// Thompson Sampling — sample a score for exploration
sampled := engine.Sample("did:aroha:flight-agent", "reserve-flight")

All Packages

aroha/cryptoEd25519 sign/verify, AES-GCM, random nonces
aroha/identityDID generation, did:aroha-web:, resolution
aroha/messagesEnvelope types, BuildEnvelope, ValidateEnvelope, NonceRegistry
aroha/transportArohaServer (net/http), ArohaClient
aroha/orchestratorSagaEngine, SagaStep, AgentSelector, CSNHandler
aroha/mandateSpendingMandate: IssueIntent, Attenuate, Verify
aroha/reputationBayesian Beta engine, Thompson Sampling