Your team’s tools and your agents run on one system and talk to each other.

crouter is an agent runtime. It runs on the Claude or ChatGPT subscription you already have, or an API key.

$ npm install -g @north-light/cli$ crtr

Needs Node.js 22.19 or later and tmux. The first crtr signs you in to a model provider.

Four tools that run Northlight

Our own engineering runs on four tools built on crouter: Postflight watches production, the PM system tracks work, the GitHub app follows pull requests, and Preflight reviews them. None of them wraps crouter from the outside. Preflight’s review is an agent run on the reviewer’s own runtime. The PM system’s daily pass is an agent run with a typed result. A Postflight problem and a GitHub pull request are objects on the canvas that an agent watches and is woken by.

P-112 → NL-451 → device #114times are UTC
  1. P-112 opened

    DirectoryError: The directory could not be reached: fetch failed

    sentry report joined

    Postflight
  2. NL-451 created from Postflight’s signal (bug)

    Desktop cannot reach the directory when minting a token

    PM system
  3. device #114 linked to NL-451

    fix(tray): keep the fetch error as the cause when the token mint cannot reach the directory

    CaptainCrouton89 · “Resolves NL-451”

    GitHub app
  4. Code health review — no findings · must_fix: 0 · should_fix: 0

    Preflight
  5. device #114 merged

    GitHub
  6. NL-451 shipped

    PM system
  7. P-112 resolved: its intent was shipped

    Postflight
// Preflight — every review is an agent run on the reviewer's own runtimeawait runtime.runs.start({  prompt,  scopes: ['crtr:llm', 'crtr:act'],  isolated: true,  deadline: '30m',  output_schema: ReviewResult,});
preflight/src/runs/agent-runs.service.ts
// PM system — the daily pass that sorts signals is an agent run with a typed resultawait rt.runs.start({  prompt: buildPrompt(snapshot),  scopes: ['crtr:llm'],  output_schema: GardenerResult,});
pm-system/src/gardener/pass.service.ts
// GitHub app — a watched pull request is an object on your canvasawait runtime.customObjects.register({  kind: 'github.pr',  name,  description: `GitHub pull request ${name}`,  privacy: repo.private ? 'private' : 'public',});
github/src/instances/instances.service.ts
// Postflight — a problem is an object with its own events; a watching agent is woken by themevents: {  regressed: { level: 5 },  'state-changed': { level: 5, supersedes: true },  recovered: { level: 3 },  'source-added': { level: 3 },}
postflight/src/plugin/plugin.ts
$ crtr github watch-pr -hwatch-pr: Watch a pull request: the calling node is woken when its checks fail or pass, a review lands, commits are pushed, or it merges or closes.

A crouter agent diagnosed this error and opened the PR, and it went from Sentry to a closed problem in under eight hours.

Start an agent from your server

client.runs.parse starts an agent from your server code and returns its answer checked against your zod schema.

import Crouter from '@north-light/sdk';import { z } from 'zod'; const client = new Crouter(); // reads CRTR_BASE_URL and CRTRD_TOKEN const Triage = z.object({  severity: z.enum(['low', 'high']),  summary: z.string(),}); const run = await client.runs.parse({  prompt: 'Triage the newest support ticket.',  output_schema: Triage,  deadline: '10m',}); if (run.kind === 'result') console.log(run.output_parsed.severity);else console.log(run.reason);
# change `severity` to `severty` and tsc --strict says:TS2551: Property 'severty' does not exist on type '{ severity: "low" | "high"; summary: string; }'. Did you mean 'severity'?

CRTR_BASE_URL is the person’s Northlight runtime and CRTRD_TOKEN an app token for it. Each run is an agent on the same runtime as your terminal agents: it can start sub-agents, read documents, and shows up in crtr canvas.

Your tools wake your agents

Your service can wake an agent by publishing an event, and a command your plugin adds to crtr gets the verified identity of whoever calls it.

