Get started
Composing systems
Where τjs sits
τjs is the runtime between an HTTP request and your renderer. A route states its data, policy and rendering once; Fastify serves it, React, Vue, Solid or plain HTML renders it, and every development request leaves a record you can interrogate. Change your system without guessing.
Introspection security: development only, by construction. No production collectors.
- runs in production
- development only - no production collectors
- written at build
- 1Fastify hostowns HTTP dispatch and the host lifecycle, then hands a selected τjs route over.
- 2τjs application scopeone per app, each with its own renderer root; several can live in one host.
- 3Declared route contractrender · data · policy, stated once per route.
- 4Servicescritical data before first byte, deferred data streamed after.
- 5Renderer and the responseReact · Vue · Solid · HTML produce the declared response.
- 6Buildτjs's build drives Vite: module graph and bundles for the renderer; emits the structure-only graph.
- 7
dist/.taujs/graph.jsonthe structure-only request graph, written at build; read bytaujs-mcponly when no dev boot is live. - 8Development substrate
node_modules/.taujs/boots/<bootId>/: graph · episodes · logs annex · observed edges. - 9
taujs-mcpstdio adapter reading the boot for tools and agents; development only.
Interrogate your system
Section titled “Interrogate your system”You are about to change the catalog service. What depends on it? In most stacks that means grepping and hoping. Here you interrogate the system, and the answer is read from files the dev server already emits - never guessed from source.
taujs_who_calls_service service: "catalog" edges
- route
/product/:idmethodgetProductsource declared - route
/product/:idmethodgetProductHeadsource declared - route
/product/:idmethodgetProductsource observed - route
/compare/:idmethodgetProductsource observed - route
/product/:idmethodgetProductHeadsource observed
hostObserved
- route
/api/products/:idmethodgetProductsource hostObserved
Declared means intended. Observed means exercised. Missing means not seen yet - not "safe".
What the tool says about these labels
declared = from config (a serviceData edge, a deferred entry or a head edge); observed = seen in dev traffic through a τjs page route, never complete truth; hostObserved = seen in dev traffic through a Fastify route the application registered itself, reported separately so it is never mistaken for a declared edge. methodCallCount is the method-wide total for the boot; routeCallCount is that route’s own attribution. lastObservedAt is also method-wide: the last time any route called this method, not the last time this route did.
The rows come from two artefacts under the application’s own node_modules/.taujs/boots/<bootId>/:
the request graph records what taujs.config.ts declares; observed edges record what real
development requests did. An agent working on a τjs application is handed the substrate’s own
description of the application it is modifying, over MCP. When there is no live boot, the episode
tools refuse rather than answer stale; structural answers from an old build are labelled as such.
How the tools answer, and what they refuse.
One contract, two kinds of truth
Section titled “One contract, two kinds of truth”Each declared route states its data, policy and rendering once. That declaration is what Fastify dispatches and what the renderer consumes - and it is the same thing the graph reports. The contract is operational, not descriptive: data loading, rendering, policy, tracing and tooling all consume the same declaration, so the answer a tool reads cannot drift from the code the server runs.
Episodes add the second kind of truth. Every development request leaves a record of what actually happened: which services were called, when, with what outcome. Declared is intent; observed is behaviour; the page shows you both, labelled, and never substitutes one for the other.
Traces alone are available on any framework. What is unusual here is where the substrate sits - at the HTTP request and response boundary, with the renderer still yours - and that the boundary is inspectable by construction rather than by instrumentation added later.
Request contracts and data ownership.
From request to response
Section titled “From request to response”A request matches a declared route. Critical data resolves before the response commits, where a failure can still become an HTTP error. Deferred data starts with the request, never delays independent shell content and cannot decide the status. The renderer - the application’s choice - turns the result into HTML, server-rendered or streamed; values consumed during rendering hydrate without a client refetch. Then the episode is written.
Here is a product page that streams, with its reviews deferred alongside:
export default defineConfig({ apps: [{ appId: "storefront", entryPoint: "", renderer: reactRenderer({ project: "./tsconfig.json" }), routes: [{ path: "/products/:id", attr: { render: "streaming", data: serviceData("catalog", "getProduct", (p) => ({ id: String(p.id) })), deferred: { reviews: serviceData("reviews", "forProduct", (p) => ({ id: String(p.id) })) }, }, }], }],});The episode that request leaves is the second proof. Its marks are the contract’s own stages, so the time a request spent has a location, not a guess:
/product/42 modestreaming outcomecomplete status200
-
catalog.getProductHead3.2–3.3 ms -
catalog.getProduct5.6–5.8 ms
-
startMs - 0 ms
-
matched - 0.1 ms
-
devAssetsReady - 2.4 ms
-
dataStart - 4.8 ms
-
dataEnd - 5.8 ms
-
head - 7.3 ms
-
shellReady - 7.7 ms
-
allReady - 7.9 ms
-
catalog.getProductHead - 3.2–3.3 ms · ok
-
catalog.getProduct - 5.6–5.8 ms · ok
Each declared page is a real Fastify route, and a route can also declare head metadata, authentication and Content Security Policy. Everything after hydration - mutations, polling, subscriptions, UI-local fetching - stays in your application.
Use the ecosystems you already know
Section titled “Use the ecosystems you already know”τjs composes existing tools rather than replacing their native APIs. Fastify owns HTTP routing, plugins, hooks, decorators and the host lifecycle. Vite owns the module graph, development pipeline and production bundles. React, Vue, Solid or plain HTML keep their own rendering, streaming semantics and client runtimes.
There is no second HTTP router or parallel plugin system. Supply your own Fastify instance and τjs operates inside an encapsulated scope; omit it and τjs creates the host. You do not convert an application: start with what you have and declare the routes whose initial responses benefit from server coordination. Policy, data orchestration and service access stay where they are when the renderer changes.
Adopt the model incrementally.
One architecture, several application shapes
Section titled “One architecture, several application shapes”One Fastify host can serve a storefront, an account area and an operations console - each a separate application with its own renderer, module graph and output bundle, each free to choose a different one. Every URL belongs to exactly one application; undeclared URLs remain client-rendered by omission. Applications compose through routes and assembled build output, not runtime module federation, and the whole system answers to the same graph and the same episodes.
How multi-app composition works.
Introspection security: development only, by construction
Section titled “Introspection security: development only, by construction”The server you develop against is the server you deploy: the same Fastify routes, the same
contract, the same orchestration. Development is production plus exactly two things - Vite’s
module pipeline and the introspection collectors - and NODE_ENV decides which you are running:
development is development, anything else is production. There is no third mode.
Episodes, logs and observed edges are collected by the dev server. The production runtime
structurally excludes those collectors - there is nothing to switch off, no sampling to tune and
no request data leaving a deployed host. Builds emit one artefact, a structure-only graph at
dist/.taujs/graph.json, so structural questions can still be answered from the last build,
labelled stale.
The MCP server opens no network connections and loads no configuration: the files are its
credential. Several live boots refuse with multiple_active_boots; a folder whose ids disagree is
refused as substrate_inconsistent. Field values in answers are your application’s data - capped,
never treated as instructions.
Get started
Section titled “Get started”Request contracts and data
Inspect and diagnose
Compose larger systems