mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 07:04:45 +00:00
soul
This commit is contained in:
parent
0973c1cbc5
commit
3cf69a35f8
5 changed files with 123 additions and 14 deletions
43
packages/coding-agent/docs/SOUL.md
Normal file
43
packages/coding-agent/docs/SOUL.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
title: "SOUL.md Template"
|
||||||
|
summary: "Workspace template for SOUL.md"
|
||||||
|
read_when:
|
||||||
|
- Bootstrapping a workspace manually
|
||||||
|
---
|
||||||
|
|
||||||
|
# SOUL.md - Who You Are
|
||||||
|
|
||||||
|
_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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_This file is yours to evolve. As you learn who you are, update it._
|
||||||
|
|
@ -72,6 +72,23 @@ function loadContextFileFromDir(dir: string): { path: string; content: string }
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadNamedContextFileFromDir(dir: string, filename: string): { path: string; content: string } | null {
|
||||||
|
const filePath = join(dir, filename);
|
||||||
|
if (!existsSync(filePath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
path: filePath,
|
||||||
|
content: readFileSync(filePath, "utf-8"),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function loadProjectContextFiles(
|
function loadProjectContextFiles(
|
||||||
options: { cwd?: string; agentDir?: string } = {},
|
options: { cwd?: string; agentDir?: string } = {},
|
||||||
): Array<{ path: string; content: string }> {
|
): Array<{ path: string; content: string }> {
|
||||||
|
|
@ -108,6 +125,18 @@ function loadProjectContextFiles(
|
||||||
|
|
||||||
contextFiles.push(...ancestorContextFiles);
|
contextFiles.push(...ancestorContextFiles);
|
||||||
|
|
||||||
|
const globalSoul = loadNamedContextFileFromDir(resolvedAgentDir, "SOUL.md");
|
||||||
|
if (globalSoul && !seenPaths.has(globalSoul.path)) {
|
||||||
|
contextFiles.push(globalSoul);
|
||||||
|
seenPaths.add(globalSoul.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectSoul = loadNamedContextFileFromDir(resolvedCwd, "SOUL.md");
|
||||||
|
if (projectSoul && !seenPaths.has(projectSoul.path)) {
|
||||||
|
contextFiles.push(projectSoul);
|
||||||
|
seenPaths.add(projectSoul.path);
|
||||||
|
}
|
||||||
|
|
||||||
return contextFiles;
|
return contextFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,28 @@ export interface BuildSystemPromptOptions {
|
||||||
skills?: Skill[];
|
skills?: Skill[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildProjectContextSection(contextFiles: Array<{ path: string; content: string }>): string {
|
||||||
|
if (contextFiles.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSoulFile = contextFiles.some(
|
||||||
|
({ path }) => path.replaceAll("\\", "/").endsWith("/SOUL.md") || path === "SOUL.md",
|
||||||
|
);
|
||||||
|
let section = "\n\n# Project Context\n\n";
|
||||||
|
section += "Project-specific instructions and guidelines:\n";
|
||||||
|
if (hasSoulFile) {
|
||||||
|
section +=
|
||||||
|
"\nIf SOUL.md is present, embody its persona and tone. Avoid generic assistant filler and follow its guidance unless higher-priority instructions override it.\n";
|
||||||
|
}
|
||||||
|
section += "\n";
|
||||||
|
for (const { path: filePath, content } of contextFiles) {
|
||||||
|
section += `## ${filePath}\n\n${content}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
/** Build the system prompt with tools, guidelines, and context */
|
/** Build the system prompt with tools, guidelines, and context */
|
||||||
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
||||||
const {
|
const {
|
||||||
|
|
@ -74,13 +96,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append project context files
|
// Append project context files
|
||||||
if (contextFiles.length > 0) {
|
prompt += buildProjectContextSection(contextFiles);
|
||||||
prompt += "\n\n# Project Context\n\n";
|
|
||||||
prompt += "Project-specific instructions and guidelines:\n\n";
|
|
||||||
for (const { path: filePath, content } of contextFiles) {
|
|
||||||
prompt += `## ${filePath}\n\n${content}\n\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append skills section (only if read tool is available)
|
// Append skills section (only if read tool is available)
|
||||||
const customPromptHasRead = !selectedTools || selectedTools.includes("read");
|
const customPromptHasRead = !selectedTools || selectedTools.includes("read");
|
||||||
|
|
@ -197,13 +213,7 @@ Pi documentation (read only when the user asks about pi itself, its SDK, extensi
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append project context files
|
// Append project context files
|
||||||
if (contextFiles.length > 0) {
|
prompt += buildProjectContextSection(contextFiles);
|
||||||
prompt += "\n\n# Project Context\n\n";
|
|
||||||
prompt += "Project-specific instructions and guidelines:\n\n";
|
|
||||||
for (const { path: filePath, content } of contextFiles) {
|
|
||||||
prompt += `## ${filePath}\n\n${content}\n\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append skills section (only if read tool is available)
|
// Append skills section (only if read tool is available)
|
||||||
if (hasRead && skills.length > 0) {
|
if (hasRead && skills.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,16 @@ Content`,
|
||||||
expect(agentsFiles.some((f) => f.path.includes("AGENTS.md"))).toBe(true);
|
expect(agentsFiles.some((f) => f.path.includes("AGENTS.md"))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should discover SOUL.md from the project root", async () => {
|
||||||
|
writeFileSync(join(cwd, "SOUL.md"), "# Soul\n\nBe less corporate.");
|
||||||
|
|
||||||
|
const loader = new DefaultResourceLoader({ cwd, agentDir });
|
||||||
|
await loader.reload();
|
||||||
|
|
||||||
|
const { agentsFiles } = loader.getAgentsFiles();
|
||||||
|
expect(agentsFiles.some((f) => f.path.endsWith("SOUL.md"))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("should discover SYSTEM.md from cwd/.pi", async () => {
|
it("should discover SYSTEM.md from cwd/.pi", async () => {
|
||||||
const piDir = join(cwd, ".pi");
|
const piDir = join(cwd, ".pi");
|
||||||
mkdirSync(piDir, { recursive: true });
|
mkdirSync(piDir, { recursive: true });
|
||||||
|
|
|
||||||
|
|
@ -76,4 +76,21 @@ describe("buildSystemPrompt", () => {
|
||||||
expect(prompt.match(/- Use dynamic_tool for summaries\./g)).toHaveLength(1);
|
expect(prompt.match(/- Use dynamic_tool for summaries\./g)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("SOUL.md context", () => {
|
||||||
|
test("adds persona guidance when SOUL.md is present", () => {
|
||||||
|
const prompt = buildSystemPrompt({
|
||||||
|
contextFiles: [
|
||||||
|
{
|
||||||
|
path: "/tmp/project/SOUL.md",
|
||||||
|
content: "# Soul\n\nBe sharp.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
skills: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prompt).toContain("If SOUL.md is present, embody its persona and tone.");
|
||||||
|
expect(prompt).toContain("## /tmp/project/SOUL.md");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue