# Build action and workflow Agents

> Build tool-using operators, process automation, approval flows, and browser Agents with explicit effects, waits, and durable progress.

## Before you begin {#before-you-begin}

List every world-changing operation and classify its effect as read-only, idempotent, reconcilable, or non-idempotent. Bind exact Resources and Credentials through the deployment, not the message. Decide which operations Policy may allow automatically and which require a human. Use durable mode when progress must survive across waits, retries, or separate invocations.

## Steps {#steps}

1. Offer the model narrow Tools with validated schemas and truthful effect ceilings. Deterministic code may invoke the same Resources directly.
2. Supply a stable deduplication key for idempotent writes. Let the runtime reconcile ambiguous outcomes; never turn an unknown result into success.
3. Use `await` for external authority and return explicit serializable state from each durable step.
4. Commit business truth only after the effect or approval has a proven outcome. A browser is just another governed Resource with interactive operations and evidence.

```ts
import { agent } from "@constal/sdk";

type State = {
  request: { ticketId: string; summary: string };
  phase: "approval" | "apply" | "done" | "rejected";
  result?: unknown;
};

export default agent<State>({
  id: "change-workflow", version: "1.0.0", model: "model", mode: "durable",
  init: (request: State["request"]) => ({ request, phase: "approval" }),
  async step(state, ctx) {
    if (state.phase === "approval") {
      const decision = await ctx.await<{ approved: boolean }>("change-approval", {
        schema: { type: "object", required: ["approved"], additionalProperties: false,
          properties: { approved: { type: "boolean" } } },
      });
      return { state: { ...state, phase: decision.approved ? "apply" : "rejected" }, done: !decision.approved };
    }
    if (state.phase === "apply") {
      const result = await ctx.invoke(ctx.resources.ticketing!, "ticket.update", state.request, {
        dedupeKey: `ticket-change:${state.request.ticketId}`,
      });
      await ctx.commit({ kind: "change-applied", ticketId: state.request.ticketId, result });
      return { state: { ...state, phase: "done", result }, done: true };
    }
    return { state, done: true };
  },
  output: (state) => ({ phase: state.phase, result: state.result }),
});
```

Tool-using operators may stay in script mode when work is bounded and immediate. Choose durable mode for onboarding, claims, procurement, remediation, or any workflow whose state matters between events. Computer-use Agents follow the same rules: screenshots are untrusted observations, clicks are effects, and completion requires verifiable evidence.

## Verify {#verify}

Exercise approval, rejection, timeout, duplicate delivery, provider failure, and ambiguous effect outcomes. Inspect the Run journal for the wait, decision, exact Resource operation, recovery evidence, and final commit. Ensure a replay cannot apply the same change twice and a denied principal cannot substitute another tenant’s Resource or Credential.

## Next steps {#next-steps}

Read [Resources and Tools](/docs/sdk/resources-and-tools.md), [Resource effects and recovery](/docs/foundations/resources-effects.md), [Policies and approvals](/docs/policies.md), and [Run controls](/docs/runs/waits-and-controls.md).
