AI workflows, written as intent

Describe the work.
Run the machine.

mashin turns one readable description into an AI workflow you can run. Its goal, model, permissions, approvals, and outputs are defined together.

A complete workflow in one readable document

Meet the email triage machine.

It reads your inbox, classifies each message by urgency, and turns the ones that matter into tasks. Reading email is a call to a Gmail machine; the whole workflow is one readable document.

machine email_triage_to_tasks
  achieves
    goal "Triage incoming emails into prioritized tasks"

  behaves
    // Fetch recent emails (calls the Gmail machine)
    ask fetch_emails , from: "@mashin/google/gmail/list_messages" max_results: input.limit query: "newer_than:1h"
    // Classify each email by urgency, using a fast model
    ask classify_emails, using: "fast"
      with role "You are an email triage specialist."
      returns
        classifications as list, is required
    // ... create tasks, update cursor, summarize ...

  ensures
    permissions
      allowed to network.http
Read the whole machine (the excerpt elides its reasoning and memory steps)
machine email_triage_to_tasks
  achieves
    goal "Triage incoming emails into prioritized tasks"
      succeeds when "every urgent email creates a task within 5 minutes"
      never "miss an email or create duplicate tasks"
      for example
        assuming last_cursor null
        assuming fetch_emails { messages: [{ id: "3", subject: "URGENT: Server down", from: "[email protected]" }, { id: "2", subject: "Weekly newsletter", from: "[email protected]" }, { id: "1", subject: "Build passed", from: "[email protected]" }] }
        assuming classify_emails { classifications: [{ email_id: "3", classification: "urgent_actionable" }, { email_id: "2", classification: "noise" }, { email_id: "1", classification: "informational" }] }
        assuming create_task_descriptions { tasks: [{ title: "Fix server outage", description: "The production server is down and needs immediate attention.", priority: "high" }] }
        assuming save_cursor {stored: true}
        given { limit: 10, since: "1h" }
        expect { processed: 3, tasks_created: 1, skipped: 2 }

  accepts
    limit as integer, default: 20
    since as string, default: "1h"

  responds with
    processed as integer
    tasks_created as integer
    skipped as integer
    classifications as list

  behaves
    // Step 1: Remember where we left off
    recall last_cursor key: "last_email_id"
    // Step 2: Fetch recent emails (uses Gmail L1 machine)
    ask fetch_emails , from: "@mashin/google/gmail/list_messages" max_results: input.limit query: "newer_than:1h"
    // Step 3: Filter out already-processed emails
    compute filter_new let cursor = steps.last_cursor.value || "" let emails = steps.fetch_emails.messages || [] let new_emails = emails.filter(e => e.id > cursor) { emails: new_emails, count: new_emails.length }
    // Step 4: Classify each email
    ask classify_emails, using: "fast"
      with role "You are an email triage specialist. Classify emails by urgency and actionability."
      with task """
        For each email, classify as:
        - urgent_actionable: needs immediate action (deadline, escalation, blocker)
        - actionable: needs action but not urgent (request, follow-up, review)
        - informational: FYI only (newsletter, notification, status update)
        - noise: can be ignored (spam, marketing, automated)
        Return a list of classifications with email_id, subject, classification, and reason.
        Emails: ${JSON.stringify(steps.filter_new.emails)}
        """
      returns
        classifications as list, is required
    // Step 5: Create tasks for actionable emails
    compute extract_actionable let actionable = steps.classify_emails.classifications.filter(c => c.classification == "urgent_actionable" || c.classification == "actionable" ) { actionable: actionable, count: actionable.length }
    // Step 6: Create Linear issues for each actionable email
    // (In a real deployment, this would use for_each + call to Linear)
    ask create_task_descriptions, using: "fast"
      with role "You create concise, actionable task titles and descriptions from emails."
      with task """
        For each email, create a task with:
        - title: short actionable title (verb-first, e.g., 'Review Q4 budget proposal')
        - description: 2-3 sentence summary of what needs to be done
        - priority: high (urgent_actionable) or medium (actionable)
        Emails: ${JSON.stringify(steps.extract_actionable.actionable)}
        """
      returns
        tasks as list, is required
    // Step 7: Update cursor
    remember save_cursor operation: "store_structured" key: "last_email_id" value: steps.fetch_emails.messages[0].id
    // Step 8: Compute summary
    compute summarize let total = steps.filter_new.count let created = steps.extract_actionable.count let skipped = total - created { processed: total, tasks_created: created, skipped: skipped, classifications: steps.classify_emails.classifications }

  ensures
    permissions
      allowed to network.http, network.http

  verifies
    test "classifies emails correctly"
      assuming last_cursor null
      assuming fetch_emails { messages: [{ id: "1", subject: "URGENT: Server down", from: "[email protected]" }] }
      assuming classify_emails { classifications: [{ email_id: "1", classification: "urgent_actionable" }] }
      assuming create_task_descriptions { tasks: [{ title: "Fix server outage", description: "Server is down", priority: "high" }] }
      assuming save_cursor {stored: true}
      given { limit: 10 }
      expect { processed: 1 }
    test "skips already processed emails"
      assuming last_cursor { value: "5" }
      assuming fetch_emails { messages: [{ id: "3", subject: "Old" }] }
      assuming classify_emails { classifications: [] }
      assuming create_task_descriptions { tasks: [] }
      assuming save_cursor {stored: true}
      given { limit: 10 }
      expect { processed: 0 }

