Introducing Runtime Rules

Jakob Heuser, CTO

Taskless turns a correction into an ast-grep compatible rule that catches a shape everywhere, on every run. Correct once, enforced forever. That's the same thing your linters do well: find the structure, flag it, every time.

Some corrections don't have a shape. Whether every exported function has a test. Whether the environment variables you read are the ones you documented. Whether every file that serves a request also checks auth. There's no single structure to match, so a static pattern can't catch them, and until now they've been out of reach. Runtime rules are how you reach them.

What shipped in 0.10.0

0.10.0 adds runtime rules support to taskless check, alongside server-owned rule reconciliation. On an authenticated run, the CLI reconciles your repo's rules against the Taskless service and runs only the set it's verified. Runtime rules live in .taskless/runtime-rules/ and run through a local harness. The release also gates runtime rules on a new-enough CLI (0.10.0 and up), and adds two states you'll see while a rule is being built: classifying, while Taskless works out which kind of rule you need, and unsupported, when a request needs a capability your plan doesn't include yet.

What a runtime rule is

A static rule is an ast-grep pattern. A runtime rule is a small directory: one or more ast-grep capture rules, and a single check.ts.

The two halves do different jobs. The capture rules are the cheap narrow, a fast ast-grep scan that finds the spots worth a closer look. The check.ts is the refinement. It runs only where the narrow matched, reads the files it needs, and decides what's actually wrong. taskless check runs the whole thing in one pass, and if the ast-grep scan finds nothing, the check never runs. The common case stays fast.

That check is real code. It gets your repo root and the matches, reads what it needs from disk, and returns a list of findings. That's the door static analysis couldn't open. A runtime rule can correlate two file trees, compare your code against a config file, or reason about a whole file at once.

More than simple static analysis

A static rule works when the correction lives inside one file's structure, a shape you can match. A runtime rule earns its place when the correction needs more than that. Three that show the range.

Every exported function has a test. The exports live in src/, the tests live in test/, and a linter sees one file at a time. Correlating what's exported over here with what's referenced over there is a cross-tree question. The capture rules gather both sides, and the check reports any export that nothing tests.

Every environment variable you read is documented. You read config with process.env.X across dozens of files, and you keep a .env.example so the next person knows what to set. A static pattern can find the process.env reads. It can't open .env.example and confirm the two lists agree. The capture rule narrows to the reads, and the check reads the manifest and reports anything missing.

Every route checks auth. In an Express app, any file that registers a route with app.get, app.post, and the rest should call your auth handler in that same file. The capture rule finds the route registrations, which narrows to the files that serve requests. The check confirms each of those files calls requireAuth, and a route file that doesn't is a finding. ast-grep can match the route and it can match the auth call. Asserting that a file with one also holds the other is a claim about the whole file that a single pattern can't make. The check makes it in a line.

Alongside the tools you already have

Runtime rules aren't a reason to throw anything out. Keep eslint. Keep Ruff. Keep your agents.md. Those tools are good at what they do, and Taskless doesn't redo them.

Taskless even routes back to them on purpose. When you author a rule, taskless help route looks at what you already have and decides where the rule belongs: your existing linter when it fits, a local ast-grep rule when the correction is structural, and a runtime rule only when the correction needs one. So Taskless reaches for the lightest tool that does the job, and it saves a runtime rule for the corrections nothing else can express: the team-specific conventions, the cross-file invariants, the "we always do it this way here" that used to live only in a senior engineer's review comments. You point Taskless at the correction, and it becomes a rule that runs on every PR, in your IDE, and from the CLI. Your existing setup gets sharper.

Running generated code, safely

Runtime rules are generated code, and they run against your codebase. Taskless is built so that's safe, in two places.

Where a rule is born: every runtime rule is generated and validated inside a locked-down sandbox with no network access and no secrets. Even in the worst case, there's nothing on that box to take and nowhere to send it. A check has to prove itself against fixtures before it ships.

What runs on your machine: taskless check reconciles the runtime rules in your repo against the Taskless service and runs only the ones it's verified. Anything it can't verify is skipped and reported, never run. Offline, runtime rules don't run unless you opt in with --dangerously-run-scripts. In CI, a token lets Taskless verify every rule and run exactly the set it blessed.

Getting started

Two steps.

npx @taskless/cli

Then ask your coding agent to onboard you onto Taskless. The agent skill takes it from there: it reads the linters and languages you already have, finds the rules worth adding, and sets them up where they fit.

Docs: Runtime Rules