# your service gets a place on the canvas with its own events$ crtr custom-object register --kind acme.deploys --name staging --description "Staging deploys" --events '{"finished":{"level":5,"ends_wait":true}}' # an agent watches it$ crtr canvas watch <object-id> # the service publishes, and the watching agent wakes$ crtr custom-object publish <object-id> --event finished --summary "Staging deploy finished"
import { defineLeaf, field, param } from '@north-light/plugin'; export const deploy = defineLeaf({  description: 'Deploy a service to staging.',  whenToUse: 'When a change is ready to try on staging.',  summary: 'deploy to staging',  sideEffect: true,  params: { service: param.string('service name', { required: true }) },  output: { url: field.string('staging URL') },  effects: ['Starts a staging deploy of the service.'],  handler: async ({ service }, ctx) => {    const who = ctx.caller?.sub; // "user:<id>", from the verified token    if (!who) throw new Error('unauthenticated');    return { url: `https://staging.example.com/${service}` };  },});

ctx.caller.sub comes from the caller’s verified token, so the command can check who is asking before it deploys.

Documents that load when needed

A document carries a rule for when it loads and how much of it the agent sees, and every agent that reads it is sent its later edits.

$ crtr canvas read --fields symbol-navigation<object type="document" kind="knowledge" name="symbol-navigation" owner="tsym" revision="1">Summary: Run `crtr tsym -h` before grepping for a symbol.  "rules": [    {      "on": "command",      "deliver": "content",      "match": ["grep *", "rg *", "git grep *"]    }  ]
# what the agent saw right after it ran rg<memory kind="knowledge" name="symbol-navigation">Before grepping for a symbol — where it is declared, who calls it, what breaks if it changes — run `crtr tsym -h`. Its answers come from the TypeScript compiler, not text matching.</memory>
$ crtr doc write -hEffects  Creates the document at revision 1 and makes the writer a watcher, so it is pushed later edits.

This rule, from the tsym plugin, loads the whole document the first time an agent runs grep, rg or git grep. A document already in the agent’s context is not sent again.

What runs underneath

Closing a terminal does not stop an agent, and an agent that is waiting on something has no process at all.

$ crtr node inspect show <node-id># <title>  (<node-id>) - kind/mode/lifecycle: developer / orchestrator / terminal- status: idle/idle-release- broker pid: (none) — dead- cwd: <cwd>- host: broker · profile <profile-id>- cycles: 187 · created 2026-10-29T08:50:38.213Z- parent: <parent-id># launched or woken 187 times in about 37 hours
$ crtr canvas dashboard- nodes: 17● crouter-code-landing-page [general/orchestrator] ctx 0k├─ ✓ competitor landing research coding-harness-landing-page-research [general/base] ctx 0k├─ ✓ internal-tools integration map map-northlight-app-integrations-crouter [explore/orchestrator] ctx 0k│  ├─ ✓ explore-preflight preflight-integration-production-audit [explore/base] ctx 313k│  ├─ ✓ explore-postflight map-postflight-current-state [explore/base] ctx 164k│  ├─ ✓ explore-github github-app-runtime-map [explore/base] ctx 300k│  └─ ✓ explore-pm-system pm-system-integration-evidence [explore/base] ctx 694k├─ ✓ own assets + claims inventory crouter-marketing-evidence [explore/base] ctx 497k├─ ✓ measure longest task trees measure-crouter-task-trees [explore/base] ctx 767k├─ ✓ snippet evidence for code.northlight.ai landing-page-snippet-evidence [general/base] ctx 0k├─ ✓ draft code.northlight.ai copy northlight-landing-page-copy [general/base] ctx 0k├─ ✓ run SDK sample against real daemon run-sdk-parse-sample-locally [general/base] ctx 0k├─ ✓ rerun SDK sample on local hosted runtime runs-parse-sample-local-runtime [general/base] ctx 0k├─ ✓ scaffold Northlight-Systems/code repo scaffold-code-northlight-site-repo [developer/base] ctx 0k├─ ✓ build code.northlight.ai landing page code-northlight-landing-page [developer/base] ctx 0k│  └─ ✓ code-page-design-review marketing-page-visual-review [review/base] ctx 150k└─ ○ draft How-it-works page copy draft-how-it-works-page-copy [general/base] ctx 0k ⚑1

These are the agents that built this page; a larger run, measured 3 Oct, used 447 agents over 5 days and merged 115 PRs across 10 repos.

What runs where, and what changed lately

crouter doesn’t bundle a model, and it runs on your machine or in a container you start.

  1. 0.3.560A read waits through a hosted runtime being created or replaced, instead of failing with 503.
  2. 0.3.559An app can run the crtr command line in its own sandbox on the person's runtime.
  3. 0.3.554RunObject.recap (runs.get, runs.list): the root's latest idle recap, { goal, state, at }, where goal says what the conversation is for and state where it stands.

Release notes for @north-light/sdk, newest first.

$ npm install -g @north-light/cli$ crtr

Needs Node.js 22.19 or later and tmux.