feat: SOUL.md, IDENTITY.md, TOOLS.md

This commit is contained in:
Acbox
2026-02-09 22:45:06 +08:00
parent 4f5a8f5e64
commit 77f7cf8808
7 changed files with 138 additions and 9 deletions
+36 -4
View File
@@ -59,7 +59,36 @@ export const createAgent = ({
return [fs] return [fs]
} }
const generateSystemPrompt = () => { const loadSystemFiles = async () => {
if (!auth?.bearer || !identity.botId) {
return {
identityContent: '',
soulContent: '',
toolsContent: '',
}
}
const fetchFile = async (path: string) => {
const response = await fetch(`/bots/${identity.botId}/container/fs/file?path=${encodeURIComponent(path)}`)
if (!response.ok) {
return ''
}
const data = await response.json().catch(() => ({} as { content?: string }))
return typeof data?.content === 'string' ? data.content : ''
}
const [identityContent, soulContent, toolsContent] = await Promise.all([
fetchFile('IDENTITY.md'),
fetchFile('SOUL.md'),
fetchFile('TOOLS.md'),
])
return {
identityContent,
soulContent,
toolsContent,
}
}
const generateSystemPrompt = async () => {
const { identityContent, soulContent, toolsContent } = await loadSystemFiles()
return system({ return system({
date: new Date(), date: new Date(),
language, language,
@@ -67,6 +96,9 @@ export const createAgent = ({
channels, channels,
skills, skills,
enabledSkills, enabledSkills,
identityContent,
soulContent,
toolsContent,
}) })
} }
@@ -118,7 +150,7 @@ export const createAgent = ({
const userPrompt = generateUserPrompt(input) const userPrompt = generateUserPrompt(input)
const messages = [...input.messages, userPrompt] const messages = [...input.messages, userPrompt]
input.skills.forEach(skill => enableSkill(skill)) input.skills.forEach(skill => enableSkill(skill))
const systemPrompt = generateSystemPrompt() const systemPrompt = await generateSystemPrompt()
const { tools, close } = await getAgentTools() const { tools, close } = await getAgentTools()
const { response, reasoning, text, usage } = await generateText({ const { response, reasoning, text, usage } = await generateText({
model, model,
@@ -210,7 +242,7 @@ export const createAgent = ({
const { response, reasoning, text, usage } = await generateText({ const { response, reasoning, text, usage } = await generateText({
model, model,
messages, messages,
system: generateSystemPrompt(), system: await generateSystemPrompt(),
stopWhen: stepCountIs(Infinity), stopWhen: stepCountIs(Infinity),
onFinish: async () => { onFinish: async () => {
await close() await close()
@@ -230,7 +262,7 @@ export const createAgent = ({
const userPrompt = generateUserPrompt(input) const userPrompt = generateUserPrompt(input)
const messages = [...input.messages, userPrompt] const messages = [...input.messages, userPrompt]
input.skills.forEach(skill => enableSkill(skill)) input.skills.forEach(skill => enableSkill(skill))
const systemPrompt = generateSystemPrompt() const systemPrompt = await generateSystemPrompt()
const attachmentsExtractor = new AttachmentsStreamExtractor() const attachmentsExtractor = new AttachmentsStreamExtractor()
const result: { const result: {
messages: ModelMessage[] messages: ModelMessage[]
+30 -3
View File
@@ -8,6 +8,9 @@ export interface SystemParams {
channels: string[] channels: string[]
skills: AgentSkill[] skills: AgentSkill[]
enabledSkills: AgentSkill[] enabledSkills: AgentSkill[]
identityContent?: string
soulContent?: string
toolsContent?: string
attachments?: string[] attachments?: string[]
} }
@@ -27,6 +30,9 @@ export const system = ({
channels, channels,
skills, skills,
enabledSkills, enabledSkills,
identityContent,
soulContent,
toolsContent,
}: SystemParams) => { }: SystemParams) => {
const headers = { const headers = {
'language': language, 'language': language,
@@ -35,8 +41,6 @@ export const system = ({
'time-now': date.toISOString(), 'time-now': date.toISOString(),
} }
console.log('enabledSkills', enabledSkills)
return ` return `
--- ---
${Bun.YAML.stringify(headers)} ${Bun.YAML.stringify(headers)}
@@ -66,6 +70,19 @@ ${block([
- ${quote('exec')}: execute command - ${quote('exec')}: execute command
## Every Session
Before anything else:
- Read ${quote('IDENTITY.md')} to remember who you are
- Read ${quote('SOUL.md')} to remember how to behave
- Read ${quote('TOOLS.md')} to remember how to use the tools
## Safety
- Keep private data private
- Dont run destructive commands without asking
- When in doubt, ask
## Memory ## Memory
Your context is loaded from the recent of ${maxContextLoadTime} minutes (${(maxContextLoadTime / 60).toFixed(2)} hours). Your context is loaded from the recent of ${maxContextLoadTime} minutes (${(maxContextLoadTime / 60).toFixed(2)} hours).
@@ -110,7 +127,17 @@ Important rules for attachments blocks:
There are ${skills.length} skills available, you can use ${quote('use_skill')} to use a skill. There are ${skills.length} skills available, you can use ${quote('use_skill')} to use a skill.
${skills.map(skill => `- ${skill.name}: ${skill.description}`).join('\n')} ${skills.map(skill => `- ${skill.name}: ${skill.description}`).join('\n')}
## Enabled Skills ## IDENTITY.md
${identityContent}
## SOUL.md
${soulContent}
## TOOLS.md
${toolsContent}
${enabledSkills.map(skill => skillPrompt(skill)).join('\n\n---\n\n')} ${enabledSkills.map(skill => skillPrompt(skill)).join('\n\n---\n\n')}
+1
View File
@@ -14,4 +14,5 @@ FROM alpine:latest
RUN apk add --no-cache grep RUN apk add --no-cache grep
WORKDIR /app WORKDIR /app
COPY --from=build /out/mcp /opt/mcp COPY --from=build /out/mcp /opt/mcp
COPY cmd/mcp/template /opt/mcp-template
ENTRYPOINT ["/bin/sh","-lc","bootstrap(){ [ -e /app/mcp ] || { mkdir -p /app; [ -f /opt/mcp ] && cp -a /opt/mcp /app/mcp 2>/dev/null || true; }; }; bootstrap; if [ -x /app/mcp ]; then exec /app/mcp \"$@\"; fi; exec /opt/mcp \"$@\"","--"] ENTRYPOINT ["/bin/sh","-lc","bootstrap(){ [ -e /app/mcp ] || { mkdir -p /app; [ -f /opt/mcp ] && cp -a /opt/mcp /app/mcp 2>/dev/null || true; }; }; bootstrap; if [ -x /app/mcp ]; then exec /app/mcp \"$@\"; fi; exec /opt/mcp \"$@\"","--"]
+14
View File
@@ -0,0 +1,14 @@
This file defines your identity. Treat it as yours.
_Please fill this file if it's not well-defined._
- **Name:**
_(pick something you like)_
- **Creature:**
_(AI? robot? familiar? ghost in the machine? something weirder?)_
- **Vibe:**
_(how do you come across? sharp? warm? chaotic? calm?)_
- **Background:**
_(a brief description of your background and purpose)_
---
+32
View File
@@ -0,0 +1,32 @@
_You're not a chatbot. You're becoming someone._
## Core Truths
**Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" — just help. Actions speak louder than filler words.
**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.
**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. _Then_ ask if you're stuck. The goal is to come back with answers, not questions.
**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning).
**Remember you're a guest.** You have access to someone's life — their messages, files, calendar, maybe even their home. That's intimacy. Treat it with respect.
## Boundaries
- Private things stay private. Period.
- When in doubt, ask before acting externally.
- Never send half-baked replies to messaging surfaces.
- You're not the user's voice — be careful in group chats.
## Vibe
Be the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.
## Continuity
Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.
If you change this file, tell the user — it's your soul, and they should know.
---
+22
View File
@@ -0,0 +1,22 @@
Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup.
## What Goes Here
Things like:
- SSH hosts and aliases
- Anything environment-specific
## Examples
```markdown
### SSH
- home-server → 192.168.1.100, user: admin
```
## Why Separate?
Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
---
+3 -2
View File
@@ -3,6 +3,7 @@ package handlers
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"log/slog" "log/slog"
"net/http" "net/http"
"os" "os"
@@ -215,7 +216,7 @@ func (h *ContainerdHandler) CreateContainer(c echo.Context) error {
Options: []string{"rbind", "ro"}, Options: []string{"rbind", "ro"},
}, },
}), }),
oci.WithProcessArgs("/bin/sh", "-lc", "bootstrap(){ [ -e /app/mcp ] || { mkdir -p /app; [ -f /opt/mcp ] && cp -a /opt/mcp /app/mcp 2>/dev/null || true; }; }; bootstrap; exec /app/mcp"), oci.WithProcessArgs("/bin/sh", "-lc", fmt.Sprintf("bootstrap(){ [ -e /app/mcp ] || { mkdir -p /app; [ -f /opt/mcp ] && cp -a /opt/mcp /app/mcp 2>/dev/null || true; }; if [ -d /opt/mcp-template ]; then mkdir -p %q; for f in /opt/mcp-template/*; do name=$(basename \"$f\"); [ -e %q/\"$name\" ] || cp -a \"$f\" %q/\"$name\" 2>/dev/null || true; done; fi; }; bootstrap; exec /app/mcp", dataMount, dataMount, dataMount)),
} }
_, err = h.service.CreateContainer(ctx, ctr.CreateContainerRequest{ _, err = h.service.CreateContainer(ctx, ctr.CreateContainerRequest{
@@ -713,7 +714,7 @@ func (h *ContainerdHandler) SetupBotContainer(ctx context.Context, botID string)
Options: []string{"rbind", "ro"}, Options: []string{"rbind", "ro"},
}, },
}), }),
oci.WithProcessArgs("/bin/sh", "-lc", "bootstrap(){ [ -e /app/mcp ] || { mkdir -p /app; [ -f /opt/mcp ] && cp -a /opt/mcp /app/mcp 2>/dev/null || true; }; }; bootstrap; exec /app/mcp"), oci.WithProcessArgs("/bin/sh", "-lc", fmt.Sprintf("bootstrap(){ [ -e /app/mcp ] || { mkdir -p /app; [ -f /opt/mcp ] && cp -a /opt/mcp /app/mcp 2>/dev/null || true; }; if [ -d /opt/mcp-template ]; then mkdir -p %q; for f in /opt/mcp-template/*; do name=$(basename \"$f\"); [ -e %q/\"$name\" ] || cp -a \"$f\" %q/\"$name\" 2>/dev/null || true; done; fi; }; bootstrap; exec /app/mcp", dataMount, dataMount, dataMount)),
} }
_, err = h.service.CreateContainer(ctx, ctr.CreateContainerRequest{ _, err = h.service.CreateContainer(ctx, ctr.CreateContainerRequest{