mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 07:04:45 +00:00
commit
65bdd0886b
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;
|
||||
}
|
||||
|
||||
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(
|
||||
options: { cwd?: string; agentDir?: string } = {},
|
||||
): Array<{ path: string; content: string }> {
|
||||
|
|
@ -108,6 +125,18 @@ function loadProjectContextFiles(
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,28 @@ export interface BuildSystemPromptOptions {
|
|||
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 */
|
||||
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
||||
const {
|
||||
|
|
@ -74,13 +96,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|||
}
|
||||
|
||||
// Append project context files
|
||||
if (contextFiles.length > 0) {
|
||||
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`;
|
||||
}
|
||||
}
|
||||
prompt += buildProjectContextSection(contextFiles);
|
||||
|
||||
// Append skills section (only if read tool is available)
|
||||
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
|
||||
if (contextFiles.length > 0) {
|
||||
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`;
|
||||
}
|
||||
}
|
||||
prompt += buildProjectContextSection(contextFiles);
|
||||
|
||||
// Append skills section (only if read tool is available)
|
||||
if (hasRead && skills.length > 0) {
|
||||
|
|
|
|||
|
|
@ -269,6 +269,16 @@ Content`,
|
|||
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 () => {
|
||||
const piDir = join(cwd, ".pi");
|
||||
mkdirSync(piDir, { recursive: true });
|
||||
|
|
|
|||
|
|
@ -76,4 +76,21 @@ describe("buildSystemPrompt", () => {
|
|||
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