Skip to content

Repository files navigation

zork

Zork connects Slack threads to a durable coding agent. The supervisor runs two processes:

  • zork-gateway owns Slack Socket Mode, thread/session mapping, delivery, the broker HTTP API, and the admin UI/API.
  • zork-agent owns each session's durable mailbox and transcript, provider and tool execution, per-session event streams, and workspace tools.

The gateway opens three listeners by default: Slack-facing HTTP on 18790, broker APIs on 3000, and admin on 3001. Agent listens only on loopback at 3010. Its APIs are unauthenticated by default; --agent-token enables a bearer shared with Gateway for that process run.

Repository layout

Path Purpose
apps/admin-ui React admin UI
crates/zork Supervisor and zork update control socket
crates/gateway Slack, broker APIs, mailbox delivery, and admin server
crates/agent Durable agent runtime and HTTP API
crates/profile Profile storage, refresh, probing, and provider execution
crates/slack Slack client, parsing, formatting, and status delivery
crates/zork-call Session-aware broker and GitHub helper CLIs
packages/zork Published npm launcher package

Run locally

Install dependencies and build:

vp install
vp run build

Start Zork with the default data root (~/.zork):

vp run dev

The first start creates config.json with mode 0600 and prints the admin URL. Configure Slack and add a Profile in the admin UI. Product settings come from that JSON file rather than a product .env file.

A minimal config looks like this:

{
  "slack": {
    "app_token": "xapp-...",
    "bot_token": "xoxb-..."
  }
}

Use a separate data root with:

vp run dev -- --data /absolute/path/to/data

--listen HOST changes the three Gateway listener hosts. It deliberately does not expose Agent. To require authentication on Agent APIs, start Zork with --agent-token <value>. When omitted, no Agent API bearer is required. The value is not read from config or persisted.

Session and workspace model

Agent generates each session ULID. Gateway stores the binding from a Slack thread to the Agent session returned by POST /v1/sessions, including the canonical workspace returned by Agent. Agent's own session directory contains only durable runtime state:

{data}/sessions/{ULID}/
  segments/
    {first-event-ULID}.jsonl.zst
    {current-first-event-ULID}.jsonl

The caller must pass an existing workspace directory when creating a session. Agent canonicalizes and persists that path, and never creates, moves, copies, or deletes the caller-owned directory. Gateway creates Slack-owned workspaces below {data}/workspaces/slack/{channel}/{root-thread-ts} and passes that path to Agent. Other callers, such as the benchmark adapter, can pass an existing checkout directly.

Every JSONL line is one event with its own ULID. A multi-event append writes multiple adjacent lines with one atomic batch boundary; there is no persisted commit envelope. Snapshots are events in the same JSONL stream, not separate files. Ordinary events always append to the current uncompressed segment. When a snapshot is due, Agent checks the current size: if it is already over 16 MiB, the snapshot becomes the first event of a new segment and the old segment is compressed asynchronously with zstd level 12. A restart loads the latest inline snapshot and replays only its later event suffix.

Agent provides bash, read, write, and wait_for. File paths are relative to the canonical workspace, symlink/path traversal is rejected, and shell commands run with bounded output and a timeout.

Every ordinary input is appended to the session's one durable mailbox. Before each model request, Agent drains every message available at that boundary into the transcript in mailbox order. This rule is identical whether the session was waiting, requesting a model, or executing a tool. Sending -stop in Slack is a control command that requests Agent cancellation; it is not a mailbox message.

Assistant transcript messages are internal Agent context and are never projected into Slack. Agent owns the application-level system instructions, tool definitions, and bounded tool environment containing the workspace roots, broker URL, and a PATH with the zork-call/gh wrappers. The model creates a visible Slack message only by explicitly running, for example:

zork-call chat post-message --text 'Work is complete.' --kind final

progress, final, block, and wait describe the purpose of the visible message; block and wait require a reason. This metadata does not affect mailbox delivery or Agent execution.

Gateway returns from input delivery as soon as Agent confirms the mailbox append. It does not inspect Agent execution state or wait for model/tool work. Gateway remains the only component holding Slack credentials and executing an explicit Slack delivery action. Background-job events also enter Agent through the same mailbox.

Profiles

Create a Profile in the admin UI. Profiles live under {data}/profiles/; a session records only profile_id, model, and thinking, never credentials or custom header values. Agent loads and refreshes the profile immediately before a provider attempt, and request callers cannot inject a provider URL.

The session UI has three independent selectors: model, thinking depth, and Profile. Only the Profile selector has 自动; Gateway resolves that value to a concrete compatible Profile without changing the selected model or thinking.

Slack app setup

Enable Socket Mode and create an app-level token with connections:write. Typical bot scopes are:

  • app_mentions:read
  • chat:write
  • channels:history
  • files:read and files:write when file input/output is needed
  • users:read when display names are needed

Subscribe to app_mention and message.channels. Add the corresponding history scope and message event for private channels or direct messages you choose to support.

Update and deployment

zork update --data DIR asks the running supervisor to drain and stop each owned child, start the updated binary, and wait for its ready PID before moving on. This is a controlled restart, so the listener being replaced has a short unavailable interval.

For Docker:

docker compose up -d

The supplied compose file publishes only the three Gateway ports. Agent remains private inside the container. To build updated Linux binaries into .data/bin and restart them, run:

bash scripts/dev/update-binaries.sh

Checks

vp check
vp lint
vp test test/

The runtime needs git, gh, and rg on PATH for coding work.

License

MIT

About

Slack Socket Mode broker that routes Slack threads into Codex app-server sessions with isolated workspaces.

Topics

Resources

Stars

16 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages