Status: archived prototype. COZ is a research proof-of-concept that Loopwork built and is sharing as-is. It is not maintained — expect no updates, releases, or responses to issues and pull requests. Fork freely.
COZ is an experiment in making bash, JavaScript, and agents feel like one workspace instead of three separate tools. Bash is where bytes, files, and pipelines move; JavaScript is the deterministic compute layer; chat is the conversation layer that can synthesize, ask the next question, and optionally call back into COZ bash or JavaScript with visible tool calls.
Start or attach to the COZ workspace:
cargo runThe default coz process is now a client. It connects to a local daemon that
owns bash sessions, JavaScript isolates, and chat sessions, and it starts that
daemon automatically when one is not already listening. You can also run the
daemon explicitly:
cargo run -- daemonSet COZ_SOCKET to choose a non-default daemon socket. Use coz --local for
the old single-process REPL while working on the split, or pass --no-daemon
to coz one-shot commands to run with fresh in-process state for testing.
Exit the REPL with .exit, exit, or Ctrl-D.
Real shell clients attach to the same daemon state:
cargo build
target/debug/js -i reports 'counter = (counter ?? 0) + 1'
target/debug/js -i reports 'counter'
target/debug/coz bash -s work -c 'name=coz'
target/debug/coz bash -s work 'echo "hello $name"'
target/debug/chat -lAfter cargo install --path ., the js and chat helper binaries are
available directly from your normal bash/sh/zsh. Bash sessions are exposed as
coz bash to avoid shadowing your system shell.
The COZ REPL launches into a rust-bash
shell. It keeps shell variables, functions, working directory, and an in-memory
filesystem across commands:
coz:/$ name=coz
coz:/$ echo "hello $name"
hello coz
coz:/$ cd /tmp
coz:/tmp$ pwd
/tmp
Press Tab in bash mode to complete command names and paths in the bash virtual filesystem.
The host .data directory is mounted inside COZ at /data. Files created or
edited under /data from bash are written through to .data on the host, and
files placed in .data on the host are visible inside COZ:
coz:/$ echo hello > /data/greeting.txt
coz:/$ cat /data/greeting.txt
hello
From bash, enter the default JavaScript isolate:
coz:/$ js
created js isolate 'default'
coz:js:default> 1+1
2
coz:js:default> function add(x) { return x + x }
undefined
coz:js:default> add(1)
2
coz:js:default> add('a')
aa
coz:js:default> .leave
left js isolate
coz:/$
Use js -i name to enter a named isolate interactively.
The bash shell also has a js command that runs JavaScript in a named isolate
and composes with normal shell pipelines. If the first target resolves to a file,
COZ runs the file; otherwise it joins the target pieces and evaluates them as
JavaScript source. Pipeline input is available as $stdin, script arguments are
available as $argv, and object/array results are written as JSON:
coz:/$ printf '{"items":[1,2,3]}' > data.json
coz:/$ cat data.json | js 'JSON.parse($stdin).items.length'
3
coz:/$ js 1 + 1
2
coz:/$ printf '$argv.join(",")' > report.js
coz:/$ js report.js alpha beta
alpha,beta
coz:/$ js
joined js isolate 'default'
coz:js:default> function makeReport() { return { ok: true, count: 2 } }
undefined
coz:js:default> .leave
left js isolate
coz:/$ js 'makeReport()' | jq .
{
"ok": true,
"count": 2
}
With no target, js enters interactive mode instead of evaluating:
coz:/$ js
joined js isolate 'default'
coz:js:default> .leave
left js isolate
coz:/$ js -i reports
created js isolate 'reports'
coz:js:reports> .leave
left js isolate
From bash, enter chat:
coz:/$ chat
created chat 'default'
coz:chat:default> say hello in five words
Hello, nice to meet you.
coz:chat:default> .leave
left chat
coz:/$ chat -j work
created chat 'work'
coz:chat:work> .leave
left chat
coz:/$ chat -l
chats: default, work
coz:/$ chat say hello from bash
Hello from bash!
coz:/$ chat -c work continue from the named chat
Chat loads .env on startup and reads GEMINI_API_KEY. It uses
gemini-flash-latest by default; set GEMINI_MODEL to override it.
Chat composes with pipes. When stdin and prompt text are both present, stdin is sent as context and the prompt text is the instruction:
coz:/$ cat README.md | chat summarize this
coz:/$ printf '{"items":[1,2,3]}' | js 'JSON.parse($stdin).items.length' | chat explain the count
Chat tools are off by default. Enable them for a single bash command with
--tool, or inside an interactive chat with .tools:
coz:/$ chat --tool bash list the top-level files
tool:bash $ ls
...
coz:/$ js -i reports 'function total(xs) { return xs.reduce((a, b) => a + b, 0) }'
coz:/$ chat -j analyst
coz:chat:analyst> .tools +bash
tools: bash
coz:chat:analyst> .tools +js:reports
tools: bash, js:reports
coz:chat:analyst> use the reports isolate to total 1, 2, and 3
tool:js reports> total([1, 2, 3])
6
coz:/$ chat --tool widget make a small interactive budget slider
tool:widget Budget Slider
opened widget window: Budget Slider
Tool calls are printed before execution. Bash tool calls run in COZ bash, and
JavaScript tool calls run in the enabled COZ isolate. Use .tools to inspect
permissions, .tools +bash, .tools +js, and .tools +js:name to enable
tools, .tools +widget to enable macOS WebView widgets, or .tools -bash and
.tools clear to disable them.
Use -i or --isolate to run source or files in a named isolate. Use -e to
force source evaluation when a source string also matches a file, or -f to
force file execution:
coz:/$ js -i reports 'makeReport()' | jq .
coz:/$ js -e '1 + 1'
coz:/$ js -f report.js -- alpha beta
The js command exposes a small fs helper for reading files from the bash
virtual filesystem. This helper currently has snapshot semantics: literal
fs.readText("path"), fs.readJson("path"), and
fs.writeText("path", "content") calls in the submitted eval are handled
against the bash VFS before JavaScript runs. Dynamic paths such as
fs.readJson(pathVar) fail predictably for now, and dynamic writes are only
reflected inside that eval's JavaScript snapshot.
coz:/$ printf '{"users":[{"name":"Ada"},{"name":"Lin"}]}' > /tmp/users.json
coz:/$ js 'fs.readJson("/tmp/users.json").users.length'
2
coz:/$ js 'fs.writeText("/data/from-js.txt", "hello from js")'
undefined
coz:/$ cat /data/from-js.txt
hello from js
coz:/$ js
joined js isolate 'default'
coz:js:default> const users = fs.readJson("/tmp/users.json").users
undefined
Available helpers:
fs.readText(path): read a UTF-8 file from the bash virtual filesystem.fs.readJson(path): read and parse a JSON file from the bash virtual filesystem.fs.writeText(path, content): write UTF-8 text to the bash virtual filesystem. Literal writes to/data/...are persisted to the host.datadirectory.js -i name target: run source or a file in a named JavaScript isolate.js -e source: force JavaScript source evaluation.js -f file: force JavaScript file execution.
Terminal behavior:
- Ctrl-C at the bash prompt keeps COZ running and returns to the bash prompt.
- Ctrl-C in an idle JavaScript isolate leaves the isolate and returns to bash.
- Ctrl-D in bash exits COZ; Ctrl-D in an idle JavaScript isolate returns to bash.
- Ctrl-Z-style suspend while idle in JavaScript is treated as leaving the isolate if the terminal passes the suspend byte through; COZ does not implement Unix job control.
- Ctrl-C during the foreground JavaScript eval asks V8 to terminate that isolate, discards it, and recreates a fresh isolate with the same name.
Async functions work too. Top-level await is not supported yet, but promise
callbacks run, so you can update globals from .then(...) or console.log:
coz:js:default> async function answer() { return 42 }
undefined
coz:js:default> answer().then(console.log)
[object Promise]
You can also load a default-exported function into a separate V8 isolate and call it like a normal function:
// adder.js
export default function add(value) {
return value + value
}coz:js:default> foo = new Isolate("adder.js")
function () { [native code] }
coz:js:default> foo("a").then(console.log)
aa
[object Promise]
Arguments and return values currently cross the isolate boundary as JSON, so
functions, symbols, undefined arguments, and cyclic objects are not supported.
COZ can also consume JavaScript or TypeScript modules compiled outside the V8
environment. The sample build uses esbuild to bundle self-contained TS, relative
ESM imports, and a Node package-import map into classic scripts under
.data/coz-modules, which are visible in COZ at /data/coz-modules:
npm install
npm run build:coz-modulesLoad a compiled module from bash or a JavaScript isolate with a literal
coz.loadModule("path") call, or by manifest name with coz.module("name"):
coz:/$ js 'coz.loadModule("/data/coz-modules/self-contained.js").label("ada")'
self:ADA
coz:/$ js 'coz.loadModule("/data/coz-modules/esm-imports.js").describe(7)'
weighted:22
coz:/$ js 'coz.loadModule("/data/coz-modules/node-imports.js").summarize("hello COZ modules")'
Hello Coz Modules:3
coz:/$ js 'coz.module("nodeImports").summarize("hello COZ modules")'
Hello Coz Modules:3
Compiled bundles register into coz.modules, so loaded exports remain available
inside the same named isolate:
coz:/$ js 'coz.modules.selfContained.default([2,3,4]).total'
9
The module manifest can also expose compiled exports as bash commands. COZ
registers each bins entry as both a command name and a /js/bin/... path.
Each command invocation creates a fresh JavaScript runtime, loads the compiled
module, calls the configured export with { stdin, argv, env, cwd }, writes the
return value to stdout, and then drops the runtime:
coz:/$ summarize hello COZ modules
Hello Coz Modules:3
coz:/$ printf 'hello COZ modules' | /js/bin/summarize
Hello Coz Modules:3
coz:/$ slug Hello, COZ Modules!
hello-coz-modules
coz:/$ which summarize
/js/bin/summarize
Commands:
coz: start or attach to the daemon-backed COZ REPL.coz --no-daemon ...: run the COZ REPL or one-shot subcommand with fresh in-process state.coz daemon: run the daemon in the foreground.coz status: show the daemon's bash, chat, and JavaScript sessions.coz bash: attach to the default bash session interactively.coz bash -s name: attach to a named bash session interactively.coz bash -s name -c 'command': run a command in a named bash session.coz js ...orjs ...: run/attach to JavaScript from the real shell.coz chat ...orchat ...: send/attach to chat from the real shell.js: enter the default JavaScript isolate interactively.js -i name: enter a named JavaScript isolate interactively.js 'expr': evaluate a JavaScript expression in the default isolate.js -i name 'expr': evaluate a JavaScript expression in a named isolate.chat: enter the default chat interactively.chat message...: send a message to the default chat.chat -c name message...: send a message to a named chat.chat -j name: enter a named chat interactively.chat -l: list known chats.chat --tool bash|js[:name]|widget message...: allow one-shot visible tool calls..leave: return from a JavaScript isolate or chat to bash..tools: list enabled tools in chat mode..tools +bash,.tools +js,.tools +js:name,.tools +widget: enable chat tools..tools -bash,.tools clear: disable chat tools..isolates: list known JavaScript isolates.