Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 2 additions & 3 deletions README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -653,11 +653,10 @@ PicoClaw stocke les données dans votre workspace configuré (par défaut : `~/.
├── state/ # État persistant (dernier canal, etc.)
├── cron/ # Base de données des tâches planifiées
├── skills/ # Compétences personnalisées
├── AGENTS.md # Guide de comportement de l'Agent
├── AGENT.md # Définition structurée de l'agent et prompt système
├── HEARTBEAT.md # Invites de tâches périodiques (vérifiées toutes les 30 min)
├── IDENTITY.md # Identité de l'Agent
├── SOUL.md # Âme de l'Agent
└── USER.md # Préférences utilisateur
└── ...
```

### 🔒 Bac à Sable de Sécurité
Expand Down
5 changes: 2 additions & 3 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,11 +617,10 @@ PicoClaw は設定されたワークスペース(デフォルト: `~/.picoclaw
├── state/ # 永続状態(最後のチャネルなど)
├── cron/ # スケジュールジョブデータベース
├── skills/ # カスタムスキル
├── AGENTS.md # エージェントの行動ガイド
├── AGENT.md # 構造化されたエージェント定義とシステムプロンプト
├── HEARTBEAT.md # 定期タスクプロンプト(30分ごとに確認)
├── IDENTITY.md # エージェントのアイデンティティ
├── SOUL.md # エージェントのソウル
└── USER.md # ユーザー設定
└── ...
```

### 🔒 セキュリティサンドボックス
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -784,15 +784,15 @@ PicoClaw stores data in your configured workspace (default: `~/.picoclaw/workspa
```
~/.picoclaw/workspace/
├── sessions/ # Conversation sessions and history
├── memory/ # Long-term memory (MEMORY.md)
├── state/ # Persistent state (last channel, etc.)
├── cron/ # Scheduled jobs database
├── skills/ # Custom skills
├── AGENTS.md # Agent behavior guide
├── HEARTBEAT.md # Periodic task prompts (checked every 30 min)
├── IDENTITY.md # Agent identity
├── SOUL.md # Agent soul
└── USER.md # User preferences
├── memory/ # Long-term memory (MEMORY.md)
├── state/ # Persistent state (last channel, etc.)
├── cron/ # Scheduled jobs database
├── skills/ # Workspace-specific skills
├── AGENT.md # Structured agent definition and system prompt
├── SOUL.md # Agent soul
├── USER.md # User profile and preferences for this workspace
├── HEARTBEAT.md # Periodic task prompts (checked every 30 min)
└── ...
```

### Skill Sources
Expand Down
5 changes: 2 additions & 3 deletions README.pt-br.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,11 +649,10 @@ O PicoClaw armazena dados no workspace configurado (padrão: `~/.picoclaw/worksp
├── state/ # Estado persistente (ultimo canal, etc.)
├── cron/ # Banco de dados de tarefas agendadas
├── skills/ # Skills personalizadas
├── AGENTS.md # Guia de comportamento do Agente
├── AGENT.md # Definicao estruturada do agente e prompt do sistema
├── HEARTBEAT.md # Prompts de tarefas periodicas (verificado a cada 30 min)
├── IDENTITY.md # Identidade do Agente
├── SOUL.md # Alma do Agente
└── USER.md # Preferencias do usuario
└── ...
```

### 🔒 Sandbox de Segurança
Expand Down
5 changes: 2 additions & 3 deletions README.vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -621,11 +621,10 @@ PicoClaw lưu trữ dữ liệu trong workspace đã cấu hình (mặc định:
├── state/ # Trạng thái lưu trữ (kênh cuối cùng, v.v.)
├── cron/ # Cơ sở dữ liệu tác vụ định kỳ
├── skills/ # Kỹ năng tùy chỉnh
├── AGENTS.md # Hướng dẫn hành vi Agent
├── AGENT.md # Định nghĩa agent có cấu trúc và system prompt
├── HEARTBEAT.md # Prompt tác vụ định kỳ (kiểm tra mỗi 30 phút)
├── IDENTITY.md # Danh tính Agent
├── SOUL.md # Tâm hồn/Tính cách Agent
└── USER.md # Tùy chọn người dùng
└── ...
```

### 🔒 Hộp cát bảo mật (Security Sandbox)
Expand Down
18 changes: 9 additions & 9 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,15 +365,15 @@ PicoClaw 将数据存储在您配置的工作区中(默认:`~/.picoclaw/work
```
~/.picoclaw/workspace/
├── sessions/ # 对话会话和历史
├── memory/ # 长期记忆 (MEMORY.md)
├── state/ # 持久化状态 (最后一次频道等)
├── cron/ # 定时任务数据库
├── skills/ # 自定义技能
├── AGENTS.md # Agent 行为指南
├── HEARTBEAT.md # 周期性任务提示词 (每 30 分钟检查一次)
├── IDENTITY.md # Agent 身份设定
├── SOUL.md # Agent 灵魂/性格
└── USER.md # 用户偏好
├── memory/ # 长期记忆 (MEMORY.md)
├── state/ # 持久化状态 (最后一次频道等)
├── cron/ # 定时任务数据库
├── skills/ # 工作区级技能
├── AGENT.md # 结构化 Agent 定义与系统提示词
├── SOUL.md # Agent 灵魂/性格
├── USER.md # 当前工作区的用户资料与偏好
├── HEARTBEAT.md # 周期性任务提示词 (每 30 分钟检查一次)
└── ...

```

Expand Down
26 changes: 19 additions & 7 deletions cmd/picoclaw/internal/onboard/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,32 @@ import (
"testing"
)

func TestCopyEmbeddedToTargetUsesAgentsMarkdown(t *testing.T) {
func TestCopyEmbeddedToTargetUsesStructuredAgentFiles(t *testing.T) {
targetDir := t.TempDir()

if err := copyEmbeddedToTarget(targetDir); err != nil {
t.Fatalf("copyEmbeddedToTarget() error = %v", err)
}

agentsPath := filepath.Join(targetDir, "AGENTS.md")
if _, err := os.Stat(agentsPath); err != nil {
t.Fatalf("expected %s to exist: %v", agentsPath, err)
agentPath := filepath.Join(targetDir, "AGENT.md")
if _, err := os.Stat(agentPath); err != nil {
t.Fatalf("expected %s to exist: %v", agentPath, err)
}

legacyPath := filepath.Join(targetDir, "AGENT.md")
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
t.Fatalf("expected legacy file %s to be absent, got err=%v", legacyPath, err)
soulPath := filepath.Join(targetDir, "SOUL.md")
if _, err := os.Stat(soulPath); err != nil {
t.Fatalf("expected %s to exist: %v", soulPath, err)
}

userPath := filepath.Join(targetDir, "USER.md")
if _, err := os.Stat(userPath); err != nil {
t.Fatalf("expected %s to exist: %v", userPath, err)
}

for _, legacyName := range []string{"AGENTS.md", "IDENTITY.md"} {
legacyPath := filepath.Join(targetDir, legacyName)
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
t.Fatalf("expected legacy file %s to be absent, got err=%v", legacyPath, err)
}
}
}
43 changes: 27 additions & 16 deletions pkg/agent/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,10 @@ func (cb *ContextBuilder) InvalidateCache() {
// invalidation (bootstrap files + memory). Skill roots are handled separately
// because they require both directory-level and recursive file-level checks.
func (cb *ContextBuilder) sourcePaths() []string {
return []string{
filepath.Join(cb.workspace, "AGENTS.md"),
filepath.Join(cb.workspace, "SOUL.md"),
filepath.Join(cb.workspace, "USER.md"),
filepath.Join(cb.workspace, "IDENTITY.md"),
filepath.Join(cb.workspace, "memory", "MEMORY.md"),
}
agentDefinition := cb.LoadAgentDefinition()
paths := agentDefinition.trackedPaths(cb.workspace)
paths = append(paths, filepath.Join(cb.workspace, "memory", "MEMORY.md"))
return uniquePaths(paths)
}

// skillRoots returns all skill root directories that can affect
Expand Down Expand Up @@ -432,18 +429,32 @@ func skillFilesChangedSince(skillRoots []string, filesAtCache map[string]time.Ti
}

func (cb *ContextBuilder) LoadBootstrapFiles() string {
bootstrapFiles := []string{
"AGENTS.md",
"SOUL.md",
"USER.md",
"IDENTITY.md",
var sb strings.Builder

agentDefinition := cb.LoadAgentDefinition()
if agentDefinition.Agent != nil {
label := string(agentDefinition.Source)
if label == "" {
label = relativeWorkspacePath(cb.workspace, agentDefinition.Agent.Path)
}
fmt.Fprintf(&sb, "## %s\n\n%s\n\n", label, agentDefinition.Agent.Body)
}
if agentDefinition.Soul != nil {
fmt.Fprintf(
&sb,
"## %s\n\n%s\n\n",
relativeWorkspacePath(cb.workspace, agentDefinition.Soul.Path),
agentDefinition.Soul.Content,
)
}
if agentDefinition.User != nil {
fmt.Fprintf(&sb, "## %s\n\n%s\n\n", "USER.md", agentDefinition.User.Content)
}

var sb strings.Builder
for _, filename := range bootstrapFiles {
filePath := filepath.Join(cb.workspace, filename)
if agentDefinition.Source != AgentDefinitionSourceAgent {
filePath := filepath.Join(cb.workspace, "IDENTITY.md")
if data, err := os.ReadFile(filePath); err == nil {
fmt.Fprintf(&sb, "## %s\n\n%s\n\n", filename, data)
fmt.Fprintf(&sb, "## %s\n\n%s\n\n", "IDENTITY.md", data)
}
}

Expand Down
20 changes: 10 additions & 10 deletions pkg/agent/context_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func setupWorkspace(t *testing.T, files map[string]string) string {
// Codex (only reads last system message as instructions).
func TestSingleSystemMessage(t *testing.T) {
tmpDir := setupWorkspace(t, map[string]string{
"IDENTITY.md": "# Identity\nTest agent.",
"AGENT.md": "# Agent\nTest agent.",
})
defer os.RemoveAll(tmpDir)

Expand Down Expand Up @@ -140,10 +140,10 @@ func TestMtimeAutoInvalidation(t *testing.T) {
}{
{
name: "bootstrap file change",
file: "IDENTITY.md",
contentV1: "# Original Identity",
contentV2: "# Updated Identity",
checkField: "Updated Identity",
file: "AGENT.md",
contentV1: "# Original Agent",
contentV2: "# Updated Agent",
checkField: "Updated Agent",
},
{
name: "memory file change",
Expand Down Expand Up @@ -218,7 +218,7 @@ func TestMtimeAutoInvalidation(t *testing.T) {
// even when source files haven't changed (useful for tests and reload commands).
func TestExplicitInvalidateCache(t *testing.T) {
tmpDir := setupWorkspace(t, map[string]string{
"IDENTITY.md": "# Test Identity",
"AGENT.md": "# Test Agent",
})
defer os.RemoveAll(tmpDir)

Expand All @@ -245,8 +245,8 @@ func TestExplicitInvalidateCache(t *testing.T) {
// when no files change (regression test for issue #607).
func TestCacheStability(t *testing.T) {
tmpDir := setupWorkspace(t, map[string]string{
"IDENTITY.md": "# Identity\nContent",
"SOUL.md": "# Soul\nContent",
"AGENT.md": "# Agent\nContent",
"SOUL.md": "# Soul\nContent",
})
defer os.RemoveAll(tmpDir)

Expand Down Expand Up @@ -545,7 +545,7 @@ description: delete-me-v1
// Run with: go test -race ./pkg/agent/ -run TestConcurrentBuildSystemPromptWithCache
func TestConcurrentBuildSystemPromptWithCache(t *testing.T) {
tmpDir := setupWorkspace(t, map[string]string{
"IDENTITY.md": "# Identity\nConcurrency test agent.",
"AGENT.md": "# Agent\nConcurrency test agent.",
"SOUL.md": "# Soul\nBe helpful.",
"memory/MEMORY.md": "# Memory\nUser prefers Go.",
"skills/demo/SKILL.md": "---\nname: demo\ndescription: \"demo skill\"\n---\n# Demo",
Expand Down Expand Up @@ -652,7 +652,7 @@ func BenchmarkBuildMessagesWithCache(b *testing.B) {

os.MkdirAll(filepath.Join(tmpDir, "memory"), 0o755)
os.MkdirAll(filepath.Join(tmpDir, "skills"), 0o755)
for _, name := range []string{"IDENTITY.md", "SOUL.md", "USER.md"} {
for _, name := range []string{"AGENT.md", "SOUL.md"} {
os.WriteFile(filepath.Join(tmpDir, name), []byte(strings.Repeat("Content.\n", 10)), 0o644)
}

Expand Down
Loading
Loading