Install openwork globally via npm
mainnpm install -g openwork. Once installed, you can launch the interface by running openwork in your terminal.npm install -g openwork
openworkrepository·main·Indexed 23 days ago
https://github.com/langchain-ai/openworkA tactical desktop interface for deepagentsjs that provides a visual environment for building deep agents. It features filesystem access via LocalSandbox, planning capabilities, subagent delegation, and support for models from Anthropic, OpenAI, and Google. The system includes a SQLite-based checkpointing mechanism using SqlJsSaver for state persistence and thread management.
npm install -g openwork. Once installed, you can launch the interface by running openwork in your terminal.npm install -g openwork
openworkYou can run the openwork desktop interface immediately without a permanent installation by using npx.
npx openworkIf you want to run the development version from the local repository, follow these steps:
Note: Requires Node.js 18+.
git clone https://github.com/langchain-ai/openwork.git
cd openwork
npm install
npm run devWhen using the createAgentRuntime, the agent operates in a LocalSandbox with virtualMode: false. This means the agent interacts with your actual filesystem using fully qualified absolute system paths.
Key Pathing Rules:
workspacePath provided during runtime creation.ls, read_file, write_file, edit_file, glob, grep) must use absolute paths.workspacePath is /Users/dev/project, a file in src should be referenced as /Users/dev/project/src/index.ts.ls("/Users/dev/project").The SqlJsSaver class is a BaseCheckpointSaver implementation that uses sql.js to persist agent checkpoints in a SQLite database. Because it uses a pure JavaScript implementation of SQLite, it is compatible with environments like Electron without requiring native module compilation.
Key Features:
SqlJsSaver will automatically back up the old file (with a .bak.[timestamp] suffix) and create a fresh database to prevent memory exhaustion.thread_id and checkpoint_ns (namespace).Beyond standard message events, ElectronIPCTransport emits several custom event types to provide rich UI context:
type: "subagents": Emits an array of Subagent objects, tracking the status (running | completed), name, and description of active sub-tasks.type: "workspace": Emits file system metadata including files (list of paths, is_dir, and size) and the path of the current workspace.type: "interrupt": Emits when the graph hits a breakpoint requiring human intervention. It includes a request object with a tool_call and allowed_decisions (e.g., ['approve', 'reject', 'edit']).type: "tool_call": Emits streaming tool call chunks or completed tool calls.type: "token_usage": Emits usage metadata including inputTokens, outputTokens, totalTokens, and cache details.When a stream emits an interrupt event, it provides a HITLRequest. The user must respond with a HITLDecision to resolve the interruption.
HITLRequest structure:
id: Unique request ID.tool_call: The ToolCall that triggered the interruption.allowed_decisions: An array of allowed decision types (approve, reject, or edit).HITLDecision structure:
type: One of approve, reject, or edit.tool_call_id: The ID of the tool call being decided upon.edited_args: (Optional) If the type is edit, provide the new arguments here.feedback: (Optional) Textual feedback for the model.The openwork project uses an Inter-Process Communication (IPC) system to communicate between the main process and the client. Events are categorized by their type and follow specific schemas depending on whether they are delivering values, tokens, tool calls, or stream chunks.
When receiving IPCMessage objects (often found within IPCValuesEvent), the type field indicates the origin of the content:
human: User input.ai: Model response.tool: Output from a tool execution.system: System-level instructions or messages.All IPC events conform to the IPCEvent union type:
values: Contains the current state of the workspace, including messages, todos, files, workspacePath, subagents, and potential interrupt states.token: Delivers individual string tokens for real-time text streaming, associated with a messageId.tool_call: Signals that a tool is being invoked, providing tool_calls with id, name, and args.stream: Forwards raw LangGraph stream chunks. The mode can be messages or values.done: Indicates the process has completed successfully.error: Indicates a failure, providing an error string.export type IPCEvent =
| IPCValuesEvent
| IPCTokenEvent
| IPCToolCallEvent
| IPCStreamEvent
| IPCDoneEvent
| IPCErrorEventThe agent emits a stream of events of type StreamEvent. You should implement a handler to process these different event types:
message: Contains a Message object.tool_call: Contains a ToolCall object.tool_result: Contains a ToolResult object.interrupt: Contains a HITLRequest (Human-in-the-loop request).token: Contains a single string token (for streaming text).todos: Contains a list of Todo items.workspace: Contains FileInfo[] and the current path.subagents: Contains a list of Subagent statuses.done: Contains the final result.error: Contains an error string.export type StreamEvent =
| { type: "message"; message: Message }
| { type: "tool_call"; toolCall: ToolCall }
| { type: "tool_result"; toolResult: ToolResult }
| { type: "interrupt"; request: HITLRequest }
| { type: "token"; token: string }
| { type: "todos"; todos: Todo[] }
| { type: "workspace"; files: FileInfo[]; path: string }
| { type: "subagents"; subagents: Subagent[] }
| { type: "done"; result: unknown }
| { type: "error"; error: string }The LocalSandbox class provides filesystem and shell access to agents. You can configure its behavior using the LocalSandboxOptions object during instantiation.
| Option | Type | Default | Description |
|---|---|---|---|
rootDir | string | process.cwd() | The root directory for file operations and command execution. |
virtualMode | boolean | false | If enabled, / maps to the rootDir. |
maxFileSizeMb | number | 10 | Maximum file size in MB for file operations. |
timeout | number | 120000 (2 mins) | Command timeout in milliseconds. |
maxOutputBytes | number | 100000 (~100KB) | Maximum output bytes before truncation. |
env | Record<string, string> | process.env | Environment variables to pass to commands. |
Security Warning: LocalSandbox has no built-in safeguards. It is highly recommended to use human-in-the-loop (HITL) middleware to approve all command executions.
const sandbox = new LocalSandbox({
rootDir: '/path/to/workspace',
virtualMode: true,
timeout: 60_000,
});openwork supports various models from Anthropic, OpenAI, and Google. You can configure these models within the application via the settings panel.
| Provider | Models |
| --------- | -------------------------------------------------------------------------------------- |
| Anthropic | Claude Opus 4.5, Claude Sonnet 4.5, Claude Haiku 4.5, Claude Opus 4.1, Claude Sonnet 4 |
| OpenAI | GPT-5.2, GPT-5.1, o3, o3 Mini, o4 Mini, o1, GPT-4.1, GPT-4o |
| Google | Gemini 3 Pro Preview, Gemini 3 Flash Preview, Gemini 2.5 Pro, Gemini 2.5 Flash, Gemini 2.5 Flash Lite |The execute(command: string) method runs a shell command within the sandbox's working directory. It supports both Windows (cmd.exe) and Unix-like (/bin/sh) environments.
The method returns a Promise<ExecuteResponse> containing:
output: A string containing the combined stdout and stderr (with [stderr] prefixes for error lines). If the output exceeds maxOutputBytes, it is truncated.exitCode: The exit code of the process, or null if the command timed out or was killed by a signal.truncated: A boolean indicating if the output was truncated due to size limits.const result = await sandbox.execute('echo "Hello World"');
// result.output: "Hello World\n"
// result.exitCode: 0
// result.truncated: falseconst result = await sandbox.execute('npm test');
console.log(result.output);
console.log('Exit code:', result.exitCode);