Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
26aec32
issue/replace-emoji-logging-with-structured-zerolog: Replace emoji-ba…
Feb 16, 2026
efa55a0
chore: add pipeline artifacts to .gitignore
Feb 16, 2026
26f27ae
Merge issue/01-replace-emoji-logging-with-structured-zerolog: Replace…
Feb 16, 2026
be4fd3e
chore: finalize repo for handoff
Feb 16, 2026
455dc41
issue/01-did-types-definitions: Define DID and Verifiable Credential …
Feb 16, 2026
c647e75
Merge issue/01-did-types-definitions: Create DID/VC type definitions …
Feb 16, 2026
e92c995
issue/02-did-client-http: Implement DIDClient HTTP communication layer
Feb 16, 2026
630fe58
Merge issue/02-did-client-http: Create the HTTP client that communica…
Feb 16, 2026
2d05ae1
issue/04-types-and-client-integration-test: Add lightweight integrati…
Feb 16, 2026
bb9c0b4
issue/03-did-manager-orchestration: Implement DIDManager orchestratio…
Feb 16, 2026
6476ec2
Merge issue/03-did-manager-orchestration: Create the DIDManager orche…
Feb 16, 2026
adc7f5a
Merge issue/04-types-and-client-integration-test: Add lightweight int…
Feb 16, 2026
1f7da0f
issue/agent-config-extension: Extend Agent Config and struct with DID…
Feb 16, 2026
076e9f2
issue/06-agent-did-initialization: Fix Authorization header and add i…
Feb 16, 2026
ea5efae
issue/agent-did-initialization: Fix authorization header bug and add …
Feb 16, 2026
928250e
Merge issue/06-agent-did-initialization: Add DID client and manager c…
Feb 16, 2026
fe07cd9
issue/08-documentation-and-examples: Add comprehensive DID/VC documen…
Feb 16, 2026
190d2e8
issue/07-execution-context-enrichment: Populate ExecutionContext DID …
Feb 16, 2026
5770710
Merge issue/07-execution-context-enrichment: Implement ExecutionConte…
Feb 16, 2026
2267245
Merge issue/08-documentation-and-examples: Add DID/VC documentation a…
Feb 16, 2026
89d57d4
issue/09-did-agent-integration: Add comprehensive end-to-end DID/VC i…
Feb 16, 2026
cbc9487
Merge issue/09-did-agent-integration: Add comprehensive end-to-end DI…
Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ reports/

# Typescript
.env

# Pipeline artifacts and worktrees
.artifacts/
.worktrees/
Binary file removed control-plane/.DS_Store
Binary file not shown.
Binary file removed control-plane/internal/.DS_Store
Binary file not shown.
36 changes: 18 additions & 18 deletions control-plane/internal/handlers/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,43 +61,43 @@ type ErrorResponse struct {
func SetMemoryHandler(storageProvider MemoryStorage) gin.HandlerFunc {
return func(c *gin.Context) {
ctx := c.Request.Context()
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: SetMemoryHandler called")
logger.Logger.Debug().Str("operation", "set_memory").Str("handler", "SetMemoryHandler").Msg("handler invoked")

var req SetMemoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.Logger.Debug().Err(err).Msg("πŸ” MEMORY_HANDLER_DEBUG: JSON binding failed")
logger.Logger.Debug().Err(err).Str("operation", "bind_request").Msg("failed to bind JSON request")
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "invalid_request",
Message: err.Error(),
Code: http.StatusBadRequest,
})
return
}
logger.Logger.Debug().Msgf("πŸ” MEMORY_HANDLER_DEBUG: Request parsed successfully: key=%s", req.Key)
logger.Logger.Debug().Str("operation", "parse_request").Str("key", req.Key).Msg("request parsed")

scope, scopeID := resolveScope(c, req.Scope)
logger.Logger.Debug().Msgf("πŸ” MEMORY_HANDLER_DEBUG: Scope resolved: scope=%s, scopeID=%s", scope, scopeID)
logger.Logger.Debug().Str("operation", "resolve_scope").Str("scope", scope).Str("scope_id", scopeID).Msg("scope resolved")

