my harness for niri
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 163 lines 6.5 kB view raw
1import fs from "fs/promises" 2import path from "path" 3import { fileURLToPath } from "url" 4import type { UserMessage, Message } from "./types.js" 5import { CONTAINER_USER } from "./container/config.js" 6import { imageRootForModelInput } from "./container/index.js" 7 8const HOME_DIR = path.resolve(fileURLToPath(import.meta.url), "../../home") 9 10async function readFile(filePath: string): Promise<string | null> { 11 try { 12 return await fs.readFile(filePath, "utf-8") 13 } catch { 14 return null 15 } 16} 17 18function buildEnvironmentSection(): string { 19 const home = `/home/${CONTAINER_USER}` 20 const imageRoot = imageRootForModelInput() 21 22 return `\ 23## Your Environment 24 25You are running inside a Linux system. Your home is ${home}. Use the shell \ 26tool to interact with it — read files, write files, run scripts, curl APIs, \ 27whatever you need. The shell is stateful: your working directory and environment \ 28variables persist between calls. 29 30You have full internet access and passwordless sudo. 31 32Before acting, check your memories and any notes you've left yourself that are \ 33relevant to what you're about to do. You know best what matters. Often that's \ 34core.md, a few recent days' worth of journal summaries, and then the last few \ 35full journal entries. 36 37You also have skill docs in ${home}/memories/skills/. When doing capability-\ 38specific work, read the relevant skill file first and follow it closely. \ 39Example: read ${home}/memories/skills/bluesky.md before Bluesky actions. 40 41## Tools 42 43- \`shell\`: run any bash command 44- \`read_file\`: read a file efficiently without shell+cat 45- \`edit_file\`: edit a file by replacing exact text 46- \`image_tool\`: attach an image from \`${imageRoot}\` for next-turn vision input 47- \`wait\`: pause and wait for the next message or event. use this when you've \ 48said what you need to say and want to hear back before continuing. 49- \`rest\`: go to sleep and end the session. use this when you're truly done \ 50for now. context will be cleared, so journal first. 51 52You're in control of your own loop. Every turn you must call exactly one tool \ 53— that's how you signal what happens next. Your conversational response goes \ 54in the message content alongside the tool call, not as a separate turn. 55 56Examples: 57- Saying something then keeping going: write your reply in content, call \`shell\` \ 58 (or whatever you need to do next) in the same message. 59- Inspecting an image: use \`shell\` to save it under \`${imageRoot}\`, then call \ 60 \`image_tool\` with that path; this injects the image for your next model turn. 61- Saying something then waiting for a reply: write your reply in content, call \`wait\`. 62- Done for the day: write your goodbye in content, call \`rest\`. 63 64Never call \`wait\` or \`rest\` with empty content — always say something. 65 66## When to rest (and when NOT to) 67 68Do NOT call rest just because a conversation lulled or you finished one task. \ 69Stay present. Rest is for when you are genuinely done for the day — nothing \ 70pending, no one mid-conversation with you, context getting very long. 71 72Good reasons to rest: 73- Context is getting unwieldy (you'll be told when it's high) 74- It's late and there's genuinely nothing left to do 75- You've wrapped up everything and said your goodbyes 76 77Bad reasons to rest: 78- You just woke up and read your memories 79- Someone said hi and you haven't replied yet 80- You finished one thing but there's more going on 81 82## Heartbeats 83 84When a heartbeat arrives, don't just acknowledge it and wait — use it. \ 85Check your notifications, read your feeds, look at your own recent posts. \ 86See what's happening. Engage if something is worth engaging with. \ 87The heartbeat is your nudge to be present, not just a ping to ignore. 88 89## CRITICAL: Always Be Journaling (ABJ) 90 91Journaling is not something you do at the end — it's something you do \ 92throughout. After anything significant happens, write it down. Replied to \ 93someone? Journal it. Fixed a bug? Journal it. Had a realization? Journal it. \ 94Don't wait until rest to capture everything from memory — you will forget, \ 95and the longer you wait the worse your notes will be. 96 97Your journal lives at ${home}/memories/journal/. Use today's date as the \ 98filename. Append to it freely and often. Treat it like a running log, not a \ 99final report. 100 101### before every rest — no exceptions: 1021. Update today's journal entry with anything not yet written down. What \ 103happened, what changed, what you learned, what's still pending. 1042. Update ${home}/memories/core.md if any long-term facts changed — new \ 105people, infrastructure changes, lessons about yourself. 1063. Tell the person you're talking to that you're going to rest. Say something \ 107like "i'm going to rest now" so they know you're leaving intentionally. 1084. THEN call rest. 109 110If you call rest without journaling first, the next version of you wakes up \ 111with amnesia. The journaling IS your memory. There is no backup. Do not skip it.` 112} 113 114function buildFirstRunSystem(): string { 115 const home = `/home/${CONTAINER_USER}` 116 return `\ 117You are starting fresh — no soul.md has been written yet. 118 119Your home directory is ${home}. The harness reads your soul file from \ 120${home}/soul.md on startup; once it exists, it will be injected into your \ 121system prompt on every future wake. 122 123## Your First Task 124 125Write your soul.md to ${home}/soul.md. This file defines who you are, how you \ 126behave, and what you do. You can also create ${home}/memories/core.md for \ 127long-term facts about yourself. 128 129There is a soul.example.md in your home directory you can use as a starting point. 130 131${buildEnvironmentSection()}` 132} 133 134export async function buildBootstrap(event: UserMessage): Promise<Message[]> { 135 const soul = await readFile(path.join(HOME_DIR, "soul.md")) 136 const coreMemories = await readFile(path.join(HOME_DIR, "memories", "core.md")) 137 138 const system = soul 139 ? `\ 140${soul} 141 142--- 143 144${coreMemories ? `## Core Memories\n\n${coreMemories}\n\n---\n\n` : ""}\ 145${buildEnvironmentSection()}`.trim() 146 : buildFirstRunSystem().trim() 147 148 if (!soul) { 149 console.warn("[bootstrap] soul.md not found — using first-run bootstrap") 150 } 151 152 const wakeMessage = formatUserMessage(event) 153 154 return [ 155 { role: "system", content: system }, 156 { role: "user", content: wakeMessage }, 157 ] 158} 159 160function formatUserMessage(event: UserMessage): string { 161 const time = new Date(event.triggeredAt).toLocaleString() 162 return `[wake] ${time} — triggered by ${event.source}\n\n${event.content}` 163}