Skip to content

LSG-2: .lm File Style Guide

This guide defines conventions for writing .lm (Lamia script) files. .lm files are the orchestration layer -- they combine Python code with Lamia syntax to wire prompts, files, web actions, and validation into workflows. Violations are enforced by lamia inspect -- see the full rule reference.

Core principle

.lm files are Python + Lamia. Follow PEP 8 for the Python parts. but keep usage of Python code to a minimum only when Lamia syntax is not sufficient for the task.

Indentation and formatting

Use 4 spaces for indentation (PEP 8). Never tabs.

def analyze_code(language="python"):
    """
    Review the code in {@source.py} for bugs and style issues.
    Focus on {language}-specific patterns.
    """

for ticker in ["AAPL", "NVDA", "GOOG"]:
    data = get_stock(ticker=ticker) -> JSON[StockQuote]
    print(f"{ticker}: {data.price}")

Return types

Always declare return types when you need validated output. Lamia supports: JSON, HTML, YAML, XML, CSV, Markdown, TEXT.

Format validation (type only)

def load_config() -> JSON:
    "./config.json"

def generate_page() -> HTML[LandingPage]:
    "Create a login form"

Strict vs permissive

Skip permissive Boolean flag and use HTML[Model] instead.

-> HTML[Model]         # permissive (default) - filler content arround the content you want to extract is allowed and model matched rules are relaxed a bit. Check Lamia documentation for more details.
-> HTML[Model, True]   # strict - response must be pure HTML, etc. and no drifts are allowed in the document conten
-> HTML[Model, False]  # Use HTML[Model] instead of this 

Use cases: - Use strict mode for content generation (you control the output). - Use permissive mode for parsing external content (HTML pages, API responses).

Don't embed format instructions

Never put output schema in comments, docstrings, or prompt text:

# Bad -- the schema belongs in a Pydantic model
def analyze():
    """
    Analyze the data.
    Output JSON:
    {"score": 0, "summary": "..."}
    """

# Good -- Pydantic model + return type
class Analysis(BaseModel):
    score: int
    summary: str

def analyze() -> JSON[Analysis]:
    "Analyze the data"

Calling .hu functions

.hu files are auto-discovered as callable functions — the filename (without .hu) becomes the function name. Call them directly; do not wrap them in a def:

# Good — call the .hu file directly
result = summarize(aspect="key findings", max_words=200) -> HTML

# Bad — wrapping a .hu file in a def (flagged by LMW022)
def summarize_text(aspect, max_words) -> HTML:
    "summarize.hu"

# Bad — positional arguments (flagged by LMW006)
result = summarize("key findings", 200) -> HTML

All arguments must be keyword arguments.

Return type on the call site

The -> Type on .hu calls is where you specify what format you expect back. The .hu file itself stays format-agnostic:

# Same .hu prompt, different output formats
html_result = summarize(topic="AI") -> HTML
json_result = summarize(topic="AI") -> JSON[Summary]
text_result = summarize(topic="AI") -> TEXT

Simplified syntax

For one-off commands that don't need a function name, use the simplified arrow syntax:

data = "./users.json" -> JSON:
page = "Create a login form" -> HTML:

File operations

Reading Files

Read files with Lamia syntax. Avoid using Python code to read files. Validation will be done automatically if needed.

def read_data():
    "./data/input.txt"

def load_settings() -> JSON:
    "../config/settings.json"

content = read_data() -> TEXT # We can select the return type during the runtime.
settings = load_settings() # load_settings will always return a JSON object.

Writing Files

Write files with Lamia syntax. Avoid using Python code to write files. Validation will be done automatically if needed.

Assign to a variable when you need the result. Use -> File(...) when you want to write directly to disk: do

"Create a landing page" -> File(HTML, "index.html"):
"Generate test data" -> File(CSV, "fixtures.csv"):

If the result is needed in memory and written to a file, use a single operation:

im_memory = "Generate test data" -> File(CSV, "fixtures.csv"):

File context

Instead of using many paths to a directory in your code, use with files("~/Documents/") to scope file search directories and to avoid writing many paths to a directory in your code:

with files("~/Documents/"):
    def answer_question(question):
        """
        Answer: {question}
        Use information from {@resume.pdf} and {@cover_letter.txt}
        """

Variable naming

Follow PEP 8:

# Good
review_result = review_code(language="python") -> JSON[Review]
stock_data = get_stock(ticker="AAPL") -> CSV[StockQuote]

# Bad
ReviewResult = review_code(language="python") -> JSON[Review]
x = get_stock(ticker="AAPL") -> CSV[StockQuote]

Pydantic models use PascalCase (standard Python class naming):

class StockQuote(BaseModel):
    ticker: str
    price: float
    volume: int

Model selection

Use a singular parameter for models when a function needs a specific LLM:

def analyze(models="openai:gpt-4"):
    "Analyze the data"

Use an array of models for models when providing fallback models:

def with_fallback(models=["openai:gpt-4", "anthropic:sonnet-4"]):
    "<some complicated task>"

Imports

Lamia auto-injects the following into .lm execution scope — do not import them explicitly:

  • Lamia: All .hu files as functions in the project and all functions in the .lm files
  • Lamia All Lamia types
  • Pydantic: All Pydantic types it uses
  • Typing: List, Dict, Optional, Any

Only import Python standard library modules (import json, import re, etc.) and third-party packages. The linter flags redundant imports with LMW021.

Pydantic models defined in .py files within the project are also auto-discovered — no import needed.