feat: implement AIEOS v1.1 profile loading for structured agent identity (#296)

Add pkg/aieos package to load, validate and render AIEOS JSON profiles
into system prompt text. When aieos.json exists in the workspace, it
replaces the legacy .md bootstrap files (SOUL.md, IDENTITY.md) with
structured identity, OCEAN psychology traits, linguistics, capabilities,
motivations and boundaries.

- pkg/aieos/profile.go: Go structs for AIEOS v1.1 specification
- pkg/aieos/loader.go: LoadProfile, ProfileExists, validation
- pkg/aieos/render.go: RenderToPrompt with OCEAN natural language mapping
- pkg/agent/context.go: AIEOS-first loading with .md fallback + deprecation warning
- pkg/agent/instance.go: pass per-agent profile path from config
- pkg/config/config.go: add Profile field to AgentConfig
- workspace/aieos.json: default template matching current PicoClaw identity
- 17 unit tests + 3 integration tests, all passing
This commit is contained in:
Edouard CLAUDE 2026-02-19 11:02:29 +04:00
parent 56a060ff61
commit bce31a0149
10 changed files with 734 additions and 4 deletions

View file

@ -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()

View file

@ -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

View file

@ -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
}
}

51
pkg/aieos/loader.go Normal file
View file

@ -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
}

105
pkg/aieos/loader_test.go Normal file
View file

@ -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)
}

65
pkg/aieos/profile.go Normal file
View file

@ -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"`
}

160
pkg/aieos/render.go Normal file
View file

@ -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"
}

186
pkg/aieos/render_test.go Normal file
View file

@ -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"))
}

View file

@ -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"`
}

42
workspace/aieos.json Normal file
View file

@ -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"
}
}