### Project Setup Commands Source: https://github.com/bead-ai/zeitlich/blob/main/CONTRIBUTING.md Commands to clone the repository, navigate into the project directory, and install dependencies. ```bash git clone https://github.com/bead-ai/zeitlich.git cd zeitlich npm install ``` -------------------------------- ### Install Zeitlich and Temporal Dependencies Source: https://context7.com/bead-ai/zeitlich/llms.txt Install the core Zeitlich package along with necessary Temporal libraries and an optional LLM adapter. ```bash npm install zeitlich ioredis \ @temporalio/workflow @temporalio/common @temporalio/worker # Optional: choose your LLM adapter npm install @langchain/core @langchain/anthropic # LangChain npm install @anthropic-ai/sdk # Native Anthropic npm install @google/genai # Google GenAI ``` -------------------------------- ### Install Zeitlich and Dependencies Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Install the Zeitlich package along with ioredis and necessary Temporalio packages. Ensure peer dependencies are met for compatibility. ```bash npm install zeitlich ioredis \ @temporalio/workflow @temporalio/common ``` -------------------------------- ### Initialize SandboxManager with Hooks Source: https://context7.com/bead-ai/zeitlich/llms.txt Demonstrates initializing SandboxManager with in-memory and Daytona providers, including pre/post creation hooks for seeding files or installing dependencies. ```typescript import { SandboxManager, withSandbox, bashHandler, editHandler, globHandler, } from "zeitlich"; import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory"; import { DaytonaSandboxProvider } from "zeitlich/adapters/sandbox/daytona"; // --- In-memory (for tests or lightweight agents) --- const inMemoryManager = new SandboxManager(new InMemorySandboxProvider(), { hooks: { onPostCreate: async (sandbox) => { // Seed files after creation await sandbox.fs.writeFile("README.md", "# Agent workspace\n"); await sandbox.exec("git init"); }, }, }); // --- Daytona (remote workspaces) --- interface WorkflowCtx { projectId: string; files: Record } const daytonaManager = new SandboxManager( new DaytonaSandboxProvider({ apiKey: process.env.DAYTONA_API_KEY }), { hooks: { onPreCreate: async (_options, ctx) => ({ modifiedOptions: { initialFiles: ctx.files }, }), onPostCreate: async (sandbox) => { await sandbox.exec("npm install"); }, }, } ); export const createActivities = ({ redis, client }) => ({ ...adapter.createActivities("myAgentWorkflow"), ...daytonaManager.createActivities("myAgentWorkflow"), // produces: daytonaMyAgentWorkflowCreateSandbox, …DestroySandbox, etc. bashHandlerActivity: withSandbox(daytonaManager, bashHandler), editHandlerActivity: withSandbox(daytonaManager, editHandler), globHandlerActivity: withSandbox(daytonaManager, globHandler), }); ``` -------------------------------- ### Daytona Sandbox Provider Example Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Illustrates setting up and using a `DaytonaSandboxProvider`. It's crucial to set `workspaceBase` to ensure relative paths are correctly resolved within the Daytona environment. ```typescript import { DaytonaSandboxProvider } from "zeitlich"; const provider = new DaytonaSandboxProvider(); const { sandbox } = await provider.create({ workspaceBase: "/home/daytona", }); const fs = sandbox.fs; console.log(fs.workspaceBase); // "/home/daytona" await fs.mkdir("project", { recursive: true }); await fs.writeFile("project/README.md", "# Hello from Daytona\n"); const content = await fs.readFile("project/README.md"); ``` -------------------------------- ### Virtual File System Example Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Demonstrates the usage of `VirtualFileSystem` for in-memory file operations. Relative paths are resolved from the root (`/`), and `workspaceBase` defines the base for these resolutions. ```typescript import { VirtualFileSystem } from "zeitlich"; const virtualFs = new VirtualFileSystem( fileTree, resolver, { projectId: "p1" }, "/repo" ); console.log(virtualFs.workspaceBase); // "/repo" await virtualFs.writeFile("src/index.ts", "export const ok = true;\n"); const content = await virtualFs.readFile("src/index.ts"); // reads /repo/src/index.ts ``` -------------------------------- ### Conventional Commit Example Source: https://github.com/bead-ai/zeitlich/blob/main/CONTRIBUTING.md An example of a commit message following the conventional commit format. ```text feat: add support for streaming tool responses ``` -------------------------------- ### Install OpenTelemetry Interceptor for Temporal Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Install the necessary packages for integrating OpenTelemetry tracing with Temporal workflows. This enables distributed tracing across your client, workflow, and activities. ```bash npm install @temporalio/interceptors-opentelemetry @opentelemetry/sdk-node ``` -------------------------------- ### Define Tool with Custom Handler Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Configure tools within `createSession` by providing their definitions and custom handlers. This example shows how to integrate `askUserQuestionTool` and `bashTool` with their respective activity handlers. ```typescript const session = await createSession({ // ... other config tools: { AskUserQuestion: defineTool({ ...askUserQuestionTool, handler: askUserQuestionHandlerActivity, }), Bash: defineTool({ ...bashTool, handler: bashHandlerActivity, }), }, }); ``` -------------------------------- ### Activity and Worker Import Paths Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Import utilities for activity-side code and worker setup. These are framework-agnostic and include core functionality and adapter implementations. ```typescript // In activity files and worker setup — framework-agnostic core import { createRunAgentActivity, SandboxManager, withSandbox, bashHandler, } from "zeitlich"; // Thread adapter — activity-side import { createLangChainAdapter } from "zeitlich/adapters/thread/langchain"; ``` -------------------------------- ### Initialize Session with New Thread Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Start a fresh thread for a new conversation. This is the default behavior when no thread configuration is provided. ```typescript import { createSession } from "zeitlich/workflow"; // First run — fresh thread const session = await createSession({ thread: { mode: "new" }, // ... other config }); ``` -------------------------------- ### Generate File Tree String with `toTree` Source: https://context7.com/bead-ai/zeitlich/llms.txt Creates a human-readable ASCII file tree from a SandboxFileSystem. Useful for providing agents with filesystem awareness at session start. Requires a Sandbox instance. ```typescript import { toTree, SandboxManager, withSandbox } from "zeitlich"; import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory"; const provider = new InMemorySandboxProvider(); const { sandbox } = await provider.create({ initialFiles: { "src/index.ts": "export const hello = 'world';\n", "src/utils.ts": "export const add = (a: number, b: number) => a + b;\n", "package.json": '{ "name": "my-project" }\n', }, }); const tree = await toTree(sandbox.fs); // . // ├── package.json // └── src/ // ├── index.ts // └── utils.ts // Use in the workflow's context message const session = await createSession({ buildContextMessage: async () => { const fileTree = await generateFileTreeActivity(); // activity wrapping toTree return [ { type: "text", text: `Project structure:\n${fileTree}` }, { type: "text", text: `Task: ${prompt}` }, ]; }, // ... }); ``` -------------------------------- ### Initialize Session with Forked Thread Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Fork an existing thread to start a new conversation based on a previous one. The original thread remains unchanged. ```typescript import { createSession } from "zeitlich/workflow"; // Later — fork the previous conversation const resumedSession = await createSession({ thread: { mode: "fork", threadId: savedThreadId }, // ... other config }); ``` -------------------------------- ### Define Zeitlich Workflow Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Defines a Zeitlich workflow that wires together a thread adapter and a sandbox adapter. Swap proxy imports to change providers. Requires setup of agent state manager, session, and tools. ```typescript import { proxyActivities, workflowInfo } from "@temporalio/workflow"; import { createAgentStateManager, createSession, defineWorkflow, proxyRunAgent, askUserQuestionTool, bashTool, defineTool, } from "zeitlich/workflow"; import { searchTool } from "./tools"; import type { MyActivities } from "./activities"; import type { StoredMessage } from "@langchain/core/messages"; import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow"; import { proxyInMemorySandboxOps } from "zeitlich/adapters/sandbox/inmemory/workflow"; const runAgentActivity = proxyRunAgent(); const { searchHandlerActivity, bashHandlerActivity, askUserQuestionHandlerActivity, } = proxyActivities({ startToCloseTimeout: "30m", retry: { maximumAttempts: 6, initialInterval: "5s", maximumInterval: "15m", backoffCoefficient: 4, }, }); export const myAgentWorkflow = defineWorkflow( { name: "myAgentWorkflow" }, async ({ prompt }: { prompt: string }, sessionInput) => { const { runId } = workflowInfo(); const stateManager = createAgentStateManager({ initialState: { systemPrompt: "You are a helpful assistant.", }, agentName: "my-agent", }); const session = await createSession({ agentName: "my-agent", maxTurns: 20, thread: { mode: "new", threadId: runId }, threadOps: proxyLangChainThreadOps(), sandboxOps: proxyInMemorySandboxOps(), runAgent: runAgentActivity, buildContextMessage: () => [{ type: "text", text: prompt }], tools: { Search: defineTool({ ...searchTool, handler: searchHandlerActivity, }), AskUserQuestion: defineTool({ ...askUserQuestionTool, handler: askUserQuestionHandlerActivity, hooks: { onPostToolUse: () => { stateManager.waitForInput(); }, }, }), Bash: defineTool({ ...bashTool, handler: bashHandlerActivity, }), }, ...sessionInput, }); const result = await session.runSession({ stateManager }); return result; } ); ``` -------------------------------- ### Configure and Register a Subagent Source: https://context7.com/bead-ai/zeitlich/llms.txt Define a subagent configuration using `defineSubagent` for integration into a parent workflow. This example configures thread management, sandbox behavior, workflow options like timeouts and retries, and defines lifecycle hooks for post-execution and failure events. ```typescript // parent.workflow.ts import { defineSubagent, createSession } from "zeitlich/workflow"; import { researcherWorkflow } from "./researcher.workflow"; export const researcherSubagent = defineSubagent(researcherWorkflow, { thread: "fork", // fork parent's thread for each invocation sandbox: { source: "own", continuation: "fork", shutdown: "pause" }, workflowOptions: { workflowRunTimeout: "30m", // override default 1h safety timeout retry: { maximumAttempts: 2 }, }, enabled: () => featureFlags.researchEnabled, hooks: { onPostExecution: ({ result }) => console.log("Research done:", result.data), onExecutionFailure: ({ error }) => console.error("Research failed:", error), }, }); const session = await createSession({ agentName: "orchestrator", subagents: [researcherSubagent, codeReviewerSubagent], // Task tool automatically added — LLM can now spawn child workflows // ... }); ``` -------------------------------- ### Zeitlich Observability Hooks for Metrics and Logging Source: https://context7.com/bead-ai/zeitlich/llms.txt Implements observability hooks to emit structured events for session and tool lifecycles. `createObservabilityHooks` generates hooks, and `composeHooks` allows sequencing multiple hooks for the same event. This example shows registering Prometheus metrics and custom Slack notifications. ```typescript // worker.ts — register sinks import { Worker, type InjectedSinks } from "@temporalio/worker"; import type { ZeitlichObservabilitySinks } from "zeitlich/workflow"; import { register, Counter, Histogram } from "prom-client"; const sessionCount = new Counter({ name: "agent_sessions_total", labelNames: ["agent"] }); const turnDuration = new Histogram({ name: "agent_turn_duration_ms", labelNames: ["agent"] }); const toolDuration = new Histogram({ name: "tool_duration_ms", labelNames: ["tool"] }); const sinks: InjectedSinks = { zeitlichMetrics: { sessionStarted: { fn(_, event) { sessionCount.inc({ agent: event.agentName }); }, callDuringReplay: false, }, sessionEnded: { fn(_, event) { turnDuration.observe({ agent: event.agentName }, event.durationMs); }, callDuringReplay: false, }, toolExecuted: { fn(_, event) { toolDuration.observe({ tool: event.toolName }, event.durationMs); }, callDuringReplay: false, }, turnCompleted: { fn(_, event) { console.log(`Turn ${event.turn} of ${event.agentName}`); }, callDuringReplay: false, }, }, }; await Worker.create({ sinks /* ... */ }); // workflow.ts — wire hooks import { createObservabilityHooks, composeHooks, createSession } from "zeitlich/workflow"; const obs = createObservabilityHooks("my-agent"); const session = await createSession({ agentName: "my-agent", hooks: { ...obs, // Compose custom end-of-session logic alongside observability onSessionEnd: composeHooks(obs.onSessionEnd, async (ctx) => { await notifySlack(`Agent ${ctx.agentName} finished: ${ctx.exitReason}`); }), }, // ... }); ``` -------------------------------- ### Define Researcher Workflow with Session Creation Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Defines a researcher subagent workflow. It sets up a session using provided session input, proxying LangChain thread operations, and then runs the session to get thread ID and final message. ```typescript export const researcherWorkflow = defineSubagentWorkflow( { name: "Researcher", description: "Researches topics and gathers information", }, async (prompt, sessionInput) => { const session = await createSession({ ...sessionInput, // spreads agentName, thread, sandbox, sandboxShutdown threadOps: proxyLangChainThreadOps(), // ... other config }); const { threadId, finalMessage } = await session.runSession({ stateManager, }); return { toolResponse: extractText(finalMessage), data: null, threadId }; } ); ``` -------------------------------- ### Full Release Flow Quick Reference Source: https://github.com/bead-ai/zeitlich/blob/main/RELEASING.md A consolidated command sequence for the complete local release process. ```bash # Full release flow export GITHUB_TOKEN=$(gh auth token) npm run release:pr # Step 1: Create PR # ... merge PR on GitHub ... # Step 2: Merge git pull # Step 3: Pull npm run release:publish # Step 4: Publish (NEVER release:npm alone) ``` -------------------------------- ### FileSystemSkillProvider Source: https://context7.com/bead-ai/zeitlich/llms.txt Loads skills from the filesystem. Supports eager loading of all skills with full instructions and resources, or a lightweight metadata-only listing. Individual skills can also be fetched by name. ```APIDOC ## `FileSystemSkillProvider` — Load Skills from Filesystem Loads [agentskills.io](https://agentskills.io)-compliant skills from a directory. Each skill lives in its own subdirectory with a `SKILL.md` (YAML frontmatter + instructions) and optional resource files. Import from `zeitlich`. ```typescript import { FileSystemSkillProvider, NodeFsSandboxFileSystem } from "zeitlich"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; // Load from worker disk (activity-side) const __dirname = dirname(fileURLToPath(import.meta.url)); const fs = new NodeFsSandboxFileSystem(join(__dirname, "skills")); const skillProvider = new FileSystemSkillProvider(fs, "/"); // Eagerly load all skills with full instructions + resource contents const skills = await skillProvider.loadAll(); // skills[0] → { name, description, instructions, location, resourceContents } // resourceContents → { "resources/checklist.md": "...", "scripts/run.py": "..." } // Lightweight metadata-only listing (no instruction text) const metadata = await skillProvider.listSkills(); // metadata[0] → { name, description, location, allowedTools?, license? } // Load a single skill by name const codeReview = await skillProvider.getSkill("code-review"); // Pass to createSession — ReadSkill tool + resource seeding are automatic const session = await createSession({ agentName: "my-agent", skills, // Skill[] — ReadSkill tool auto-registered with enum of names sandboxOps: proxyInMemorySandboxOps(), // skill resources seeded as initial files // ... }); ``` ```markdown --- name: code-review description: Review pull requests for correctness, style, and security allowed-tools: Bash Grep Read license: MIT --- ## Instructions 1. Read the diff with `Bash` 2. Check for security issues with `Grep` 3. Read the checklist from `resources/checklist.md` ``` ``` -------------------------------- ### Initialize DaytonaSandboxProvider and Create Sandbox Source: https://context7.com/bead-ai/zeitlich/llms.txt Instantiate DaytonaSandboxProvider with API credentials and server URL. Use it to create a sandbox environment with initial files and perform filesystem operations like creating directories and writing/reading files. Execute commands within the sandbox context. ```typescript import { DaytonaSandboxProvider } from "zeitlich/adapters/sandbox/daytona"; import { proxyDaytonaSandboxOps } from "zeitlich/adapters/sandbox/daytona/workflow"; const provider = new DaytonaSandboxProvider({ apiKey: process.env.DAYTONA_API_KEY, serverUrl: process.env.DAYTONA_SERVER_URL, workspaceBase: "/home/daytona", }); const manager = new SandboxManager(provider); // In activities, write files with relative paths const { sandbox } = await provider.create({ language: "typescript", initialFiles: { "package.json": JSON.stringify({ name: "agent-ws" }) }, }); await sandbox.fs.mkdir("src", { recursive: true }); await sandbox.fs.writeFile("src/index.ts", "export const hello = 'world';\n"); const content = await sandbox.fs.readFile("src/index.ts"); const result = await sandbox.exec("npx tsc --noEmit", { cwd: "/home/daytona" }); // Register for Temporal worker export const createActivities = ({ redis, client }) => ({ ...adapter.createActivities("myAgentWorkflow"), ...manager.createActivities("myAgentWorkflow"), bashHandlerActivity: withSandbox(manager, bashHandler), }); ``` -------------------------------- ### Create LangChain Adapter and Activities Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Demonstrates how to create a LangChain thread adapter and integrate it with Zeitlich's agent activities. Requires Redis and a LangChain model instance. ```typescript import { ChatAnthropic } from "@langchain/anthropic"; import { createLangChainAdapter } from "zeitlich/adapters/thread/langchain"; import { createRunAgentActivity } from "zeitlich"; const adapter = createLangChainAdapter({ redis, model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }), }); export function createActivities(client: WorkflowClient) { return { ...adapter.createActivities("myAgentWorkflow"), ...createRunAgentActivity(client, adapter.invoker, "myAgentWorkflow"), }; } ``` -------------------------------- ### Troubleshoot: Complete Missed GitHub Release Step Source: https://github.com/bead-ai/zeitlich/blob/main/RELEASING.md Manual steps to recover from a skipped 'release:github' step by creating the tag and GitHub release. ```bash # 1. Find the merge commit of the merged-but-untagged release PR MERGE_SHA=$(gh pr view --json mergeCommit --jq '.mergeCommit.oid') # 2. Create + push the tag git tag vX.Y.Z $MERGE_SHA git push origin vX.Y.Z # 3. Create the GitHub Release (notes are best-copied from CHANGELOG.md) gh release create vX.Y.Z --verify-tag --title "vX.Y.Z" --notes "…CHANGELOG section…" # 4. Flip the label — this is what actually unsticks release-please gh pr edit \ --remove-label "autorelease: pending" \ --add-label "autorelease: tagged" ``` -------------------------------- ### Load Skills from Filesystem with FileSystemSkillProvider Source: https://context7.com/bead-ai/zeitlich/llms.txt Loads agentskills.io-compliant skills from a directory. Each skill should have a SKILL.md file and optional resource files. Supports eager loading of all skills or lightweight metadata listing. ```typescript import { FileSystemSkillProvider, NodeFsSandboxFileSystem } from "zeitlich"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; // Load from worker disk (activity-side) const __dirname = dirname(fileURLToPath(import.meta.url)); const fs = new NodeFsSandboxFileSystem(join(__dirname, "skills")); const skillProvider = new FileSystemSkillProvider(fs, "/"); // Eagerly load all skills with full instructions + resource contents const skills = await skillProvider.loadAll(); // skills[0] → { name, description, instructions, location, resourceContents } // resourceContents → { "resources/checklist.md": "...", "scripts/run.py": "..." } // Lightweight metadata-only listing (no instruction text) const metadata = await skillProvider.listSkills(); // metadata[0] → { name, description, location, allowedTools?, license? } // Load a single skill by name const codeReview = await skillProvider.getSkill("code-review"); // Pass to createSession — ReadSkill tool + resource seeding are automatic const session = await createSession({ agentName: "my-agent", skills, // Skill[] — ReadSkill tool auto-registered with enum of names sandboxOps: proxyInMemorySandboxOps(), // skill resources seeded as initial files // ... }); ``` ```markdown --- name: code-review description: Review pull requests for correctness, style, and security allowed-tools: Bash Grep Read license: MIT --- ## Instructions 1. Read the diff with `Bash` 2. Check for security issues with `Grep` 3. Read the checklist from `resources/checklist.md` ``` -------------------------------- ### Install Custom Logger for Zeitlich Worker Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Configure a custom logger, such as Winston, for your Zeitlich worker. This ensures logs are captured and routed correctly through Temporal's workflow logger. ```typescript import { Runtime, makeTelemetryFilterString, } from "@temporalio/worker"; import winston from "winston"; const logger = winston.createLogger({ level: "info", format: winston.format.json(), transports: [new winston.transports.File({ filename: "worker.log" })], }); Runtime.install({ logger, telemetryOptions: { logging: { filter: makeTelemetryFilterString({ core: "INFO", other: "INFO" }), forward: {}, }, }, }); ``` -------------------------------- ### Load Skills with FileSystemSkillProvider (Node.js FS) Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Loads skills from the local filesystem using Node.js's file system adapter. Useful when skill files are bundled with application code. ```typescript import { NodeFsSandboxFileSystem, FileSystemSkillProvider } from "zeitlich"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; const __dirname = dirname(fileURLToPath(import.meta.url)); const fs = new NodeFsSandboxFileSystem(join(__dirname, "skills")); const skillProvider = new FileSystemSkillProvider(fs, "/"); const skills = await skillProvider.loadAll(); ``` -------------------------------- ### Load Skills with FileSystemSkillProvider (In-Memory Sandbox) Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Loads all skills and their resource contents from a specified directory using an in-memory sandbox. Ensures all skill data is eagerly read. ```typescript import { FileSystemSkillProvider } from "zeitlich"; import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory"; const provider = new InMemorySandboxProvider(); const { sandbox } = await provider.create({}); const skillProvider = new FileSystemSkillProvider(sandbox.fs, "/skills"); const skills = await skillProvider.loadAll(); // Each skill has: { name, description, instructions, resourceContents } // resourceContents: { "resources/checklist.md": "...", ... } ``` -------------------------------- ### E2bSandboxProvider Configuration and Usage Source: https://context7.com/bead-ai/zeitlich/llms.txt Illustrates configuring E2bSandboxProvider for persistent cloud sandboxes with options like template, workspace base, and timeouts. Includes activity registration and workflow continuation strategies. ```typescript import { E2bSandboxProvider } from "zeitlich/adapters/sandbox/e2b"; import { proxyE2bSandboxOps } from "zeitlich/adapters/sandbox/e2b/workflow"; // Activity side const provider = new E2bSandboxProvider({ template: "my-custom-template", workspaceBase: "/home/user", timeoutMs: 300_000, // 5 minutes keepAliveMs: 60_000, }); const manager = new SandboxManager(provider); // Register activities on the worker const activities = { ...manager.createActivities("myAgentWorkflow"), bashHandlerActivity: withSandbox(manager, bashHandler), }; // Workflow side — snapshot-driven continuation to avoid idle sandbox costs export const analystSubagent = defineSubagent(analystWorkflow, { thread: "continue", sandbox: { source: "own", init: "once", // create once per thread, reuse on follow-up calls continuation: "snapshot", // snapshot on exit, restore on next call }, }); const session = await createSession({ sandboxOps: proxyE2bSandboxOps(), sandboxShutdown: "snapshot", // capture snapshot + destroy on exit // snapshot exposed in session result: { snapshot, baseSnapshot } }); ``` -------------------------------- ### Configure E2bSandboxProvider Source: https://github.com/bead-ai/zeitlich/blob/main/src/adapters/sandbox/e2b/README.md Instantiate the E2bSandboxProvider with template and timeout configurations. Use `timeoutMs` for the sandbox lifetime and `keepAliveMs` for per-call refresh windows. ```typescript import { E2bSandboxProvider } from "zeitlich/adapters/sandbox/e2b"; const provider = new E2bSandboxProvider({ template: "my-template", timeoutMs: 15 * 60 * 1000, // kill-on-abandon safety net keepAliveMs: 15 * 60 * 1000, // refreshed on every tool call }); ``` -------------------------------- ### Sandbox Initialization and Shutdown Modes Source: https://context7.com/bead-ai/zeitlich/llms.txt Manages sandbox initialization strategy and actions upon session exit. Modes include 'new', 'from-snapshot', and 'fork'. Sandbox shutdown options determine whether the sandbox is destroyed, paused, kept running, or snapshotted before destruction. ```typescript import { createSession } from "zeitlich/workflow"; // Create fresh sandbox, capture snapshot on exit, destroy sandbox const session = await createSession({ sandboxOps: proxyE2bSandboxOps(), sandbox: { mode: "new" }, sandboxShutdown: "snapshot", onSandboxReady: ({ sandboxId, baseSnapshot }) => { console.log("Sandbox ready:", sandboxId); }, onSessionExit: ({ sandboxId, snapshot, threadId }) => { // Persist snapshot reference for next run saveToDb({ sandboxId, snapshotData: snapshot, threadId }); }, // ... }); const { snapshot, baseSnapshot } = await session.runSession({ stateManager }); // Restore from a previous snapshot const resumedSession = await createSession({ sandbox: { mode: "from-snapshot", snapshot: savedSnapshot }, sandboxShutdown: "snapshot", // ... }); // Fork an existing sandbox const forkedSession = await createSession({ sandbox: { mode: "fork", sandboxId: existingSandboxId }, sandboxShutdown: "destroy", // ... }); // Sandbox shutdown options: // "destroy" — delete sandbox on exit (default) // "pause" — pause for later resume // "keep" — leave running (no-op) // "snapshot" — snapshot then destroy ``` -------------------------------- ### Define a Subagent Workflow for Research Source: https://context7.com/bead-ai/zeitlich/llms.txt Define a reusable subagent workflow using `defineSubagentWorkflow`. This example sets up a researcher agent with a specific name, description, and result schema. It configures session inputs, thread operations, and context message building for running the research task. ```typescript // researcher.workflow.ts import { createAgentStateManager, createSession, defineSubagentWorkflow, proxyRunAgent, } from "zeitlich/workflow"; import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow"; import type { StoredMessage } from "@langchain/core/messages"; import { z } from "zod"; const runResearcherActivity = proxyRunAgent(); export const researcherWorkflow = defineSubagentWorkflow( { name: "Researcher", description: "Deep-dives into a topic and returns a structured report", resultSchema: z.object({ summary: z.string(), sources: z.array(z.string()) }), }, async (prompt, sessionInput) => { const stateManager = createAgentStateManager({ initialState: { systemPrompt: "You are a thorough research analyst." }, }); const session = await createSession({ ...sessionInput, // spreads agentName, thread, sandbox, sandboxShutdown threadOps: proxyLangChainThreadOps(), runAgent: runResearcherActivity, buildContextMessage: () => [{ type: "text", text: prompt }], }); const { finalMessage, threadId } = await session.runSession({ stateManager }); return { toolResponse: finalMessage ? String(finalMessage) : "No response", data: { summary: String(finalMessage), sources: [] }, threadId, }; } ); ``` -------------------------------- ### Thread Initialization Modes in Zeitlich Source: https://context7.com/bead-ai/zeitlich/llms.txt Controls how a conversation thread is initialized at session start. Options include 'new' (default), 'fork', and 'continue'. The 'new' mode can optionally pin the thread ID to the workflow run ID. Use `getShortId` for compact, workflow-deterministic IDs. ```typescript import { createSession } from "zeitlich/workflow"; // Fresh thread (default) — optionally pin the thread ID to the workflow runId const newSession = await createSession({ thread: { mode: "new", threadId: workflowInfo().runId }, // ... }); // result.threadId → the generated or provided thread ID // Fork — copy all messages into a new thread; original is never mutated const forkedSession = await createSession({ thread: { mode: "fork", threadId: previousThreadId }, // ... }); // Continue — append directly to an existing thread const continuedSession = await createSession({ thread: { mode: "continue", threadId: savedThreadId }, // ... }); ``` ```typescript // getShortId — compact workflow-deterministic IDs (~12 base-62 chars) import { getShortId } from "zeitlich/workflow"; const id = getShortId(); // e.g. "aB3xKm9pLqRt" ``` -------------------------------- ### Development Build Commands Source: https://github.com/bead-ai/zeitlich/blob/main/CONTRIBUTING.md Commands for building and managing the project during development. Includes watch mode, one-off builds, type checking, and linting. ```bash npm run dev # Build in watch mode npm run build # One-off build npm run typecheck # Type checking npm run lint # Lint with ESLint npm run lint:fix # Lint and auto-fix npm run format # Format with Prettier ``` -------------------------------- ### InMemorySandboxProvider Usage Source: https://context7.com/bead-ai/zeitlich/llms.txt Shows how to use InMemorySandboxProvider for creating, managing, and snapshotting sandboxes directly or via workflow proxies. ```typescript import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory"; import { proxyInMemorySandboxOps } from "zeitlich/adapters/sandbox/inmemory/workflow"; // Activity side const provider = new InMemorySandboxProvider({ bashOptions: { executionLimits: { maxStringLength: 52_428_800 } }, }); const manager = new SandboxManager(provider); // Create and use directly (outside Temporal, e.g. tests) const { sandbox } = await provider.create({ initialFiles: { "index.ts": 'console.log("hello");\n' }, }); const content = await sandbox.fs.readFile("index.ts"); const result = await sandbox.exec("node -e 'console.log(1+1)'"); // result → { exitCode: 0, stdout: "2\n", stderr: "" } // Snapshot / restore const snapshot = await provider.snapshot(sandbox.id); const restored = await provider.restore(snapshot); // Workflow side — use proxy const session = await createSession({ sandboxOps: proxyInMemorySandboxOps(), sandboxShutdown: "destroy", // ... }); ``` -------------------------------- ### Skill Directory Structure Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Illustrates the expected file organization for skills, including the main SKILL.md file and resource directories. ```treeview skills/ ├── code-review/ │ ├── SKILL.md │ └── resources/ │ └── checklist.md ├── pdf-processing/ │ ├── SKILL.md │ └── templates/ │ └── extraction-prompt.txt ``` -------------------------------- ### Publish Release Source: https://github.com/bead-ai/zeitlich/blob/main/RELEASING.md Publishes the release to GitHub and npm. This command must be run in full; do not run 'release:npm' standalone. ```bash export GITHUB_TOKEN=$(gh auth token) npm run release:publish ``` -------------------------------- ### Set Up Zeitlich Worker Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Sets up and runs a Temporal worker. Connects to Temporal and Redis, and defines the path to workflows and activities. ```typescript import { Worker, NativeConnection } from "@temporalio/worker"; import Redis from "ioredis"; import { fileURLToPath } from "node:url"; import { createActivities } from "./activities"; async function run() { const connection = await NativeConnection.connect({ address: "localhost:7233", }); const redis = new Redis({ host: "localhost", port: 6379 }); const worker = await Worker.create({ connection, taskQueue: "my-agent", workflowsPath: fileURLToPath(new URL("./workflows.ts", import.meta.url)), activities: createActivities({ redis, client }), }); await worker.run(); } ``` -------------------------------- ### Create Google GenAI Adapter for Temporal Activities Source: https://context7.com/bead-ai/zeitlich/llms.txt Bundles Redis thread management with the `@google/genai` SDK. Import from `zeitlich/adapters/thread/google-genai`. ```typescript import { GoogleGenAI } from "@google/genai"; import { createGoogleGenAIAdapter } from "zeitlich/adapters/thread/google-genai"; import { createRunAgentActivity } from "zeitlich"; export const createActivities = ({ redis, client }) => { const genai = new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY }); const adapter = createGoogleGenAIAdapter({ redis, model: genai.getGenerativeModel({ model: "gemini-2.0-flash" }), }); return { ...adapter.createActivities("myAgentWorkflow"), ...createRunAgentActivity(client, adapter.invoker, "myAgentWorkflow"), }; }; // Workflow side import { proxyGoogleGenAIThreadOps } from "zeitlich/adapters/thread/google-genai/workflow"; const session = await createSession({ threadOps: proxyGoogleGenAIThreadOps(), // ... }); ``` -------------------------------- ### Force Specific Version Source: https://github.com/bead-ai/zeitlich/blob/main/RELEASING.md Pins the next release to a specific version by adding a 'Release-As' trailer to an empty commit. ```bash git commit --allow-empty -m "chore: release X.Y.Z Release-As: X.Y.Z" git push origin main npm run release:pr ``` -------------------------------- ### Create Release PR Source: https://github.com/bead-ai/zeitlich/blob/main/RELEASING.md Initiates the release process by creating a Pull Request that bumps the version and updates the changelog. ```bash export GITHUB_TOKEN=$(gh auth token) npm run release:pr ``` -------------------------------- ### Create Session with Skills Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Pass loaded skills to `createSession`. Zeitlich automatically registers a `ReadSkill` tool and seeds resource contents into the sandbox. ```typescript import { createSession } from "zeitlich/workflow"; const session = await createSession({ // ... other config skills, // Skill[] — loaded via FileSystemSkillProvider or manually }); ``` -------------------------------- ### Generate File Tree Activity Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Use `toTree` to generate a file tree string from an IFileSystem instance. This is useful for providing an agent with filesystem awareness in its context. ```typescript import { toTree } from "zeitlich"; // In activities - generate a file tree string for agent context export const createActivities = ({ redis, client }) => ({ generateFileTreeActivity: async () => toTree(inMemoryFileSystem), // ... }); ``` -------------------------------- ### Import Zeitlich Workflow Tools Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Import definitions for various Zeitlich tools like `readTool`, `writeTool`, `editTool`, `globTool`, `grepTool`, `bashTool`, and `askUserQuestionTool` for use in workflows. ```typescript // Import tool definitions in workflows import { readTool, writeTool, editTool, globTool, grepTool, bashTool, askUserQuestionTool, } from "zeitlich/workflow"; // Import handlers + wrapper in activities import { withSandbox, editHandler, globHandler, bashHandler, createAskUserQuestionHandler, } from "zeitlich"; ``` -------------------------------- ### Build Context Message with File Tree Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Integrate the generated file tree into the `buildContextMessage` function to provide an agent with filesystem context. This allows the agent to understand the available files. ```typescript // In workflow const fileTree = await generateFileTreeActivity(); const session = await createSession({ // ... other config buildContextMessage: () => [ { type: "text", text: `Files in the filesystem: ${fileTree}` }, { type: "text", text: prompt }, ], }); ``` -------------------------------- ### Create Zeitlich Activities Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Factory function to create activities, receiving infrastructure dependencies. Configures thread and sandbox adapters. Swap imports to change LLM or sandbox backend. ```typescript import type Redis from "ioredis"; import type { WorkflowClient } from "@temporalio/client"; import { ChatAnthropic } from "@langchain/anthropic"; import { SandboxManager, withSandbox, bashHandler, createAskUserQuestionHandler, createRunAgentActivity, } from "zeitlich"; import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory"; import { createLangChainAdapter } from "zeitlich/adapters/thread/langchain"; const sandboxProvider = new InMemorySandboxProvider(); const sandboxManager = new SandboxManager(sandboxProvider); export const createActivities = ({ redis, client, }: { redis: Redis; client: WorkflowClient; }) => { const adapter = createLangChainAdapter({ redis, model: new ChatAnthropic({ model: "claude-sonnet-4-20250514", maxTokens: 4096, }), }); return { ...adapter.createActivities("myAgentWorkflow"), ...sandboxManager.createActivities("myAgentWorkflow"), ...createRunAgentActivity(client, adapter.invoker, "myAgentWorkflow"), searchHandlerActivity: async (args: { query: string }) => ({ toolResponse: JSON.stringify(await performSearch(args.query)), data: null, }), bashHandlerActivity: withSandbox(sandboxManager, bashHandler), askUserQuestionHandlerActivity: createAskUserQuestionHandler(), }; }; export type MyActivities = ReturnType; ``` -------------------------------- ### Create Temporal Activity for LLM Invoker with createRunAgentActivity Source: https://context7.com/bead-ai/zeitlich/llms.txt Wraps a model invoker into a scope-prefixed Temporal activity. It auto-fetches parent workflow state (agent state including system prompt, tools, etc.) before each invocation. Requires Temporal client and an adapter for the LLM invoker. ```typescript import { createRunAgentActivity } from "zeitlich"; import { createLangChainAdapter } from "zeitlich/adapters/thread/langchain"; import type { WorkflowClient } from "@temporalio/client"; import type Redis from "ioredis"; export const createActivities = ({ redis, client, }: { redis: Redis; client: WorkflowClient }) => { const adapter = createLangChainAdapter({ redis, model }); return { ...adapter.createActivities("myAgentWorkflow"), // Creates: { runMyAgentWorkflow: async (config) => invoker({ ...config, state }) } ...createRunAgentActivity(client, adapter.invoker, "myAgentWorkflow"), }; }; // Workflow side — proxy with sensible LLM defaults import { proxyRunAgent } from "zeitlich/workflow"; import type { StoredMessage } from "@langchain/core/messages"; const runAgentActivity = proxyRunAgent(); // configured with: heartbeatTimeout=30s, startToCloseTimeout=10m, // retry.maximumAttempts=3, retry.maximumInterval=30s ``` -------------------------------- ### List Skills Metadata Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Performs lightweight discovery of skills without reading their full content. Returns an array of SkillMetadata objects containing name, description, and location. ```typescript const metadata = await skillProvider.listSkills(); // SkillMetadata[] — name, description, location only ``` -------------------------------- ### Create an Agent Session with Zeitlich Workflow Source: https://context7.com/bead-ai/zeitlich/llms.txt Defines a Temporal workflow using Zeitlich to create an agent session. This includes setting up state management, session configuration, tool definitions, and lifecycle hooks. ```typescript // workflows/coding-agent.workflow.ts import { proxyActivities, workflowInfo } from "@temporalio/workflow"; import { createAgentStateManager, createSession, defineWorkflow, proxyRunAgent, bashTool, defineTool, } from "zeitlich/workflow"; import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow"; import { proxyInMemorySandboxOps } from "zeitlich/adapters/sandbox/inmemory/workflow"; import type { StoredMessage } from "@langchain/core/messages"; import type { MyActivities } from "./activities"; const runAgentActivity = proxyRunAgent(); const { bashHandlerActivity } = proxyActivities({ startToCloseTimeout: "30m", retry: { maximumAttempts: 6, initialInterval: "5s", backoffCoefficient: 4 }, }); export const codingAgentWorkflow = defineWorkflow( { name: "codingAgentWorkflow" }, async ({ prompt }: { prompt: string }, sessionInput) => { const { runId } = workflowInfo(); const stateManager = createAgentStateManager({ initialState: { systemPrompt: "You are an expert TypeScript developer." }, agentName: "coding-agent", }); const session = await createSession({ agentName: "coding-agent", maxTurns: 30, thread: { mode: "new", threadId: runId }, threadOps: proxyLangChainThreadOps(), sandboxOps: proxyInMemorySandboxOps(), sandboxShutdown: "destroy", runAgent: runAgentActivity, buildContextMessage: () => [{ type: "text", text: prompt }], tools: { Bash: defineTool({ ...bashTool, handler: bashHandlerActivity }), }, hooks: { onSessionStart: ({ threadId, agentName }) => console.log(`Session started: ${agentName} / ${threadId}`), onSessionEnd: ({ exitReason, turns }) => console.log(`Done after ${turns} turns — ${exitReason}`), onPreToolUse: ({ toolCall }) => { if (toolCall.name === "Bash" && toolCall.args.command.includes("rm -rf /")) { return { skip: true }; } return {}; }, onPostToolUse: ({ toolCall, durationMs }) => console.log(`${toolCall.name} finished in ${durationMs}ms`), onPostToolUseFailure: ({ toolCall, error }) => ({ fallbackContent: `Tool ${toolCall.name} failed: ${String(error)}`, }), }, ...sessionInput, }); // Returns { threadId, finalMessage, exitReason, usage, sandboxId } return session.runSession({ stateManager }); } ); ``` -------------------------------- ### Set GitHub Token Source: https://github.com/bead-ai/zeitlich/blob/main/RELEASING.md Authenticates with GitHub CLI and exports the token for subsequent commands. ```bash export GITHUB_TOKEN=$(gh auth token) ``` -------------------------------- ### Define Analyst Workflow with Session Creation Source: https://github.com/bead-ai/zeitlich/blob/main/README.md Defines a subagent workflow that creates a session with specified sandbox operations, run agent logic, and thread operations. The `sandboxShutdown: "snapshot"` option indicates that the sandbox will be managed via snapshots. ```typescript export const analystWorkflow = defineSubagentWorkflow( { name: "analyst", description: "...", sandboxShutdown: "snapshot" }, async (prompt, sessionInput) => { const session = await createSession({ ...sessionInput, sandboxOps, runAgent, threadOps, buildContextMessage: () => prompt, }); const result = await session.runSession({ stateManager }); return result; // result.deleteSnapshots is forwarded automatically } ); ```