# 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](../../../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:

```go
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:

```go
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:

```go
// 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:

```go
// 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:

```go
// 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:

```go
// 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:

```go
// 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:

```go
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

```bash
# 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.