Effects are calls.

Reading email is ask ... from: "@mashin/google/gmail/list_messages". Effects happen by calling governed machines.

The grant is the kernel primitive.

allowed to network.http: every effect reduces to a small, closed set of I/O primitives you can see.

The model is a tier.

using: "fast" names a tier, not a vendor. Swap it without touching the workflow.

The document is the workflow.

In a typical AI application, intent is spread across prompts, orchestration code, permission checks, and audit tools. A mashin machine keeps those decisions together in a document people can read and the runtime can execute. There is no second implementation of the workflow to build or maintain.

Typical AI workflowA mashin machine
Behavior spread across the stackIntended behavior declared together
Permissions implemented separatelyPermissions visible in the machine
Model changes can require reworkModel choice is explicit and replaceable
Audit added around the workflowEach run produces a record

Bound before it acts. Recorded after it runs.

See what a machine may do, then verify what it did.

Every machine declares its permissions and approval points. When it runs, mashin checks each action against those boundaries first, and writes a receipt after: every entry cryptographically linked to the one before, so edits and gaps show, and verification runs in your own browser.

Declared permissions

See which tools and data the machine may use. An undeclared action is refused, and the refusal names its reason.

Human approval

Consequential actions pause until a person approves them. The answer is part of the record.

Run receipts

Inspect the steps, decisions, and outputs of each run. Replay it. Verify the record yourself.

Write it once. Run it where the work happens.

The same machine can run on a schedule, through an API, from a shortcut, as an MCP tool, in the cloud, or on your own computer.

email_triage_to_tasks
ScheduleREST APIPhone shortcutMCP toolCloudYour own computer

One definition, multiple ways to run it, with the same rules and records.

Less plumbing. More legible software.

Build the behavior once

Define the goal, reasoning steps, boundaries, and outputs together instead of scattering them across orchestration code.

Change models without changing the workflow

Choose the model for each reasoning step and replace it without redesigning the machine.

Know what happened

Every run leaves a receipt that makes the machine's actions and results inspectable, by you and by anyone you answer to.

Soon: watch the email triage machine run here, and read its receipt.

Build your first machine at launch.

Get launch access and occasional research notes.

No spam. The launch announcement and occasional research notes.

mashin exists to enable the intelligence economy: intelligent systems trustworthy by construction rather than by human effort.The mashin Way