Read the transcript
The agent finds the steps that were actually repeatable, and drops the small talk.
Chat · Temporal · Pi · agentgateway
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.
Interactive, streaming, temporary. The draft.
Repeatable steps become inputs and typed agent steps.
Durable, scheduled, repeatable. The saved version.
How it works
The agent finds the steps that were actually repeatable, and drops the small talk.
A date, a repo, a customer — whatever the chat pinned down becomes a workflow input.
Interactive turns become activities with a declared output schema. Text where an object was promised fails the step, and Temporal retries it.
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.
A schedule, a webhook, or another workflow. The file lands on a shared volume and a worker restarts with it loaded — no redeploy.
Architecture
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”.
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.
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.
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
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"]
A workflow file imports nothing from the runtime. agent_call, http_fetch, emit_event, save_artifact are called as strings.
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.
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.
Unknown keys prefixed x_ are preserved rather than rejected, so a workflow can carry data this runtime does not understand yet.
Principles
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.
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.
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
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
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.
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.
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.
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.
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.
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.
Have it once. Keep it. Let it run at 8.