Sequences

Sequences orchestrate agents, MCP tools, API requests, scripts, SQL, and checks as one repeatable flow. Each step can pass data to the next.

Basic Sequence

Wrap related requests in a sequence block:

.norn
sequence CreateAndVerifyUser
    POST https://api.example.com/users
    Content-Type: application/json

    {"name": "Alice", "email": "alice@example.com"}

    GET https://api.example.com/users/1
end sequence

Response Capture

Use $1, $2, etc. to reference responses by their order:

.norn
sequence UserWorkflow
    POST https://api.example.com/users
    {"name": "Alice"}
    
    # $1 refers to the POST response above
    var userId = $1.body.id
    
    GET https://api.example.com/users/{{userId}}
    
    # $2 refers to this GET response
    assert $2.status == 200
end sequence

Response Properties

Access different parts of each response:

  • $1.status - HTTP status code
  • $1.body - Response body (parsed as JSON)
  • $1.body.path.to.value - Nested value in body
  • $1.headers.Content-Type - Response header
  • $1.duration - Request duration in milliseconds
  • $1.cookies - Cookies captured from the response

In VS Code, after a successful Send Request or Run Sequence, Norn can suggest cached response-body properties for $1.body. and captured response variables like user.body.. CLI execution stays the same and does not write this editor cache.

Test Sequences

Mark sequences as tests with the test keyword. Test sequences run in the Test Explorer and CLI:

.norn
test sequence UserAPITests
    GET https://api.example.com/users
    assert $1.status == 200
    assert $1.body[0].id exists
end sequence

Sequence Composition

Sequences can call other sequences with run so you can separate setup, shared flows, and cleanup.

.norn
sequence Login
    POST {{baseUrl}}/auth/login
    Content-Type: application/json

    {"username": "admin", "password": "secret"}

    var token = $1.body.accessToken
    return token
end sequence

test sequence UserFlow
    var token = run Login
    GET {{baseUrl}}/users/me
    Authorization: Bearer {{token}}
    assert $1.status == 200
end sequence

Sequence Parameters and Returns

Sequences can accept parameters. A single returned value comes back as the raw value; multiple return values come back as an object keyed by name.

.norn
sequence GetTodoTitle(id)
    GET {{baseUrl}}/todos/{{id}}
    var title = $1.body.title
    return title
end sequence

sequence AuthenticateAndFetch
    POST {{baseUrl}}/login
    {"user": "demo"}
    var sessionId = $1.body.sessionId

    GET {{baseUrl}}/todos/1
    var todoTitle = $2.body.title

    return sessionId, todoTitle
end sequence

test sequence ReturnsExample
    var title = run GetTodoTitle(1)
    var auth = run AuthenticateAndFetch

    assert title isType string
    assert auth.sessionId exists
    assert auth.todoTitle isType string
end sequence

When one sequence is selected from the command line, its parameters can be supplied per run with --param and --param-json, which override authored defaults. See CLI.

Named Requests

Named requests let you define a reusable request block once and invoke it with run.

.norn
[FetchTodo]
GET {{baseUrl}}/todos/1

[FetchUser]
GET {{baseUrl}}/users/1

test sequence ReuseRequests
    run FetchTodo
    assert $1.status == 200

    var user = run FetchUser
    assert user.status == 200
end sequence

Agent Steps and Judged Expectations

A sequence can also call an AI agent declared in an imported .nornai sidecar, and score free text that an assertion cannot reach:

.norn
import "./agents.nornai"

test sequence ReviewTicket
    var ticket = run readJson "./ticket.json"
    var report = run TicketRouter ticket

    assert report.text exists
    judge report with Reviewer expects "A test covers the happy path for POST /orders"
end sequence

run <Agent> is an ordinary step and binds a result you can assert on. judge is one line like assert, but it is a paid, probabilistic model call rather than a deterministic check — every expectation must be met, and one unmet expectation fails the sequence. See AI Agents for contracts, delegation, judging, and replay.

Sequence Tags

Add tags to organize and filter tests:

.norn
@smoke
@team(backend)
test sequence CriticalPathTest
    GET https://api.example.com/health
    assert $1.status == 200
end sequence

Run tagged tests from CLI: norn tests/ --tag smoke

Parameterized Tests

Use @data for data-driven tests:

.norn
@data(1, "Alice")
@data(2, "Bob")
test sequence UserTest(id, expectedName)
    GET https://api.example.com/users/{{id}}
    assert $1.body.name == "{{expectedName}}"
end sequence

For external test data, use @theory with a JSON array of objects whose keys match the sequence parameters.

.norn
@theory("./testdata/users.json")
test sequence UserTheory(userId, expectedId)
    GET {{baseUrl}}/users/{{userId}}
    assert $1.status == 200
    assert $1.body.id == {{expectedId}}
end sequence
testdata/users.json
[
  { "userId": 1, "expectedId": 1 },
  { "userId": 2, "expectedId": 2 }
]