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
{
"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 installInstallation 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.
{
"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 Nshares 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.