stemedb/.claude/guides/integrations/adk-go-episteme.md
jordan 1ce4004807 feat: Complete Phase 2 (The Cortex) - query, lens, and API layers
This commit adds the read path (Cortex) to complement the write path (Spine):

## Crates
- stemedb-api: HTTP API with axum + utoipa OpenAPI
  - /v1/assert, /v1/query, /v1/epoch, /v1/skeptic, /v1/trace, /v1/audit
  - Metered endpoints with quota enforcement
  - Ed25519 signature verification
- stemedb-lens: Truth resolution lenses
  - RecencyLens, ConsensusLens, ConfidenceLens
  - VoteAwareConsensusLens (Ballot Box pattern)
  - TrustAwareAuthorityLens (The Hive pattern)
  - SkepticLens (conflict analysis)
  - EpochAwareLens (paradigm-safe queries)
- stemedb-query: Query engine with materialized views

## Storage Extensions
- VoteStore: Vote aggregation with cached counts
- TrustRankStore: Agent reputation with decay
- AuditStore: Query audit trail
- IndexStore: SP/P/S index structures
- SupersessionStore: Epoch supersession chains

## SDKs
- sdk/go/steme: Go HTTP client with Ed25519 signing
- sdk/go/adk: ADK-Go tools for AI agents

## Documentation
- Updated CLAUDE.md, architecture.md, roadmap.md
- New ai-lookup entries for all services
- Use case docs for consumer health intelligence
- Arena roadmap for simulation advancement

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:22:44 -07:00

16 KiB

ADK-Go + Episteme Integration Guide

Purpose: Technical reference for integrating AI agents with Episteme using Google's Agent Development Kit for Go (ADK-Go). For use case context: See use-cases/agile-agent-team.md

Overview

This guide shows how AI agents integrate with Episteme for:

  • Query - Retrieve knowledge with lens-based resolution
  • Assert - Store knowledge with lifecycle and confidence
  • Constraint Check - Pre-flight validation before actions
  • Trace - Audit trail for incident investigation
  • Supersede - Correct errors with cascade tracking

Each agent type has specific patterns for tool usage and callback integration.


Tool Definitions

Every Episteme operation is exposed as an ADK-Go tool:

package episteme

import (
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/functiontool"
)

// === QUERY TOOL ===
// Used by: All agents

type QueryInput struct {
    Subject       string  `json:"subject" jsonschema:"Entity to query (e.g., auth/jwt)"`
    Predicate     string  `json:"predicate" jsonschema:"Relation to query (e.g., signing_algorithm)"`
    Lens          string  `json:"lens,omitempty" jsonschema:"Resolution: consensus, authority, recency, constraints"`
    Lifecycle     string  `json:"lifecycle,omitempty" jsonschema:"Filter: proposed, approved, deprecated"`
    MinConfidence float32 `json:"min_confidence,omitempty" jsonschema:"Minimum confidence threshold (0.0-1.0)"`
    AsOf          string  `json:"as_of,omitempty" jsonschema:"Time-travel: ISO8601 timestamp"`
}

type QueryOutput struct {
    Value      interface{} `json:"value"`
    Confidence float32     `json:"confidence"`
    Lifecycle  string      `json:"lifecycle"`
    Sources    []Source    `json:"sources"`
    QueryID    string      `json:"query_id"`  // CRITICAL: for audit trail
}

type Source struct {
    Hash       string  `json:"hash"`
    SourceHash string  `json:"source_hash"`
    Weight     float32 `json:"weight"`
}

func queryEpisteme(ctx tool.Context, input QueryInput) QueryOutput {
    // Call Episteme API
    result, err := epistemeClient.Query(ctx, input)
    if err != nil {
        return QueryOutput{Error: err.Error()}
    }
    return result
}

// === ASSERT TOOL ===
// Used by: Research Agent, Human Supervisor

type AssertInput struct {
    Subject    string      `json:"subject" jsonschema:"Entity being described"`
    Predicate  string      `json:"predicate" jsonschema:"Relation being asserted"`
    Object     interface{} `json:"object" jsonschema:"Value being claimed"`
    SourceHash string      `json:"source_hash" jsonschema:"BLAKE3 hash of evidence"`
    Confidence float32     `json:"confidence" jsonschema:"Certainty level (0.0-1.0)"`
    Lifecycle  string      `json:"lifecycle,omitempty" jsonschema:"proposed, under_review, approved"`
    ParentHash string      `json:"parent_hash,omitempty" jsonschema:"Hash of assertion being updated"`
    Meta       *AssertMeta `json:"meta,omitempty" jsonschema:"Negative constraints and metadata"`
}

