Docs

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

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. ~/.tranquil is 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.json or install step. The runner always passes Tranquil’s own config, ~/.tranquil/automations/deno.json, and ignores any other (ADR-0022 specific 8). That config maps tranquil/automation, the common @std modules and capnweb by 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.md in tranquil-llm, the repo that holds everything Tranquil writes for LLMs (ADR-0029). On every start, Tranquil copies that repo’s users/ 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.md

    The 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.

  1. 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.
  2. Find the targets. A first version of the script prints tab.fields() (it needs only tabs); 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.
  3. Write the script, copying targets from that list.
  4. Check it with deno check and deno lint, both with --config ~/.tranquil/automations/deno.json, which includes the Tranquil lint rules.
  5. 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).
  6. 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 any FieldError.
  7. 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 by tq 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.md and a one-line CLAUDE.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.