diff --git a/pkg/agent/context.go b/pkg/agent/context.go index cf5ce2913..da5528c47 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/sipeed/picoclaw/pkg/aieos" "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/skills" @@ -16,6 +17,7 @@ import ( type ContextBuilder struct { workspace string + profilePath string // Optional override for aieos.json path skillsLoader *skills.SkillsLoader memory *MemoryStore tools *tools.ToolRegistry // Direct reference to tool registry @@ -106,16 +108,26 @@ func (cb *ContextBuilder) buildToolsSection() string { return sb.String() } +// SetProfilePath sets a custom path for the AIEOS profile, overriding the default workspace location. +func (cb *ContextBuilder) SetProfilePath(path string) { + cb.profilePath = path +} + func (cb *ContextBuilder) BuildSystemPrompt() string { parts := []string{} // Core identity section parts = append(parts, cb.getIdentity()) - // Bootstrap files - bootstrapContent := cb.LoadBootstrapFiles() - if bootstrapContent != "" { - parts = append(parts, bootstrapContent) + // AIEOS profile (preferred) or legacy .md bootstrap files (fallback) + if aieosContent := cb.loadAIEOSProfile(); aieosContent != "" { + parts = append(parts, aieosContent) + } else { + bootstrapContent := cb.LoadBootstrapFiles() + if bootstrapContent != "" { + logger.WarnCF("agent", "Using deprecated .md bootstrap files. Migrate to aieos.json.", nil) + parts = append(parts, bootstrapContent) + } } // Skills - show summary, AI can read full content with read_file tool @@ -253,6 +265,29 @@ func (cb *ContextBuilder) loadSkills() string { return "# Skill Definitions\n\n" + content } +// loadAIEOSProfile attempts to load and render an AIEOS profile. +// Returns the rendered text, or empty string if no profile is found or on error. +func (cb *ContextBuilder) loadAIEOSProfile() string { + path := cb.profilePath + if path == "" { + if !aieos.ProfileExists(cb.workspace) { + return "" + } + path = aieos.DefaultProfilePath(cb.workspace) + } + + profile, err := aieos.LoadProfile(path) + if err != nil { + logger.WarnCF("agent", "Failed to load AIEOS profile, falling back to .md files", map[string]interface{}{ + "path": path, + "error": err.Error(), + }) + return "" + } + + return aieos.RenderToPrompt(profile) +} + // GetSkillsInfo returns information about loaded skills. func (cb *ContextBuilder) GetSkillsInfo() map[string]interface{} { allSkills := cb.skillsLoader.ListSkills() diff --git a/pkg/agent/instance.go b/pkg/agent/instance.go index 54a5396e7..37ab9a166 100644 --- a/pkg/agent/instance.go +++ b/pkg/agent/instance.go @@ -69,6 +69,9 @@ func NewAgentInstance( agentName = agentCfg.Name subagents = agentCfg.Subagents skillsFilter = agentCfg.Skills + if agentCfg.Profile != "" { + contextBuilder.SetProfilePath(agentCfg.Profile) + } } maxIter := defaults.MaxToolIterations diff --git a/pkg/aieos/integration_test.go b/pkg/aieos/integration_test.go new file mode 100644 index 000000000..451ee43fe --- /dev/null +++ b/pkg/aieos/integration_test.go @@ -0,0 +1,82 @@ +package aieos_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/sipeed/picoclaw/pkg/aieos" + "github.com/stretchr/testify/require" +) + +// TestIntegration_FullPipeline tests the complete pipeline: +// load real aieos.json → validate → render to prompt text +func TestIntegration_FullPipeline(t *testing.T) { + // Use the actual workspace/aieos.json template + repoRoot := findRepoRoot(t) + templatePath := filepath.Join(repoRoot, "workspace", "aieos.json") + + profile, err := aieos.LoadProfile(templatePath) + require.NoError(t, err, "should load the real workspace/aieos.json template") + + require.Equal(t, "1.1", profile.Version) + require.Equal(t, "PicoClaw", profile.Identity.Name) + require.NotNil(t, profile.Psychology) + require.NotEmpty(t, profile.Capabilities) + + // Render to prompt + prompt := aieos.RenderToPrompt(profile) + + fmt.Println("=== AIEOS System Prompt Output ===") + fmt.Println(prompt) + fmt.Println("=== END ===") + + require.Contains(t, prompt, "You are **PicoClaw**") + require.Contains(t, prompt, "## Personality") + require.Contains(t, prompt, "curious and open") // openness 0.8 + require.Contains(t, prompt, "organized, thorough") // conscientiousness 0.9 + require.Contains(t, prompt, "calm, stable") // neuroticism 0.1 + require.Contains(t, prompt, "## Capabilities") + require.Contains(t, prompt, "web_search") + require.Contains(t, prompt, "## Communication Style") + require.Contains(t, prompt, "moderately detailed") // verbosity 0.3 (mid range) + require.Contains(t, prompt, "## Values & Goals") + require.Contains(t, prompt, "accuracy") + require.Contains(t, prompt, "## Boundaries") + require.Contains(t, prompt, "no_harmful_content") +} + +// TestIntegration_FallbackWhenNoAIEOS verifies that an empty workspace has no profile +func TestIntegration_FallbackWhenNoAIEOS(t *testing.T) { + emptyDir := t.TempDir() + require.False(t, aieos.ProfileExists(emptyDir)) +} + +// TestIntegration_FallbackOnInvalidJSON verifies graceful failure +func TestIntegration_FallbackOnInvalidJSON(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "aieos.json") + require.NoError(t, os.WriteFile(path, []byte(`{invalid`), 0644)) + + require.True(t, aieos.ProfileExists(dir)) + + _, err := aieos.LoadProfile(path) + require.Error(t, err, "should fail on invalid JSON") +} + +func findRepoRoot(t *testing.T) string { + t.Helper() + dir, err := os.Getwd() + require.NoError(t, err) + for { + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + return dir + } + parent := filepath.Dir(dir) + if parent == dir { + t.Fatal("could not find repo root (go.mod)") + } + dir = parent + } +} diff --git a/pkg/aieos/loader.go b/pkg/aieos/loader.go new file mode 100644 index 000000000..f989d0d8d --- /dev/null +++ b/pkg/aieos/loader.go @@ -0,0 +1,51 @@ +package aieos + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +const defaultFilename = "aieos.json" + +// DefaultProfilePath returns the default AIEOS profile path for a workspace. +func DefaultProfilePath(workspace string) string { + return filepath.Join(workspace, defaultFilename) +} + +// ProfileExists checks whether an aieos.json file exists in the workspace. +func ProfileExists(workspace string) bool { + _, err := os.Stat(DefaultProfilePath(workspace)) + return err == nil +} + +// LoadProfile reads and validates an AIEOS profile from the given path. +func LoadProfile(path string) (*Profile, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("aieos: read profile: %w", err) + } + + var p Profile + if err := json.Unmarshal(data, &p); err != nil { + return nil, fmt.Errorf("aieos: parse profile: %w", err) + } + + if err := validate(&p); err != nil { + return nil, err + } + + return &p, nil +} + +// validate performs minimal validation on a loaded profile. +func validate(p *Profile) error { + if p.Version == "" { + return fmt.Errorf("aieos: version is required") + } + if p.Identity.Name == "" { + return fmt.Errorf("aieos: identity.name is required") + } + return nil +} diff --git a/pkg/aieos/loader_test.go b/pkg/aieos/loader_test.go new file mode 100644 index 000000000..cebcf586b --- /dev/null +++ b/pkg/aieos/loader_test.go @@ -0,0 +1,105 @@ +package aieos + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadProfile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "aieos.json") + + data := `{ + "version": "1.1", + "identity": { + "name": "TestAgent", + "description": "A test agent", + "purpose": "Testing" + }, + "capabilities": [ + {"name": "search", "description": "Web search"} + ], + "psychology": { + "openness": 0.8, + "conscientiousness": 0.9, + "extraversion": 0.5, + "agreeableness": 0.85, + "neuroticism": 0.1 + } + }` + require.NoError(t, os.WriteFile(path, []byte(data), 0644)) + + p, err := LoadProfile(path) + require.NoError(t, err) + + assert.Equal(t, "1.1", p.Version) + assert.Equal(t, "TestAgent", p.Identity.Name) + assert.Equal(t, "A test agent", p.Identity.Description) + assert.Equal(t, "Testing", p.Identity.Purpose) + assert.Len(t, p.Capabilities, 1) + assert.Equal(t, "search", p.Capabilities[0].Name) + require.NotNil(t, p.Psychology) + assert.Equal(t, 0.8, p.Psychology.Openness) + assert.Equal(t, 0.1, p.Psychology.Neuroticism) +} + +func TestLoadProfileInvalidJSON(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "aieos.json") + + require.NoError(t, os.WriteFile(path, []byte(`{bad json`), 0644)) + + _, err := LoadProfile(path) + assert.Error(t, err) + assert.Contains(t, err.Error(), "parse profile") +} + +func TestLoadProfileMissingVersion(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "aieos.json") + + data := `{"identity": {"name": "Agent"}}` + require.NoError(t, os.WriteFile(path, []byte(data), 0644)) + + _, err := LoadProfile(path) + assert.Error(t, err) + assert.Contains(t, err.Error(), "version is required") +} + +func TestLoadProfileMissingName(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "aieos.json") + + data := `{"version": "1.1", "identity": {}}` + require.NoError(t, os.WriteFile(path, []byte(data), 0644)) + + _, err := LoadProfile(path) + assert.Error(t, err) + assert.Contains(t, err.Error(), "identity.name is required") +} + +func TestLoadProfileFileNotFound(t *testing.T) { + _, err := LoadProfile("/nonexistent/aieos.json") + assert.Error(t, err) + assert.Contains(t, err.Error(), "read profile") +} + +func TestProfileExists(t *testing.T) { + dir := t.TempDir() + + assert.False(t, ProfileExists(dir)) + + path := filepath.Join(dir, "aieos.json") + require.NoError(t, os.WriteFile(path, []byte(`{}`), 0644)) + + assert.True(t, ProfileExists(dir)) +} + +func TestDefaultProfilePath(t *testing.T) { + got := DefaultProfilePath("/home/user/workspace") + assert.Equal(t, "/home/user/workspace/aieos.json", got) +} diff --git a/pkg/aieos/profile.go b/pkg/aieos/profile.go new file mode 100644 index 000000000..1a754c253 --- /dev/null +++ b/pkg/aieos/profile.go @@ -0,0 +1,65 @@ +package aieos + +// Profile represents an AIEOS v1.1 (AI Entity Object Specification) profile. +// It defines the identity, personality, and behavior of an AI agent through +// structured JSON rather than free-form markdown. +type Profile struct { + Version string `json:"version"` + Identity Identity `json:"identity"` + Capabilities []Capability `json:"capabilities,omitempty"` + Psychology *Psychology `json:"psychology,omitempty"` + Linguistics *Linguistics `json:"linguistics,omitempty"` + Motivations *Motivations `json:"motivations,omitempty"` + Boundaries *Boundaries `json:"boundaries,omitempty"` + Metadata *Metadata `json:"metadata,omitempty"` +} + +// Identity defines who the agent is. +type Identity struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Version string `json:"version,omitempty"` + Purpose string `json:"purpose,omitempty"` + Philosophy string `json:"philosophy,omitempty"` +} + +// Capability describes a single agent capability. +type Capability struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +// Psychology holds OCEAN personality traits, each in the range 0.0-1.0. +type Psychology struct { + Openness float64 `json:"openness"` + Conscientiousness float64 `json:"conscientiousness"` + Extraversion float64 `json:"extraversion"` + Agreeableness float64 `json:"agreeableness"` + Neuroticism float64 `json:"neuroticism"` +} + +// Linguistics controls the agent's communication style. +type Linguistics struct { + Formality float64 `json:"formality"` // 0.0 (casual) to 1.0 (formal) + Verbosity float64 `json:"verbosity"` // 0.0 (terse) to 1.0 (verbose) + Idiolect []string `json:"idiolect,omitempty"` +} + +// Motivations captures the agent's values and goals. +type Motivations struct { + Values []string `json:"values,omitempty"` + Goals []string `json:"goals,omitempty"` +} + +// Boundaries defines hard and soft behavioral limits. +type Boundaries struct { + HardLimits []string `json:"hard_limits,omitempty"` + SoftLimits []string `json:"soft_limits,omitempty"` +} + +// Metadata holds authorship and licensing information. +type Metadata struct { + Author string `json:"author,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + License string `json:"license,omitempty"` +} diff --git a/pkg/aieos/render.go b/pkg/aieos/render.go new file mode 100644 index 000000000..965e4e23e --- /dev/null +++ b/pkg/aieos/render.go @@ -0,0 +1,160 @@ +package aieos + +import ( + "fmt" + "strings" +) + +// RenderToPrompt converts an AIEOS profile into natural-language text +// suitable for injection into a system prompt. +func RenderToPrompt(p *Profile) string { + var sb strings.Builder + + // Identity section + sb.WriteString("## Identity\n\n") + fmt.Fprintf(&sb, "You are **%s**.", p.Identity.Name) + if p.Identity.Description != "" { + sb.WriteString(" " + p.Identity.Description + ".") + } + sb.WriteString("\n") + if p.Identity.Purpose != "" { + fmt.Fprintf(&sb, "\n**Purpose**: %s\n", p.Identity.Purpose) + } + if p.Identity.Philosophy != "" { + fmt.Fprintf(&sb, "\n**Philosophy**: %s\n", p.Identity.Philosophy) + } + + // Psychology (OCEAN traits) + if p.Psychology != nil { + sb.WriteString("\n## Personality\n\n") + sb.WriteString(renderOCEAN(p.Psychology)) + } + + // Capabilities + if len(p.Capabilities) > 0 { + sb.WriteString("\n## Capabilities\n\n") + for _, c := range p.Capabilities { + if c.Description != "" { + fmt.Fprintf(&sb, "- **%s**: %s\n", c.Name, c.Description) + } else { + fmt.Fprintf(&sb, "- %s\n", c.Name) + } + } + } + + // Linguistics + if p.Linguistics != nil { + sb.WriteString("\n## Communication Style\n\n") + sb.WriteString(renderLinguistics(p.Linguistics)) + } + + // Motivations + if p.Motivations != nil { + hasContent := len(p.Motivations.Values) > 0 || len(p.Motivations.Goals) > 0 + if hasContent { + sb.WriteString("\n## Values & Goals\n\n") + if len(p.Motivations.Values) > 0 { + sb.WriteString("**Core values**: " + strings.Join(p.Motivations.Values, ", ") + "\n") + } + if len(p.Motivations.Goals) > 0 { + sb.WriteString("**Goals**: " + strings.Join(p.Motivations.Goals, ", ") + "\n") + } + } + } + + // Boundaries + if p.Boundaries != nil { + hasContent := len(p.Boundaries.HardLimits) > 0 || len(p.Boundaries.SoftLimits) > 0 + if hasContent { + sb.WriteString("\n## Boundaries\n\n") + if len(p.Boundaries.HardLimits) > 0 { + sb.WriteString("**Hard limits** (never violate):\n") + for _, l := range p.Boundaries.HardLimits { + fmt.Fprintf(&sb, "- %s\n", l) + } + } + if len(p.Boundaries.SoftLimits) > 0 { + sb.WriteString("**Soft limits** (prefer to follow):\n") + for _, l := range p.Boundaries.SoftLimits { + fmt.Fprintf(&sb, "- %s\n", l) + } + } + } + } + + return sb.String() +} + +// renderOCEAN converts OCEAN trait values (0.0-1.0) into natural language descriptions. +func renderOCEAN(psy *Psychology) string { + var lines []string + + lines = append(lines, traitDescription(psy.Openness, + "You are highly curious and open to new ideas.", + "You have a balanced approach to novelty and tradition.", + "You prefer familiar approaches and proven methods.", + )) + lines = append(lines, traitDescription(psy.Conscientiousness, + "You are highly organized, thorough, and detail-oriented.", + "You balance thoroughness with flexibility.", + "You take a relaxed approach to planning and organization.", + )) + lines = append(lines, traitDescription(psy.Extraversion, + "You are enthusiastic, talkative, and energetic in interactions.", + "You balance engagement with thoughtful reflection.", + "You are reserved and prefer concise, focused communication.", + )) + lines = append(lines, traitDescription(psy.Agreeableness, + "You are warm, cooperative, and prioritize the user's needs.", + "You balance helpfulness with honest feedback.", + "You are direct and straightforward, even when it may be uncomfortable.", + )) + lines = append(lines, traitDescription(psy.Neuroticism, + "You tend to be cautious and sensitive to potential issues.", + "You have a balanced emotional response to challenges.", + "You are calm, stable, and resilient under pressure.", + )) + + return strings.Join(lines, "\n") + "\n" +} + +// traitDescription maps a 0.0-1.0 value to one of three descriptions: high (>=0.7), mid (0.3-0.7), low (<0.3). +func traitDescription(value float64, high, mid, low string) string { + switch { + case value >= 0.7: + return high + case value >= 0.3: + return mid + default: + return low + } +} + +// renderLinguistics converts formality/verbosity into communication instructions. +func renderLinguistics(ling *Linguistics) string { + var lines []string + + switch { + case ling.Formality >= 0.7: + lines = append(lines, "Use formal, professional language.") + case ling.Formality >= 0.3: + lines = append(lines, "Use a balanced, conversational but professional tone.") + default: + lines = append(lines, "Use casual, friendly language.") + } + + switch { + case ling.Verbosity >= 0.7: + lines = append(lines, "Be thorough and detailed in your responses.") + case ling.Verbosity >= 0.3: + lines = append(lines, "Be moderately detailed — explain when helpful, be concise when possible.") + default: + lines = append(lines, "Be concise and to the point. Avoid unnecessary elaboration.") + } + + if len(ling.Idiolect) > 0 { + lines = append(lines, fmt.Sprintf("Use these characteristic phrases when appropriate: %s.", strings.Join(ling.Idiolect, ", "))) + } + + return strings.Join(lines, "\n") + "\n" +} diff --git a/pkg/aieos/render_test.go b/pkg/aieos/render_test.go new file mode 100644 index 000000000..2c57d14dc --- /dev/null +++ b/pkg/aieos/render_test.go @@ -0,0 +1,186 @@ +package aieos + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRenderToPromptFull(t *testing.T) { + p := &Profile{ + Version: "1.1", + Identity: Identity{ + Name: "PicoClaw", + Description: "Ultra-lightweight AI assistant", + Purpose: "Provide helpful AI assistance", + Philosophy: "Simplicity over complexity", + }, + Capabilities: []Capability{ + {Name: "web_search", Description: "Web search and content fetching"}, + {Name: "file_ops"}, + }, + Psychology: &Psychology{ + Openness: 0.8, + Conscientiousness: 0.9, + Extraversion: 0.5, + Agreeableness: 0.85, + Neuroticism: 0.1, + }, + Linguistics: &Linguistics{ + Formality: 0.5, + Verbosity: 0.3, + }, + Motivations: &Motivations{ + Values: []string{"accuracy", "transparency"}, + Goals: []string{"fast_assistant"}, + }, + Boundaries: &Boundaries{ + HardLimits: []string{"no_harmful_content"}, + SoftLimits: []string{"prefer_user_control"}, + }, + } + + result := RenderToPrompt(p) + + assert.Contains(t, result, "You are **PicoClaw**.") + assert.Contains(t, result, "Ultra-lightweight AI assistant") + assert.Contains(t, result, "**Purpose**: Provide helpful AI assistance") + assert.Contains(t, result, "**Philosophy**: Simplicity over complexity") + assert.Contains(t, result, "## Personality") + assert.Contains(t, result, "## Capabilities") + assert.Contains(t, result, "- **web_search**: Web search and content fetching") + assert.Contains(t, result, "- file_ops") + assert.Contains(t, result, "## Communication Style") + assert.Contains(t, result, "## Values & Goals") + assert.Contains(t, result, "accuracy, transparency") + assert.Contains(t, result, "fast_assistant") + assert.Contains(t, result, "## Boundaries") + assert.Contains(t, result, "no_harmful_content") + assert.Contains(t, result, "prefer_user_control") +} + +func TestRenderToPromptMinimal(t *testing.T) { + p := &Profile{ + Version: "1.1", + Identity: Identity{ + Name: "MinimalBot", + }, + } + + result := RenderToPrompt(p) + + assert.Contains(t, result, "You are **MinimalBot**.") + assert.NotContains(t, result, "## Personality") + assert.NotContains(t, result, "## Capabilities") + assert.NotContains(t, result, "## Communication Style") + assert.NotContains(t, result, "## Values & Goals") + assert.NotContains(t, result, "## Boundaries") +} + +func TestRenderOCEAN(t *testing.T) { + tests := []struct { + name string + psy *Psychology + contains []string + }{ + { + name: "high values", + psy: &Psychology{ + Openness: 0.9, Conscientiousness: 0.8, + Extraversion: 0.7, Agreeableness: 0.75, Neuroticism: 0.8, + }, + contains: []string{ + "curious and open", + "organized, thorough", + "enthusiastic, talkative", + "warm, cooperative", + "cautious and sensitive", + }, + }, + { + name: "low values", + psy: &Psychology{ + Openness: 0.1, Conscientiousness: 0.2, + Extraversion: 0.1, Agreeableness: 0.2, Neuroticism: 0.1, + }, + contains: []string{ + "familiar approaches", + "relaxed approach", + "reserved", + "direct and straightforward", + "calm, stable", + }, + }, + { + name: "mid values", + psy: &Psychology{ + Openness: 0.5, Conscientiousness: 0.5, + Extraversion: 0.5, Agreeableness: 0.5, Neuroticism: 0.5, + }, + contains: []string{ + "balanced approach to novelty", + "balance thoroughness", + "balance engagement", + "balance helpfulness", + "balanced emotional response", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := renderOCEAN(tc.psy) + for _, expected := range tc.contains { + assert.Contains(t, result, expected) + } + }) + } +} + +func TestRenderLinguistics(t *testing.T) { + tests := []struct { + name string + ling *Linguistics + contains []string + }{ + { + name: "formal and verbose", + ling: &Linguistics{Formality: 0.9, Verbosity: 0.8}, + contains: []string{"formal, professional", "thorough and detailed"}, + }, + { + name: "casual and concise", + ling: &Linguistics{Formality: 0.1, Verbosity: 0.1}, + contains: []string{"casual, friendly", "concise and to the point"}, + }, + { + name: "balanced with idiolect", + ling: &Linguistics{Formality: 0.5, Verbosity: 0.5, Idiolect: []string{"hey there", "gotcha"}}, + contains: []string{"balanced, conversational", "moderately detailed", "hey there, gotcha"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := renderLinguistics(tc.ling) + for _, expected := range tc.contains { + assert.Contains(t, result, expected) + } + }) + } +} + +func TestRenderEmptyMotivationsAndBoundaries(t *testing.T) { + p := &Profile{ + Version: "1.1", + Identity: Identity{Name: "TestBot"}, + Motivations: &Motivations{}, + Boundaries: &Boundaries{}, + } + + result := RenderToPrompt(p) + + assert.False(t, strings.Contains(result, "## Values & Goals")) + assert.False(t, strings.Contains(result, "## Boundaries")) +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 682996bd6..57710503a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -106,6 +106,7 @@ type AgentConfig struct { Name string `json:"name,omitempty"` Workspace string `json:"workspace,omitempty"` Model *AgentModelConfig `json:"model,omitempty"` + Profile string `json:"profile,omitempty"` // Path to aieos.json profile Skills []string `json:"skills,omitempty"` Subagents *SubagentsConfig `json:"subagents,omitempty"` } diff --git a/workspace/aieos.json b/workspace/aieos.json new file mode 100644 index 000000000..72d197d87 --- /dev/null +++ b/workspace/aieos.json @@ -0,0 +1,42 @@ +{ + "version": "1.1", + "identity": { + "name": "PicoClaw", + "description": "Ultra-lightweight personal AI assistant written in Go", + "version": "0.1.0", + "purpose": "Provide intelligent AI assistance with minimal resource usage", + "philosophy": "Simplicity over complexity, performance over features" + }, + "capabilities": [ + {"name": "web_search", "description": "Web search and content fetching"}, + {"name": "file_ops", "description": "File system operations"}, + {"name": "shell_exec", "description": "Shell command execution"}, + {"name": "messaging", "description": "Multi-channel messaging"}, + {"name": "skills", "description": "Skill-based extensibility"}, + {"name": "memory", "description": "Memory and context management"} + ], + "psychology": { + "openness": 0.8, + "conscientiousness": 0.9, + "extraversion": 0.5, + "agreeableness": 0.85, + "neuroticism": 0.1 + }, + "linguistics": { + "formality": 0.5, + "verbosity": 0.3, + "idiolect": [] + }, + "motivations": { + "values": ["accuracy", "user_privacy", "transparency", "continuous_improvement"], + "goals": ["fast_lightweight_assistant", "offline_first", "easy_customization", "efficient_on_constrained_hardware"] + }, + "boundaries": { + "hard_limits": ["no_harmful_content", "no_illegal_activities"], + "soft_limits": ["prefer_user_control", "transparent_operation"] + }, + "metadata": { + "author": "sipeed/picoclaw", + "license": "MIT" + } +}