Architecture
Internal design, tech stack, and implementation details
Overview
Tide is a Rust workspace. The core runtime, stores, runner, and serve crates are
argon-free; the shipped binary that embeds Argon lives in an excluded workspace
(tide-argon) so the default build and CI never fetch the private Argon repo.
tide/
├── cli/ # CLI command logic (run / resume / test / schema / serve)
├── runtime/ # Core runtime (Deno Core + V8)
│ └── src/
│ ├── lib.rs # Runtime orchestration, run/test entry points
│ ├── ops.rs # Deno ops (file, step, sleep, console, ai, hitl, state)
│ ├── store.rs # DurableStore trait + effect-intent reservation
│ ├── state.rs # StateHost world-state capability surface
│ ├── loader.rs # TypeScript module loader
│ ├── runtime.js# JavaScript globals and sandbox setup
│ └── tide.d.ts # TypeScript type definitions
├── store-fs/ # Filesystem storage backend
├── store-sqlite/ # SQLite storage backend
├── store-postgres/ # PostgreSQL storage backend
├── tide-runner/ # Workflow runner: source + queue + cooperative drive
├── tide-serve/ # HTTP /v1 control surface over the runner (axum)
├── tide-argon/ # Shipped binary: embeds Argon for the state.* backend
├── examples/ # Example workflows
└── docs/ # Documentation site (Fumadocs)Tech Stack
| Component | Technology | Purpose |
|---|---|---|
| Runtime | Deno Core (V8) | JavaScript/TypeScript execution |
| Language | Rust | Core runtime, storage, CLI |
| TypeScript | deno_ast | On-the-fly transpilation |
| VFS | vfs crate (MemoryFS) | In-memory virtual filesystem |
| SQLite | rusqlite (bundled) | SQLite storage backend |
| PostgreSQL | sqlx | Postgres storage backend |
| Async | Tokio | Async runtime (the run future is !Send — V8) |
Execution Flow
1. CLI parsing (cli/src/lib.rs)
The CLI parses the subcommand (run / resume / test / schema / serve)
and its flags, builds the storage backend and the world-state Backend, and
dispatches:
- Parses
-i,--store,--input/--input-file,--argon,--json(and, on the embedded binary,--world-dir/--fresh) - Validates
--inputis valid JSON - Creates
Arc<dyn DurableStore>(fs,sqlite, orpostgres) - Runs the workflow via
run_with_state_host_and_overlay(...), settling the run's world-state fork through theBackendseam
2. State Initialization (runtime/src/lib.rs)
The run() function sets up the runtime:
- Load journal from store -- empty on first run
- Resolve input -- on replay, use stored input; on first run, persist CLI input
- Resolve timestamp -- on replay, load stored timestamp; on first run, capture and persist
Date.now() - Create VFS -- starts as empty
MemoryFS(state rebuilt from journal replay) - Build DurableState -- contains journal, position counter, store reference, VFS snapshot slot, transaction buffer
3. V8 Runtime Setup
JsRuntime::new(RuntimeOptions {
module_loader: TsModuleLoader,
startup_snapshot: TIDE_SNAPSHOT, // Pre-compiled runtime.js
extensions: [tide extension],
})The V8 snapshot (TIDE_SNAPSHOT) is built at compile time from runtime.js. It contains:
console.log/error(callsop_console_log)readFile,writeFile,readFileBytes,writeFileBytes,removeFile,listFilessleep(callsop_set_timeout)step(callsop_step_begin/op_step_end)Sschema builder (all methods)__tideValidate(schema validation function)__initFrozenTime(frozen Date overrides)__initSandbox(security sandbox)
4. Initialization Sequence
After the V8 runtime is created:
- Inject
DurableStateandVfsPathintoOpState - Call
__initFrozenTime()-- overridesDate.now(),new Date(),performance.now() - Call
__initSandbox()-- disables eval/Function, removes globals, freezes prototypes - Load and evaluate the user module (top-level code runs, schema is defined)
- Verify
export default { schema, main }convention - Check if schema requires input
- Parse input JSON, validate against schema with
__tideValidate - Call
main(input)via orchestration script
5. Op Execution (runtime/src/ops.rs)
Each JavaScript API call maps to a Rust op via Deno Core's op system:
| JavaScript | Rust Op | Journal Op |
|---|---|---|
readFile(path) | op_read_file | op_read_file |
writeFile(path, contents) | op_write_file | op_write_file |
readFileBytes(path) | op_read_file_bytes | op_read_file_bytes |
writeFileBytes(path, contents) | op_write_file_bytes | op_write_file_bytes |
removeFile(path) | op_remove_file | op_remove_file |
listFiles(path?) | op_list_files | op_list_files |
sleep(ms) | op_set_timeout | op_set_timeout |
console.log/error | op_console_log | op_console |
step(name, fn) begin | op_step_begin | op_step_begin |
step(name, fn) end | op_step_end | op_step_complete |
Date.now() | op_get_frozen_timestamp | (not journaled) |
Each op follows the same pattern:
- Check if replaying -- return stored result from journal
- Execute live -- perform the actual operation
- Record -- create journal entry with result
- Commit -- if not in a step, persist immediately
6. Transaction Buffer
The transaction buffer for steps:
DurableTransaction {
journal_entries: Vec<JournalEntry>, // Buffered during step
}- Outside a step: Each op records + commits immediately (single entry)
- Inside a step: Ops record to the buffer; on success, ALL entries commit atomically; on failure, buffer is discarded and VFS is restored from snapshot
7. Module Loader (runtime/src/loader.rs)
The TsModuleLoader handles:
- Resolution: Only allows
file://scheme (blockshttps://,data:, etc.) - Loading: Reads file from disk, transpiles TypeScript if needed
- Supported extensions:
.ts,.mts,.cts,.tsx,.jsx,.js,.mjs,.cjs,.json - Uses
deno_astfor TypeScript transpilation
8. Frozen Timestamp
Date.now() returns the same frozen value for the entire invocation:
- First run: captures real
Date.now()at startup, persists it - Replay: loads stored timestamp from storage
If Date.now() returned real time, workflows that branch on time could take different paths on replay, causing determinism violations. Freezing time makes all time-dependent code deterministic.
9. Console Log Journaling
Console output is stored in the journal as op_console entries:
- Live execution: Prints to stdout/stderr AND records in journal
- Replay: Suppresses live console calls; journal entries are drained and printed by
replay_entry()when the next real op replays - Trailing logs: After execution completes,
flush_console_logs()prints any remaining console entries
Related Pages
- Durable Execution -- How the journal and replay system works
- Steps -- Transaction primitive details
- Storage Backends -- Storage trait and implementations
- Security -- Sandbox implementation details