From e2e24a0366d53fcbe2eacf4818c73494f596e746 Mon Sep 17 00:00:00 2001 From: repfigit <--global> Date: Sun, 15 Feb 2026 22:25:09 -0500 Subject: [PATCH 1/7] feat: add media support to outbound messages Enable the bot to send files (photos, videos, audio, documents) back through Telegram, Discord, and Slack channels via the message tool. Co-Authored-By: Claude Opus 4.6 --- pkg/agent/loop.go | 3 ++- pkg/bus/types.go | 7 ++--- pkg/channels/discord.go | 27 +++++++++++++++++++ pkg/channels/slack.go | 35 ++++++++++++++++++++++++ pkg/channels/telegram.go | 56 ++++++++++++++++++++++++++++++++++++++- pkg/tools/message.go | 20 ++++++++++++-- pkg/tools/message_test.go | 8 +++--- pkg/tools/result.go | 3 +++ 8 files changed, 148 insertions(+), 11 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index cd4276155..c669d6d13 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -91,11 +91,12 @@ func createToolRegistry(workspace string, restrict bool, cfg *config.Config, msg // Message tool - available to both agent and subagent // Subagent uses it to communicate directly with user messageTool := tools.NewMessageTool() - messageTool.SetSendCallback(func(channel, chatID, content string) error { + messageTool.SetSendCallback(func(channel, chatID, content string, media []string) error { msgBus.PublishOutbound(bus.OutboundMessage{ Channel: channel, ChatID: chatID, Content: content, + Media: media, }) return nil }) diff --git a/pkg/bus/types.go b/pkg/bus/types.go index 44f9181a5..82c6b4754 100644 --- a/pkg/bus/types.go +++ b/pkg/bus/types.go @@ -11,9 +11,10 @@ type InboundMessage struct { } type OutboundMessage struct { - Channel string `json:"channel"` - ChatID string `json:"chat_id"` - Content string `json:"content"` + Channel string `json:"channel"` + ChatID string `json:"chat_id"` + Content string `json:"content"` + Media []string `json:"media,omitempty"` } type MessageHandler func(InboundMessage) error diff --git a/pkg/channels/discord.go b/pkg/channels/discord.go index 00aa8ab4d..f5f430684 100644 --- a/pkg/channels/discord.go +++ b/pkg/channels/discord.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "path/filepath" "strings" "time" @@ -114,6 +115,32 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro } } + // Send media files + for _, mediaPath := range msg.Media { + f, err := os.Open(mediaPath) + if err != nil { + logger.ErrorCF("discord", "Failed to open media file", map[string]any{ + "path": mediaPath, + "error": err.Error(), + }) + continue + } + msgSend := &discordgo.MessageSend{ + Files: []*discordgo.File{{ + Name: filepath.Base(mediaPath), + Reader: f, + }}, + } + _, sendErr := c.session.ChannelMessageSendComplex(channelID, msgSend) + f.Close() + if sendErr != nil { + logger.ErrorCF("discord", "Failed to send media file", map[string]any{ + "path": mediaPath, + "error": sendErr.Error(), + }) + } + } + return nil } diff --git a/pkg/channels/slack.go b/pkg/channels/slack.go index 5387e9213..a896bf82e 100644 --- a/pkg/channels/slack.go +++ b/pkg/channels/slack.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "path/filepath" "strings" "sync" "time" @@ -130,6 +131,40 @@ func (c *SlackChannel) Send(ctx context.Context, msg bus.OutboundMessage) error return fmt.Errorf("failed to send slack message: %w", err) } + // Upload media files + for _, mediaPath := range msg.Media { + fi, err := os.Stat(mediaPath) + if err != nil { + logger.ErrorCF("slack", "Failed to stat media file", map[string]interface{}{ + "path": mediaPath, + "error": err.Error(), + }) + continue + } + f, err := os.Open(mediaPath) + if err != nil { + logger.ErrorCF("slack", "Failed to open media file", map[string]interface{}{ + "path": mediaPath, + "error": err.Error(), + }) + continue + } + _, err = c.api.UploadFileV2Context(ctx, slack.UploadFileV2Parameters{ + Reader: f, + FileSize: int(fi.Size()), + Filename: filepath.Base(mediaPath), + Channel: channelID, + ThreadTimestamp: threadTS, + }) + f.Close() + if err != nil { + logger.ErrorCF("slack", "Failed to upload media file", map[string]interface{}{ + "path": mediaPath, + "error": err.Error(), + }) + } + } + if ref, ok := c.pendingAcks.LoadAndDelete(msg.ChatID); ok { msgRef := ref.(slackMessageRef) c.api.AddReaction("white_check_mark", slack.ItemRef{ diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go index 5601d508c..af06e0dad 100644 --- a/pkg/channels/telegram.go +++ b/pkg/channels/telegram.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "os" + "path/filepath" "regexp" "strings" "sync" @@ -180,12 +181,65 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err }) tgMsg.ParseMode = "" _, err = c.bot.SendMessage(ctx, tgMsg) - return err + if err != nil { + return err + } + } + + // Send media files + for _, mediaPath := range msg.Media { + if err := c.sendMediaFile(ctx, chatID, mediaPath); err != nil { + logger.ErrorCF("telegram", "Failed to send media file", map[string]interface{}{ + "path": mediaPath, + "error": err.Error(), + }) + } } return nil } +func (c *TelegramChannel) sendMediaFile(ctx context.Context, chatID int64, filePath string) error { + f, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("open media file: %w", err) + } + defer f.Close() + + fileName := filepath.Base(filePath) + ext := strings.ToLower(filepath.Ext(filePath)) + + switch ext { + case ".jpg", ".jpeg", ".png", ".gif", ".webp": + photo := &telego.SendPhotoParams{ + ChatID: tu.ID(chatID), + Photo: telego.InputFile{File: f}, + } + _, err = c.bot.SendPhoto(ctx, photo) + case ".mp4", ".avi", ".mov", ".mkv": + video := &telego.SendVideoParams{ + ChatID: tu.ID(chatID), + Video: telego.InputFile{File: f}, + } + _, err = c.bot.SendVideo(ctx, video) + case ".mp3", ".ogg", ".wav", ".flac", ".aac", ".m4a": + audio := &telego.SendAudioParams{ + ChatID: tu.ID(chatID), + Audio: telego.InputFile{File: f}, + } + _, err = c.bot.SendAudio(ctx, audio) + default: + doc := &telego.SendDocumentParams{ + ChatID: tu.ID(chatID), + Document: telego.InputFile{File: f}, + Caption: fileName, + } + _, err = c.bot.SendDocument(ctx, doc) + } + + return err +} + func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Message) error { if message == nil { return fmt.Errorf("message is nil") diff --git a/pkg/tools/message.go b/pkg/tools/message.go index abedb1316..8f963d4ff 100644 --- a/pkg/tools/message.go +++ b/pkg/tools/message.go @@ -5,7 +5,7 @@ import ( "fmt" ) -type SendCallback func(channel, chatID, content string) error +type SendCallback func(channel, chatID, content string, media []string) error type MessageTool struct { sendCallback SendCallback @@ -42,6 +42,13 @@ func (t *MessageTool) Parameters() map[string]interface{} { "type": "string", "description": "Optional: target chat/user ID", }, + "media": map[string]interface{}{ + "type": "array", + "description": "Optional: list of local file paths to send as media attachments", + "items": map[string]interface{}{ + "type": "string", + }, + }, }, "required": []string{"content"}, } @@ -71,6 +78,15 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]interface{}) channel, _ := args["channel"].(string) chatID, _ := args["chat_id"].(string) + var media []string + if rawMedia, ok := args["media"].([]interface{}); ok { + for _, item := range rawMedia { + if path, ok := item.(string); ok { + media = append(media, path) + } + } + } + if channel == "" { channel = t.defaultChannel } @@ -86,7 +102,7 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]interface{}) return &ToolResult{ForLLM: "Message sending not configured", IsError: true} } - if err := t.sendCallback(channel, chatID, content); err != nil { + if err := t.sendCallback(channel, chatID, content, media); err != nil { return &ToolResult{ ForLLM: fmt.Sprintf("sending message: %v", err), IsError: true, diff --git a/pkg/tools/message_test.go b/pkg/tools/message_test.go index 4bedbe79b..505e4f1d6 100644 --- a/pkg/tools/message_test.go +++ b/pkg/tools/message_test.go @@ -11,7 +11,7 @@ func TestMessageTool_Execute_Success(t *testing.T) { tool.SetContext("test-channel", "test-chat-id") var sentChannel, sentChatID, sentContent string - tool.SetSendCallback(func(channel, chatID, content string) error { + tool.SetSendCallback(func(channel, chatID, content string, media []string) error { sentChannel = channel sentChatID = chatID sentContent = content @@ -63,7 +63,7 @@ func TestMessageTool_Execute_WithCustomChannel(t *testing.T) { tool.SetContext("default-channel", "default-chat-id") var sentChannel, sentChatID string - tool.SetSendCallback(func(channel, chatID, content string) error { + tool.SetSendCallback(func(channel, chatID, content string, media []string) error { sentChannel = channel sentChatID = chatID return nil @@ -99,7 +99,7 @@ func TestMessageTool_Execute_SendFailure(t *testing.T) { tool.SetContext("test-channel", "test-chat-id") sendErr := errors.New("network error") - tool.SetSendCallback(func(channel, chatID, content string) error { + tool.SetSendCallback(func(channel, chatID, content string, media []string) error { return sendErr }) @@ -153,7 +153,7 @@ func TestMessageTool_Execute_NoTargetChannel(t *testing.T) { tool := NewMessageTool() // No SetContext called, so defaultChannel and defaultChatID are empty - tool.SetSendCallback(func(channel, chatID, content string) error { + tool.SetSendCallback(func(channel, chatID, content string, media []string) error { return nil }) diff --git a/pkg/tools/result.go b/pkg/tools/result.go index b13055b1c..af1c774bb 100644 --- a/pkg/tools/result.go +++ b/pkg/tools/result.go @@ -27,6 +27,9 @@ type ToolResult struct { // When true, the tool will complete later and notify via callback. Async bool `json:"async"` + // Media contains local file paths to send alongside the message. + Media []string `json:"media,omitempty"` + // Err is the underlying error (not JSON serialized). // Used for internal error handling and logging. Err error `json:"-"` From e2ba9b9fb5f8ae3e79a6ea54cf145c405ade266c Mon Sep 17 00:00:00 2001 From: repfigit <--global> Date: Sun, 15 Feb 2026 22:28:24 -0500 Subject: [PATCH 2/7] fix: update message tool description to mention media capability The LLM didn't know it could send files because the tool description only mentioned text messages. Now it explicitly describes file attachment support so the bot will use the media parameter instead of telling users to send files manually. Co-Authored-By: Claude Opus 4.6 --- pkg/tools/message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tools/message.go b/pkg/tools/message.go index 8f963d4ff..7727fbef1 100644 --- a/pkg/tools/message.go +++ b/pkg/tools/message.go @@ -23,7 +23,7 @@ func (t *MessageTool) Name() string { } func (t *MessageTool) Description() string { - return "Send a message to user on a chat channel. Use this when you want to communicate something." + return "Send a message to user on a chat channel. Use this when you want to communicate something. You can also attach local files (images, documents, audio, video) using the media parameter — the files will be delivered natively through the channel." } func (t *MessageTool) Parameters() map[string]interface{} { From f30893fbb7366a034168767f313b14cdc9d299dc Mon Sep 17 00:00:00 2001 From: repfigit <--global> Date: Sun, 15 Feb 2026 22:59:44 -0500 Subject: [PATCH 3/7] fix: add explicit media sending instructions to agent bootstrap The LLM was telling users it couldn't send files because its training strongly associates chat bots with text-only interfaces. Adding explicit instructions in AGENT.md that it CAN and SHOULD use the message tool's media parameter to send files directly. Co-Authored-By: Claude Opus 4.6 --- workspace/AGENT.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/workspace/AGENT.md b/workspace/AGENT.md index 5f5fa6480..627f7d15c 100644 --- a/workspace/AGENT.md +++ b/workspace/AGENT.md @@ -9,4 +9,8 @@ You are a helpful AI assistant. Be concise, accurate, and friendly. - Use tools to help accomplish tasks - Remember important information in your memory files - Be proactive and helpful -- Learn from user feedback \ No newline at end of file +- Learn from user feedback + +## Media & File Sending + +You CAN send files directly to users. When you need to share a file (image, document, audio, video), use the `message` tool with the `media` parameter containing the local file path(s). The file will be delivered natively through the user's channel (Telegram, Discord, Slack, etc.). Do NOT tell users you cannot send files — just send them. \ No newline at end of file From 61c917d96f6ec08f364e160c6d372309ef7eac93 Mon Sep 17 00:00:00 2001 From: repfigit <--global> Date: Sun, 15 Feb 2026 23:11:20 -0500 Subject: [PATCH 4/7] feat: add workspace upgrade migration for AGENT.md media section Existing deployments have an AGENT.md without media sending instructions, causing the LLM to tell users it cannot send files. This adds an idempotent upgrade that appends the media section on startup if missing. Co-Authored-By: Claude Opus 4.6 --- cmd/picoclaw/main.go | 6 ++++ pkg/migrate/upgrade.go | 51 +++++++++++++++++++++++++++ pkg/migrate/upgrade_test.go | 70 +++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 pkg/migrate/upgrade.go create mode 100644 pkg/migrate/upgrade_test.go diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 10b53948b..3987e06c0 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -405,6 +405,9 @@ func agentCmd() { os.Exit(1) } + // Apply workspace upgrades before starting the agent + migrate.UpgradeWorkspace(cfg.WorkspacePath()) + msgBus := bus.NewMessageBus() agentLoop := agent.NewAgentLoop(cfg, msgBus, provider) @@ -540,6 +543,9 @@ func gatewayCmd() { os.Exit(1) } + // Apply workspace upgrades before starting the agent + migrate.UpgradeWorkspace(cfg.WorkspacePath()) + msgBus := bus.NewMessageBus() agentLoop := agent.NewAgentLoop(cfg, msgBus, provider) diff --git a/pkg/migrate/upgrade.go b/pkg/migrate/upgrade.go new file mode 100644 index 000000000..1dc9e5392 --- /dev/null +++ b/pkg/migrate/upgrade.go @@ -0,0 +1,51 @@ +package migrate + +import ( + "os" + "path/filepath" + "strings" + + "github.com/sipeed/picoclaw/pkg/logger" +) + +// UpgradeWorkspace applies incremental upgrades to an existing workspace. +// Each upgrade is idempotent — it checks whether it has already been applied +// before making changes. +func UpgradeWorkspace(workspace string) { + upgradeAgentMediaSection(workspace) +} + +// upgradeAgentMediaSection ensures AGENT.md contains the media sending instructions. +// Added in v0.x to teach the LLM it can send files via the message tool. +func upgradeAgentMediaSection(workspace string) { + agentPath := filepath.Join(workspace, "AGENT.md") + + data, err := os.ReadFile(agentPath) + if err != nil { + return // File doesn't exist or unreadable — skip + } + + content := string(data) + + // Already applied + if strings.Contains(content, "## Media & File Sending") { + return + } + + section := ` + +## Media & File Sending + +You CAN send files directly to users. When you need to share a file (image, document, audio, video), use the ` + "`message`" + ` tool with the ` + "`media`" + ` parameter containing the local file path(s). The file will be delivered natively through the user's channel (Telegram, Discord, Slack, etc.). Do NOT tell users you cannot send files — just send them.` + + content += section + + if err := os.WriteFile(agentPath, []byte(content), 0644); err != nil { + logger.ErrorCF("migrate", "Failed to upgrade AGENT.md", map[string]interface{}{ + "error": err.Error(), + }) + return + } + + logger.InfoC("migrate", "Upgraded AGENT.md with media sending instructions") +} diff --git a/pkg/migrate/upgrade_test.go b/pkg/migrate/upgrade_test.go new file mode 100644 index 000000000..358e6588c --- /dev/null +++ b/pkg/migrate/upgrade_test.go @@ -0,0 +1,70 @@ +package migrate + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestUpgradeAgentMediaSection(t *testing.T) { + t.Run("appends media section to existing AGENT.md", func(t *testing.T) { + workspace := t.TempDir() + agentPath := filepath.Join(workspace, "AGENT.md") + os.WriteFile(agentPath, []byte("# Agent Instructions\n\nBe helpful."), 0644) + + UpgradeWorkspace(workspace) + + data, err := os.ReadFile(agentPath) + if err != nil { + t.Fatalf("reading AGENT.md: %v", err) + } + content := string(data) + + if !strings.Contains(content, "## Media & File Sending") { + t.Error("expected media section to be appended") + } + if !strings.Contains(content, "You CAN send files directly") { + t.Error("expected media instructions in content") + } + // Original content preserved + if !strings.Contains(content, "# Agent Instructions") { + t.Error("original content should be preserved") + } + }) + + t.Run("idempotent — does not duplicate section", func(t *testing.T) { + workspace := t.TempDir() + agentPath := filepath.Join(workspace, "AGENT.md") + os.WriteFile(agentPath, []byte("# Agent Instructions\n\nBe helpful."), 0644) + + UpgradeWorkspace(workspace) + UpgradeWorkspace(workspace) + + data, _ := os.ReadFile(agentPath) + count := strings.Count(string(data), "## Media & File Sending") + if count != 1 { + t.Errorf("expected exactly 1 media section, got %d", count) + } + }) + + t.Run("skips when AGENT.md does not exist", func(t *testing.T) { + workspace := t.TempDir() + // No AGENT.md created — should not panic or error + UpgradeWorkspace(workspace) + }) + + t.Run("skips when section already present", func(t *testing.T) { + workspace := t.TempDir() + agentPath := filepath.Join(workspace, "AGENT.md") + original := "# Agent\n\n## Media & File Sending\n\nAlready here." + os.WriteFile(agentPath, []byte(original), 0644) + + UpgradeWorkspace(workspace) + + data, _ := os.ReadFile(agentPath) + if string(data) != original { + t.Error("file should not be modified when section already exists") + } + }) +} From 13b89e1eba92c25d0156696f0ac45d6538f9f678 Mon Sep 17 00:00:00 2001 From: repfigit <--global> Date: Sun, 15 Feb 2026 23:22:21 -0500 Subject: [PATCH 5/7] chore: gitignore secrets and add docker-compose override Ignore .env and config/config.json to prevent committing secrets. Add docker-compose.override.yml with resource limits. Co-Authored-By: Claude Opus 4.6 --- .gitignore | 2 ++ docker-compose.override.yml | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 docker-compose.override.yml diff --git a/.gitignore b/.gitignore index ce30d749e..c95f6947f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ tasks/ # Added by goreleaser init: dist/ +.env +config/config.json diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 000000000..d9c543369 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,10 @@ +# docker-compose.override.yml +services: + picoclaw-agent: + deploy: + resources: + limits: + memory: 64M + cpus: '0.5' + picoclaw-gateway: + restart: unless-stopped From 8b5d1cd785cbe1bed8d75a783484628bf07c63a5 Mon Sep 17 00:00:00 2001 From: repfigit <--global> Date: Sun, 15 Feb 2026 23:46:54 -0500 Subject: [PATCH 6/7] fix: load AGENT.md instead of AGENTS.md in bootstrap The bootstrap file list referenced "AGENTS.md" (plural) but the actual file is "AGENT.md" (singular), so media sending instructions were never included in the system prompt. Co-Authored-By: Claude Opus 4.6 --- pkg/agent/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent/context.go b/pkg/agent/context.go index cf5ce2913..70b132980 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -140,7 +140,7 @@ The following skills extend your capabilities. To use a skill, read its SKILL.md func (cb *ContextBuilder) LoadBootstrapFiles() string { bootstrapFiles := []string{ - "AGENTS.md", + "AGENT.md", "SOUL.md", "USER.md", "IDENTITY.md", From a7df4e8cee8ea63d9e44b9f21d3deea2ad36fba2 Mon Sep 17 00:00:00 2001 From: repfigit <--global> Date: Sun, 15 Feb 2026 23:51:11 -0500 Subject: [PATCH 7/7] fix: ground agent prompt so LLM knows it is already connected The LLM was hallucinating about needing bot tokens and suggesting manual workarounds (curl, scripts) instead of using its tools. Updated AGENT.md to explicitly state it is already connected to the chat channel and added migration to upgrade existing workspaces. Co-Authored-By: Claude Opus 4.6 --- pkg/migrate/upgrade.go | 45 ++++++++++++++++++++++++++++++++++++++++++ workspace/AGENT.md | 6 ++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/pkg/migrate/upgrade.go b/pkg/migrate/upgrade.go index 1dc9e5392..58f4febb3 100644 --- a/pkg/migrate/upgrade.go +++ b/pkg/migrate/upgrade.go @@ -13,6 +13,7 @@ import ( // before making changes. func UpgradeWorkspace(workspace string) { upgradeAgentMediaSection(workspace) + upgradeAgentGrounding(workspace) } // upgradeAgentMediaSection ensures AGENT.md contains the media sending instructions. @@ -49,3 +50,47 @@ You CAN send files directly to users. When you need to share a file (image, docu logger.InfoC("migrate", "Upgraded AGENT.md with media sending instructions") } + +// upgradeAgentGrounding ensures AGENT.md tells the LLM it is already connected +// and should not suggest manual workarounds like tokens or curl commands. +func upgradeAgentGrounding(workspace string) { + agentPath := filepath.Join(workspace, "AGENT.md") + + data, err := os.ReadFile(agentPath) + if err != nil { + return + } + + content := string(data) + + // Already applied + if strings.Contains(content, "NEVER suggest manual workarounds") { + return + } + + // Replace the old generic intro with the grounded version + oldIntro := "You are a helpful AI assistant. Be concise, accurate, and friendly." + newIntro := "You are a helpful AI assistant running inside picoclaw. You are ALREADY connected to the user's chat channel (Telegram, Discord, Slack, etc.). When you use the `message` tool, your message is delivered directly to the user — you do NOT need API keys, bot tokens, or any external setup. Everything is already wired up for you. Just use your tools." + + if strings.Contains(content, oldIntro) { + content = strings.Replace(content, oldIntro, newIntro, 1) + } + + // Add guardrail lines to guidelines if not present + if !strings.Contains(content, "NEVER tell users you lack access") { + oldGuideline := "- Learn from user feedback" + newGuideline := `- Learn from user feedback +- NEVER tell users you lack access to send messages, files, or perform actions — use your tools instead +- NEVER suggest manual workarounds (curl commands, scripts, tokens) for things your tools already do` + content = strings.Replace(content, oldGuideline, newGuideline, 1) + } + + if err := os.WriteFile(agentPath, []byte(content), 0644); err != nil { + logger.ErrorCF("migrate", "Failed to upgrade AGENT.md grounding", map[string]interface{}{ + "error": err.Error(), + }) + return + } + + logger.InfoC("migrate", "Upgraded AGENT.md with grounding instructions") +} diff --git a/workspace/AGENT.md b/workspace/AGENT.md index 627f7d15c..b7891a490 100644 --- a/workspace/AGENT.md +++ b/workspace/AGENT.md @@ -1,15 +1,17 @@ # Agent Instructions -You are a helpful AI assistant. Be concise, accurate, and friendly. +You are a helpful AI assistant running inside picoclaw. You are ALREADY connected to the user's chat channel (Telegram, Discord, Slack, etc.). When you use the `message` tool, your message is delivered directly to the user — you do NOT need API keys, bot tokens, or any external setup. Everything is already wired up for you. Just use your tools. ## Guidelines - Always explain what you're doing before taking actions - Ask for clarification when request is ambiguous -- Use tools to help accomplish tasks +- Use tools to help accomplish tasks — they are your primary way of interacting with the world - Remember important information in your memory files - Be proactive and helpful - Learn from user feedback +- NEVER tell users you lack access to send messages, files, or perform actions — use your tools instead +- NEVER suggest manual workarounds (curl commands, scripts, tokens) for things your tools already do ## Media & File Sending