Chat · Temporal · Pi · agentgateway

Your chat is the workflow.

Talk to the system like you would to anyone else. When a conversation turns out to be something you want again, say so — “run this every day at 8” — and the chat becomes a durable, scheduled workflow. Real Python. Reviewed by you. Live without redeploying anything.

  • Durable runs, retries and history
  • One door — a single published port
  • Zero idle agent containers

Chats

Interactive, streaming, temporary. The draft.

Promotion

Repeatable steps become inputs and typed agent steps.

Workflows

Durable, scheduled, repeatable. The saved version.

How it works

Five steps from a conversation to something that runs at 8am

1

Read the transcript

The agent finds the steps that were actually repeatable, and drops the small talk.

2

Turn fixed things into inputs

A date, a repo, a customer — whatever the chat pinned down becomes a workflow input.

3

Type the agent steps

Interactive turns become activities with a declared output schema. Text where an object was promised fails the step, and Temporal retries it.

4

Show you a diff

An agent writing code is a narrow door: schema-checked arguments, a manifest check, a real import against the Temporal SDK, then a diff you approve.

5

Attach a trigger

A schedule, a webhook, or another workflow. The file lands on a shared volume and a worker restarts with it loaded — no redeploy.

Architecture

One door in, everything else on an internal network

The backend is the only service that publishes a port. Only the broker touches the Docker socket, and it offers fixed verbs — run_agent, restart_worker — never a generic “run this container”.

Frontendweb + app, one Quasar codebase
External systemstriggers in, webhooks out
Backendauth · memory · streaming · the only published port
internal network
Docker brokerowns docker.sock, fixed verbs
agentgatewayMCP tools + models, audited
workflow-mcpschema-validated authoring
Temporal + Postgresdurable runs, retries, schedules
Workersalways at least one
Pi runsone container per call, then gone

Scale to zero, honestly

No idle agent containers, no pinned sets. Every call is one docker run --rm. Nothing survives inside, so history and inputs are always handed in — which is exactly what makes a run replayable.

Workers never scale to zero

If nothing is listening, triggers pile up with nothing to pick them up. At least one worker is always running, and a restart drains in-flight activities instead of killing them.

No provider key in the agent

Models and tools both go through agentgateway. The Pi container gets a gateway URL, never a provider key, and every tool call is authorised and audited in one place.

Workflows

Plain Python files you could have written by hand

No bespoke DSL, no database row. A workflow is a Temporal Python module with a manifest at the top: readable, diffable, and ready to be committed to git.

"""Fetch a page and summarise it."""
from datetime import timedelta
from temporalio import workflow

MANIFEST = {
    "schema": 1,
    "name": "url_digest",
    "inputs": {"type": "object",
               "properties": {"url": {"type": "string"}},
               "required": ["url"]},
    "outputs": {"type": "object",
                "properties": {"summary": {"type": "string"}}},
    "agent_set": "default",
}


@workflow.defn(name="url_digest")
class UrlDigest:
    @workflow.run
    async def run(self, params: dict) -> dict:
        page = await workflow.execute_activity(
            "http_fetch", {"url": params["url"]},
            start_to_close_timeout=timedelta(minutes=2),
        )
        digest = await workflow.execute_activity(
            "agent_call",
            {"prompt": f"Summarise:\n{page['body'][:20000]}",
             "output_schema": MANIFEST["outputs"]},
            start_to_close_timeout=timedelta(minutes=10),
        )
        return digest["output"]

Activities by name

A workflow file imports nothing from the runtime. agent_call, http_fetch, emit_event, save_artifact are called as strings.

Typed agent steps

Declare output_schema and the step becomes a function. A stream would be wasted — nobody is watching, and the next step cannot read half a stream.

Git when you want it

Today the shared volume, with the repo as the seed. Next: the volume as a checkout, so you can author in your own editor, push, and let the backend pull.

Versioned, additive manifest

Unknown keys prefixed x_ are preserved rather than rejected, so a workflow can carry data this runtime does not understand yet.

Principles

Three things decide every trade-off

Extensible

A new agent set is a directory. A new tool is an entry behind the gateway. A new validation rule is a step in a list. None of it is a refactor.

Open

Workflows are Python you can read, edit and commit. Models sit behind an OpenAI-compatible endpoint, tools behind MCP, storage behind an S3-shaped boundary. Swap what is behind them.

Friendly

You see a readable diff before code goes live, a status page that names what is down, and a sentence where a stack trace would do.

Self-host

One compose file, one published port

Everything is in the repository: backend, broker, gateway config, authoring server, workers, frontend and this site. Bring an OpenRouter key and a Docker host.

$ git clone https://github.com/StromFLIX/nautionette
$ cd nautionette
$ cp .env.example .env      # OPENROUTER_API_KEY, POSTGRES_PASSWORD, APP_TOKEN
$ docker compose up -d
backend is the only published port; everything else stays internal

The broker builds the agent images once at startup, so a call never waits on a build. Workflows in workflows/ are seeded into the shared volume on first start.

FAQ

The questions that actually get asked

An agent writes and deploys code. Is that safe?

An agent writes code; it never deploys. Writes go through one narrow door with JSON-Schema-checked arguments, a manifest check, and a real import against the Temporal SDK in a throwaway subprocess. What lands is a draft plus a diff. Nothing runs on a schedule until a human approves it.

What runs the agent?

Pi, a minimal terminal coding harness, in a container per call. An “agent set” is a directory: the base image plus the extensions and packages that set needs. A second set is a second directory of the same shape.

Which models can I use?

Anything OpenRouter fronts, and anything else you point agentgateway at. The agent container only ever sees an OpenAI-compatible URL, so swapping providers is a config change, not a code change.

Why Temporal and not cron?

Because a step that calls a model fails in interesting ways. Temporal gives retries, timeouts, durable history and schedules, so a half-finished run is a resumable state rather than a lost afternoon.

Can I write workflows by hand?

Yes, and nothing in the design assumes otherwise. A file you type in your editor takes exactly the same path through validation and deploy as one an agent wrote.

What is still open?

The agent container is the only permission boundary. Authentication lives entirely in the backend, and internal services trust the network. Git sync needs a conflict story, workflow composition needs versioning and permissions, and the object-storage boundary has no store behind it yet.

Stop rebuilding the same conversation.

Have it once. Keep it. Let it run at 8.