process.spawn

Starts a shell command and returns a LiveProcHandle immediately. The process runs in the background, tracked by the capsule and visible to the agent in the AIR <processes> context on each cognitive tick.

const proc = process.spawn("bun dev")
await proc.waitFor("ready on port")

Signature

process.spawn(
    command: string,
    opts?: {
        cwd?: string
        env?: Record<string, string>
    }
): LiveProcHandle

LiveProcHandle

State

proc.procId     // string — unique ID tracked by the capsule
proc.command    // string — original command string
proc.pid        // number | undefined
proc.cwd        // string | undefined
proc.status     // "running" | "exited"
proc.exitCode   // number | undefined — set after exit
proc.startedAt  // number — unix ms
proc.endedAt    // number | undefined — set after exit

Interaction

proc.kill()             // terminate the process
proc.stdin(data)        // write a string to stdin

Output

All output methods return buffered content captured since spawn.

proc.stdout()                        // full stdout as string
proc.stdout("stderr")                // stderr only
proc.stdout(["stdout", "stderr"])    // both streams interleaved
proc.tail(n)                         // last n lines
proc.extract(regex)                  // all regex matches from stdout

Waiting

// Resolves when the process exits
const { exitCode, ok, stdout } = await proc.exited

// Resolves when a line matches the pattern
const { line, stdout } = await proc.waitFor("Listening on")
const { line }         = await proc.waitFor(/ready/, { timeoutMs: 10_000 })

Streaming

// Async generator — yields every line as it arrives
for await (const line of proc.watch()) { ... }

// Filtered — yields only lines matching the pattern
for await (const line of proc.watch(/compiled in/)) { ... }

// Subscribe with a callback — returns an unsubscribe function
const off = proc.on(/error/, line => console.error(line))
off() // unsubscribe

Querying buffered output

const snapshot = proc.query({
    search: "FAIL",        // substring match
    regex: /FAIL\s+\d+/,  // regex match (use one or the other)
    context: 3,            // lines before and after each match
    lines: 100,            // limit total lines considered
    include: ["stdout", "stderr"],
    caseSensitive: false,
})

// snapshot.lines          — all lines in the buffer
// snapshot.matches        — matched entries with .text, .before, .after
// snapshot.totalLines
// snapshot.matchedLines
// snapshot.raw            — full buffer as a single string

Patterns

Wait for a server to be ready

const server = process.spawn("bun run server.ts")
await server.waitFor("Listening on", { timeoutMs: 15_000 })

// server is ready — do work
const result = await axon.request("check the server health")

server.kill()

Stream build output

const build = process.spawn("bun run build --watch")

for await (const line of build.watch(/compiled/)) {
    console.log("Build:", line)
}

Run a process and inspect failures

const proc = process.spawn("bun test --reporter=verbose")
const { ok } = await proc.exited

if (!ok) {
    const failures = proc.query({ search: "FAIL", context: 5 })
    for (const match of failures.matches) {
        console.log(match.text)
        console.log(match.after.join("\n"))
    }
}

For commands where you just need the complete output, use process.run instead.