// Get existing memory value for event publishing
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Getting existing memory value...")
logger.Logger.Debug().Str("operation", "get_existing").Str("key", req.Key).Msg("retrieving existing memory value")
var previousData json.RawMessage
if existingMemory, err := storageProvider.GetMemory(ctx, scope, scopeID, req.Key); err == nil {
previousData = existingMemory.Data
}
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Existing memory check completed")
logger.Logger.Debug().Str("operation", "get_existing").Bool("exists", previousData != nil).Msg("existing memory check completed")

logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Marshaling data to JSON...")
logger.Logger.Debug().Str("operation", "marshal_data").Str("key", req.Key).Msg("marshaling data to JSON")
dataJSON, err := marshalDataWithLogging(req.Data, "memory_data")
if err != nil {
logger.Logger.Error().Err(err).Msg("❌ MEMORY_MARSHAL_ERROR: Failed to marshal memory data")
logger.Logger.Error().Err(err).Str("operation", "marshal").Str("key", req.Key).Msg("failed to marshal memory data")
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "marshal_error",
Message: err.Error(),
Code: http.StatusBadRequest,
})
return
}
logger.Logger.Debug().Msgf("πŸ” MEMORY_HANDLER_DEBUG: JSON marshaling successful, length: %d", len(dataJSON))
logger.Logger.Debug().Str("operation", "marshal_data").Int("size_bytes", len(dataJSON)).Msg("data marshaled successfully")

now := time.Now()
memory := &types.Memory{
Expand All @@ -108,22 +108,22 @@ func SetMemoryHandler(storageProvider MemoryStorage) gin.HandlerFunc {
CreatedAt: now,
UpdatedAt: now,
}
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Memory object created")
logger.Logger.Debug().Str("operation", "create_memory_object").Str("key", req.Key).Msg("memory object created")

logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Calling storageProvider.SetMemory...")
logger.Logger.Debug().Str("operation", "store_memory").Str("key", req.Key).Msg("storing memory to backend")
if err := storageProvider.SetMemory(ctx, memory); err != nil {
logger.Logger.Debug().Err(err).Msg("πŸ” MEMORY_HANDLER_DEBUG: SetMemory failed")
logger.Logger.Debug().Err(err).Str("operation", "store_memory").Str("key", req.Key).Msg("failed to store memory")
c.JSON(http.StatusInternalServerError, ErrorResponse{
Error: "storage_error",
Message: err.Error(),
Code: http.StatusInternalServerError,
})
return
}
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: SetMemory completed successfully")
logger.Logger.Debug().Str("operation", "store_memory").Str("key", req.Key).Msg("memory stored successfully")

// Publish memory change event
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Creating memory change event...")
logger.Logger.Debug().Str("operation", "create_event").Str("action", "set").Str("key", req.Key).Msg("creating memory change event")
event := &types.MemoryChangeEvent{
Type: "memory_change",
Scope: scope,
Expand All @@ -140,18 +140,18 @@ func SetMemoryHandler(storageProvider MemoryStorage) gin.HandlerFunc {
}

// Store event (don't fail the request if event storage fails)
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Storing event...")
logger.Logger.Debug().Str("operation", "store_event").Str("key", req.Key).Msg("storing memory change event")
if err := storageProvider.StoreEvent(ctx, event); err != nil {
// Log error but continue
logger.Logger.Warn().Err(err).Msg("Warning: Failed to store memory change event")
} else if err := storageProvider.PublishMemoryChange(ctx, *event); err != nil {
logger.Logger.Warn().Err(err).Msg("Warning: Failed to publish memory change event")
}
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Event storage completed")
logger.Logger.Debug().Str("operation", "store_event").Str("key", req.Key).Msg("event storage completed")

logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Sending response...")
logger.Logger.Debug().Str("operation", "send_response").Str("key", req.Key).Msg("sending response")
c.JSON(http.StatusOK, memory)
logger.Logger.Debug().Msg("πŸ” MEMORY_HANDLER_DEBUG: Response sent successfully")
logger.Logger.Debug().Str("operation", "send_response").Str("key", req.Key).Msg("response sent")
}
}

