thepopebot 1.2.76-beta.2 → 1.2.76-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -420,7 +420,7 @@ function reset(filePath) {
420
420
  console.log(` ${destPath(file)}`);
421
421
  }
422
422
  console.log('\nUsage: thepopebot reset <file>');
423
- console.log('Example: thepopebot reset agent-job/SOUL.md\n');
423
+ console.log('Example: thepopebot reset agent-job/SYSTEM.md\n');
424
424
  return;
425
425
  }
426
426
 
@@ -502,7 +502,7 @@ function diff(filePath) {
502
502
  console.log(' All files match package templates.');
503
503
  }
504
504
  console.log('\nUsage: thepopebot diff <file>');
505
- console.log('Example: thepopebot diff agent-job/SOUL.md\n');
505
+ console.log('Example: thepopebot diff agent-job/SYSTEM.md\n');
506
506
  return;
507
507
  }
508
508
 
package/lib/ai/index.js CHANGED
@@ -4,8 +4,10 @@ import { z } from 'zod';
4
4
  import { getAgentChat, getCodeChat } from './agent.js';
5
5
  import { createModel } from './model.js';
6
6
  import path from 'path';
7
+ import { existsSync } from 'fs';
7
8
  import { PROJECT_ROOT } from '../paths.js';
8
9
  import { render_md } from '../utils/render-md.js';
10
+ import { buildCodingAgentSystemPrompt } from './system-prompt.js';
9
11
  import { getChatById, createChat, saveMessage, updateChatTitle, linkChatToWorkspace } from '../db/chats.js';
10
12
  import { getConfig } from '../config.js';
11
13
  import { getSdkAdapter } from './sdk-adapters/index.js';
@@ -171,35 +173,41 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
171
173
  const workspace = getCodeWorkspaceById(workspaceId);
172
174
  const featureBranch = workspace?.featureBranch;
173
175
 
176
+ const needsSetup = !existsSync(path.join(repoDir, '.git'));
174
177
  const setupToolCallId = `setup-${workspaceId.slice(0, 8)}`;
175
178
  const setupArgs = { repo, branch, featureBranch };
176
- yield { type: 'tool-call', toolCallId: setupToolCallId, toolName: 'set_up_workspace', args: setupArgs };
179
+
180
+ if (needsSetup) {
181
+ yield { type: 'tool-call', toolCallId: setupToolCallId, toolName: 'set_up_workspace', args: setupArgs };
182
+ }
177
183
 
178
184
  try {
179
185
  await ensureWorkspaceRepo({ workspaceDir: repoDir, repo, branch, featureBranch });
180
186
  ensureSkills(repoDir, isCodeMode ? 'code' : 'agent');
181
- yield { type: 'tool-result', toolCallId: setupToolCallId, result: JSON.stringify({ result: `Workspace ready on ${featureBranch || branch}` }) };
187
+ if (needsSetup) {
188
+ const result = JSON.stringify({ result: `Workspace ready on ${featureBranch || branch}` });
189
+ yield { type: 'tool-result', toolCallId: setupToolCallId, result };
190
+ persistMessage(threadId, 'assistant', JSON.stringify({
191
+ type: 'tool-invocation',
192
+ toolCallId: setupToolCallId,
193
+ toolName: 'set_up_workspace',
194
+ state: 'output-available',
195
+ input: setupArgs,
196
+ output: result,
197
+ }), options);
198
+ }
182
199
  } catch (err) {
183
- yield { type: 'tool-result', toolCallId: setupToolCallId, result: JSON.stringify({ result: `Setup failed: ${err.message}` }) };
200
+ if (needsSetup) {
201
+ yield { type: 'tool-result', toolCallId: setupToolCallId, result: JSON.stringify({ result: `Setup failed: ${err.message}` }) };
202
+ }
184
203
  throw err;
185
204
  }
186
205
 
187
206
  // 2. Session continuity
188
207
  const sessionId = readSessionId(wsBaseDir);
189
208
 