type AssertMeta struct {
    ForbiddenAlternative string `json:"forbidden_alternative,omitempty"`
    Reason               string `json:"reason,omitempty"`
}

type AssertOutput struct {
    Hash    string `json:"hash"`
    Success bool   `json:"success"`
    Error   string `json:"error,omitempty"`
}

// === CONSTRAINT CHECK TOOL ===
// Used by: Implementation Agent (pre-flight)

type ConstraintCheckInput struct {
    Context string `json:"context" jsonschema:"Domain context (e.g., python_http, auth_jwt)"`
}

type ConstraintCheckOutput struct {
    Constraints []Constraint `json:"constraints"`
}

type Constraint struct {
    Subject   string `json:"subject"`
    MustUse   string `json:"must_use,omitempty"`
    Forbidden string `json:"forbidden,omitempty"`
    Reason    string `json:"reason"`
}

// === TRACE TOOL ===
// Used by: On-Call SRE, Human Supervisor

type TraceInput struct {
    AgentID string `json:"agent_id" jsonschema:"Agent to trace"`
    From    string `json:"from" jsonschema:"Start time (ISO8601 or relative like -6h)"`
    To      string `json:"to,omitempty" jsonschema:"End time (default: now)"`
    Subject string `json:"subject,omitempty" jsonschema:"Filter by subject pattern (e.g., auth/*)"`
}

type TraceOutput struct {
    Queries []QueryTrace `json:"queries"`
}

type QueryTrace struct {
    QueryID      string   `json:"query_id"`
    Timestamp    string   `json:"timestamp"`
    Subject      string   `json:"subject"`
    Predicate    string   `json:"predicate"`
    Lens         string   `json:"lens"`
    Result       string   `json:"result"`
    Confidence   float32  `json:"confidence"`
    Contributing []string `json:"contributing_assertions"`
}

// === SUPERSEDE TOOL ===
// Used by: Human Supervisor

type SupersedeInput struct {
    Hash   string `json:"hash" jsonschema:"Assertion hash to supersede"`
    Reason string `json:"reason" jsonschema:"Why this is being superseded"`
    Type   string `json:"type" jsonschema:"Invalidate, Temporal, RequiresReview, Additive"`
}

type SupersedeOutput struct {
    NewHash            string   `json:"new_hash"`
    AffectedAssertions []string `json:"affected_assertions"`
}

Callback Integration

Callbacks are critical for enforcing constraints and maintaining audit trails:

import (
    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model"
    "google.golang.org/adk/tool"
)

// Implementation Agent with BeforeToolCallback for constraint checking
implementationAgent, err := llmagent.New(llmagent.Config{
    Name:        "implementation_agent",
    Model:       model,
    Description: "Writes code against current approved patterns",
    Instruction: "You write code. Always use approved patterns only.",
    Tools:       []tool.Tool{queryTool, constraintTool},

    // CRITICAL: Check constraints BEFORE any tool that generates code
    BeforeToolCallback: func(ctx agent.CallbackContext, call *tool.Call) (*tool.Call, error) {
        // If agent is about to write code, check constraints first
        if needsConstraintCheck(call) {
            context := extractDomainContext(call)  // e.g., "python_http"
            constraints, err := checkConstraints(ctx, context)
            if err != nil {
                return nil, fmt.Errorf("constraint check failed: %w", err)
            }

            for _, c := range constraints {
                if violatesConstraint(call, c) {
                    return nil, fmt.Errorf("blocked: %s is forbidden - %s",
                        c.Forbidden, c.Reason)
                }
            }
        }
        return call, nil
    },

    // Log decisions for audit trail
    AfterModelCallback: func(ctx agent.CallbackContext, resp *model.LLMResponse) (*model.LLMResponse, error) {
        decision := extractDecision(resp)
        logToEpisteme(ctx, AuditEntry{
            AgentID:   ctx.Agent().Name(),
            Timestamp: time.Now(),
            Decision:  decision,
            SessionID: ctx.Session().ID(),
        })
        return resp, nil
    },
})

