Engineering

7 min read 20 Apr 2026

The .sky Language: A Deep Dive

Why a new language?

Most workflow tools either go too high-level (drag-and-drop, no escape hatch) or too low-level (write Python, manage your own state machine). We wanted something in between: text-based, version-controllable, readable by non-engineers, but expressive enough for real production workflows.

The result is .sky.

The four block types

Every .sky file is a sequence of blocks separated by Unicode delimiters.

Meta block (⊕meta⊕ ... ⊕⊕)

Defines the workflow itself: name, description, trigger configuration, required secrets, and output style.

⊕meta⊕
name = "fix-issue"
description = "Triage a GitHub issue and plan a fix"
trigger.github.events = ["issues.labeled"]
secrets = ["GITHUB_TOKEN"]
output_style = "terse"
⊕⊕

Node config block (§name§ ... §§)

Configures a single node: model, effort level, dependencies, budget, isolation mode.

§plan§
model = "opus"
effort = "high"
depends_on = ["fetch"]
isolation = "worktree"
max_budget_usd = 0.5
§§

Prompt block (∆name∆ ... ∆∆)

The actual instructions for the node. Supports template variables from previous node outputs.

∆plan∆
Issue details:
$fetch.output

Write a concise implementation plan with root cause, files to change, and test strategy.
∆∆

Doc block (※※ ... ※※)

Inline documentation. Stripped at runtime, useful for describing what the workflow does.

Template variables

.sky supports three variable types:

  • {{variable}} — workflow-level inputs
  • $node.output — output from a named node
  • ${env:SECRET_NAME} — environment secrets

What’s supported today

The current runtime supports HTTP fetch nodes, LLM nodes with model/effort/budget config, GitHub triggers, and worktree isolation. More node types are coming in the next release.

TypeScript

Strongly typed, async-first HTTP client

TypeScript
interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUser(id: number): Promise<User> {
  const url = 'https://api.example.com/users/' + String(id);
  const res = await fetch(url, {
    headers: { Authorization: 'Bearer ' + process.env.API_KEY },
  });
  if (!res.ok) throw new Error('HTTP ' + res.status);
  return res.json() as Promise<User>;
}

Python

Async with httpx, idiomatic error handling

Python
import httpx

async def fetch_user(user_id: int) -> dict:
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://api.example.com/users/{user_id}",
            headers={"Authorization": f"Bearer {API_KEY}"},
        )
        response.raise_for_status()
        return response.json()

Go

Standard library, zero dependencies

Go
func fetchUser(id int) (*User, error) {
    url := fmt.Sprintf("https://api.example.com/users/%d", id)
    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Set("Authorization", "Bearer "+apiKey)
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    var user User
    return &user, json.NewDecoder(resp.Body).Decode(&user)
}