Tide

Driving Argon

Install Argon as a Tide plugin and use its typed, journaled client.

Argon is Tide's first first-class plugin. It is not a global and it is not part of defineWorkflow's core context. A project declares an exact plugin version, tide install generates a verified project-local module, and workflow code imports the typed client from tide:plugins/argon.

Configure and install

tide.json
{
  "name": "employment-onboarding",
  "plugins": {
    "argon": {
      "version": "0.0.1",
      "source": "builtin",
      "configuration": {
        "packagePath": "./ontology"
      }
    }
  }
}

packagePath points to the directory containing ox.toml, relative to tide.json.

tide install

Installation writes tide.lock and deterministic artifacts under .tide/plugins/. The lockfile should be committed; .tide/ is generated and gitignored. The shipped Tide executable contains the static Argon plugin and compiles the ontology in-process. The contributor/root Argon-free binary fails explicitly because it cannot add private Rust code to an already-compiled executable.

For a separately operated Argon runtime, lock its exact generated and runtime identities under configuration.remote. Tide requires Argon's restart-durable mutation-receipt capability by default. Setting requireMutationReceipts to false explicitly permits non-durable mutation dispatch. Tide reserves each effect intent before one non-retrying HTTP request and never retries an ambiguous existing intent, but it cannot recover a response lost after Argon commits. Runtime identity and fork lifecycle checks still run.

tide.json
{
  "plugins": {
    "argon": {
      "version": "0.0.1",
      "source": "builtin",
      "configuration": {
        "remote": {
          "tenant": "test0",
          "generationScope": "composed",
          "generatorSha256": "<sha256>",
          "artifactSha256": "<sha256>",
          "schemaHash": "<sha256>",
          "moduleHash": "<sha256>",
          "requireMutationReceipts": false
        }
      }
    }
  }
}

Authoring

import { defineWorkflow, S } from "tide";
import { argon, Company, Person } from "tide:plugins/argon";

export default defineWorkflow({
  input: S.object({
    company: S.text(),
    name: S.text(),
    salary: S.integer(),
  }),
  async run({ company, name, salary }, { log }) {
    const receipt = await argon.mutations.hire.receipt({
      employer: Company.ref("#i1"),
      employee: Person.ref("#i2"),
      employer_name: company,
      employee_name: name,
      salary,
    });
    log("events:", receipt.eventsEmitted.length);

    const employees = await argon.queries.all_employees.run({});
    return { hired: name, headcount: employees.length };
  },
});

The generated module binds the ontology client to Tide's generic namespaced plugin transport. It does not use fetch, globalThis.state, connectArgon, or ctx.argon.

Durability and lifecycle

  • A query result is journaled with plugin ID, exact version, operation, and request. Replay returns that result without calling Argon.
  • A mutation reserves a deterministic effect intent and is journaled. It must run outside step() because a step retry cannot roll back an Argon fork.
  • Reads pin the run's transaction point and see the run's own writes.
  • Every invocation owns a fork. Completion promotes it, failure aborts it, and human-in-the-loop suspension retains it so resume can reattach.
  • tide serve --workers N shares one Argon world while keeping invocation fork state separate.

Effect reservation does not remove the crash window between host dispatch and journal persistence; Tide does not claim general exactly-once external effects. Argon fork persistence and deterministic operation replay provide the current state-mutation guarantees.

Tests

Tests import the same plugin module:

import { describe, expect, test } from "tide:test";
import { argon, Company, Person } from "tide:plugins/argon";

describe("employment onboarding", () => {
  test("records a hire", async () => {
    await argon.mutations.hire.run({
      employer: Company.ref("#i1"),
      employee: Person.ref("#i2"),
      employer_name: "Acme",
      employee_name: "Ada",
      salary: 120000,
    });
    const employees = await argon.queries.all_employees.run({});
    expect(employees.length).to.equal(1);
  });
});

Each case uses a fresh invocation fork over an ephemeral test world and never promotes it into the project's durable main.

Migration

- import { connectArgon } from "tide";
- import { sdk } from "../.tide/argon-client.ts";
- const argon = connectArgon(sdk);
- export default defineWorkflow({ argon: sdk, async run(input, { argon }) {
+ import { argon } from "tide:plugins/argon";
+ export default defineWorkflow({ async run(input) {

New plugin calls use op_plugin_call. Do not resume an in-flight invocation whose journal contains old op_state_* calls with migrated source; start a new invocation ID. The Rust StateHost path remains temporarily for old embedder journals but is no longer the supported workflow authoring surface.

See the full plugin architecture and the repository design note at docs/design/plugins.md for the staged WASI/IPC path.

On this page