// Lead Orchestrator with confidence threshold escalation
leadOrchestrator, err := llmagent.New(llmagent.Config{
    Name:        "lead_orchestrator",
    Model:       model,
    Description: "Coordinates agent team, routes work based on knowledge",
    Instruction: `You coordinate the agent team. Query Episteme for current state.
                  If confidence < 0.8, escalate to human supervisor.`,
    Tools:       []tool.Tool{queryTool, delegateTool},
    OutputKey:   "orchestrator_decision",  // Pass to downstream agents

    // Check confidence scores and escalate if too low
    AfterToolCallback: func(ctx agent.CallbackContext, call *tool.Call, result *tool.Result) (*tool.Result, error) {
        if call.Name == "episteme_query" {
            var queryResult QueryOutput
            json.Unmarshal(result.Output, &queryResult)

            if queryResult.Confidence < 0.8 {
                // Mark for human review
                ctx.Session().State().Set("needs_human_review", true)
                ctx.Session().State().Set("low_confidence_query", queryResult.QueryID)
            }
        }
        return result, nil
    },
})

Agent-Specific Patterns

Lead Orchestrator

Fast queries with confidence thresholds for routing decisions:

// Query current auth config with confidence threshold
result := queryEpisteme(ctx, QueryInput{
    Subject:       "auth/jwt",
    Predicate:     "signing_algorithm",
    Lens:          "authority",
    MinConfidence: 0.8,
})

if result.Confidence < 0.8 {
    // Escalate to human - can't route confidently
    ctx.Session().State().Set("escalation_reason",
        fmt.Sprintf("Confidence %.2f below threshold for %s/%s",
            result.Confidence, result.Subject, result.Predicate))
    return escalateToHuman(ctx)
}

// Route to implementation agent with high confidence
ctx.Session().State().Set("auth_config", result.Value)
return delegateTo(ctx, "implementation_agent")

Implementation Agent

Queries approved patterns only, with pre-flight constraint checks:

// CRITICAL: Filter to approved lifecycle ONLY
result := queryEpisteme(ctx, QueryInput{
    Subject:   "auth/jwt",
    Predicate: "signing_algorithm",
    Lens:      "authority",
    Lifecycle: "approved",  // Never use proposed patterns!
})

if result.Lifecycle != "approved" {
    return fmt.Errorf("no approved pattern found for %s/%s",
        input.Subject, input.Predicate)
}

// Pre-flight: check for forbidden alternatives
constraints := checkConstraints(ctx, "auth_jwt")
for _, c := range constraints.Constraints {
    // Agent now knows: "use X, don't use Y, because Z"
    // This enables contrastive learning
}

Research Agent

Stores conflicting information with uncertainty:

// Store first source
assertKnowledge(ctx, AssertInput{
    Subject:    "jwt_rotation",
    Predicate:  "best_practice",
    Object:     "rotate_daily",
    SourceHash: hashURL("https://security.io/jwt-best-practices"),
    Confidence: 0.7,  // Express uncertainty
    Lifecycle:  "proposed",  // Not approved yet
})

// Store contradicting source - don't flatten!
assertKnowledge(ctx, AssertInput{
    Subject:    "jwt_rotation",
    Predicate:  "best_practice",
    Object:     "rotate_hourly",
    SourceHash: hashURL("https://owasp.org/jwt-rotation"),
    Confidence: 0.8,
    Lifecycle:  "proposed",
})

// Let Lead Orchestrator resolve via Lens::Consensus or Lens::Authority

Human Supervisor

Time-travel queries and corrections with impact analysis:

// What was believed during the incident?
result := queryEpisteme(ctx, QueryInput{
    Subject:   "auth/jwt",
    Predicate: "signing_algorithm",
    AsOf:      "2024-01-15T21:00:00Z",  // Time of incident
})

// Trace agent queries during the incident window
traces := traceQueries(ctx, TraceInput{
    AgentID: "deployment-agent",
    From:    "2024-01-15T20:00:00Z",
    To:      "2024-01-15T22:00:00Z",
    Subject: "auth/*",
})

// Found the bug: agent queried without lifecycle filter
// Correct the record
impact := supersede(ctx, SupersedeInput{
    Hash:   badAssertionHash,
    Reason: "Proposal treated as approved - agent didn't filter by lifecycle",
    Type:   "Invalidate",
})

// impact.AffectedAssertions shows downstream effects
fmt.Printf("Corrected. %d downstream assertions affected.\n",
    len(impact.AffectedAssertions))

On-Call SRE

Sub-second trace commands for incident investigation:

// It's 3am. Auth is broken. What happened?

// Step 1: What did deployment agent query? (<500ms required)
traces := traceQueries(ctx, TraceInput{
    AgentID: "deployment-agent",
    From:    "-6h",
    Subject: "auth/*",
})

