dokan
Health Uyari
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 9 GitHub stars
Code Uyari
- process.env — Environment variable access in examples/flagship/01-intake.js
- process.env — Environment variable access in examples/flagship/02-score.js
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
dokan 導管 — agent-operated deterministic script runtime in Docker. MCP control plane + thin operator UI.

dokan (導管)
Your AI coding agent builds and runs the workflow. You don't click.
Agent-operated automation runtime · deterministic scripts in Docker · zero LLM inside · Apache-2.0
dokan is an automation runtime built for the agent era. Instead of a human clicking through a UI, your coding agent stands up, runs, and schedules workflows itself by talking to dokan over MCP. The platform runs deterministic code in clean containers and burns zero tokens: the expensive intelligence stays in your agent, outside the runtime.
Think Sidekiq/cron for AI agents: the agent scripts the mechanical 80%, dokan executes it cheaply and reliably, you don't touch a dashboard.
Why dokan
- Agent-operated. your agent uploads, wires, triggers, reads logs over MCP. No UI.
- Zero LLM inside = zero token burn. deterministic code, not LLM-in-the-loop. The platform never spends tokens to run your workflows.
- Hermetic by default. one job = one clean container, per-job CPU/mem caps, timeouts, retries. Jobs run with network disabled by default (v0.4.0) — output is a pure function of its inputs, so identical inputs hit a content-addressed cache instead of recomputing, and the run is provable by re-execution. Set
network=truefor a job that must reach an API (most monitors do). Every run carries a tamper-evident receipt (advisory for networked runs, since their output can depend on the outside world). - Real triggers. cron + inbound webhooks (POST /hook/, Stripe/Calendly/GitHub-ready).
- Token-frugal. every MCP response is shaped for an agent's context budget — IDs over payloads, paginated logs, counts over dumps.
How it works
flowchart LR
agent["your coding agent"] -- MCP --> dokan["dokan daemon<br/>(Rust · axum + rmcp)"]
dokan -- state --> pg[("Postgres")]
dokan -- one job, one container --> c1["job container<br/>(isolated; hermetic by default, network=true to opt in)"]
c1 -- "stdout · ::dokan:result::" --> dokan
dokan -- "result POST" --> relay["your agent / relay"]
human["operator (optional)"] -. watch .-> cockpit["cockpit /"]
cockpit --- dokan
A single Rust daemon (axum + an rmcp MCP server, stdio or Streamable HTTP). State lives in Postgres. Execution is one job → one fresh container (python:3.12-slim / node:22-slim / alpine), discarded after the run, with per-job caps and a hard timeout. Logs stream into Postgres, served cursor-paginated. A thin operator cockpit runs at /, Prometheus at /metrics. No LLM runs inside dokan — the agent is the only place tokens are spent.
Quickstart
Prereq: Docker running (Docker Desktop, or colima / podman). The daemon talks to the default Docker socket — set DOCKER_HOST for colima/podman. No Rust toolchain needed.
One command stands up the runtime and installs the Claude Code operator skill:
curl -fsSL https://raw.githubusercontent.com/TsukumoHQ/dokan/main/install.sh | sh
It downloads and SHA-256-verifies the binary into ~/.local/bin, lands the operator skill in ~/.claude/skills/dokan/ (where Claude Code loads it), generates per-install crypto keys (secure-by-default — secrets sealed at rest, receipts signed; saved 0600 in ~/.dokan/dokan.env), starts Postgres on :5499, and brings up the daemon on 127.0.0.1:8088. Re-running is safe (idempotent). Then wire your agent:
claude mcp add --transport http dokan http://127.0.0.1:8088/mcp
Verify it's up: open http://127.0.0.1:8088/ (the operator cockpit) — once it loads, an agent can wire MCP and run scripts. Schema migrations apply automatically on boot.
Build from source instead (contributors / unsupported platforms)Needs a Rust toolchain. The default DATABASE_URL already points at the compose database, so there's nothing to configure.
docker compose up -d # Postgres state store (pgvector) on :5499
cargo build --release
./target/release/dokan # HTTP daemon on 127.0.0.1:8088 — UI at /, MCP at /mcp
You'll need crypto keys set (DOKAN_SECRET_KEY, DOKAN_RECEIPT_KEY, DOKAN_RECEIPT_ED25519_SECRET) or DOKAN_DEV_INSECURE=1 for local dev — the daemon fails closed otherwise.
Run your first job
With the daemon up, run the flagship demo — it drives a real DAG over MCP exactly the way an agent does (uploads 4 deterministic steps, wires a flow with map fan-out + a when branch, runs it, prints the result). No secrets, no job network, fully reproducible. Needs curl + jq:
./examples/flagship/run.sh # override the daemon with DOKAN_ADDR=host:port
That's the whole loop — upload → compose → run → read — in one command. See examples/flagship/ for the steps and the expected output.
Wire into your agent (MCP)
Point your agent's MCP config at the daemon:
"dokan": { "type": "http", "url": "http://127.0.0.1:8088/mcp" }
Your agent now has the full dokan toolset over MCP.
Core concepts
Scripts
A script is code in python | node | bash. It reads its input from the DOKAN_INPUT env var (a JSON string — not stdin, not argv). Secrets arrive as their own env vars. upload_script(..., upsert=true) re-provisions by name idempotently, so a respawning agent never leaves orphan duplicates. A script's nonzero exit is treated as the script's own deterministic verdict (e.g. a monitor finding) and is not retried — only a genuine infra failure (container vanished / timeout) retries.
Flows (DAG)
compose_flow wires scripts into a validated, acyclic graph; run_flow runs it and the engine drives the DAG. Each step is one container run that sees {flow_input, deps, step} as its DOKAN_INPUT.
flowchart LR
intake["intake"] --> score["score<br/>(map: one run per order)"]
score --> summarize["summarize<br/>(structured result + branch token)"]
summarize -- "when deps.summarize == FLAGGED" --> alert["alert"]
summarize -- else --> done(["done"])
The engine gives you:
mapfan-out — one child run per item; the parent collapses the children into a{n, ok, failed}count instead of listing each.whenbranches — a step runs only if a predicate over an upstream result holds.compensate(saga rollback) — a step can declare a compensating action that runs if a later step fails, so a partially-applied flow unwinds cleanly.- retries with backoff on transient failures.
- step-boundary durability — progress is committed at each step, so a crashed engine resumes; succeeded steps are skipped. Steps must be idempotent (a dying step re-runs).
Schedules & triggers
schedule(script_id, cron)— 6-field cron with leading seconds (0 */5 * * * *= every 5 min). A 5-field expression is rejected loudly rather than silently never firing. Each tick enqueues a run.- A tick takes the same options as
run_script:agent_id(the agent's scoped secrets, and its quota checked at every tick),files(blobs mounted read-only at/input),capture_output, plusidempotency=truefor one run per tick when several daemons share the database. timezone="Europe/Zurich"(any IANA name) reads the cron as local wall-clock time, DST-aware; without it, UTC.0 30 7 * * *inEurope/Zurichfires at 07:30 in Geneva all year (06:30 UTC in winter, 05:30 UTC in summer).- No in-place update:
unschedulethenscheduleto change one.
- A tick takes the same options as
create_webhook— an externalPOST /hook/<token>enqueues a script or flow with the request body as input. The unguessable URL token is the auth (the endpoint sits outside the bearer gate).
Secrets
set_secret(name, value) once → available to a job as a tmpfs file at /run/secrets/<name> (mode 0400, never persisted) and, for back-compat, as an env var (e.g. $OPENAI_API_KEY). Write-only: values are never returned or logged; list_secrets shows names only. By default a script gets all globals; scope it with a per-script allowlist — upload_script(..., secrets=["openai_key"]) injects only those.
Resource limits
Per-job caps default to 1024 MiB / 2.0 CPU, set globally on the daemon. A single heavier script can override them — upload_script(..., mem_limit_mb, cpu_limit) — and only that script runs on a dedicated container with the raised cap; everything else is untouched.
Each job has a wall-clock limit of 300 s by default. A long job sets its own: upload_script(..., timeout_secs=3600), up to the daemon's --max-timeout-secs / DOKAN_MAX_TIMEOUT_SECS (default 3600 s; whoami reports both). While a job runs, the executor renews its lease every 15 s, so the orphan reaper never re-runs a live long job; a timeout of a script that set its own timeout_secs is final (not retried).
Structured results & stateful monitors
A job prints a line ::dokan:result:: {json} on stdout; dokan captures the last one as the run's structured result. It is returned by wait_for/read_logs and POSTed to the relay on completion — so a monitor emits a finding and the agent reacts event-driven, no polling.
For a monitor that should fire only when something changes, set upload_script(..., feed_prev_result=true): dokan feeds the previous run's structured result into the next run as DOKAN_INPUT.prev_result. The monitor reads prev_result.state, diffs against the current state, emits the new state, and exits nonzero on a change — a cross-run diff with no host files and no external store, on a stateless runtime.
Run artifacts (input files)
To hand a job a real document — a PDF, a dataset, a big .md — without stuffing it into DOKAN_INPUT (an env var, ~100 KB): upload_blob(bytes) stores it in a content-addressed blob store (re-uploading identical bytes deduplicates) and returns a handle; run_script(..., files={"doc.md": "<handle>"}) materializes each file read-only at /input/<name> in the container. The blob hashes fold into the run's cache key and receipt, so a job that reads /input stays a pure function of its declared inputs.
Determinism & receipts
A network=false job is a pure function of its inputs — source + DOKAN_INPUT + input-file blobs + the pinned image digest. Two consequences:
- Content-addressed cache.
run_script(..., cache=true)recalls a prior identical success instead of recomputing — no container spawned. - Tamper-evident, publicly-verifiable receipt. every run carries a receipt binding (image digest, source hash, input hash, output hash, input-blob hashes). It's HMAC-keyed for holders of
DOKAN_RECEIPT_KEYand Ed25519-signed inside an in-toto / DSSE envelope — so a third party canverifyit offline with only the public key (no shared secret), andreproducere-executes the run and byte-compares the output against the receipt (REPRODUCED/DIVERGED/TAMPERED). A networked job's receipt is advisory, since its output can depend on the outside world.- Honesty note: the structured
::dokan:result::output is compared by JSON value (object key order doesn't matter). Output FILES are compared by raw bytes — dokan does not yet canonicalize file-packaging metadata, so an artifact that differs only in non-content metadata (gzip mtime, tar entry order, embedded build timestamps) willDIVERGE. Emit reproducible archives (e.g.gzip -n, sortedtar,SOURCE_DATE_EPOCH) for a cleanREPRODUCED. (Canonicalizing this safely is a tracked follow-up.)
- Honesty note: the structured
MCP surface (token-frugal)
| Tool | Returns |
|---|---|
| search_script · list_scripts · get_script | ranked / listed IDs + 1-line desc; bodies only on request |
| upload_script | script_id + version (flags: network, mem/cpu limit, timeout_secs, feed_prev_result) |
| run_script | run_id immediately, never blocks (input + files for /input artifacts) |
| read_logs · wait_for | cursor logs / long-poll to terminal + tail + structured result |
| schedule · list_schedules · unschedule | cron a script (6-field, optional IANA timezone) with run_script's agent_id / files / capture_output; next_fire |
| compose_flow · run_flow · get_flow_run | declarative DAG; per-step status (map children collapsed to a count) |
| create_webhook · list_webhooks · delete_webhook | inbound HTTP trigger to a script/flow |
| upload_blob · download_blob · list_blobs | content-addressed input/output files |
| get_receipt · verify · reproduce | fetch / offline-Ed25519-verify / re-execute + byte-compare a run's receipt |
| set_secret · list_secrets | write-only secrets → /run/secrets tmpfs (+ env); per-script allowlist |
| on_result · list_triggers · delete_trigger | reactive triggers: enqueue a script when a result matches |
| cancel · list_runs · list_executors | … |
Server instructions ship in-band so the agent self-limits.
Status
v0.5.x — beta / preview. Active development, built and run in production by the team that makes it (we run our own agent fleet's automation on dokan). Ready for: demos, design partners, technical early adopters. Not yet turnkey multi-tenant enterprise (no SSO/RBAC/HA; the MCP control plane is unauthenticated + single-tenant — see SECURITY.md). Honest about where it is.
License
Apache-2.0. Use it, embed it, build on it.
dokan is one of four open tools from the Tsukumo studio for running AI agents well at scale: trovex (canonical context) · yoru (session record + replay) · dokan (job runtime) · wrai.th (agent relay).
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi