// Package adk provides ADK-Go tool wrappers for StemeDB. // // This package wraps the base StemeDB SDK with tool definitions suitable // for Google's Agent Development Kit (ADK-Go). It provides: // // - QueryTool - Query with lens-based resolution // - AssertTool - Create assertions with confidence // - ConstraintCheckTool - Pre-flight validation // - TraceTool - Audit trail queries // - SupersedeTool - Epoch/correction management // // Since ADK-Go is not yet publicly released, these tools use interface-based // design and can be adapted to any ADK implementation. // // Example usage: // // client := steme.NewClient("http://localhost:3000", signer) // tools := adk.AllTools(client) // // // Use tools with your ADK agent configuration // agent := configureAgent(tools) package adk import ( "context" "encoding/json" "fmt" "time" "github.com/orchard9/stemedb-go/steme" ) // Tool represents a generic ADK tool interface. // // This interface is designed to be compatible with any ADK implementation. type Tool interface { Name() string Description() string Execute(ctx context.Context, input []byte) ([]byte, error) } // EpistemeClient defines the interface for StemeDB operations. // // This interface allows for easy mocking and testing. type EpistemeClient interface { Query(ctx context.Context, params steme.QueryParams) (*steme.QueryResult, error) Assert(ctx context.Context, assertion steme.Assertion) (string, error) Trace(ctx context.Context, params steme.TraceParams) (*steme.TraceResult, error) Supersede(ctx context.Context, params steme.SupersedeParams) (*steme.SupersedeResult, error) } // QueryTool provides lens-based knowledge retrieval. // // Used by all agents to retrieve current knowledge with conflict resolution. type QueryTool struct { client EpistemeClient } // NewQueryTool creates a new Query tool. func NewQueryTool(client EpistemeClient) *QueryTool { return &QueryTool{client: client} } // Name returns the tool name. func (t *QueryTool) Name() string { return "episteme_query" } // Description returns the tool description. func (t *QueryTool) Description() string { return "Query Episteme knowledge graph with lens-based conflict resolution. " + "Returns resolved value with confidence score and source provenance. " + "CRITICAL: Always filter by lifecycle=approved for production decisions." } // Execute performs the query operation. func (t *QueryTool) Execute(ctx context.Context, input []byte) ([]byte, error) { var params QueryInput if err := json.Unmarshal(input, ¶ms); err != nil { return nil, fmt.Errorf("invalid query input: %w", err) } // Build StemeDB query params builder := steme.NewQuery() if params.Subject != "" { builder.WithSubject(params.Subject) } if params.Predicate != "" { builder.WithPredicate(params.Predicate) } if params.Lens != "" { lens := parseLen(params.Lens) builder.WithLens(lens) } if params.Lifecycle != "" { lifecycle := parseLifecycle(params.Lifecycle) builder.WithLifecycle(lifecycle) } // Execute query result, err := t.client.Query(ctx, builder.Build()) if err != nil { errorOutput, marshalErr := json.Marshal(QueryOutput{ Error: err.Error(), }) if marshalErr != nil { return nil, fmt.Errorf("query failed: %w", err) } return errorOutput, nil } // Convert to QueryOutput format output := convertQueryResult(result, params.MinConfidence) outputBytes, err := json.Marshal(output) if err != nil { return nil, fmt.Errorf("failed to marshal query output: %w", err) } return outputBytes, nil } // AssertTool stores knowledge with lifecycle and confidence. // // Used by Research Agent and Human Supervisor to store assertions. type AssertTool struct { client EpistemeClient } // NewAssertTool creates a new Assert tool. func NewAssertTool(client EpistemeClient) *AssertTool { return &AssertTool{client: client} } // Name returns the tool name. func (t *AssertTool) Name() string { return "episteme_assert" } // Description returns the tool description. func (t *AssertTool) Description() string { return "Store knowledge assertion in Episteme. " + "Always include source_hash for provenance. " + "Use confidence to express uncertainty. " + "Mark as lifecycle=proposed unless explicitly approved." } // Execute performs the assert operation. func (t *AssertTool) Execute(ctx context.Context, input []byte) ([]byte, error) { var params AssertInput if err := json.Unmarshal(input, ¶ms); err != nil { return nil, fmt.Errorf("invalid assert input: %w", err) } // Build assertion builder := steme.NewAssertion(params.Subject, params.Predicate) // Set object value based on type if err := setObjectValue(builder, params.Object); err != nil { errorOutput, marshalErr := json.Marshal(AssertOutput{ Success: false, Error: err.Error(), }) if marshalErr != nil { return nil, fmt.Errorf("invalid object value: %w", err) } return errorOutput, nil } builder.WithConfidence(float64(params.Confidence)). WithSourceHash(params.SourceHash) if params.Lifecycle != "" { lifecycle := parseLifecycle(params.Lifecycle) builder.WithLifecycle(lifecycle) } if params.ParentHash != "" { builder.WithParentHash(params.ParentHash) } // Execute assertion hash, err := t.client.Assert(ctx, builder.Build()) if err != nil { errorOutput, marshalErr := json.Marshal(AssertOutput{ Success: false, Error: err.Error(), }) if marshalErr != nil { return nil, fmt.Errorf("assertion failed: %w", err) } return errorOutput, nil } outputBytes, err := json.Marshal(AssertOutput{ Hash: hash, Success: true, }) if err != nil { return nil, fmt.Errorf("failed to marshal assert output: %w", err) } return outputBytes, nil } // ConstraintCheckTool provides pre-flight validation for Implementation Agent. // // Returns must-use and forbidden patterns for a given context. type ConstraintCheckTool struct { client EpistemeClient } // NewConstraintCheckTool creates a new ConstraintCheck tool. func NewConstraintCheckTool(client EpistemeClient) *ConstraintCheckTool { return &ConstraintCheckTool{client: client} } // Name returns the tool name. func (t *ConstraintCheckTool) Name() string { return "episteme_constraint_check" } // Description returns the tool description. func (t *ConstraintCheckTool) Description() string { return "Check for must-use and forbidden patterns before code generation. " + "Returns constraints with explanations for contrastive learning. " + "Implementation Agent MUST call this before writing code." } // Execute performs the constraint check. func (t *ConstraintCheckTool) Execute(ctx context.Context, input []byte) ([]byte, error) { var params ConstraintCheckInput if err := json.Unmarshal(input, ¶ms); err != nil { return nil, fmt.Errorf("invalid constraint check input: %w", err) } // Query for constraints in the given context // This queries for assertions with predicate "must_use" or "forbidden" builder := steme.NewQuery(). WithSubject(params.Context). WithLifecycle(steme.LifecycleApproved). // Only approved constraints WithLens(steme.LensAuthority) result, err := t.client.Query(ctx, builder.Build()) if err != nil { return nil, fmt.Errorf("constraint query failed: %w", err) } // Convert assertions to constraints constraints := convertToConstraints(result) outputBytes, err := json.Marshal(ConstraintCheckOutput{ Constraints: constraints, }) if err != nil { return nil, fmt.Errorf("failed to marshal constraint output: %w", err) } return outputBytes, nil } // TraceTool provides audit trail queries for incident investigation. // // Used by On-Call SRE and Human Supervisor to trace agent decisions. type TraceTool struct { client EpistemeClient } // NewTraceTool creates a new Trace tool. func NewTraceTool(client EpistemeClient) *TraceTool { return &TraceTool{client: client} } // Name returns the tool name. func (t *TraceTool) Name() string { return "episteme_trace" } // Description returns the tool description. func (t *TraceTool) Description() string { return "Trace agent queries for incident investigation. " + "Shows what an agent queried, when, and what values were returned. " + "CRITICAL for debugging production incidents." } // Execute performs the trace operation. func (t *TraceTool) Execute(ctx context.Context, input []byte) ([]byte, error) { var params TraceInput if err := json.Unmarshal(input, ¶ms); err != nil { return nil, fmt.Errorf("invalid trace input: %w", err) } // Build StemeDB trace params traceParams := steme.TraceParams{ AgentID: params.AgentID, From: params.From, To: params.To, Subject: params.Subject, Limit: 100, // Default limit } // Execute trace result, err := t.client.Trace(ctx, traceParams) if err != nil { return nil, fmt.Errorf("trace query failed: %w", err) } // Convert to TraceOutput format output := convertTraceResult(result) outputBytes, err := json.Marshal(output) if err != nil { return nil, fmt.Errorf("failed to marshal trace output: %w", err) } return outputBytes, nil } // SupersedeTool provides error correction with cascade tracking. // // Used by Human Supervisor to correct bad assertions. type SupersedeTool struct { client EpistemeClient } // NewSupersedeTool creates a new Supersede tool. func NewSupersedeTool(client EpistemeClient) *SupersedeTool { return &SupersedeTool{client: client} } // Name returns the tool name. func (t *SupersedeTool) Name() string { return "episteme_supersede" } // Description returns the tool description. func (t *SupersedeTool) Description() string { return "Supersede a bad assertion with correction tracking. " + "Shows cascade impact on downstream assertions. " + "Used for incident remediation and knowledge correction." } // Execute performs the supersede operation. func (t *SupersedeTool) Execute(ctx context.Context, input []byte) ([]byte, error) { var params SupersedeInput if err := json.Unmarshal(input, ¶ms); err != nil { return nil, fmt.Errorf("invalid supersede input: %w", err) } // Convert SupersessionType superType := parseSupersessionType(params.Type) // Build SupersedeParams supersedeParams := steme.SupersedeParams{ TargetHash: params.Hash, SupersessionType: superType, Reason: params.Reason, NewHash: params.NewHash, AgentID: params.AgentID, Signature: params.Signature, } // Execute supersede result, err := t.client.Supersede(ctx, supersedeParams) if err != nil { return nil, fmt.Errorf("supersede failed: %w", err) } // Convert to SupersedeOutput format output := SupersedeOutput{ Success: true, TargetHash: result.TargetHash, SupersessionType: string(result.SupersessionType), Timestamp: result.Timestamp, } outputBytes, err := json.Marshal(output) if err != nil { return nil, fmt.Errorf("failed to marshal supersede output: %w", err) } return outputBytes, nil } // AllTools returns all available Episteme tools. // // Use this for agent configuration: // // tools := adk.AllTools(client) func AllTools(client EpistemeClient) []Tool { return []Tool{ NewQueryTool(client), NewAssertTool(client), NewConstraintCheckTool(client), NewTraceTool(client), NewSupersedeTool(client), } } // Helper functions func parseLen(lens string) steme.Lens { switch lens { case "recency": return steme.LensRecency case "consensus": return steme.LensConsensus case "authority": return steme.LensAuthority case "vote_aware_consensus": return steme.LensVoteAwareConsensus case "trust_aware_authority": return steme.LensTrustAwareAuthority default: return steme.LensConsensus // Default to consensus } } func parseLifecycle(lifecycle string) steme.LifecycleStage { switch lifecycle { case "proposed": return steme.LifecycleProposed case "under_review": return steme.LifecycleUnderReview case "approved": return steme.LifecycleApproved case "deprecated": return steme.LifecycleDeprecated case "rejected": return steme.LifecycleRejected default: return steme.LifecycleProposed // Default to proposed } } func parseSupersessionType(t string) steme.SupersessionType { switch t { case "Invalidate", "invalidate": return steme.SupersessionInvalidate case "Temporal", "temporal": return steme.SupersessionTemporal case "Refinement", "refinement": return steme.SupersessionRefinement case "RequiresReview", "requires_review": return steme.SupersessionRequiresReview case "Additive", "additive": return steme.SupersessionAdditive default: return steme.SupersessionInvalidate // Default to invalidate } } func setObjectValue(builder *steme.AssertionBuilder, obj any) error { switch v := obj.(type) { case string: builder.WithText(v) case float64: builder.WithNumber(v) case bool: builder.WithBoolean(v) case map[string]any: // Handle ObjectValue from JSON if typeStr, ok := v["type"].(string); ok { if value, ok := v["value"]; ok { switch typeStr { case "Text": if str, ok := value.(string); ok { builder.WithText(str) } case "Number": if num, ok := value.(float64); ok { builder.WithNumber(num) } case "Boolean": if b, ok := value.(bool); ok { builder.WithBoolean(b) } case "Reference": if ref, ok := value.(string); ok { builder.WithReference(ref) } } } } default: return fmt.Errorf("unsupported object type: %T", obj) } return nil } func convertQueryResult(result *steme.QueryResult, minConfidence float32) QueryOutput { if len(result.Assertions) == 0 { return QueryOutput{ Error: "no results found", } } // Take the first result (lens already applied resolution) assertion := result.Assertions[0] // Check confidence threshold confidence := float32(assertion.Confidence) if minConfidence > 0 && confidence < minConfidence { return QueryOutput{ Value: assertion.Object.Value, Confidence: confidence, Lifecycle: string(assertion.Lifecycle), QueryID: generateQueryID(), Error: fmt.Sprintf("confidence %.2f below threshold %.2f", confidence, minConfidence), } } // Convert sources sources := make([]Source, 0, len(result.Assertions)) for _, a := range result.Assertions { sources = append(sources, Source{ Hash: a.Hash, SourceHash: a.SourceHash, Weight: float32(a.Confidence), }) } return QueryOutput{ Value: assertion.Object.Value, Confidence: confidence, Lifecycle: string(assertion.Lifecycle), Sources: sources, QueryID: generateQueryID(), } } func convertToConstraints(result *steme.QueryResult) []Constraint { constraints := make([]Constraint, 0) for _, assertion := range result.Assertions { constraint := Constraint{ Subject: assertion.Subject, } // Determine if this is a must-use or forbidden constraint // based on predicate switch assertion.Predicate { case "must_use": if text, ok := assertion.Object.Value.(string); ok { constraint.MustUse = text } case "forbidden": if text, ok := assertion.Object.Value.(string); ok { constraint.Forbidden = text } } // Extract reason from parent assertion if available // (This is a simplified implementation) constraint.Reason = "Approved organizational pattern" constraints = append(constraints, constraint) } return constraints } func generateQueryID() string { // Generate a unique query ID for audit trail // In production, this would be generated server-side return fmt.Sprintf("query_%d", time.Now().UnixNano()) } func convertTraceResult(result *steme.TraceResult) TraceOutput { queries := make([]QueryTrace, 0, len(result.Audits)) for _, audit := range result.Audits { // Extract subject and predicate from params subject := "" if audit.Params.Subject != nil { subject = *audit.Params.Subject } predicate := "" if audit.Params.Predicate != nil { predicate = *audit.Params.Predicate } // Extract lens (convert to lowercase for consistency) lens := "consensus" // Default if audit.Params.Lens != nil { lensVal := string(*audit.Params.Lens) // Convert "Consensus" -> "consensus", etc. switch lensVal { case "Recency": lens = "recency" case "Consensus": lens = "consensus" case "Authority": lens = "authority" case "VoteAwareConsensus": lens = "vote_aware_consensus" case "TrustAwareAuthority": lens = "trust_aware_authority" default: lens = "consensus" } } // Extract result value (simplified - just use hash if available) resultValue := "" if audit.ResultHash != nil { resultValue = *audit.ResultHash } // Extract contributing assertion hashes contributing := make([]string, 0, len(audit.ContributingAssertions)) for _, ca := range audit.ContributingAssertions { contributing = append(contributing, ca.AssertionHash) } queries = append(queries, QueryTrace{ QueryID: audit.QueryID, Timestamp: fmt.Sprintf("%d", audit.Timestamp), Subject: subject, Predicate: predicate, Lens: lens, Result: resultValue, Confidence: audit.ResultConfidence, Contributing: contributing, }) } return TraceOutput{ Queries: queries, } }