WIP: Save agent roles integration work before CHORUS rebrand
- Agent roles and coordination features - Chat API integration testing - New configuration and workspace management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
292
workspace/hcfs_manager.go
Normal file
292
workspace/hcfs_manager.go
Normal file
@@ -0,0 +1,292 @@
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anthonyrawlins/bzzz/pkg/config"
|
||||
)
|
||||
|
||||
// HCFSWorkspaceManager manages agent workspaces using HCFS as the backing store
|
||||
type HCFSWorkspaceManager struct {
|
||||
hcfsAPIURL string
|
||||
mountPath string
|
||||
client *http.Client
|
||||
activeSpaces map[string]*HCFSWorkspace
|
||||
}
|
||||
|
||||
// HCFSWorkspace represents an HCFS-backed workspace for an agent
|
||||
type HCFSWorkspace struct {
|
||||
ID string `json:"id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
TaskID string `json:"task_id"`
|
||||
HCFSPath string `json:"hcfs_path"`
|
||||
LocalMount string `json:"local_mount"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
LastUsed time.Time `json:"last_used"`
|
||||
}
|
||||
|
||||
// HCFSContextRequest represents a request to create context in HCFS
|
||||
type HCFSContextRequest struct {
|
||||
Path string `json:"path"`
|
||||
Content string `json:"content"`
|
||||
Summary string `json:"summary"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
// HCFSContextResponse represents the response from HCFS context creation
|
||||
type HCFSContextResponse struct {
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
// NewHCFSWorkspaceManager creates a new HCFS workspace manager
|
||||
func NewHCFSWorkspaceManager(hcfsAPIURL string, mountPath string) *HCFSWorkspaceManager {
|
||||
return &HCFSWorkspaceManager{
|
||||
hcfsAPIURL: hcfsAPIURL,
|
||||
mountPath: mountPath,
|
||||
client: &http.Client{Timeout: 30 * time.Second},
|
||||
activeSpaces: make(map[string]*HCFSWorkspace),
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWorkspace creates a new HCFS-backed workspace for an agent
|
||||
func (m *HCFSWorkspaceManager) CreateWorkspace(ctx context.Context, agentID, taskID string, agentConfig *config.AgentConfig) (*HCFSWorkspace, error) {
|
||||
// Generate unique workspace ID
|
||||
workspaceID := fmt.Sprintf("bzzz-workspace-%s-%d", agentID, time.Now().Unix())
|
||||
|
||||
// Create HCFS path for the workspace
|
||||
hcfsPath := fmt.Sprintf("/agents/%s/workspaces/%s", agentID, workspaceID)
|
||||
|
||||
// Create workspace context in HCFS
|
||||
contextReq := HCFSContextRequest{
|
||||
Path: hcfsPath,
|
||||
Content: fmt.Sprintf("Workspace for agent %s, task %s", agentID, taskID),
|
||||
Summary: fmt.Sprintf("Agent workspace: %s", workspaceID),
|
||||
Metadata: map[string]interface{}{
|
||||
"agent_id": agentID,
|
||||
"task_id": taskID,
|
||||
"workspace_id": workspaceID,
|
||||
"created_by": "bzzz_sandbox",
|
||||
"workspace_type": "agent_sandbox",
|
||||
},
|
||||
}
|
||||
|
||||
contextID, err := m.createHCFSContext(ctx, contextReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create HCFS context: %w", err)
|
||||
}
|
||||
|
||||
// Create local mount point using HCFS FUSE
|
||||
localMount := filepath.Join(m.mountPath, workspaceID)
|
||||
if err := m.createHCFSMount(ctx, hcfsPath, localMount); err != nil {
|
||||
return nil, fmt.Errorf("failed to create HCFS mount: %w", err)
|
||||
}
|
||||
|
||||
workspace := &HCFSWorkspace{
|
||||
ID: contextID,
|
||||
AgentID: agentID,
|
||||
TaskID: taskID,
|
||||
HCFSPath: hcfsPath,
|
||||
LocalMount: localMount,
|
||||
CreatedAt: time.Now(),
|
||||
LastUsed: time.Now(),
|
||||
}
|
||||
|
||||
// Track active workspace
|
||||
m.activeSpaces[workspaceID] = workspace
|
||||
|
||||
fmt.Printf("✅ HCFS workspace created: %s -> %s\n", hcfsPath, localMount)
|
||||
return workspace, nil
|
||||
}
|
||||
|
||||
// DestroyWorkspace cleans up an HCFS workspace
|
||||
func (m *HCFSWorkspaceManager) DestroyWorkspace(ctx context.Context, workspace *HCFSWorkspace) error {
|
||||
if workspace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmount HCFS FUSE mount
|
||||
if err := m.unmountHCFS(workspace.LocalMount); err != nil {
|
||||
fmt.Printf("⚠️ Warning: failed to unmount HCFS: %v\n", err)
|
||||
}
|
||||
|
||||
// Update HCFS context with final state
|
||||
if err := m.finalizeWorkspaceContext(ctx, workspace); err != nil {
|
||||
fmt.Printf("⚠️ Warning: failed to finalize workspace context: %v\n", err)
|
||||
}
|
||||
|
||||
// Remove from active workspaces
|
||||
delete(m.activeSpaces, workspace.ID)
|
||||
|
||||
fmt.Printf("✅ HCFS workspace destroyed: %s\n", workspace.HCFSPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWorkspacePath returns the local path for container mounting
|
||||
func (m *HCFSWorkspaceManager) GetWorkspacePath(workspace *HCFSWorkspace) string {
|
||||
if workspace == nil {
|
||||
return ""
|
||||
}
|
||||
return workspace.LocalMount
|
||||
}
|
||||
|
||||
// UpdateWorkspaceUsage updates the last used timestamp
|
||||
func (m *HCFSWorkspaceManager) UpdateWorkspaceUsage(workspace *HCFSWorkspace) {
|
||||
if workspace != nil {
|
||||
workspace.LastUsed = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
// StoreWorkspaceArtifacts stores final workspace artifacts in HCFS
|
||||
func (m *HCFSWorkspaceManager) StoreWorkspaceArtifacts(ctx context.Context, workspace *HCFSWorkspace, artifacts map[string]string) error {
|
||||
if workspace == nil {
|
||||
return fmt.Errorf("workspace is nil")
|
||||
}
|
||||
|
||||
// Store each artifact as a separate context in HCFS
|
||||
for artifactName, content := range artifacts {
|
||||
artifactPath := fmt.Sprintf("%s/artifacts/%s", workspace.HCFSPath, artifactName)
|
||||
|
||||
artifactReq := HCFSContextRequest{
|
||||
Path: artifactPath,
|
||||
Content: content,
|
||||
Summary: fmt.Sprintf("Artifact from workspace %s: %s", workspace.ID, artifactName),
|
||||
Metadata: map[string]interface{}{
|
||||
"workspace_id": workspace.ID,
|
||||
"agent_id": workspace.AgentID,
|
||||
"task_id": workspace.TaskID,
|
||||
"artifact_name": artifactName,
|
||||
"artifact_type": "workspace_output",
|
||||
"created_by": "bzzz_sandbox",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := m.createHCFSContext(ctx, artifactReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store artifact %s: %w", artifactName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createHCFSContext creates a new context in HCFS
|
||||
func (m *HCFSWorkspaceManager) createHCFSContext(ctx context.Context, req HCFSContextRequest) (string, error) {
|
||||
reqBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", m.hcfsAPIURL+"/contexts", strings.NewReader(string(reqBody)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := m.client.Do(httpReq)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to make request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("HCFS API returned status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var contextResp HCFSContextResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&contextResp); err != nil {
|
||||
return "", fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return contextResp.ID, nil
|
||||
}
|
||||
|
||||
// createHCFSMount creates a FUSE mount for the HCFS path
|
||||
func (m *HCFSWorkspaceManager) createHCFSMount(ctx context.Context, hcfsPath, localPath string) error {
|
||||
// Ensure the mount directory exists
|
||||
if err := os.MkdirAll(localPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create mount directory: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Implement HCFS FUSE mount
|
||||
// For now, we'll create a placeholder directory structure
|
||||
// In a full implementation, this would mount the HCFS path using FUSE
|
||||
fmt.Printf("🔗 Creating HCFS FUSE mount: %s -> %s\n", hcfsPath, localPath)
|
||||
|
||||
// Create basic workspace structure
|
||||
subdirs := []string{"src", "build", "output", "logs"}
|
||||
for _, subdir := range subdirs {
|
||||
if err := os.MkdirAll(filepath.Join(localPath, subdir), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create workspace subdir %s: %w", subdir, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unmountHCFS unmounts the HCFS FUSE mount
|
||||
func (m *HCFSWorkspaceManager) unmountHCFS(localPath string) error {
|
||||
// TODO: Implement HCFS FUSE unmount
|
||||
// For now, we'll just remove the directory
|
||||
fmt.Printf("🔌 Unmounting HCFS: %s\n", localPath)
|
||||
|
||||
return os.RemoveAll(localPath)
|
||||
}
|
||||
|
||||
// finalizeWorkspaceContext updates the workspace context with final metadata
|
||||
func (m *HCFSWorkspaceManager) finalizeWorkspaceContext(ctx context.Context, workspace *HCFSWorkspace) error {
|
||||
// Update workspace context with completion metadata
|
||||
finalReq := HCFSContextRequest{
|
||||
Path: workspace.HCFSPath + "/completion",
|
||||
Content: fmt.Sprintf("Workspace %s completed at %s", workspace.ID, time.Now().Format(time.RFC3339)),
|
||||
Summary: fmt.Sprintf("Completion record for workspace %s", workspace.ID),
|
||||
Metadata: map[string]interface{}{
|
||||
"workspace_id": workspace.ID,
|
||||
"agent_id": workspace.AgentID,
|
||||
"task_id": workspace.TaskID,
|
||||
"completed_at": time.Now().Format(time.RFC3339),
|
||||
"total_duration": time.Since(workspace.CreatedAt).Seconds(),
|
||||
"record_type": "workspace_completion",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := m.createHCFSContext(ctx, finalReq)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetActiveWorkspaces returns all currently active workspaces
|
||||
func (m *HCFSWorkspaceManager) GetActiveWorkspaces() map[string]*HCFSWorkspace {
|
||||
return m.activeSpaces
|
||||
}
|
||||
|
||||
// CleanupIdleWorkspaces removes workspaces that haven't been used recently
|
||||
func (m *HCFSWorkspaceManager) CleanupIdleWorkspaces(ctx context.Context, maxIdleTime time.Duration) error {
|
||||
now := time.Now()
|
||||
var toCleanup []*HCFSWorkspace
|
||||
|
||||
for _, workspace := range m.activeSpaces {
|
||||
if now.Sub(workspace.LastUsed) > maxIdleTime {
|
||||
toCleanup = append(toCleanup, workspace)
|
||||
}
|
||||
}
|
||||
|
||||
for _, workspace := range toCleanup {
|
||||
if err := m.DestroyWorkspace(ctx, workspace); err != nil {
|
||||
fmt.Printf("⚠️ Failed to cleanup idle workspace %s: %v\n", workspace.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user