feat: update heartbeat defaults

This commit is contained in:
Peter Steinberger
2026-01-06 21:54:19 +00:00
parent dba09058f5
commit 7aa7fa79d0
18 changed files with 128 additions and 33 deletions

View File

@@ -12,6 +12,7 @@ import {
SettingsManager,
type Skill,
} from "@mariozechner/pi-coding-agent";
import { resolveHeartbeatPrompt } from "../auto-reply/heartbeat.js";
import type { ThinkLevel, VerboseLevel } from "../auto-reply/thinking.js";
import { formatToolAggregate } from "../auto-reply/tool-meta.js";
import type { ClawdbotConfig } from "../config/config.js";
@@ -469,6 +470,9 @@ export async function compactEmbeddedPiSession(params: {
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
reasoningTagHint,
heartbeatPrompt: resolveHeartbeatPrompt(
params.config?.agent?.heartbeat?.prompt,
),
runtimeInfo,
sandboxInfo,
toolNames: tools.map((tool) => tool.name),
@@ -765,6 +769,9 @@ export async function runEmbeddedPiAgent(params: {
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
reasoningTagHint,
heartbeatPrompt: resolveHeartbeatPrompt(
params.config?.agent?.heartbeat?.prompt,
),
runtimeInfo,
sandboxInfo,
toolNames: tools.map((tool) => tool.name),

View File

@@ -9,6 +9,7 @@ export function buildAgentSystemPromptAppend(params: {
toolNames?: string[];
userTimezone?: string;
userTime?: string;
heartbeatPrompt?: string;
runtimeInfo?: {
host?: string;
os?: string;
@@ -113,6 +114,10 @@ export function buildAgentSystemPromptAppend(params: {
: undefined;
const userTimezone = params.userTimezone?.trim();
const userTime = params.userTime?.trim();
const heartbeatPrompt = params.heartbeatPrompt?.trim();
const heartbeatPromptLine = heartbeatPrompt
? `Heartbeat prompt: ${heartbeatPrompt}`
: "Heartbeat prompt: (configured)";
const runtimeInfo = params.runtimeInfo;
const runtimeLines: string[] = [];
if (runtimeInfo?.host) runtimeLines.push(`Host: ${runtimeInfo.host}`);
@@ -207,7 +212,8 @@ export function buildAgentSystemPromptAppend(params: {
lines.push(
"## Heartbeats",
'If you receive a heartbeat poll (a user message containing just "HEARTBEAT"), and there is nothing that needs attention, reply exactly:',
heartbeatPromptLine,
"If you receive a heartbeat poll (a user message matching the heartbeat prompt above), and there is nothing that needs attention, reply exactly:",
"HEARTBEAT_OK",
'Clawdbot treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
'If something needs attention, do NOT include "HEARTBEAT_OK"; reply with the alert text instead.',

View File

@@ -23,9 +23,11 @@ describe("ensureAgentWorkspace", () => {
const identity = path.join(path.resolve(nested), "IDENTITY.md");
const user = path.join(path.resolve(nested), "USER.md");
const heartbeat = path.join(path.resolve(nested), "HEARTBEAT.md");
const bootstrap = path.join(path.resolve(nested), "BOOTSTRAP.md");
await expect(fs.stat(identity)).resolves.toBeDefined();
await expect(fs.stat(user)).resolves.toBeDefined();
await expect(fs.stat(heartbeat)).resolves.toBeDefined();
await expect(fs.stat(bootstrap)).resolves.toBeDefined();
});

View File

@@ -22,6 +22,7 @@ export const DEFAULT_SOUL_FILENAME = "SOUL.md";
export const DEFAULT_TOOLS_FILENAME = "TOOLS.md";
export const DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
export const DEFAULT_USER_FILENAME = "USER.md";
export const DEFAULT_HEARTBEAT_FILENAME = "HEARTBEAT.md";
export const DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
const DEFAULT_AGENTS_TEMPLATE = `# AGENTS.md - Clawdbot Workspace
@@ -53,6 +54,9 @@ git commit -m "Add agent workspace"
- On session start, read today + yesterday if present.
- Capture durable facts, preferences, and decisions; avoid secrets.
## Heartbeats (optional)
- HEARTBEAT.md can hold a tiny checklist for heartbeat runs; keep it small.
## Customize
- Add your preferred style, rules, and "memory" here.
`;
@@ -83,6 +87,12 @@ It does not define which tools exist; Clawdbot provides built-in tools internall
Add whatever else you want the assistant to know about your local toolchain.
`;
const DEFAULT_HEARTBEAT_TEMPLATE = `# HEARTBEAT.md - Optional heartbeat notes
Keep this file small. Leave it empty unless you want a short checklist or reminders
to follow during heartbeat runs.
`;
const DEFAULT_BOOTSTRAP_TEMPLATE = `# BOOTSTRAP.md - First Run Ritual (delete after)
Hello. I was just born.
@@ -174,6 +184,7 @@ export type WorkspaceBootstrapFileName =
| typeof DEFAULT_TOOLS_FILENAME
| typeof DEFAULT_IDENTITY_FILENAME
| typeof DEFAULT_USER_FILENAME
| typeof DEFAULT_HEARTBEAT_FILENAME
| typeof DEFAULT_BOOTSTRAP_FILENAME;
export type WorkspaceBootstrapFile = {
@@ -205,6 +216,7 @@ export async function ensureAgentWorkspace(params?: {
toolsPath?: string;
identityPath?: string;
userPath?: string;
heartbeatPath?: string;
bootstrapPath?: string;
}> {
const rawDir = params?.dir?.trim()
@@ -220,10 +232,18 @@ export async function ensureAgentWorkspace(params?: {
const toolsPath = path.join(dir, DEFAULT_TOOLS_FILENAME);
const identityPath = path.join(dir, DEFAULT_IDENTITY_FILENAME);
const userPath = path.join(dir, DEFAULT_USER_FILENAME);
const heartbeatPath = path.join(dir, DEFAULT_HEARTBEAT_FILENAME);
const bootstrapPath = path.join(dir, DEFAULT_BOOTSTRAP_FILENAME);
const isBrandNewWorkspace = await (async () => {
const paths = [agentsPath, soulPath, toolsPath, identityPath, userPath];
const paths = [
agentsPath,
soulPath,
toolsPath,
identityPath,
userPath,
heartbeatPath,
];
const existing = await Promise.all(
paths.map(async (p) => {
try {
@@ -257,6 +277,10 @@ export async function ensureAgentWorkspace(params?: {
DEFAULT_USER_FILENAME,
DEFAULT_USER_TEMPLATE,
);
const heartbeatTemplate = await loadTemplate(
DEFAULT_HEARTBEAT_FILENAME,
DEFAULT_HEARTBEAT_TEMPLATE,
);
const bootstrapTemplate = await loadTemplate(
DEFAULT_BOOTSTRAP_FILENAME,
DEFAULT_BOOTSTRAP_TEMPLATE,
@@ -267,6 +291,7 @@ export async function ensureAgentWorkspace(params?: {
await writeFileIfMissing(toolsPath, toolsTemplate);
await writeFileIfMissing(identityPath, identityTemplate);
await writeFileIfMissing(userPath, userTemplate);
await writeFileIfMissing(heartbeatPath, heartbeatTemplate);
if (isBrandNewWorkspace) {
await writeFileIfMissing(bootstrapPath, bootstrapTemplate);
}
@@ -278,6 +303,7 @@ export async function ensureAgentWorkspace(params?: {
toolsPath,
identityPath,
userPath,
heartbeatPath,
bootstrapPath,
};
}
@@ -311,6 +337,10 @@ export async function loadWorkspaceBootstrapFiles(
name: DEFAULT_USER_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_USER_FILENAME),
},
{
name: DEFAULT_HEARTBEAT_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_HEARTBEAT_FILENAME),
},
{
name: DEFAULT_BOOTSTRAP_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_BOOTSTRAP_FILENAME),