Why Janux
The web is growing a second audience. People still click, but agents now read, plan and act on the same pages — through MCP clients, through browser agents, through copilots embedded in your own product. The Agentic Web is the web both of them can operate, and it is being standardized in the open: MCP for tools over HTTP, WebMCP for tools in the browser, llms.txt for discovery, Web Bot Auth for identity.
Today, making an app agent-operable means building it twice. The UI already holds the logic — the validation, the permissions, the business rules — and then a second, hand-written integration re-declares a fraction of it as tools. Two artifacts, one source of truth, and the gap between them grows with every sprint. Tools drift, guardrails are ad-hoc, and nobody can say exactly what an agent is allowed to do.
Janux removes the second artifact. A component is simultaneously a view, an agent-readable resource and a set of typed tools — one definition, projected three ways by the framework. A human click and an agent tool call enter the same pipeline: guard check → schema validation → run() → audit entry. The contract can't rot, because it is generated from the code that renders.
Named after Janus, the two-faced Roman god of doorways: one face toward the human, one toward the agent, one threshold. Designed in RFC 0001.
Table of Contents
- Install
- Quick start
- One component, three projections
- Two agent surfaces, zero integration
- Humans stay in the loop
- Highlights
- How it works
- Performance
- Packages
- Documentation
- Benchmarks
- Templates
- Examples
- Develop
- Contributing
- Releases
- License
Install
bunx create-janux my-app
cd my-app && bun install && bun run dev
Requires Bun ≥ 1.3 for the dev server and the build. Production is a choice: Bun, Node 24+ via @janux/node, Vercel, or a static export — same app, one adapter.
Or add the pieces to an existing workspace:
bun add janux @janux/server @janux/agent @janux/cli
Quick start
import { component, intent, schema, str, int, money, list } from 'janux';
import { pay } from './pay.api';
// UI component + 2 WebMCP tools (intents), grouped together for maintainability
export const Cart = component({
name: 'cart',
description: 'Shopping cart with line items.',
state: schema({ items: list({ productId: str(), qty: int().min(1), unitPrice: money() }) }),
derived: { total: (s) => s.items.reduce((a, i) => a + i.qty * i.unitPrice, 0) },
intents: {
addItem: intent({
description: 'Add a product to the cart',
input: schema({ productId: str(), qty: int().default(1), unitPrice: money().default(0) }),
run: ({ state, input }) => state.items.push(input),
}),
checkout: intent({ description: 'Pay for the cart', guard: 'confirm', run: ({ state }) => pay({ items: state.items }) }),
},
view: ({ state, derived, intents }) => (
<section>
<ul>
{state.items.map((i) => (
<li key={i.productId}>{i.productId} × {i.qty}</li>
))}
</ul>
<button onClick={intents.checkout}>Pay ({derived.total}¢)</button>
</section>
),
});
You wrote a shopping cart. You also shipped an agent surface — generated, no second file:
{
"resources": ["ui://cart"],
"tools": [
{ "name": "cart.addItem", "description": "Add a product to the cart", "guard": "auto" },
{ "name": "cart.checkout", "description": "Pay for the cart", "guard": "confirm" }
]
}
One component, three projections
They cannot drift: there is one definition, and the framework derives the other two. A human click and an agent tool call run the exact same pipeline — guard check → schema validation → run() → audit entry.
Two agent surfaces, zero integration
Every Janux app speaks the Agentic Web's protocols out of the box. You declare no tools twice, and you write no adapters.
Pointing Claude, Cursor or any MCP client at your app is a URL, not an integration project:
claude mcp add --transport http my-app https://your.app/_janux/mcp
Humans stay in the loop
Guards are a language feature, not a convention. Every intent and every api() declares who may call it:
auto— agents call it directly.confirm— a human click runs it; an agent call parks as a Proposal that a person approves or rejects on the real UI, executing exactly once.forbidden— never exposed as a tool. The agent falls back to the DOM, under the same permissions as a user.
Every invocation records its origin (human / agent) in an audit trail, and janux verify fails the build if an agent-reachable tool ships without a description. See examples/human-in-the-loop in the table below.
Highlights
- 🧿 One definition, three projections. The mounted tree is the MCP tree — UI and agent surface cannot drift.
- 🪶 0 KB JS static pages. Components without state compile to plain HTML; a page with no islands ships no
<script>at all. - ⚡ Structural resumability. State is schema-typed JSON, behavior is named — the client resumes from snapshots with no hydration replay and no closure serialization. Zero component code runs until first interaction (asserted in the test suite).
- 🔌
api()= endpoint + stub + tool. A server function is at once a validated HTTP endpoint, a ~100-byte typed client stub (SWC transform) and an agent tool. - 🤖 Zero-config copilot.
JANUX_MODELor one provider API key (Anthropic, OpenAI, Google or OpenRouter) is all it takes. Every app ships the agent endpoint, the manifest and the gui-agent bridge (window.janux). - 🗺️ App-wide agent control. Every turn advertises built-in client tools (
ui_navigate,ui_get_view_context,ui_read_page,ui_click,ui_fill,ui_wait_settled) plus the full route map — andui_callsturns resume with their results (act → observe → continue), so navigate-then-act works in one turn. - ⚛️ Foreign-UI interop.
foreign()mounts React components unchanged — real embedded roots, tracked props, callbacks→intents — surviving SPA navigation. - 🧘 Observable quiescence.
await janux.settled()— thesleep(500)idiom dies here. - 🧪 CI for the agent surface.
janux verifygates undescribed tools;janux evalreplays scripted agent tasks — including real human-approval steps — against a live app.
How it works
Browser ── janux core (signals, resume, morph, delegation, window.janux bridge)
│ HTML + state snapshots │ RPC │ agent turns
Server ── @janux/server (SSR, api(), manifest, proposals)
└── @janux/agent (model resolution, provider loop: api.* server-side, ui_calls → bridge)
- SSR: sources load server-side; islands arrive with real content plus a JSON state snapshot.
- Resume:
boot()indexes islands, installs two delegated listeners, and mounts an island only on first interaction or agent call — from the snapshot, morphing the SSR DOM in place. - Agents:
GET /_janux/manifest?path=/shopfor discovery;POST /_janux/api/*for server tools;window.janux.call()for UI tools;POST /_janux/approvefor proposals. - Static export:
output: "static"prerenders every page intodist/client— deploy docs and marketing sites to any static host, agent face included, no server.
Configure the copilot model
Zero config — first match wins:
defineAgent({ model: 'anthropic/claude-fable-5' })JANUX_MODEL=provider/model- Provider key sniffing:
ANTHROPIC_API_KEY/OPENAI_API_KEY/GOOGLE_GENERATIVE_AI_API_KEY - Nothing set → the endpoint answers with a setup card; the app never crashes.
Performance
The documentation site is built with Janux (apps/docs) and scores 100 across the board — including Agentic Browsing, Lighthouse's check for whether an agent can actually read and operate the page. A CI job re-runs the audit on every pull request.
Packages
Documentation
janux.build — 114 pages, ⌘K search, dark mode, and a copilot that answers from the docs themselves.
Agents read the same docs at /llms.txt and any page as Markdown by appending .md.
Every example is verified. packages/docs-tests compiles every snippet, checks it imports only symbols the packages really export, runs the main example of a page and asserts what the prose claims. Three guards fail the build when an export has no reference page, when a page's executable claims aren't executed, or when any documented link stops resolving. Both backlogs are empty: every public export is documented, and every page that imports the framework has a test that runs it.
Benchmarks
19 multi-framework suites — client runtime, hydration, SSR, streaming and
shipped bytes — measuring Janux against react 19, preact, solid 2, svelte 5
and vue-vapor, with correctness gates before any number counts. The harness
is a port of octane's benchmarks (MIT,
Dominic Gannaway; js-framework fixtures derive from
krausest, Apache-2.0).
Across the 19 suites, 88 of 156 janux/react cells are ahead of react and 68 are
behind (2026-08-02 pass; bytes above are the 2026-08-06 measurement) — the full
signed table is in
benchmarks/BASELINE-2026-07.md.
Full tables, methodology and machine specs:
docs page · reproduce with
bun run bench from benchmarks/.
Templates
An example teaches a feature; a template starts a product. Each one is a complete app with its own README, a one-command deploy, and agent evals that prove its agent surface works — scaffold one and you have something to ship, not something to read.
bun create janux my-app --template dashboard
cd my-app && bun install && bun run dev
Run --template with no name and the gallery lists itself. Every template starts with no
API key: the copilot degrades to a setup card plus a no-model demo that drives the page
with real tool calls.
Full gallery with screenshots: janux.build/docs/more/templates.
Examples
43 runnable apps, each a real Janux project — bun run --cwd examples/<name> dev.
Start here
The agentic surface
Components & state
React interop
One example per category, each verified in CI — the compatibility matrix states what works, what works with caveats, and what does not.
Rendering & routing
Styling
Server & data
Develop
bun install
bun test # the whole suite: schema, signals, runtime, SSR, resume, morph, interop, router, cache, guards, agent loop, harness, SWC stubs
bun run test:census # per-area counts, the coverage floor, and the count the docs claim
bun run typecheck
Contributing
PRs welcome — see CONTRIBUTING.md and our Code of Conduct. Third-party work Janux builds on is credited in CREDITS.md; security reports go through SECURITY.md.
Releases
Janux is 0.x, and every published package moves on one version.
- CHANGELOG.md — what changed, newest first.
- VERSIONING.md — what a minor is allowed to break, how much notice you get, and how long each one is supported.
- STABILITY.md — every public export marked stable, experimental or internal. Generated from the exports themselves.