Engineering
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
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
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
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)
}