for _, t := range traces.Queries {
    fmt.Printf("[%s] %s/%s via %s -> %s (conf: %.2f)\n",
        t.Timestamp, t.Subject, t.Predicate,
        t.Lens, t.Result, t.Confidence)

    if len(t.Contributing) > 0 {
        fmt.Printf("  Contributing: %v\n", t.Contributing)
    }
}

// Step 2: What changed in the last 24 hours?
diff := queryDiff(ctx, DiffInput{
    Subject: "auth/jwt",
    From:    "-24h",
})

// Step 3: Mark bad assertion (found via trace)
supersede(ctx, SupersedeInput{
    Hash:   badAssertionHash,
    Reason: "RFC proposal incorrectly treated as approved config",
    Type:   "Invalidate",
})

// Step 4: Verify fix
result := queryEpisteme(ctx, QueryInput{
    Subject:   "auth/jwt",
    Predicate: "signing_algorithm",
    Lifecycle: "approved",
})
fmt.Printf("Current approved value: %s\n", result.Value)

Multi-Agent Pipeline Example

Complete example showing agents coordinating through Episteme:

package main

import (
    "context"
    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/agent/sequentialagent"
    "google.golang.org/adk/model/gemini"
)

func main() {
    ctx := context.Background()
    model, _ := gemini.NewModel(ctx, "gemini-3-flash-preview", nil)

    // Research Agent: Discovers and stores knowledge
    researchAgent, _ := llmagent.New(llmagent.Config{
        Name:        "research_agent",
        Model:       model,
        Description: "Researches and stores knowledge with source attribution",
        Instruction: `Research the topic. Store findings with confidence scores.
                      Mark conflicting sources - don't resolve them.`,
        Tools:       []tool.Tool{assertTool, searchTool},
        OutputKey:   "research_findings",
    })

    // Lead Orchestrator: Queries and routes
    leadAgent, _ := llmagent.New(llmagent.Config{
        Name:        "lead_orchestrator",
        Model:       model,
        Description: "Coordinates team based on current knowledge state",
        Instruction: `Query Episteme for current approved patterns.
                      Research findings available in {research_findings}.
                      If confidence >= 0.8, route to implementation.
                      If confidence < 0.8, flag for human review.`,
        Tools:       []tool.Tool{queryTool},
        OutputKey:   "routing_decision",

        AfterToolCallback: confidenceEscalationCallback,
    })

    // Implementation Agent: Writes code
    implAgent, _ := llmagent.New(llmagent.Config{
        Name:        "implementation_agent",
        Model:       model,
        Description: "Writes code against approved patterns only",
        Instruction: `Write code for the task.
                      Routing decision in {routing_decision}.
                      ONLY use approved patterns. Check constraints first.`,
        Tools:       []tool.Tool{queryTool, constraintTool, writeCodeTool},
        OutputKey:   "implementation",

        BeforeToolCallback: constraintEnforcementCallback,
    })

    // Sequential pipeline
    pipeline, _ := sequentialagent.New(sequentialagent.Config{
        AgentConfig: agent.Config{
            Name:        "AgileDevPipeline",
            Description: "Research -> Route -> Implement with Episteme coordination",
            SubAgents:   []agent.Agent{researchAgent, leadAgent, implAgent},
        },
    })

    // Run the pipeline
    runner := agent.NewRunner(pipeline, sessionService)
    for event, err := range runner.Run(ctx, userID, sessionID, userMessage, nil) {
        if err != nil {
            log.Printf("Pipeline error: %v", err)
        }
        handleEvent(event)
    }
}

Session State vs. Episteme

Use Case Mechanism Why
Agent-to-agent handoff in pipeline Session State + OutputKey Ephemeral, same conversation
Permanent organizational knowledge Episteme Assert Persists across sessions/agents
"What was decided in this conversation" Session State Temporary, conversation-scoped
"What should all agents know forever" Episteme Assert + lifecycle: approved Permanent, query-audited
Temporary working data temp: prefixed state keys Auto-cleared after session
Agent decision audit trail Episteme QueryAudit Immutable, time-travel enabled

CLI Commands for Debugging

# Trace what an agent queried
episteme trace --agent deployment-agent --time "6 hours ago" --subject "auth/*"

# Show assertion provenance
episteme show --hash $ASSERTION_HASH --provenance

# Supersede bad assertion
episteme supersede --hash $BAD_HASH --reason "Proposal treated as approved" \
    --type "Invalidate"

# See cascade impact
episteme cascade --root $BAD_HASH

# Verify correction
episteme query --subject auth/jwt --predicate signing_algorithm --lifecycle approved

SRE's 3am workflow: Query -> Trace -> Identify -> Correct -> Verify. Under 10 minutes.