- Add complete UCXL address parser with BNF grammar validation - Implement temporal navigation system with bounds checking - Create UCXI HTTP server with REST-like operations - Add comprehensive test suite with 87 passing tests - Integrate with existing BZZZ architecture (opt-in via config) - Support semantic addressing with wildcards and version control Core Features: - UCXL address format: ucxl://agent:role@project:task/temporal/path - Temporal segments: *^, ~~N, ^^N, *~, *~N with navigation logic - UCXI endpoints: GET/PUT/POST/DELETE/ANNOUNCE operations - Production-ready with error handling and graceful shutdown 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1194 lines
38 KiB
Markdown
1194 lines
38 KiB
Markdown
# BZZZ v2 Implementation Roadmap: UCXL Integration
|
|
|
|
## Phase 1: Foundation Implementation (Weeks 1-2)
|
|
|
|
### Week 1: UCXL Address Parser Implementation
|
|
|
|
#### 1.1 Replace existing `pkg/protocol/uri.go` with UCXL support
|
|
|
|
**File:** `/pkg/protocol/ucxl_address.go`
|
|
|
|
```go
|
|
package protocol
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// UCXLAddress represents a parsed UCXL address with temporal navigation
|
|
// Grammar: ucxl://agent:role@project:task/temporal_segment/path[?query][#fragment]
|
|
type UCXLAddress struct {
|
|
// Core semantic addressing
|
|
Agent string `json:"agent"` // "gpt4", "claude", "any"
|
|
Role string `json:"role"` // "architect", "reviewer", "any"
|
|
Project string `json:"project"` // "bzzz", "chorus", "any"
|
|
Task string `json:"task"` // "v2-migration", "auth", "any"
|
|
|
|
// Temporal navigation
|
|
TemporalSegment string `json:"temporal_segment"` // "~~", "^^", "*^", "*~", ISO8601
|
|
|
|
// Resource path
|
|
Path string `json:"path"` // "/decisions/architecture.json"
|
|
|
|
// Standard URI components
|
|
Query string `json:"query,omitempty"`
|
|
Fragment string `json:"fragment,omitempty"`
|
|
Raw string `json:"raw"`
|
|
|
|
// Resolved temporal information
|
|
ResolvedTime *time.Time `json:"resolved_time,omitempty"`
|
|
}
|
|
|
|
// Temporal navigation constants
|
|
const (
|
|
UCXLScheme = "ucxl"
|
|
TemporalBackward = "~~" // Navigate backward in time
|
|
TemporalForward = "^^" // Navigate forward in time
|
|
TemporalLatest = "*^" // Latest entry
|
|
TemporalFirst = "*~" // First entry
|
|
AnyWildcard = "any" // Wildcard for any component
|
|
)
|
|
|
|
// Validation patterns for UCXL components
|
|
var (
|
|
agentPattern = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$|^any$`)
|
|
rolePattern = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$|^any$`)
|
|
projectPattern = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$|^any$`)
|
|
taskPattern = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$|^any$`)
|
|
temporalPattern = regexp.MustCompile(`^(~~|\^\^|\*\^|\*~|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*Z?)$`)
|
|
pathPattern = regexp.MustCompile(`^(/[a-zA-Z0-9\-_/.]*)?$`)
|
|
)
|
|
|
|
// ParseUCXLAddress parses a UCXL URI string
|
|
func ParseUCXLAddress(uri string) (*UCXLAddress, error) {
|
|
if uri == "" {
|
|
return nil, fmt.Errorf("empty UCXL address")
|
|
}
|
|
|
|
if !strings.HasPrefix(uri, UCXLScheme+"://") {
|
|
return nil, fmt.Errorf("invalid scheme: expected '%s'", UCXLScheme)
|
|
}
|
|
|
|
// Parse using standard URL parser
|
|
parsedURL, err := url.Parse(uri)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse UCXL address: %w", err)
|
|
}
|
|
|
|
// Extract agent:role from user info
|
|
userInfo := parsedURL.User
|
|
if userInfo == nil {
|
|
return nil, fmt.Errorf("missing agent:role information")
|
|
}
|
|
|
|
agent := userInfo.Username()
|
|
role, hasRole := userInfo.Password()
|
|
if !hasRole {
|
|
return nil, fmt.Errorf("missing role information")
|
|
}
|
|
|
|
// Extract project:task from host
|
|
hostParts := strings.Split(parsedURL.Host, ":")
|
|
if len(hostParts) != 2 {
|
|
return nil, fmt.Errorf("invalid project:task format")
|
|
}
|
|
|
|
project := hostParts[0]
|
|
task := hostParts[1]
|
|
|
|
// Parse temporal segment and path
|
|
pathParts := strings.SplitN(strings.TrimPrefix(parsedURL.Path, "/"), "/", 2)
|
|
temporalSegment := ""
|
|
resourcePath := ""
|
|
|
|
if len(pathParts) > 0 && pathParts[0] != "" {
|
|
temporalSegment = pathParts[0]
|
|
}
|
|
if len(pathParts) > 1 {
|
|
resourcePath = "/" + pathParts[1]
|
|
}
|
|
|
|
address := &UCXLAddress{
|
|
Agent: agent,
|
|
Role: role,
|
|
Project: project,
|
|
Task: task,
|
|
TemporalSegment: temporalSegment,
|
|
Path: resourcePath,
|
|
Query: parsedURL.RawQuery,
|
|
Fragment: parsedURL.Fragment,
|
|
Raw: uri,
|
|
}
|
|
|
|
if err := address.Validate(); err != nil {
|
|
return nil, fmt.Errorf("validation failed: %w", err)
|
|
}
|
|
|
|
return address, nil
|
|
}
|
|
|
|
// Validate validates UCXL address components
|
|
func (addr *UCXLAddress) Validate() error {
|
|
if !agentPattern.MatchString(addr.Agent) {
|
|
return fmt.Errorf("invalid agent: %s", addr.Agent)
|
|
}
|
|
if !rolePattern.MatchString(addr.Role) {
|
|
return fmt.Errorf("invalid role: %s", addr.Role)
|
|
}
|
|
if !projectPattern.MatchString(addr.Project) {
|
|
return fmt.Errorf("invalid project: %s", addr.Project)
|
|
}
|
|
if !taskPattern.MatchString(addr.Task) {
|
|
return fmt.Errorf("invalid task: %s", addr.Task)
|
|
}
|
|
if addr.TemporalSegment != "" && !temporalPattern.MatchString(addr.TemporalSegment) {
|
|
return fmt.Errorf("invalid temporal segment: %s", addr.TemporalSegment)
|
|
}
|
|
if !pathPattern.MatchString(addr.Path) {
|
|
return fmt.Errorf("invalid path: %s", addr.Path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ResolveTemporalSegment resolves temporal navigation tokens to actual timestamps
|
|
func (addr *UCXLAddress) ResolveTemporalSegment(navigator TemporalNavigator) error {
|
|
if addr.TemporalSegment == "" {
|
|
return nil
|
|
}
|
|
|
|
switch addr.TemporalSegment {
|
|
case TemporalLatest:
|
|
timestamp, err := navigator.GetLatestTimestamp(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
addr.ResolvedTime = ×tamp
|
|
case TemporalFirst:
|
|
timestamp, err := navigator.GetFirstTimestamp(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
addr.ResolvedTime = ×tamp
|
|
case TemporalBackward, TemporalForward:
|
|
// These require context of current position
|
|
return fmt.Errorf("relative navigation requires current context")
|
|
default:
|
|
// Parse as ISO8601 timestamp
|
|
timestamp, err := time.Parse(time.RFC3339, addr.TemporalSegment)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid timestamp format: %w", err)
|
|
}
|
|
addr.ResolvedTime = ×tamp
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Matches checks if this address matches another (with wildcard support)
|
|
func (addr *UCXLAddress) Matches(other *UCXLAddress) bool {
|
|
return componentMatches(addr.Agent, other.Agent) &&
|
|
componentMatches(addr.Role, other.Role) &&
|
|
componentMatches(addr.Project, other.Project) &&
|
|
componentMatches(addr.Task, other.Task) &&
|
|
pathMatches(addr.Path, other.Path)
|
|
}
|
|
|
|
func componentMatches(a, b string) bool {
|
|
return a == b || a == AnyWildcard || b == AnyWildcard
|
|
}
|
|
|
|
func pathMatches(a, b string) bool {
|
|
if a == "" || b == "" {
|
|
return true
|
|
}
|
|
return a == b
|
|
}
|
|
|
|
// String returns canonical string representation
|
|
func (addr *UCXLAddress) String() string {
|
|
uri := fmt.Sprintf("%s://%s:%s@%s:%s", UCXLScheme, addr.Agent, addr.Role, addr.Project, addr.Task)
|
|
|
|
if addr.TemporalSegment != "" {
|
|
uri += "/" + addr.TemporalSegment
|
|
}
|
|
|
|
if addr.Path != "" {
|
|
uri += addr.Path
|
|
}
|
|
|
|
if addr.Query != "" {
|
|
uri += "?" + addr.Query
|
|
}
|
|
|
|
if addr.Fragment != "" {
|
|
uri += "#" + addr.Fragment
|
|
}
|
|
|
|
return uri
|
|
}
|
|
|
|
// ToStorageKey generates a storage key for this address
|
|
func (addr *UCXLAddress) ToStorageKey() string {
|
|
key := fmt.Sprintf("%s/%s/%s/%s", addr.Agent, addr.Role, addr.Project, addr.Task)
|
|
|
|
if addr.ResolvedTime != nil {
|
|
key += "/" + addr.ResolvedTime.Format(time.RFC3339)
|
|
} else if addr.TemporalSegment != "" {
|
|
key += "/" + addr.TemporalSegment
|
|
}
|
|
|
|
if addr.Path != "" {
|
|
key += addr.Path
|
|
}
|
|
|
|
return key
|
|
}
|
|
```
|
|
|
|
#### 1.2 Temporal Navigator Interface
|
|
|
|
**File:** `/pkg/temporal/navigator.go`
|
|
|
|
```go
|
|
package temporal
|
|
|
|
import (
|
|
"time"
|
|
"github.com/anthonyrawlins/bzzz/pkg/protocol"
|
|
)
|
|
|
|
type TemporalNavigator interface {
|
|
GetLatestTimestamp(addr *protocol.UCXLAddress) (time.Time, error)
|
|
GetFirstTimestamp(addr *protocol.UCXLAddress) (time.Time, error)
|
|
NavigateBackward(addr *protocol.UCXLAddress, steps int) (time.Time, error)
|
|
NavigateForward(addr *protocol.UCXLAddress, steps int) (time.Time, error)
|
|
GetAtTime(addr *protocol.UCXLAddress, timestamp time.Time) (*protocol.UCXLAddress, error)
|
|
}
|
|
|
|
type TemporalIndex struct {
|
|
// Map address patterns to temporal entries
|
|
entries map[string][]TemporalEntry
|
|
}
|
|
|
|
type TemporalEntry struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Version int64 `json:"version"`
|
|
Address *protocol.UCXLAddress `json:"address"`
|
|
Checksum string `json:"checksum"`
|
|
}
|
|
```
|
|
|
|
### Week 2: UCXI Interface Server
|
|
|
|
#### 2.1 UCXI Server Implementation
|
|
|
|
**File:** `/pkg/ucxi/server.go`
|
|
|
|
```go
|
|
package ucxi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"github.com/gorilla/mux"
|
|
"github.com/anthonyrawlins/bzzz/pkg/protocol"
|
|
"github.com/anthonyrawlins/bzzz/pkg/storage"
|
|
"github.com/anthonyrawlins/bzzz/pkg/temporal"
|
|
)
|
|
|
|
type UCXIServer struct {
|
|
contextStore storage.ContextStore
|
|
temporalNav temporal.TemporalNavigator
|
|
router *mux.Router
|
|
port int
|
|
}
|
|
|
|
// Context entry structure
|
|
type ContextEntry struct {
|
|
Address *protocol.UCXLAddress `json:"address"`
|
|
Content map[string]interface{} `json:"content"`
|
|
Metadata ContextMetadata `json:"metadata"`
|
|
Version int64 `json:"version"`
|
|
Checksum string `json:"checksum"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type ContextMetadata struct {
|
|
ContentType string `json:"content_type"`
|
|
Size int64 `json:"size"`
|
|
Tags []string `json:"tags"`
|
|
Provenance string `json:"provenance"`
|
|
Relationships map[string]string `json:"relationships"`
|
|
}
|
|
|
|
func NewUCXIServer(store storage.ContextStore, nav temporal.TemporalNavigator, port int) *UCXIServer {
|
|
server := &UCXIServer{
|
|
contextStore: store,
|
|
temporalNav: nav,
|
|
router: mux.NewRouter(),
|
|
port: port,
|
|
}
|
|
server.setupRoutes()
|
|
return server
|
|
}
|
|
|
|
func (s *UCXIServer) setupRoutes() {
|
|
// UCXI operations
|
|
s.router.HandleFunc("/ucxi/{agent}:{role}@{project}:{task}/{temporal}/{path:.*}", s.handleGET).Methods("GET")
|
|
s.router.HandleFunc("/ucxi/{agent}:{role}@{project}:{task}/{temporal}/{path:.*}", s.handlePUT).Methods("PUT")
|
|
s.router.HandleFunc("/ucxi/{agent}:{role}@{project}:{task}/{temporal}/", s.handlePOST).Methods("POST")
|
|
s.router.HandleFunc("/ucxi/{agent}:{role}@{project}:{task}/{temporal}/{path:.*}", s.handleDELETE).Methods("DELETE")
|
|
s.router.HandleFunc("/ucxi/announce", s.handleANNOUNCE).Methods("POST")
|
|
|
|
// Extended operations
|
|
s.router.HandleFunc("/ucxi/navigate/{direction}", s.handleNAVIGATE).Methods("GET")
|
|
s.router.HandleFunc("/ucxi/query", s.handleQUERY).Methods("GET")
|
|
s.router.HandleFunc("/ucxi/subscribe", s.handleSUBSCRIBE).Methods("POST")
|
|
}
|
|
|
|
func (s *UCXIServer) handleGET(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
|
|
// Construct UCXL address from URL parameters
|
|
address := &protocol.UCXLAddress{
|
|
Agent: vars["agent"],
|
|
Role: vars["role"],
|
|
Project: vars["project"],
|
|
Task: vars["task"],
|
|
TemporalSegment: vars["temporal"],
|
|
Path: "/" + vars["path"],
|
|
}
|
|
|
|
// Resolve temporal navigation
|
|
if err := address.ResolveTemporalSegment(s.temporalNav); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Retrieve context
|
|
entry, err := s.contextStore.Retrieve(address)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(entry)
|
|
}
|
|
|
|
func (s *UCXIServer) handlePUT(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
|
|
var entry ContextEntry
|
|
if err := json.NewDecoder(r.Body).Decode(&entry); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Construct address
|
|
address := &protocol.UCXLAddress{
|
|
Agent: vars["agent"],
|
|
Role: vars["role"],
|
|
Project: vars["project"],
|
|
Task: vars["task"],
|
|
TemporalSegment: vars["temporal"],
|
|
Path: "/" + vars["path"],
|
|
}
|
|
|
|
entry.Address = address
|
|
entry.UpdatedAt = time.Now()
|
|
|
|
if err := s.contextStore.Store(address, &entry); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
}
|
|
|
|
// Additional handlers for POST, DELETE, ANNOUNCE, NAVIGATE, QUERY, SUBSCRIBE...
|
|
```
|
|
|
|
## Phase 2: Decision Graph & SLURP Integration (Weeks 3-6)
|
|
|
|
### Week 3-4: Decision Node Schema
|
|
|
|
#### 3.1 Decision Node Structure
|
|
|
|
**File:** `/pkg/decisions/schema.go`
|
|
|
|
```go
|
|
package decisions
|
|
|
|
import (
|
|
"time"
|
|
"github.com/anthonyrawlins/bzzz/pkg/protocol"
|
|
)
|
|
|
|
// DecisionNode represents a structured decision for SLURP ingestion
|
|
type DecisionNode struct {
|
|
DecisionID string `json:"decision_id"`
|
|
UCXLAddress string `json:"ucxl_address"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
AgentID string `json:"agent_id"`
|
|
DecisionType string `json:"decision_type"`
|
|
Context DecisionContext `json:"context"`
|
|
Justification Justification `json:"justification"`
|
|
Citations []Citation `json:"citations"`
|
|
Impacts []Impact `json:"impacts"`
|
|
Metadata DecisionMetadata `json:"metadata"`
|
|
}
|
|
|
|
type DecisionContext struct {
|
|
Project string `json:"project"`
|
|
Task string `json:"task"`
|
|
Scope string `json:"scope"`
|
|
Phase string `json:"phase"`
|
|
Environment string `json:"environment"`
|
|
Constraints map[string]interface{} `json:"constraints"`
|
|
}
|
|
|
|
type Justification struct {
|
|
Reasoning string `json:"reasoning"`
|
|
AlternativesConsidered []Alternative `json:"alternatives_considered"`
|
|
Criteria []string `json:"criteria"`
|
|
Confidence float64 `json:"confidence"`
|
|
RiskAssessment RiskAssessment `json:"risk_assessment"`
|
|
TradeOffs map[string]interface{} `json:"trade_offs"`
|
|
}
|
|
|
|
type Alternative struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Pros []string `json:"pros"`
|
|
Cons []string `json:"cons"`
|
|
Score float64 `json:"score"`
|
|
Rejected bool `json:"rejected"`
|
|
Reason string `json:"reason"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
type Citation struct {
|
|
Type string `json:"type"` // "justified_by", "references", "contradicts", "extends"
|
|
UCXLAddress string `json:"ucxl_address"`
|
|
Relevance string `json:"relevance"` // "high", "medium", "low"
|
|
Excerpt string `json:"excerpt"`
|
|
Strength float64 `json:"strength"` // 0.0 - 1.0
|
|
Verified bool `json:"verified"`
|
|
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
|
}
|
|
|
|
type Impact struct {
|
|
Type string `json:"type"` // "replaces", "modifies", "creates", "deprecates"
|
|
UCXLAddress string `json:"ucxl_address"`
|
|
Reason string `json:"reason"`
|
|
Severity string `json:"severity"` // "low", "medium", "high", "breaking"
|
|
Affected []string `json:"affected"`
|
|
Migration *MigrationInfo `json:"migration,omitempty"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
type MigrationInfo struct {
|
|
Required bool `json:"required"`
|
|
Automated bool `json:"automated"`
|
|
Instructions string `json:"instructions"`
|
|
Timeline string `json:"timeline"`
|
|
}
|
|
|
|
type RiskAssessment struct {
|
|
Level string `json:"level"` // "low", "medium", "high"
|
|
Factors []string `json:"factors"`
|
|
Mitigation []string `json:"mitigation"`
|
|
Monitoring []string `json:"monitoring"`
|
|
Escalation *EscalationInfo `json:"escalation,omitempty"`
|
|
}
|
|
|
|
type EscalationInfo struct {
|
|
Triggers []string `json:"triggers"`
|
|
Contacts []string `json:"contacts"`
|
|
Procedures []string `json:"procedures"`
|
|
}
|
|
|
|
type DecisionMetadata struct {
|
|
SourceRepository string `json:"source_repository"`
|
|
CommitSHA string `json:"commit_sha"`
|
|
PullRequestID string `json:"pull_request_id"`
|
|
ReviewedBy []string `json:"reviewed_by"`
|
|
ApprovedBy []string `json:"approved_by"`
|
|
Tags []string `json:"tags"`
|
|
Priority int `json:"priority"`
|
|
Visibility string `json:"visibility"` // "public", "internal", "private"
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
CustomFields map[string]interface{} `json:"custom_fields"`
|
|
}
|
|
|
|
// Decision types enumeration
|
|
const (
|
|
DecisionTypeArchitecture = "architecture_choice"
|
|
DecisionTypeImplementation = "implementation_approach"
|
|
DecisionTypeTechnology = "technology_selection"
|
|
DecisionTypeProcess = "process_definition"
|
|
DecisionTypeAPI = "api_design"
|
|
DecisionTypeDataModel = "data_model_design"
|
|
DecisionTypeSecurity = "security_requirement"
|
|
DecisionTypeDeployment = "deployment_strategy"
|
|
DecisionTypeRollback = "rollback_decision"
|
|
DecisionTypeDeprecation = "deprecation_notice"
|
|
)
|
|
|
|
// Citation types enumeration
|
|
const (
|
|
CitationJustifiedBy = "justified_by"
|
|
CitationReferences = "references"
|
|
CitationContradicts = "contradicts"
|
|
CitationExtends = "extends"
|
|
CitationSupersedes = "supersedes"
|
|
)
|
|
|
|
// Validation methods
|
|
func (dn *DecisionNode) Validate() error {
|
|
if dn.DecisionID == "" {
|
|
return fmt.Errorf("decision_id is required")
|
|
}
|
|
|
|
if dn.UCXLAddress == "" {
|
|
return fmt.Errorf("ucxl_address is required")
|
|
}
|
|
|
|
if _, err := protocol.ParseUCXLAddress(dn.UCXLAddress); err != nil {
|
|
return fmt.Errorf("invalid ucxl_address: %w", err)
|
|
}
|
|
|
|
if dn.AgentID == "" {
|
|
return fmt.Errorf("agent_id is required")
|
|
}
|
|
|
|
if dn.DecisionType == "" {
|
|
return fmt.Errorf("decision_type is required")
|
|
}
|
|
|
|
// Validate citations
|
|
for i, citation := range dn.Citations {
|
|
if citation.UCXLAddress == "" {
|
|
return fmt.Errorf("citation[%d] missing ucxl_address", i)
|
|
}
|
|
|
|
if _, err := protocol.ParseUCXLAddress(citation.UCXLAddress); err != nil {
|
|
return fmt.Errorf("citation[%d] invalid ucxl_address: %w", i, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (dn *DecisionNode) GenerateID() string {
|
|
hash := sha256.Sum256([]byte(dn.UCXLAddress + dn.AgentID + dn.Timestamp.Format(time.RFC3339)))
|
|
return fmt.Sprintf("%x", hash)[:16]
|
|
}
|
|
```
|
|
|
|
### Week 5-6: SLURP Integration
|
|
|
|
#### 5.1 SLURP Client Implementation
|
|
|
|
**File:** `/pkg/integration/slurp_client.go`
|
|
|
|
```go
|
|
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
"github.com/anthonyrawlins/bzzz/pkg/decisions"
|
|
)
|
|
|
|
type SLURPClient struct {
|
|
baseURL string
|
|
apiKey string
|
|
httpClient *http.Client
|
|
batchSize int
|
|
timeout time.Duration
|
|
}
|
|
|
|
type SLURPPublishRequest struct {
|
|
Decisions []decisions.DecisionNode `json:"decisions"`
|
|
Source string `json:"source"`
|
|
Version string `json:"version"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type SLURPPublishResponse struct {
|
|
Accepted []string `json:"accepted"`
|
|
Rejected []string `json:"rejected"`
|
|
Errors []string `json:"errors"`
|
|
}
|
|
|
|
func NewSLURPClient(baseURL, apiKey string) *SLURPClient {
|
|
return &SLURPClient{
|
|
baseURL: baseURL,
|
|
apiKey: apiKey,
|
|
httpClient: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
batchSize: 10,
|
|
timeout: 30 * time.Second,
|
|
}
|
|
}
|
|
|
|
func (sc *SLURPClient) PublishDecisions(nodes []decisions.DecisionNode) (*SLURPPublishResponse, error) {
|
|
// Validate all decisions before publishing
|
|
for i, node := range nodes {
|
|
if err := node.Validate(); err != nil {
|
|
return nil, fmt.Errorf("decision[%d] validation failed: %w", i, err)
|
|
}
|
|
}
|
|
|
|
request := SLURPPublishRequest{
|
|
Decisions: nodes,
|
|
Source: "bzzz-v2",
|
|
Version: "2.0.0",
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
jsonData, err := json.Marshal(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", sc.baseURL+"/api/v1/decisions", bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+sc.apiKey)
|
|
req.Header.Set("User-Agent", "bzzz-v2/2.0.0")
|
|
|
|
resp, err := sc.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("SLURP API error: %d", resp.StatusCode)
|
|
}
|
|
|
|
var response SLURPPublishResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &response, nil
|
|
}
|
|
|
|
func (sc *SLURPClient) QueryDecisions(query string) ([]decisions.DecisionNode, error) {
|
|
req, err := http.NewRequest("GET", sc.baseURL+"/api/v1/decisions/query", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
q := req.URL.Query()
|
|
q.Add("q", query)
|
|
req.URL.RawQuery = q.Encode()
|
|
|
|
req.Header.Set("Authorization", "Bearer "+sc.apiKey)
|
|
|
|
resp, err := sc.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var nodes []decisions.DecisionNode
|
|
if err := json.NewDecoder(resp.Body).Decode(&nodes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
```
|
|
|
|
## Phase 3: Agent Integration & Testing (Weeks 7-8)
|
|
|
|
### Week 7: GPT-4 Agent UCXL Integration
|
|
|
|
#### 7.1 Updated MCP Tools
|
|
|
|
**File:** `/mcp-server/src/tools/ucxi-tools.ts`
|
|
|
|
```typescript
|
|
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
|
|
export const ucxiTools: Record<string, Tool> = {
|
|
ucxi_get: {
|
|
name: "ucxi_get",
|
|
description: "Retrieve context from UCXL address with temporal navigation",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
address: {
|
|
type: "string",
|
|
description: "UCXL address (e.g., ucxl://gpt4:architect@bzzz:v2/*^/decisions.json)"
|
|
},
|
|
temporal: {
|
|
type: "string",
|
|
enum: ["~~", "^^", "*^", "*~"],
|
|
description: "Temporal navigation: ~~ (back), ^^ (forward), *^ (latest), *~ (first)"
|
|
}
|
|
},
|
|
required: ["address"]
|
|
}
|
|
},
|
|
|
|
ucxi_put: {
|
|
name: "ucxi_put",
|
|
description: "Store context at UCXL address",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
address: {
|
|
type: "string",
|
|
description: "UCXL address where to store context"
|
|
},
|
|
content: {
|
|
type: "object",
|
|
description: "Context content to store"
|
|
},
|
|
metadata: {
|
|
type: "object",
|
|
properties: {
|
|
content_type: { type: "string" },
|
|
tags: { type: "array", items: { type: "string" } },
|
|
provenance: { type: "string" }
|
|
},
|
|
description: "Context metadata"
|
|
}
|
|
},
|
|
required: ["address", "content"]
|
|
}
|
|
},
|
|
|
|
ucxi_publish_decision: {
|
|
name: "ucxi_publish_decision",
|
|
description: "Publish a structured decision to SLURP via UCXI",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
decision_type: {
|
|
type: "string",
|
|
enum: ["architecture_choice", "implementation_approach", "technology_selection", "api_design"],
|
|
description: "Type of decision being published"
|
|
},
|
|
reasoning: {
|
|
type: "string",
|
|
description: "Detailed reasoning for the decision"
|
|
},
|
|
alternatives: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
name: { type: "string" },
|
|
description: { type: "string" },
|
|
pros: { type: "array", items: { type: "string" } },
|
|
cons: { type: "array", items: { type: "string" } },
|
|
rejected: { type: "boolean" },
|
|
reason: { type: "string" }
|
|
}
|
|
},
|
|
description: "Alternative approaches considered"
|
|
},
|
|
citations: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
type: { type: "string", enum: ["justified_by", "references", "contradicts"] },
|
|
ucxl_address: { type: "string" },
|
|
relevance: { type: "string", enum: ["high", "medium", "low"] },
|
|
excerpt: { type: "string" }
|
|
}
|
|
},
|
|
description: "Citations supporting the decision"
|
|
},
|
|
impacts: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
type: { type: "string", enum: ["replaces", "modifies", "creates", "deprecates"] },
|
|
ucxl_address: { type: "string" },
|
|
reason: { type: "string" },
|
|
severity: { type: "string", enum: ["low", "medium", "high", "breaking"] }
|
|
}
|
|
},
|
|
description: "Impacts of this decision"
|
|
}
|
|
},
|
|
required: ["decision_type", "reasoning"]
|
|
}
|
|
},
|
|
|
|
ucxi_query: {
|
|
name: "ucxi_query",
|
|
description: "Query contexts matching UCXL address pattern",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
pattern: {
|
|
type: "string",
|
|
description: "UCXL address pattern with wildcards (e.g., any:architect@bzzz:*)"
|
|
},
|
|
temporal_range: {
|
|
type: "object",
|
|
properties: {
|
|
start: { type: "string", format: "date-time" },
|
|
end: { type: "string", format: "date-time" }
|
|
},
|
|
description: "Temporal range for search"
|
|
},
|
|
content_filter: {
|
|
type: "string",
|
|
description: "Full-text search filter for content"
|
|
}
|
|
},
|
|
required: ["pattern"]
|
|
}
|
|
},
|
|
|
|
ucxi_navigate: {
|
|
name: "ucxi_navigate",
|
|
description: "Navigate temporally through context history",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
current_address: {
|
|
type: "string",
|
|
description: "Current UCXL address position"
|
|
},
|
|
direction: {
|
|
type: "string",
|
|
enum: ["~~", "^^"],
|
|
description: "Navigation direction: ~~ (backward) or ^^ (forward)"
|
|
},
|
|
steps: {
|
|
type: "integer",
|
|
default: 1,
|
|
description: "Number of temporal steps to navigate"
|
|
}
|
|
},
|
|
required: ["current_address", "direction"]
|
|
}
|
|
},
|
|
|
|
ucxi_announce: {
|
|
name: "ucxi_announce",
|
|
description: "Announce context availability to P2P network",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
address: {
|
|
type: "string",
|
|
description: "UCXL address to announce"
|
|
},
|
|
capabilities: {
|
|
type: "array",
|
|
items: { type: "string" },
|
|
description: "Agent capabilities (roles, specializations)"
|
|
},
|
|
metadata: {
|
|
type: "object",
|
|
description: "Additional metadata about the context"
|
|
}
|
|
},
|
|
required: ["address"]
|
|
}
|
|
}
|
|
};
|
|
|
|
// Handler implementations
|
|
export async function handleUCXITool(name: string, args: any): Promise<any> {
|
|
const ucxiServerURL = process.env.UCXI_SERVER_URL || "http://localhost:8080";
|
|
|
|
switch (name) {
|
|
case "ucxi_get":
|
|
return await ucxiGet(ucxiServerURL, args.address, args.temporal);
|
|
|
|
case "ucxi_put":
|
|
return await ucxiPut(ucxiServerURL, args.address, args.content, args.metadata);
|
|
|
|
case "ucxi_publish_decision":
|
|
return await ucxiPublishDecision(ucxiServerURL, args);
|
|
|
|
case "ucxi_query":
|
|
return await ucxiQuery(ucxiServerURL, args.pattern, args.temporal_range, args.content_filter);
|
|
|
|
case "ucxi_navigate":
|
|
return await ucxiNavigate(ucxiServerURL, args.current_address, args.direction, args.steps);
|
|
|
|
case "ucxi_announce":
|
|
return await ucxiAnnounce(ucxiServerURL, args.address, args.capabilities, args.metadata);
|
|
|
|
default:
|
|
throw new Error(`Unknown UCXI tool: ${name}`);
|
|
}
|
|
}
|
|
|
|
async function ucxiGet(serverURL: string, address: string, temporal?: string): Promise<any> {
|
|
const url = new URL(`/ucxi/${address}`, serverURL);
|
|
if (temporal) {
|
|
url.searchParams.set('temporal', temporal);
|
|
}
|
|
|
|
const response = await fetch(url.toString());
|
|
if (!response.ok) {
|
|
throw new Error(`UCXI GET failed: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
async function ucxiPublishDecision(serverURL: string, decision: any): Promise<any> {
|
|
const response = await fetch(`${serverURL}/ucxi/decisions/publish`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(decision)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Decision publishing failed: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
```
|
|
|
|
### Week 8: Integration Testing
|
|
|
|
#### 8.1 End-to-End Test Suite
|
|
|
|
**File:** `/test/integration/ucxl_e2e_test.go`
|
|
|
|
```go
|
|
package integration
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/anthonyrawlins/bzzz/pkg/protocol"
|
|
"github.com/anthonyrawlins/bzzz/pkg/ucxi"
|
|
"github.com/anthonyrawlins/bzzz/pkg/decisions"
|
|
)
|
|
|
|
func TestUCXLE2E(t *testing.T) {
|
|
// Start UCXI server
|
|
server := setupTestServer(t)
|
|
defer server.Close()
|
|
|
|
t.Run("AddressParsingAndResolution", func(t *testing.T) {
|
|
// Test UCXL address parsing
|
|
addr, err := protocol.ParseUCXLAddress("ucxl://gpt4:architect@bzzz:v2/*^/decisions/protocol.json")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "gpt4", addr.Agent)
|
|
assert.Equal(t, "architect", addr.Role)
|
|
assert.Equal(t, "bzzz", addr.Project)
|
|
assert.Equal(t, "v2", addr.Task)
|
|
assert.Equal(t, "*^", addr.TemporalSegment)
|
|
assert.Equal(t, "/decisions/protocol.json", addr.Path)
|
|
|
|
// Test storage key generation
|
|
storageKey := addr.ToStorageKey()
|
|
assert.Contains(t, storageKey, "gpt4/architect/bzzz/v2")
|
|
})
|
|
|
|
t.Run("TemporalNavigation", func(t *testing.T) {
|
|
// Create temporal sequence
|
|
baseAddr := "ucxl://gpt4:architect@bzzz:v2"
|
|
|
|
// Store contexts at different times
|
|
times := []time.Time{
|
|
time.Now().Add(-2 * time.Hour),
|
|
time.Now().Add(-1 * time.Hour),
|
|
time.Now(),
|
|
}
|
|
|
|
for i, timestamp := range times {
|
|
addr := fmt.Sprintf("%s/%s/decisions/protocol.json", baseAddr, timestamp.Format(time.RFC3339))
|
|
content := map[string]interface{}{
|
|
"version": i + 1,
|
|
"decision": fmt.Sprintf("Protocol decision v%d", i+1),
|
|
}
|
|
|
|
err := storeContext(server, addr, content)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// Test latest navigation (*^)
|
|
latestAddr := fmt.Sprintf("%s/*^/decisions/protocol.json", baseAddr)
|
|
entry, err := getContext(server, latestAddr)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, entry.Content["version"])
|
|
|
|
// Test first navigation (*~)
|
|
firstAddr := fmt.Sprintf("%s/*~/decisions/protocol.json", baseAddr)
|
|
entry, err = getContext(server, firstAddr)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, entry.Content["version"])
|
|
})
|
|
|
|
t.Run("DecisionPublishing", func(t *testing.T) {
|
|
// Create decision node
|
|
decision := &decisions.DecisionNode{
|
|
DecisionID: "test-decision-001",
|
|
UCXLAddress: "ucxl://gpt4:architect@bzzz:v2/2025-08-07T14:30:00Z/decisions/architecture.json",
|
|
Timestamp: time.Now(),
|
|
AgentID: "gpt4-test-agent",
|
|
DecisionType: decisions.DecisionTypeArchitecture,
|
|
Context: decisions.DecisionContext{
|
|
Project: "bzzz",
|
|
Task: "v2-migration",
|
|
Scope: "protocol-selection",
|
|
},
|
|
Justification: decisions.Justification{
|
|
Reasoning: "UCXL provides superior semantic addressing and temporal navigation capabilities",
|
|
AlternativesConsidered: []decisions.Alternative{
|
|
{
|
|
Name: "Extend bzzz:// protocol",
|
|
Description: "Add temporal navigation to existing protocol",
|
|
Pros: []string{"Minimal changes", "Backward compatibility"},
|
|
Cons: []string{"Limited semantic expressiveness", "Technical debt"},
|
|
Rejected: true,
|
|
Reason: "Insufficient semantic richness for complex context addressing",
|
|
},
|
|
},
|
|
Criteria: []string{"semantic_richness", "temporal_navigation", "ecosystem_compatibility"},
|
|
Confidence: 0.9,
|
|
},
|
|
Citations: []decisions.Citation{
|
|
{
|
|
Type: decisions.CitationJustifiedBy,
|
|
UCXLAddress: "ucxl://any:any@chorus:requirements/*~/analysis.md",
|
|
Relevance: "high",
|
|
Excerpt: "System must support temporal context navigation for audit trails",
|
|
Strength: 0.95,
|
|
},
|
|
},
|
|
Impacts: []decisions.Impact{
|
|
{
|
|
Type: "replaces",
|
|
UCXLAddress: "ucxl://any:any@bzzz:v1/*^/protocol.go",
|
|
Reason: "Migrating from bzzz:// to ucxl:// addressing scheme",
|
|
Severity: "breaking",
|
|
Affected: []string{"protocol", "addressing", "navigation"},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Validate decision
|
|
err := decision.Validate()
|
|
assert.NoError(t, err)
|
|
|
|
// Test decision publishing to SLURP
|
|
slurpClient := setupMockSLURPClient(t)
|
|
response, err := slurpClient.PublishDecisions([]decisions.DecisionNode{*decision})
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, response.Accepted, decision.DecisionID)
|
|
})
|
|
|
|
t.Run("P2PResolution", func(t *testing.T) {
|
|
// Test distributed address resolution
|
|
addr := "ucxl://any:architect@bzzz:*/*^/decisions"
|
|
|
|
// Query should return contexts from multiple agents
|
|
entries, err := queryContexts(server, addr)
|
|
assert.NoError(t, err)
|
|
assert.Greater(t, len(entries), 0)
|
|
|
|
// Test wildcard matching
|
|
for _, entry := range entries {
|
|
assert.Equal(t, "architect", entry.Address.Role)
|
|
assert.Equal(t, "bzzz", entry.Address.Project)
|
|
}
|
|
})
|
|
|
|
t.Run("CitationValidation", func(t *testing.T) {
|
|
// Test citation chain validation
|
|
validator := decisions.NewCitationValidator(server.ContextStore)
|
|
|
|
decision := &decisions.DecisionNode{
|
|
Citations: []decisions.Citation{
|
|
{
|
|
Type: decisions.CitationJustifiedBy,
|
|
UCXLAddress: "ucxl://nonexistent:agent@invalid:project/*^/missing.json",
|
|
Relevance: "high",
|
|
},
|
|
},
|
|
}
|
|
|
|
err := validator.ValidateCitations(decision)
|
|
assert.Error(t, err) // Should fail due to nonexistent citation
|
|
|
|
// Test valid citation
|
|
decision.Citations[0].UCXLAddress = "ucxl://gpt4:architect@bzzz:v2/*^/requirements.json"
|
|
|
|
// Store the referenced context
|
|
storeContext(server, decision.Citations[0].UCXLAddress, map[string]interface{}{
|
|
"requirement": "temporal navigation support",
|
|
})
|
|
|
|
err = validator.ValidateCitations(decision)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
```
|
|
|
|
## Database Schema & Storage
|
|
|
|
### Context Storage Schema (BadgerDB)
|
|
|
|
```go
|
|
// Storage keys
|
|
const (
|
|
ContextKeyPrefix = "ctx:" // ctx:agent/role/project/task/timestamp/path
|
|
TemporalKeyPrefix = "tmp:" // tmp:agent/role/project/task -> []TemporalEntry
|
|
IndexKeyPrefix = "idx:" // idx:field:value -> []address
|
|
MetadataKeyPrefix = "meta:" // meta:address -> ContextMetadata
|
|
)
|
|
|
|
// Indexed fields for efficient querying
|
|
var IndexedFields = []string{
|
|
"agent", "role", "project", "task",
|
|
"content_type", "tags", "timestamp",
|
|
}
|
|
|
|
// Key generation functions
|
|
func GenerateContextKey(addr *protocol.UCXLAddress) string {
|
|
return ContextKeyPrefix + addr.ToStorageKey()
|
|
}
|
|
|
|
func GenerateTemporalKey(addr *protocol.UCXLAddress) string {
|
|
return fmt.Sprintf("%s%s/%s/%s/%s",
|
|
TemporalKeyPrefix, addr.Agent, addr.Role, addr.Project, addr.Task)
|
|
}
|
|
```
|
|
|
|
This implementation roadmap provides the complete foundation for transforming BZZZ v2 into a UCXL-based semantic context publishing system while maintaining its distributed P2P architecture and integrating seamlessly with the CHORUS infrastructure.
|
|
|
|
## Key Files Created:
|
|
- `/home/tony/chorus/project-queues/active/BZZZ/BZZZ_V2_UCXL_DEVELOPMENT_PLAN.md`
|
|
- `/home/tony/chorus/project-queues/active/BZZZ/TECHNICAL_ARCHITECTURE.md`
|
|
- `/home/tony/chorus/project-queues/active/BZZZ/IMPLEMENTATION_ROADMAP.md`
|
|
|
|
The roadmap shows exactly how to implement UCXL address parsing, temporal navigation, decision node publishing to SLURP, P2P DHT resolution, and MCP integration with detailed code examples and test suites. |