Architecture
How Linda works.
Linda is six primitives stacked into a tiny browser SDK. Each is independently replaceable, and together they make the model behave like an agent on your page — not a stateless chat bubble.
1. The Virtual Filesystem
Modern LLMs are trained on filesystem-shaped workflows — they're remarkably good
at ls, cat, grep, and editing files. Linda
leans into that. The model doesn't get a giant system prompt with everything jammed
in; it gets a workspace it can navigate.
linda.vfs.tree("/") / ├── /page # live DOM snapshot │ ├── dom.html │ ├── url.txt │ └── form/ │ └── fields/ │ ├── email.json │ └── company.json ├── /conversation # full message log │ ├── messages.jsonl │ └── state.json ├── /user # files the user drops in │ └── files/ │ └── f_a1b2c3/ │ ├── info.md │ └── parsed/ │ └── text.md ├── /skills # composable capability bundles │ └── installed/ │ ├── resume-intake/ │ │ ├── SKILL.md │ │ └── schema.json │ └── kyc-id-check/ │ └── SKILL.md ├── /host # your data, mounted by you │ └── crm/ │ └── accounts.json ├── /config ├── /scratchpad └── /tools
Each path is mounted by a VfsMountHandler. Writes to
/page/form/fields/<name>.json are intercepted and dispatched to
the DOM — that's how the agent fills your form. Reads from
/host/crm/accounts.json hit the data source you wired up.
2. The Tool Loop
Every call goes through a real agentic loop. The model has six core tools:
tree— list the workspace structureread— open a filewrite— create or update a file (this is how it fills inputs)ls— list a directorygrep— search for a stringcomplete— signal the flow is done
A seventh, handoff, is available when multi-agent is enabled. The
model's plain-text output is what the user sees in the chat; tool calls happen
silently in the background. Everything streams: token-by-token UI updates, and
the loop runs until the model calls complete or the user closes the
chat.
3. Three usage modes
Linda ships in three drop-ins. All three drive the same agent loop — pick whichever fits where you are in your build.
<script src="https://unpkg.com/@linda/script/dist/linda.js"></script>
<form
data-linda
data-linda-config="/configs/signup.json"
data-linda-provider="anthropic"
data-linda-model="claude-sonnet-4-6"
data-linda-key="sk-ant-..."
>
<input name="email" />
<input name="company" />
<input name="team_size" />
</form>4. Nineteen lifecycle hooks
Linda's hook registry has 19 events covering the full conversation lifecycle —
onUserMessage, beforeFieldFill, afterFieldFill,
onComplete, onFileUpload, onSkillActivate,
onHandoff, and more. Handlers can veto or transform: return
{ veto: true } to block, return { mutation: { value: ... } }
to rewrite.
linda.on("beforeFieldFill", ({ field, value, source }) => {
if (field === "email" && !value.includes("@")) {
return { veto: true, reason: "needs @" };
}
if (source === "agent") {
return { mutation: { value: value.toLowerCase() } };
}
});
linda.on("onComplete", async ({ data }) => {
await fetch("/api/save", {
method: "POST",
body: JSON.stringify(data),
});
});
Hooks can also be declared as JSON pointing to webhook URLs — useful when the SDK
is wired up via the <script> tag and you don't ship JS yourself.
5. Skills
A skill is a directory: SKILL.md (instructions the LLM reads),
schema.json (data shape it produces), optional examples/.
The LLM reads them lazily from /skills/installed/<name>/ when it
needs them. Eight ship built-in: resume-intake, kyc-id-check, payment-intake,
signature-capture, consent-flow, multi-step-wizard, calendar-booking, address-verify.
6. Multi-agent handoffs
For longer flows — onboarding splits into intake → verification → review;
a stalled checkout fires an exit-intent rescue agent — you can declare multiple
agents with different personas and triggers. The handoff tool passes
the workspace between them; the user just sees one continuous chat.
import { onExitIntent, combineTriggers } from "@linda/core";
const linda = new Linda({
transport: { /* ... */ },
config: {
agents: [
{
name: "onboarding",
persona: "Helpful, brief, never robotic",
target: { type: "form", selector: "#signup" },
},
{
name: "rescue",
persona: "Empathetic, save-the-sale specialist",
triggers: combineTriggers(
onExitIntent(),
{ type: "hesitation", waitMs: 12000 }
),
},
],
},
}); 7. MCP — client and server
Linda speaks Model Context Protocol both ways.
As a client: mount a remote MCP server under
/host/<name>/ and the LLM treats it like any other VFS path.
Plug your CRM, your knowledge base, or your internal tools into the agent in one line.
As a server: expose your page's VFS + primitives over MCP so Claude Desktop, Cursor, or Continue can read your page directly.
import { LindaMcpServer } from "@linda/core";
const server = new LindaMcpServer({
linda,
expose: {
resources: ["/page", "/conversation", "/user"],
tools: ["read", "write", "act", "extract", "observe"],
},
});
// Connect from Claude Desktop:
// { "mcpServers": { "linda": { "transport": "sse", "url": "wss://..." } } } What you don't need to learn
You don't write the system prompt. You don't decide which tools to expose. You don't manage streaming, SSE parsing, or message persistence. You don't build the chat UI. Linda owns all of that. You bring three things: a config (what to collect), a provider+model, and (optionally) hooks for the moments you care about.
What runs where
- Browser: the agent loop, the VFS, all hooks, the chat UI, every parser, every browser-API mount. The model call hits the provider directly if you BYOK.
- Optional proxy: a thin Node server (
@linda/server) that holds your API key and streams responses over SSE. ~1 KB of code, no business logic. - Provider: Anthropic, OpenAI, Groq, OpenRouter, or your local Ollama box. Linda is fetch-only — no axios, no Node-specific deps in the browser path.
Ready to ship?
Drop the script tag, paste a config, and you have an agent on your page.