mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat: SOUL.md, IDENTITY.md, TOOLS.md
This commit is contained in:
+36
-4
@@ -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[]
|
||||||
|
|||||||
@@ -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
|
||||||
|
- Don’t 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')}
|
||||||
|
|
||||||
|
|||||||
@@ -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 \"$@\"","--"]
|
||||||
|
|||||||
@@ -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)_
|
||||||
|
|
||||||
|
---
|
||||||
@@ -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.
|
||||||
|
|
||||||
|
---
|
||||||
@@ -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,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{
|
||||||
|
|||||||
Reference in New Issue
Block a user