Shared runtime and building blocks for pi-mono based bots (e.g. pi-discord-bot, pi-telegram-bot).
Inspired by pi-mono; some modules were originally ported from pi-mono/packages/mom and remain MIT-licensed under the original copyright. See LICENSE.
The Executor abstraction lets the same agent tools work on the host or inside a Docker container without tool changes. Higher-level modules (workspace, message log, event scheduler, telegraph, …) are pulled out of the individual bot repos so they share one implementation.
ExecutorinterfaceHostExecutor— runs commands locally viash -cDockerExecutor— runs commands inside a running container viadocker exec ... sh -ccreateExecutor(config)/parseSandboxArg(arg)/validateSandbox(config)helpers
All tools are AgentTool instances from @mariozechner/pi-agent-core and drop into any Agent session.
createBashTool(executor)— run bash with tail truncation + temp file spillcreateReadTool(executor)— read text files (offset/limit) and images (base64)createWriteTool(executor)— write files, creates parent dirscreateEditTool(executor)— exact-match text replacement with unified diff outputcreateAttachTool()— let the LLM upload a file back to the user; bot wires the platform-specific uploadercreateChatHistoryTool({ logFilePath })— search the conversation'slog.jsonl(respects edit/delete tombstones)createScheduleTool(config)— typedschedule_eventtool for immediate / one-shot / periodic eventscreateTelegraphPublishTool/createTelegraphGetTool/createTelegraphEditTool/createTelegraphListToolcreateBotTools(executor)— convenience bundle of the four core tools (read, bash, edit, write)- Truncation helpers (
truncateHead,truncateTail)
pi-bot-core/workspace— per-chat directory layout (MEMORY.md,attachments/,scratch/,skills/), skill loading, host↔container path translationpi-bot-core/message-log— append-onlylog.jsonlwith the multi-row edit/delete contract used by every botpi-bot-core/events— generic event-file watcher that fires immediate / one-shot / periodic JSON events into a dispatcherpi-bot-core/log— platform-agnostic logger factory (chalk colours, usage summaries, tool/LLM lifecycle lines)pi-bot-core/download-queue— sequential background downloader for attachmentspi-bot-core/fs-watch—fs.watchwrapper that survives macOS inode rotationpi-bot-core/system-prompt—buildBaseSystemPromptshared scaffold consumed by per-bot system promptspi-bot-core/telegraph— in-house Telegraph API client + Markdown ↔ Telegraph node parser/serializer
import { HostExecutor, createBotTools } from "pi-bot-core";
const executor = new HostExecutor();
const tools = createBotTools(executor);import { DockerExecutor, createBotTools } from "pi-bot-core";
const executor = new DockerExecutor("pi-sandbox");
const tools = createBotTools(executor);
// all bash/read/write/edit now run inside the `pi-sandbox` containerimport { parseSandboxArg, validateSandbox, createExecutor, createBotTools } from "pi-bot-core";
// e.g. --sandbox=host or --sandbox=docker:pi-sandbox
const config = parseSandboxArg(process.env.SANDBOX ?? "host");
await validateSandbox(config); // throws SandboxConfigError if docker is missing / container stopped
const tools = createBotTools(createExecutor(config));import { HostExecutor, createBashTool, createReadTool } from "pi-bot-core";
const executor = new HostExecutor();
const tools = [createBashTool(executor), createReadTool(executor)];import {
createBotTools,
createAttachTool,
createChatHistoryTool,
createScheduleTool,
} from "pi-bot-core";
const tools = [
...createBotTools(executor),
createAttachTool().tool, // wire setUploader at run time
createChatHistoryTool({ logFilePath }),
createScheduleTool(scheduleConfig), // bot supplies routing schema
];What this package owns:
Executorinterface + host/docker implementationsSandboxConfig+ parse/validate/create helpers
What each bot repo owns:
- Wiring the CLI flag / env var to
parseSandboxArg - Container lifecycle scripts (
docker.sh/ Makefile / Dockerfile) - System-prompt wording that tells the LLM whether it's on host or in a container
(paths, package manager, etc. — see each bot's
buildSystemPrompt)
- Node/Bun with
sh(POSIX) orcmd(Windows) on the host.
dockerCLI available on the hostPATH- A running container with at least
/bin/sh(Alpine works; nobashneeded) - Workspace mounted into the container, conventionally at
/workspace(DockerExecutor.getWorkspacePath()always returns/workspace)
Example one-liner to create a throwaway Alpine container:
docker run -d --name pi-sandbox \
-v "$(pwd)/data:/workspace" \
alpine:latest tail -f /dev/nullNo new npm dependency for sandboxing — DockerExecutor is string wrapping around docker exec and reuses HostExecutor for the actual child-process work.
bun install
bun run typecheck
bun testNo build step — consumers import src/*.ts directly (Bun / tsc bundler mode). Subpath exports in package.json map each module to its source file.