Expand Down
8 changes: 4 additions & 4 deletions control-plane/internal/handlers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ import (
// marshalDataWithLogging marshals data to JSON with proper error handling and logging
func marshalDataWithLogging(data interface{}, fieldName string) ([]byte, error) {
if data == nil {
logger.Logger.Debug().Msgf("πŸ” MARSHAL_DEBUG: %s is nil, returning null", fieldName)
logger.Logger.Debug().Str("operation", "marshal").Str("field_name", fieldName).Bool("is_nil", true).Msg("field is nil")
return []byte("null"), nil
}

// Log the type and content of data being marshaled
logger.Logger.Debug().Msgf("πŸ” MARSHAL_DEBUG: Marshaling %s (type: %T)", fieldName, data)
logger.Logger.Debug().Str("operation", "marshal").Str("field_name", fieldName).Str("type", fmt.Sprintf("%T", data)).Msg("marshaling field")

// Attempt to marshal with detailed error reporting
jsonData, err := json.Marshal(data)
if err != nil {
logger.Logger.Error().Err(err).Msgf("❌ MARSHAL_ERROR: Failed to marshal %s (type: %T): %v", fieldName, data, data)
logger.Logger.Error().Err(err).Str("operation", "marshal").Str("field_name", fieldName).Str("type", fmt.Sprintf("%T", data)).Msg("failed to marshal field")
return nil, fmt.Errorf("failed to marshal %s: %w", fieldName, err)
}

logger.Logger.Debug().Msgf("βœ… MARSHAL_SUCCESS: Successfully marshaled %s (%d bytes): %s", fieldName, len(jsonData), string(jsonData))
logger.Logger.Debug().Str("operation", "marshal").Str("field_name", fieldName).Int("size_bytes", len(jsonData)).Msg("field marshaled successfully")
return jsonData, nil
}
Binary file removed control-plane/internal/storage/.DS_Store
Binary file not shown.
Binary file removed control-plane/web/.DS_Store
Binary file not shown.
37 changes: 37 additions & 0 deletions sdk/go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,43 @@ func main() {
- `client`: Low-level HTTP client for the AgentField control plane.
- `types`: Shared data structures and contracts.
- `ai`: Helpers for interacting with AI providers via the control plane.
- `did`: Decentralized Identity (DID) and Verifiable Credentials (VC) for compliance audit trails.

## DID/VC Features

Enable cryptographic identity management and tamper-proof audit trails for compliance-grade workflows:

```go
// Enable DID/VC in agent configuration
agent, err := agentfieldagent.New(agentfieldagent.Config{
NodeID: "my-agent",
AgentFieldURL: "https://control-plane.example.com",
Token: "your-api-token",
VCEnabled: true, // Enable DIDs and verifiable credentials
})

// Get the agent's DID
if agent.DID().IsEnabled() {
agentDID := agent.DID().GetAgentDID()
log.Printf("Agent DID: %s", agentDID)
}

// Generate credentials for executions (W3C compliant)
cred, err := agent.DID().GenerateCredential(context.Background(), did.GenerateCredentialOptions{
ExecutionID: "exec-123",
InputData: map[string]any{"query": "analyze"},
OutputData: map[string]any{"result": 42},
Status: "succeeded",
})

// Export audit trail for compliance
export, err := agent.DID().ExportAuditTrail(context.Background(), did.AuditTrailFilter{
WorkflowID: stringPtr("workflow-xyz"),
Limit: intPtr(1000),
})
```

For detailed usage, see the [`did` package documentation](./did/README.md).

## Testing

Expand Down
75 changes: 75 additions & 0 deletions sdk/go/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/Agent-Field/agentfield/sdk/go/ai"
"github.com/Agent-Field/agentfield/sdk/go/client"
"github.com/Agent-Field/agentfield/sdk/go/did"
"github.com/Agent-Field/agentfield/sdk/go/types"
)

Expand All @@ -39,6 +40,11 @@ type ExecutionContext struct {
AgentNodeID string
ReasonerName string
StartedAt time.Time

// DID fields (optional, populated if VCEnabled)
CallerDID string
TargetDID string
AgentNodeDID string
}

func init() {
Expand Down Expand Up @@ -179,6 +185,12 @@ type Config struct {
// MemoryBackend allows plugging in a custom memory storage backend.
// Optional. If nil, an in-memory backend is used (data lost on restart).
MemoryBackend MemoryBackend

// VCEnabled enables Decentralized Identity and Verifiable Credentials.
// When true, the agent registers with the DID system and can generate
// credentials for compliance audit trails. When false (default), all DID
// operations are disabled and return empty results. Optional; default: false.
VCEnabled bool
}

// CLIConfig controls CLI behaviour and presentation.
Expand All @@ -201,6 +213,7 @@ type Agent struct {
reasoners map[string]*Reasoner
aiClient *ai.Client // AI/LLM client
memory *Memory // Memory system for state management
did *did.DIDManager // DID manager for identity and credentials

serverMu sync.RWMutex
server *http.Server
Expand Down Expand Up @@ -277,6 +290,38 @@ func New(cfg Config) (*Agent, error) {
a.client = c
}

// Initialize DID/VC if enabled
if cfg.VCEnabled && strings.TrimSpace(cfg.AgentFieldURL) != "" {
headers := make(map[string]string)
if cfg.Token != "" {
headers["Authorization"] = "Bearer " + cfg.Token
}
didClient, err := did.NewDIDClient(
cfg.AgentFieldURL,
headers,
)
if err != nil {
a.logger.Printf("warning: failed to create DID client: %v", err)
a.did = did.NewDIDManager(nil, cfg.NodeID)
} else {
a.did = did.NewDIDManager(didClient, cfg.NodeID)

// Extract reasoners for registration
reasoners := make([]map[string]any, 0, len(a.reasoners))
for name := range a.reasoners {
reasoners = append(reasoners, map[string]any{"id": name})
}

// Register agent; non-fatal if fails
if err := a.did.RegisterAgent(context.Background(), reasoners, []map[string]any{}); err != nil {
a.logger.Printf("warning: DID registration failed: %v", err)
}
}
} else {
// VCEnabled=false: create disabled manager
a.did = did.NewDIDManager(nil, cfg.NodeID)
}

return a, nil
}

Expand Down Expand Up @@ -829,6 +874,21 @@ func (a *Agent) handleReasoner(w http.ResponseWriter, r *http.Request) {
execCtx.RootWorkflowID = execCtx.WorkflowID
}

// Populate DID fields if VCEnabled
if a.cfg.VCEnabled && a.did != nil {
// CallerDID: resolved from ReasonerName in the execution context.
// In the handler context, ReasonerName is the target reasoner.
// Per AC3, use GetFunctionDID(ec.ReasonerName) which falls back to agent DID.
execCtx.CallerDID = a.did.GetFunctionDID(execCtx.ReasonerName)

// TargetDID: resolved from the target function name (the reasoner being invoked).
// Per AC4, targetFunctionName is derived from routing logic (the URL path).
execCtx.TargetDID = a.did.GetFunctionDID(name)

// AgentNodeDID: the agent's own DID per AC4.
execCtx.AgentNodeDID = a.did.GetAgentDID()
}

ctx := contextWithExecution(r.Context(), execCtx)

// In serverless mode we want a synchronous execution so the control plane can return
Expand Down Expand Up @@ -1278,3 +1338,18 @@ func ExecutionContextFrom(ctx context.Context) ExecutionContext {
func (a *Agent) Memory() *Memory {
return a.memory
}

// DID returns the agent's DID manager for identity and credential operations.
// DID provides methods for accessing agent and function DIDs, generating verifiable
// credentials, and exporting audit trails. The manager is always present but may be
// disabled if VCEnabled is not set in the config.
//
// Example usage:
//
// if agent.DID().IsEnabled() {
// agentDID := agent.DID().GetAgentDID()
// credential, err := agent.DID().GenerateCredential(ctx, opts)
// }
func (a *Agent) DID() *did.DIDManager {
return a.did
}
Loading
Loading