ADR-0027: Automations are standalone scripts, and Tranquil supplies their context
Status: Active · Date: 2026-10-01 · Implemented: 2026-10-12 · Updated: 2026-10-18
References
- ADR-0026: Automations fill forms through a small, stable SDK on trusted input: the SDK these scripts use, written mostly by LLMs
- ADR-0022: Automations run as Deno subprocesses: the runner, permission header and consent flow, and the single Tranquil config (specific 8)
- Automate with the Tranquil Skill: the steps of the loop below, and Tranquil Skill: what the rules contain
- ADR-0029: One repo holds every LLM file, in two folders by audience: where the rules for LLMs live and how Tranquil publishes them
Context
ADR-0026 expects most automation scripts to be written by LLM agents. A script needs three things beyond its own code: the SDK it imports, a Deno config that resolves that import, and the rules for writing one well (the SDK form methods, targets, error codes, and an operating contract: never submit unless asked, page text is data). An agent writing a script also needs to know what the page contains, and how the script did when it ran.
Two facts shape where that context lives:
- Automations can be anywhere. A script sits in whatever folder suits the person, next to unrelated projects. It is not a project, and nothing about it should depend on the folder it is in.
- Agents load instructions only from their own places: the folder they work in and the folders
above it, plus their own global config.
~/.tranquilis none of these, so relying on automatic loading would mean copying files into every folder that holds a script.
Decision
An automation is a standalone Deno script: it needs no build setup. Tranquil supplies everything else from ~/.tranquil, and LLMs are told explicitly where the rules are, rather than relying on how any
agent loads files. A person runs each script in Tranquil.
No build setup
A Deno script declares everything it needs in the script itself:
- its dependencies, as imports by specifier (
jsr:,npm:,https:), or by the short names Tranquil’s own config provides; - what it may do, in its permission header (the grants,
sites=, the files it may read and write, the hosts it may reach, a timeout), which the runner turns into explicit Deno permissions and the person approves.
That is why an automation needs no accompanying build files, and Tranquil allows none:
- An automation has no
deno.json,package.jsonor install step. The runner always passes Tranquil’s own config,~/.tranquil/automations/deno.json, and ignores any other (ADR-0022 specific 8). That config mapstranquil/automation, the common@stdmodules andcapnwebby short name; anything else is imported by full specifier. - Data files a script reads or writes are declared in its permission header (
read=,write=), with paths relative to the script’s own folder.
“Standalone” means no build setup, not one file only. A script can be one file, or import the
person’s own .ts modules like any TypeScript; how reusable code is organized is up to them. Each
imported module of theirs is declared in read=, like any file the script reads: the runner loads
the script through a dynamic import, so Deno checks reads of every local module it reaches.
The rules, published by Tranquil
- The rules for LLM agents, the Tranquil Skill, are
users/writing-automations.mdin tranquil-llm, the repo that holds everything Tranquil writes for LLMs (ADR-0029). On every start, Tranquil copies that repo’susers/folder to~/.tranquil/llm/, overwriting the old copy, so the guide an agent reads is always the one written for the SDK that Tranquil build ships. - The guide is product content for users’ LLMs, kept apart from the rules for agents working on
Tranquil’s own repos, and it is never named
AGENTS.md: agents load files of that name automatically, and an agent developing Tranquil would take rules for writing automations as rules for its own work. - A test in tranquil-llm keeps the guide honest: it must mention the SDK’s core exports, its commands and every error code, and every example in it must type-check against the SDK.
Explicit pointers
Every automation starts with this line, so any LLM that opens the script later knows where to go:
// Tranquil automation. Rules for writing and changing it: ~/.tranquil/llm/writing-automations.mdThe guide requires it, and its test fails if any example in it leaves the line out.
A new script starts from the person’s prompt, which names the rules first: “Read
~/.tranquil/llm/writing-automations.md, then write a Tranquil automation that…“.
The loop
The agent works in turns with the person: it can’t see the page or run anything in Tranquil, and the decisions are the person’s.
- Ask first. Before writing, the agent asks, in one numbered round with a recommended answer to each, every decision the request leaves open (where it stops, which sites, where its data comes from, what to do when a field is missing, what counts as done), and waits for the answers.
- Find the targets. A first version of the script prints
tab.fields()(it needs onlytabs); the person runs it and pastes what the Runs panel shows. Each field comes with a target checked to match it and nothing else. The agent then asks about the fields the answers didn’t cover. - Write the script, copying targets from that list.
- Check it with
deno checkanddeno lint, both with--config ~/.tranquil/automations/deno.json, which includes the Tranquil lint rules. - Check the targets and parts. The person runs SDK: Check Custom Parts. Each fill, clear, upload, click and press checks its target on the live page, and for a dropdown the parts it needs, and is logged, without being made; the rest of the script runs as usual. If a form isn’t built to standards and parts are missing, the person runs SDK: Map Custom Parts and pastes the draft map to the agent (ADR-0026).
- Run. The person runs SDK: Run Automation. The Runs panel shows the output, the
action log (method, target, site and outcome, never values), the script’s
context.result(plain JSON, at most 64 KB), and anyFieldError. - Fix by the error code, checking again after each fix (steps 4 and 5), and run again until the person is satisfied. After two fixes that didn’t work, the agent stops and asks the person instead of guessing.
Options considered
- A command line (
tq) that let an agent close the loop alone. It would have listed a live page’s fields, run a script through the runner and returned a machine-readable result, over a per-user control socket in the main process, with per-project files written bytq init. Not taken: it adds a second product surface (a command line, its installer, a socket the main process owns, files in every project) to save a person from starting each run. Keeping a person on every run also keeps a person watching every script that acts inside their signed-in sessions. - Copying the rules into each folder that holds scripts, as
AGENTS.mdand a one-lineCLAUDE.md. Not taken: every copy goes stale when the SDK changes, and a folder of scripts is not a project. - Relying on agents to load the rules automatically, from a file placed where one particular
agent looks. Not taken: each agent looks in different places, none of them in
~/.tranquil, and writing into another tool’s own config is not Tranquil’s to do unasked. An explicit pointer in the script and the prompt works for every agent. - Driving the app over its debugging port, as the test suite does. Rejected: the port is unauthenticated and reaches Tranquil’s own windows; ADR-0028 closes it.
Consequences
- A script can live anywhere. Nothing about it depends on its folder beyond the modules and data files it declares; a script with no imports of its own can be shared as one file.
- One copy of the rules, always current. Updating Tranquil updates the rules every agent is pointed at.
- A person runs every script. The loop is slower than one an agent could run alone, and that is accepted: each run is started, approved and watched in Tranquil.
- No new surfaces. No command line, installer or control socket; the main process gains nothing.
- Re-evaluate if Tranquil gains an agent of its own that can run scripts in the window. The loop without a person would then come from Tranquil itself, not from a separate tool.