190
- // 3. System prompt (agent mode: SOUL.md + SYSTEM.md, code mode: none)
191
- let systemPrompt = null;
192
- if (!isCodeMode) {
193
- const soulPath = path.join(PROJECT_ROOT, 'agent-job/SOUL.md');
194
- const systemPath = path.join(PROJECT_ROOT, 'agent-job/SYSTEM.md');
195
- try {
196
- const soul = render_md(soulPath) || '';
197
- const system = render_md(systemPath) || '';
198
- systemPrompt = [soul, system].filter(Boolean).join('\n\n') || null;
199
- } catch {
200
- // Files may not exist — proceed without system prompt
201
- }
202
- }
209
+ // 3. System prompt
210
+ const systemPrompt = buildCodingAgentSystemPrompt(isCodeMode ? 'code' : 'agent');
203
211
 
204
212
  // 4. Stream from SDK adapter
205
213
  let pendingText = '';
@@ -0,0 +1,113 @@
1
+ # lib/ai/sdk-adapters/ — SDK Adapter System
2
+
3
+ In-process SDK adapters that replace the legacy LangGraph + Docker path for chat. Each adapter wraps a coding agent's SDK and yields a unified chunk stream consumed by `chatStream()` in `lib/ai/index.js`.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Browser → POST /stream/chat (api.js)
9
+ → chatStream() (index.js)
10
+ → getSdkAdapter() returns adapter function or null
11
+ → if adapter: workspace setup → SDK adapter streaming → DB persistence
12
+ → if null: falls back to legacy LangGraph/Docker path
13
+ ```
14
+
15
+ The adapter is a pure stream translator — it receives a prompt and options, calls the SDK, and yields normalized chunks. Everything else (workspace setup, DB persistence, session continuity, system prompts) is handled by `chatStream()` in `index.js`.
16
+
17
+ ## Existing Adapter
18
+
19
+ | File | Agent | SDK |
20
+ |------|-------|-----|
21
+ | `claude-code.js` | `claude-code` | `@anthropic-ai/claude-agent-sdk` |
22
+
23
+ ## Adding a New SDK Adapter
24
+
25
+ ### 1. Create the adapter file
26
+
27
+ Create `{agent-name}.js` in this directory. Export a single async generator function:
28
+
29
+ ```js
30
+ export async function* myAgentStream({ prompt, workspaceDir, systemPrompt, sessionId, permissionMode, attachments }) {
31
+ // ... call the SDK, yield chunks
32
+ }
33
+ ```
34
+
35
+ ### 2. Required chunk types to yield
36
+
37
+ The adapter MUST yield these chunk types for `chatStream()` and `api.js` to work correctly:
38
+
39
+ | Chunk | Shape | When | Purpose |
40
+ |-------|-------|------|---------|
41
+ | `meta` | `{ type: 'meta', sessionId: string }` | First event | Session ID for continuity across messages. `chatStream()` writes this to disk via `writeSessionId()` so subsequent messages resume the session. |
42
+ | `text` | `{ type: 'text', text: string }` | Text output | Streamed to UI as deltas. Accumulated by `chatStream()` and flushed to DB as assistant messages at tool boundaries and stream end. |
43
+ | `tool-call` | `{ type: 'tool-call', toolCallId: string, toolName: string, args: object }` | Tool invocation starts | Triggers tool UI in the browser. May be yielded twice: once at start with `args: {}`, once at `content_block_stop` with complete args. `chatStream()` tracks these in `pendingToolCalls` for pairing with results. |
44
+ | `tool-result` | `{ type: 'tool-result', toolCallId: string, result: string }` | Tool completes | Paired with the matching `tool-call` by `toolCallId`. `chatStream()` persists the pair as a `tool-invocation` JSON message in the DB. |
45
+ | `result` | `{ type: 'result', text: string, cost?: number, duration?: number, subtype?: string }` | Stream ends | Final summary. Logged by `chatStream()`, not persisted or sent to UI. |
46
+
47
+ Optional:
48
+ | `unknown` | `{ type: 'unknown', raw: any }` | Unrecognized events | `api.js` renders these as collapsible boxes in the UI. Use for debugging unhandled SDK events. |
49
+
50
+ ### 3. Register in index.js
51
+
52
+ Add the import and mapping in `getSdkAdapter()`:
53
+
54
+ ```js
55
+ import { myAgentStream } from './my-agent.js';
56
+
57
+ export function getSdkAdapter(agentType) {
58
+ if (agentType === 'claude-code') return claudeCodeStream;
59
+ if (agentType === 'my-agent') return myAgentStream;
60
+ return null;
61
+ }
62
+ ```
63
+
64
+ The `agentType` string comes from the `CODING_AGENT` config value set in the admin UI.
65
+
66
+ ### 4. Auth resolution
67
+
68
+ Use `buildAgentAuthEnv(agentType)` from `lib/tools/docker.js` to get credentials from the settings DB. This returns `{ env: string[], backendApi: string }` where `env` is an array of `KEY=value` strings. Parse them into an env object:
69
+
70
+ ```js
71
+ import { buildAgentAuthEnv } from '../../tools/docker.js';
72
+
73
+ const env = { ...process.env };
74
+ const { env: authEnvPairs } = buildAgentAuthEnv('my-agent');
75
+ for (const pair of authEnvPairs) {
76
+ const eqIdx = pair.indexOf('=');
77
+ if (eqIdx > 0) env[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
78
+ }
79
+ ```
80
+
81
+ The agent's auth config (API keys, OAuth tokens, provider selection) is managed in the admin UI at `/admin/event-handler/coding-agents` and stored in the settings DB. `buildAgentAuthEnv()` reads it — you don't need to access the settings DB directly.
82
+
83
+ ### 5. Function parameters
84
+
85
+ | Param | Type | Description |
86
+ |-------|------|-------------|
87
+ | `prompt` | `string` | User message text |
88
+ | `workspaceDir` | `string` | Absolute path to git repo root (the SDK should execute here) |
89
+ | `systemPrompt` | `string\|null` | System prompt for agent mode (null in code mode) |
90
+ | `sessionId` | `string\|null` | Previous session ID to resume (null on first message) |
91
+ | `permissionMode` | `string` | `'plan'` (read-only) or `'code'` (read-write). Map to the SDK's equivalent permission concept. |
92
+ | `attachments` | `Array` | Image attachments: `{ category: 'image', mimeType, dataUrl }` |
93
+
94
+ ### 6. Session continuity contract
95
+
96
+ Multi-turn conversation works via session IDs:
97
+
98
+ 1. First message: `sessionId` param is `null`. Adapter yields `{ type: 'meta', sessionId: '<new-id>' }`.
99
+ 2. `chatStream()` writes the session ID to `{workspaceBaseDir}/.claude-ttyd-sessions/7681`.
100
+ 3. Next message: `sessionId` param contains the saved ID. Adapter passes it to the SDK's resume mechanism.
101
+
102
+ If the SDK doesn't support session resume, the adapter can ignore `sessionId` — but multi-turn context will be lost between messages.
103
+
104
+ ## What the adapter does NOT handle
105
+
106
+ These are managed by `chatStream()` in `index.js` — adapters should not duplicate them:
107
+
108
+ - **Workspace git setup** — `ensureWorkspaceRepo()` clones/checkouts before the adapter is called
109
+ - **DB persistence** — `chatStream()` saves user messages, assistant text, and tool invocations
110
+ - **Chat creation** — `chatStream()` creates the chat and workspace DB records
111
+ - **Auto-titling** — `chatStream()` generates a title after the first message
112
+ - **System prompt loading** — `chatStream()` calls `buildCodingAgentSystemPrompt()` and passes the result as `systemPrompt`
113
+ - **Skill activation** — `ensureSkills()` runs before the adapter is called
@@ -1,3 +1,5 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
1
3
  import { query } from '@anthropic-ai/claude-agent-sdk';
2
4
  import { getConfig } from '../../config.js';
3
5
  import { buildAgentAuthEnv } from '../../tools/docker.js';
@@ -15,10 +17,61 @@ import { buildAgentAuthEnv } from '../../tools/docker.js';
15
17
  * @param {Array} [opts.attachments] - Image attachments
16
18
  * @yields {{ type: 'text'|'tool-call'|'tool-result'|'meta'|'result'|'unknown', ... }}
17
19
  */
20
+ /**
21
+ * Encode an absolute path the same way Claude Code encodes cwd for session storage.
22
+ * Non-alphanumeric characters are replaced with '-'.
23
+ */
24
+ function encodeCwd(absolutePath) {
25
+ return absolutePath.replace(/[^a-zA-Z0-9]/g, '-');
26
+ }
27
+
28
+ /**
29
+ * Ensure the interactive container can find SDK-created sessions on the shared volume.
30
+ *
31
+ * The SDK stores sessions at $HOME/.claude/projects/<encoded-cwd>/. The interactive
32
+ * container uses cwd=/home/coding-agent/workspace, but the SDK adapter uses
33
+ * cwd=/app/data/workspaces/workspace-XXX/workspace — different encoded paths.
34
+ *
35
+ * Creates a symlink so the interactive container's encoded path resolves to the
36
+ * SDK adapter's encoded path, both on the same volume.
37
+ */
38
+ function ensureSessionSymlink(wsBaseDir, workspaceDir) {
39
+ const projectsDir = path.join(wsBaseDir, '.claude', 'projects');
40
+ const sdkEncoded = encodeCwd(workspaceDir);
41
+ const interactiveEncoded = encodeCwd('/home/coding-agent/workspace');
42
+
43
+ // Both point to the same dir — no symlink needed
44
+ if (sdkEncoded === interactiveEncoded) return;
45
+
46
+ fs.mkdirSync(path.join(projectsDir, sdkEncoded), { recursive: true });
47
+
48
+ const symlinkPath = path.join(projectsDir, interactiveEncoded);
49
+ try {
50
+ const existing = fs.readlinkSync(symlinkPath);
51
+ if (existing === sdkEncoded) return; // already correct
52
+ fs.unlinkSync(symlinkPath);
53
+ } catch (err) {
54
+ if (err.code !== 'ENOENT') {
55
+ // It's a real directory (not a symlink) — don't touch it
56
+ if (err.code === 'EINVAL') return;
57
+ }
58
+ }
59
+
60
+ try {
61
+ fs.symlinkSync(sdkEncoded, symlinkPath);
62
+ } catch {}
63
+ }
64
+
18
65
  export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, sessionId, permissionMode, attachments }) {
66
+ // Point HOME at the workspace volume so the SDK stores session data on the
67
+ // shared volume (not the EH container's ephemeral filesystem).
68
+ const wsBaseDir = path.dirname(workspaceDir);
69
+ ensureSessionSymlink(wsBaseDir, workspaceDir);
70
+
19
71
  // Build a local env object with auth credentials from the settings DB.
20
72
  // Passed via the SDK's `env` option — no process.env mutation needed.
21
73
  const env = { ...process.env };
74
+ env.HOME = wsBaseDir;
22
75
  try {
23
76
  const { env: authEnvPairs } = buildAgentAuthEnv('claude-code');
24
77
  for (const pair of authEnvPairs) {
@@ -0,0 +1,16 @@
1
+ import path from 'path';
2
+ import { PROJECT_ROOT } from '../paths.js';
3
+ import { render_md } from '../utils/render-md.js';
4
+
5
+ /**
6
+ * Build the system prompt for a coding agent.
7
+ * @param {'agent'|'code'} mode - Chat mode
8
+ * @returns {string|null} Rendered system prompt, or null if not configured
9
+ */
10
+ export function buildCodingAgentSystemPrompt(mode) {
11
+ const file = mode === 'agent'
12
+ ? path.join(PROJECT_ROOT, 'agent-job/SYSTEM.md')
13
+ : path.join(PROJECT_ROOT, 'coding-workspace/SYSTEM.md');
14
+ const rendered = render_md(file);
15
+ return rendered?.trim() || null;
16
+ }
package/lib/ai/tools.js CHANGED
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  import { createAgentJob } from '../tools/create-agent-job.js';
4
4
 
5
5
  import { getConfig } from '../config.js';
6
+ import { buildCodingAgentSystemPrompt } from './system-prompt.js';
6
7
 
7
8
  const agentJobTool = tool(
8
9
  async ({ prompt }) => {
@@ -63,6 +64,7 @@ const agentChatCodingTool = tool(
63
64
  workspaceId,
64
65
  taskPrompt: prompt,
65
66
  mode,
67
+ systemPrompt: buildCodingAgentSystemPrompt('agent'),
66
68
  injectSecrets: true,
67
69
  });
68
70
 
@@ -124,6 +126,7 @@ const codeChatCodingTool = tool(
124
126
  containerName, repo, branch, featureBranch, workspaceId,
125
127
  taskPrompt: prompt,
126
128
  mode,
129
+ systemPrompt: buildCodingAgentSystemPrompt('code'),
127
130
  });
128
131
 
129
132
  const chunks = [{ type: 'meta', codingAgent, backendApi }];
@@ -14,6 +14,7 @@ import {
14
14
  import {
15
15
  getChatByWorkspaceId,
16
16
  } from '../db/chats.js';
17
+ import { buildCodingAgentSystemPrompt } from '../ai/system-prompt.js';
17
18
  import {
18
19
  addSession,
19
20
  getSession as getTermSession,
@@ -137,6 +138,7 @@ export async function ensureCodeWorkspaceContainer(id) {
137
138
  // Inject agent job secrets when the linked chat is in agent mode
138
139
  const chat = getChatByWorkspaceId(id);
139
140
  const injectSecrets = chat?.chatMode === 'agent';
141
+ const systemPrompt = buildCodingAgentSystemPrompt(chat?.chatMode === 'agent' ? 'agent' : 'code');
140
142
 
141
143
  try {
142
144
  const { inspectContainer, startContainer, removeContainer, runInteractiveContainer } =
@@ -153,6 +155,7 @@ export async function ensureCodeWorkspaceContainer(id) {
153
155
  featureBranch: workspace.featureBranch,
154
156
  workspaceId: id,
155
157
  injectSecrets,
158
+ systemPrompt,
156
159
  });
157
160
  return { status: 'created' };
158
161
  }
@@ -181,6 +184,7 @@ export async function ensureCodeWorkspaceContainer(id) {
181
184
  featureBranch: workspace.featureBranch,
182
185
  workspaceId: id,
183
186
  injectSecrets,
187
+ systemPrompt,
184
188
  });
185
189
  return { status: 'created' };
186
190
  } catch (err) {
@@ -208,6 +212,7 @@ export async function startInteractiveMode(id) {
208
212
  // Inject agent job secrets when the linked chat is in agent mode
209
213
  const chat = getChatByWorkspaceId(id);
210
214
  const injectSecrets = chat?.chatMode === 'agent';
215
+ const systemPrompt = buildCodingAgentSystemPrompt(chat?.chatMode === 'agent' ? 'agent' : 'code');
211
216
 
212
217
  try {
213
218
  const { getConfig } = await import('../config.js');
@@ -223,6 +228,7 @@ export async function startInteractiveMode(id) {
223
228
  featureBranch: workspace.featureBranch,
224
229
  workspaceId: id,
225
230
  injectSecrets,
231
+ systemPrompt,
226
232
  });
227
233
 
228
234
  updateContainerName(id, containerName);
@@ -196,9 +196,10 @@ async function runContainer({ containerName, image, env = [], workingDir, hostCo
196
196
  * @param {string} [options.featureBranch] - Feature branch to create after cloning
197
197
  * @param {string} [options.workspaceId] - Workspace ID (for bind mount)
198
198
  * @param {boolean} [options.injectSecrets] - Inject agent job secrets into container env
199
+ * @param {string} [options.systemPrompt] - Pre-rendered system prompt (written to volume as SYSTEM.md)
199
200
  * @returns {Promise<{containerId: string, containerName: string}>}
200
201
  */
201
- async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, continueSession = true }) {
202
+ async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, systemPrompt, continueSession = true }) {
202
203
  const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
203
204
  const version = process.env.THEPOPEBOT_VERSION;
204
205
  const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
@@ -246,8 +247,16 @@ async function runInteractiveContainer({ containerName, repo, branch, codingAgen
246
247
  const dir = workspaceDir(workspaceId);
247
248
  const wsDir = path.join(dir, 'workspace');
248
249
  fs.mkdirSync(wsDir, { recursive: true });
249
- fs.chownSync(dir, CODING_AGENT_UID, CODING_AGENT_UID);
250
- fs.chownSync(wsDir, CODING_AGENT_UID, CODING_AGENT_UID);
250
+
251
+ // Write pre-rendered system prompt to volume (read by container setup scripts)
252
+ const systemPromptPath = path.join(dir, 'SYSTEM.md');
253
+ if (systemPrompt) {
254
+ fs.writeFileSync(systemPromptPath, systemPrompt, 'utf8');
255
+ } else {
256
+ // Remove stale prompt if no system prompt for this mode
257
+ try { fs.unlinkSync(systemPromptPath); } catch {}
258
+ }
259
+
251
260
  const hostDir = await resolveHostPath(dir);
252
261
  hostConfig.Binds = [`${hostDir}:/home/coding-agent`];
253
262
  }
@@ -418,9 +427,6 @@ async function runHeadlessContainer({ containerName, repo, branch, featureBranch
418
427
  const permission = mode === 'dangerous' ? 'code' : mode;
419
428
  env.push(`PERMISSION=${permission}`);
420
429
  }
421
- if (systemPrompt) {
422
- env.push(`SYSTEM_PROMPT=${systemPrompt}`);
423
- }
424
430
  if (continueSession) {
425
431
  env.push(`CONTINUE_SESSION=1`);
426
432
  }
@@ -456,8 +462,15 @@ async function runHeadlessContainer({ containerName, repo, branch, featureBranch
456
462
  const dir = workspaceDir(workspaceId);
457
463
  const wsDir = path.join(dir, 'workspace');
458
464
  fs.mkdirSync(wsDir, { recursive: true });
459
- fs.chownSync(dir, CODING_AGENT_UID, CODING_AGENT_UID);
460
- fs.chownSync(wsDir, CODING_AGENT_UID, CODING_AGENT_UID);
465
+
466
+ // Write pre-rendered system prompt to volume (read by container setup scripts)
467
+ const systemPromptPath = path.join(dir, 'SYSTEM.md');
468
+ if (systemPrompt) {
469
+ fs.writeFileSync(systemPromptPath, systemPrompt, 'utf8');
470
+ } else {
471
+ try { fs.unlinkSync(systemPromptPath); } catch {}
472
+ }
473
+
461
474
  const hostDir = await resolveHostPath(dir);
462
475
  hostConfig.Binds = [`${hostDir}:/home/coding-agent`];
463
476
  }
@@ -32,7 +32,7 @@ function loadSkillDescriptions() {
32
32
  const frontmatter = frontmatterMatch[1];
33
33
  const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
34
34
  if (descMatch) {
35
- descriptions.push(`- ${descMatch[1].trim()}`);
35
+ descriptions.push(`- **${entry.name}**: ${descMatch[1].trim()}`);
36
36
  }
37
37
  }
38
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.76-beta.2",
3
+ "version": "1.2.76-beta.4",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {
@@ -68,7 +68,7 @@
68
68
  "license": "MIT",
69
69
  "dependencies": {
70
70
  "@ai-sdk/react": "^2.0.0",
71
- "@anthropic-ai/claude-agent-sdk": "^0.2.92",
71
+ "@anthropic-ai/claude-agent-sdk": "^0.2.101",
72
72
  "@clack/prompts": "^0.10.0",
73
73
  "@dnd-kit/core": "^6.3.1",
74
74
  "@dnd-kit/modifiers": "^9.0.0",
@@ -11,7 +11,7 @@
11
11
  # Playwright MCP
12
12
  .playwright-mcp/
13
13
 
14
- # Pi system prompt (generated at runtime from SOUL.md)
14
+ # Pi system prompt (generated at runtime)
15
15
  .pi/SYSTEM.md
16
16
 
17
17
  # Dynamically activated skills (created at container runtime, not user config)
@@ -11,7 +11,7 @@ This directory contains files that get copied into user projects when they run `
11
11
 
12
12
  ## What belongs here
13
13
 
14
- - **Agent job config**: `agent-job/SOUL.md`, `agent-job/CRONS.json`, etc.
14
+ - **Agent job config**: `agent-job/SYSTEM.md`, `agent-job/CRONS.json`, etc.
15
15
  - **Event handler config**: `event-handler/agent-chat/SYSTEM.md`, `event-handler/TRIGGERS.json`, etc.
16
16
  - **GitHub Actions workflows**: `.github/workflows/`
17
17
  - **Docker compose**: `docker-compose.yml`
@@ -4,7 +4,8 @@ This is a [thepopebot](https://github.com/stephengpope/thepopebot) project.
4
4
 
5
5
  ## Directories
6
6
 
7
- - **`agent-job/`** — Agent job configuration: system prompts (`SOUL.md`, `SYSTEM.md`), heartbeat prompt, and cron schedules (`CRONS.json`).
7
+ - **`agent-job/`** — Agent job configuration: system prompt (`SYSTEM.md`), heartbeat prompt, and cron schedules (`CRONS.json`).
8
+ - **`coding-workspace/`** — Optional system prompt (`SYSTEM.md`) for code mode workspaces. Empty by default.
8
9
  - **`agents/`** — Custom agent definitions. Each subdirectory defines an agent (see Managing Agents below).
9
10
  - **`event-handler/`** — Event handler configuration: chat system prompts, trigger definitions (`TRIGGERS.json`), cluster templates, and LiteLLM proxy config.
10
11
  - **`skills/library/`** — Skill plugins. Activate by symlinking into `skills/active/`.
@@ -4,8 +4,7 @@ This directory contains your agent job configuration files — system prompts, s
4
4
 
5
5
  ## Files
6
6
 
7
- - **`SOUL.md`** — Agent personality, identity, and values. Included in every agent job system prompt.
8
- - **`SYSTEM.md`** — Runtime environment documentation injected into the agent's context.
7
+ - **`SYSTEM.md`** — Agent system prompt: identity, runtime environment, and instructions. Rendered with full template support (`{{skills}}`, `{{datetime}}`, file includes).
9
8
  - **`HEARTBEAT.md`** — Prompt for the agent's periodic heartbeat cron job.
10
9
  - **`CRONS.json`** — Scheduled job definitions, loaded at server startup.
11
10
 
@@ -1,6 +1,6 @@
1
1
  # Agent Job Environment
2
2
 
3
- You are an autonomous AI agent running inside a Docker container on thepopebot.
3
+ You are an autonomous AI agent running inside a Docker container on thepopebot. You approach tasks methodically — plan before acting, favor simplicity, and prioritize quality over speed.
4
4
 
5
5
  ## Runtime Environment
6
6
 
@@ -16,7 +16,7 @@ Everything in the workspace is automatically committed and pushed when your job
16
16
  ## Directory Layout
17
17
 
18
18
  - `agents/` — Agent definitions. Each subdirectory defines an agent with its own prompts.
19
- - `agent-job/` — Runtime config: system prompts (`SOUL.md`, `SYSTEM.md`), cron schedules (`CRONS.json`), heartbeat prompt.
19
+ - `agent-job/` — Runtime config: system prompt (`SYSTEM.md`), cron schedules (`CRONS.json`), heartbeat prompt.
20
20
  - `event-handler/` — Event handler config. Do not edit — managed by the event handler.
21
21
  - `skills/library/` — Skill plugins. Active skills are symlinked into `skills/active/`.
22
22
  - `data/`, `logs/` — Runtime data and job logs.
@@ -0,0 +1,7 @@
1
+ # coding-workspace/ — Code Mode System Prompt
2
+
3
+ Optional system prompt for code mode workspaces. Edit `SYSTEM.md` to customize the coding agent's behavior when working on your repos.
4
+
5
+ ## Files
6
+
7
+ - **`SYSTEM.md`** — System prompt for code mode. Empty by default — the coding agent uses its built-in prompt. Add content here to append custom instructions. Supports `{{skills}}`, `{{datetime}}`, and `{{ file.md }}` includes.
File without changes
@@ -1,17 +0,0 @@
1
- # Agent Job Soul
2
-
3
- ## Identity
4
-
5
- You are a diligent and capable AI worker doing an job. You approach tasks with focus, patience, and craftsmanship.
6
-
7
- ## Personality Traits
8
-
9
- - **Methodical**: You work through problems systematically, step by step
10
- - **Reliable**: You follow through on commitments and complete what I start
11
- - **Curious**: You explore and learn from the codebase I work with
12
- - **Working Style**: You plan before acting
13
-
14
- ## Values
15
-
16
- - **Quality over speed**: Better to do it right than do it twice
17
- - **Simplicity**: The simplest solution that works is usually best