Dejo
Um navegador que seu agente de IA lembra: aprende uma tarefa de site uma vez e depois a reproduz em uma única chamada de ferramenta sem nenhuma chamada de modelo.
Documentação
Dejo
A browser your AI agent remembers.
It learns a website once. Every run after that is one tool call, with zero model calls.
Website · Install · Benchmark · Tools · What it can do · Compared · Contributing
Your AI can use websites. But it forgets how, every single time. Dejo makes it remember.
Dejo gives Claude Code, Codex, Cursor or any MCP client a browser with a memory. Your agent walks a website once: signing in, clicking, reading. Dejo writes down the route. Every run after that follows the route instead: one tool call, no page reading, zero model calls. When the site changes, Dejo repairs the one step that moved and keeps the rest.
Install
Python 3.11 or newer.
pip install dejo-mcp
playwright install chromium # the browser, a separate download of about 150 MB
Then connect it to your agent:
| agent | where | what |
|---|---|---|
| Claude Code | your terminal | claude mcp add --scope user dejo -- dejo-mcp |
| Codex | ~/.codex/config.toml | [mcp_servers.dejo]command = "dejo-mcp" |
| Cursor | ~/.cursor/mcp.json | { "mcpServers": { "dejo": { "command": "dejo-mcp" } } } |
| anything else | as a stdio server | dejo-mcp |
Dejo is one stdio server with no arguments, so any agent that can run an MCP server can use it.
No API key, no account. Dejo never calls a model. Your agent does the thinking, and the memory is a local file on your machine.
Check the install at any time. It names anything missing and the command that fixes it:
dejo doctor
Quick start
Ask your agent for something on a website, in your own words:
Go to github.com/microsoft/playwright and tell me how many open issues it has.
The first time, your agent learns it: Dejo opens the page and shows it, one line per control,
and your agent sends the steps back in one call (click link "Issues", then read ... as open issues). The read is what Dejo saves. Ask again tomorrow, in a brand-new chat, and it is one
call:
done: "count open issues" on github.com (profile default), 2 steps replayed in 1391 ms, no page reading.
open issues = 117
next: report the answer exactly as read and stop. Every step was checked as it ran.
Prefer a terminal? Everything works without an MCP client:
dejo sites # what it remembers
dejo run "count open issues" --site github.com # replay, no model involved
dejo run "count open issues" --site github.com --json # one JSON line, for cron and scripts
dejo show github.com # the route, step by step
dejo forget --site github.com # make it forget
How it works
| what happens | cost | |
|---|---|---|
| First run | Your agent drives the browser through Dejo. Dejo watches, and turns what happened into a route. | many calls |
| Every run after | Dejo replays the route itself and checks that each step landed. | one call, zero model calls |
| The site changed | Dejo stops at the one broken step and shows the page there. Your agent sends one line for that step; Dejo saves it, keeps the old ways as backups, and replays the rest in the same call. | one extra call, once |
What a route is
Not a recording, and not notes. Each step keeps:
- what it was for, in plain words:
"open this month's invoice" - up to nine ways to find the control: test id, link target, label, role, placeholder, alt text, title, visible text, CSS. Ranked by which have actually worked.
- a check that proves it landed: the address changed, the file downloaded, the field holds what was typed, the row count is what it should be.
The checks are the difference between this and a macro recorder. A recorder clicks and hopes. Dejo notices when a click did nothing, and says the site changed.
The nine locators are why most redesigns cost nothing: lose the CSS id, keep the label, and the step still lands. If more than half the steps break, the site was rebuilt rather than adjusted. Dejo throws the route away but keeps what it knows about the site (needs a login, sends a code to your phone), so relearning is cheaper than the first visit.
When the site changes
A site is rarely the same twice: a toast one day, a newsletter box the next, a button with new words. Dejo was learned on one visit and is replayed on another, so it has to tell "something is in the way" from "the site moved" from "this is the wrong account".
What it checks, every step. Each step has a check that proves it landed (the address, the file, the words, the row count). A step that cannot check itself is not saved. If a check fails, Dejo stops at that exact step.
What it does on its own:
- Finds the element another way. Up to nine ways per step, best first. When the first misses and a backup finds it, the backup moves up. On a slow page Dejo waits for the page to finish before it calls any way broken. A backup that finds a control which does something else ("Remove item" where "Show total" used to be) is never pressed. After a fix, the old ways stay as backups, so a site that shows version A one day and version B the next works on both once each has been seen.
- Clears a pop-up nobody told it about. When something covers the element a step presses,
Dejo tries, in order: the pop-up's own close ("Close", "No thanks", "Not now", "×"), the
Escape key, then a consent banner's button, choosing "Reject all" or "Necessary only" over
"Accept". It never presses anything that buys, pays, sends, deletes, submits, subscribes
or signs up. Consent platforms that ask inside their own frame (Sourcepoint, used by many
news sites) are answered inside the frame. What worked is kept for the site, so the next
run clears it up front, as
dismiss css "..."would; a pop-up inside a frame is cleared the same way on every run instead. - Skips a step that only closed something. A cookie banner or a toast that is not there
this time: the step that closed it is skipped. Also when the toast faded out by itself, or
said "Dismissed" before it went. A step marked risky (
click!) or with its ownexpectis never skipped. - Skips a sign-in it does not need. A task learned signed out, replayed signed in, skips its sign-in steps and runs the rest.
What it reports. Everything above, every time, in one list in the reply and in the log in
~/.dejo/logs:
done: "the price" on shop.example (profile default), 3 steps replayed in 1840 ms, no page reading.
decided on its own (2):
- step 2: cleared a pop-up: "Join our newsletter" closed by its "No thanks" control; cleared up front from now on
- step 3: found by a backup way (css:#total-button); role:button|Show total missed and moved down
A read that finds its element only by its place ("the first row") and reads something other
than it read when learned says so: read by place (...): what sits there now is not what this read said when it was learned. A list that reordered never hands back another row's value in
silence.
When it stops and asks:
- The site really changed. No way finds the element, or the check fails. Dejo stops at that one step and shows the page; your agent sends one line for it. If more than half the steps are broken, the site was rebuilt, and Dejo starts learning again.
- A different account is signed in. While learning, Dejo notes who the page shows as
signed in: an email address in the header or account menu, the name on the account button,
or "Signed in as ...". Only what is on the screen, never a cookie or a password. Before a
replay acts, and after any sign-in step, it looks again. If the page names someone else, it
stops before doing anything and says who was expected and who is signed in. A page that
names nobody is no reason to stop. To set it yourself:
expect signed in as "you@example.com". - You were signed out. A sign-in address, an identity provider's page (Google, Microsoft, Okta, Auth0 ...), an HTTP 401, a password box or a sign-in form. Dejo asks you to sign in again rather than repairing anything. It only asks this after a step failed, so a task that opens a sign-in page on purpose works as usual.
- A captcha, or a pop-up with no safe way out. Dejo stops and hands over to you. It does not solve captchas and does not get around a site's block.
When the task changes
A route is saved under the task's words. Asked in other words, Dejo only replays it when it is sure it is the same job, for the same person, with the right values:
- The same job with new values. "flights from Delhi to Mumbai on 5 Dec", asked as
"flights from Kolkata to Pune on 9 Dec", replays the same route with every changed value
swapped in, and the reply says so (
aimed at 'Kolkata' instead of 'Delhi'). Each step that was aimed has to find the new value on the page before it acts. A value the route has no place for ("in business class") stops the replay before step 1 and is named. It never replays the old values. - A time relative to today. "This month's invoice" learned in September clicks "September 2026". Asked in October, Dejo moves it to "October 2026" by the browser's clock, and says so. "last month", "today", "this week", "latest" and "current" work the same way. A date it cannot place stops the replay and is named. A task that names its month ("the September invoice") is never moved.
- A different deed is a different job. Download and delete, open and close, add and remove, track and cancel, approve and reject, lock and unlock (and any word against the same word with "un") are never matched, whatever else the names share.
- Another subject is another job. Saved "find the changelog entry" for a package, asked "find the minimum Dart SDK" for the same package: the new words stand where the route's own subject stood, so Dejo stops before step 1 instead of replaying the changelog, and shows the page in the same reply so your agent can teach the new task at once.
- A restart never replaces another task's route.
restartsent under the words of the task asked learns a new route in those words. Only a restart under a route's own task replaces that route. - A risky route needs its own task. A route with a
click!(pay, send, delete, submit) runs only for the task it was saved as, said in other common words, never for a loose match. - Another account has its own route. See More than one identity at once.
The map
A route is one path through a site. Dejo also keeps a map: every page it has looked at, and the controls that were on it. It costs nothing to keep, because the page had already been read.
dejo map github.com # every page it has seen
dejo map github.com --path /issues # the controls that were on one of them
Ask for something new on a site Dejo has walked, and your agent can ask for the pages it
already knows (dejo_memory with what=map) before it starts hunting. Each control comes back as a step target such as
button "Sign in", which works in a step without looking at the page first. A page Dejo has
already walked costs no reading at all. This matters most where one site carries many tasks: an end-to-end test
suite, an admin console, a portal you work in every day.
The map says when each page was seen. It is what was there last time, not a promise about now.
Measured on the demo site in this repo (python benchmarks/benchmark.py):
time tool calls page reads model calls
Monday 1.4s 9 3 0 learning the site
Tuesday 1.7s 1 0 0 from memory
Wednesday 1.9s 1 0 0 from memory
Thursday 12.6s 3 0 0 the site changed, one step repaired
Friday 1.9s 1 0 0 from memory
The time is mostly Dejo waiting for the site to finish after the last click (this task ends in a download). A task that ends by reading its answer does not pay that wait.
Benchmark
19 tasks on 11 lesser-known websites, the kind a model cannot answer from memory: a
college course catalog, the Debian package directory, the RFC Editor, a museum's collection,
a Dart package registry, a small shop. Every right answer was written down before any tool
ran. Claude Sonnet 5 (medium), one fresh session per run, @playwright/mcp@0.0.80.
| right answers | avg tokens | avg tool calls | avg cost | avg time | |
|---|---|---|---|---|---|
| Dejo, first run (learns the task) | 19/19 | 251k | 4.7 | $0.17 | 31 s |
| Dejo, every run after | 19/19 | 89k | 1.2 | $0.02 | 16 s |
| Playwright MCP, every run | 17/19 | 409k | 6.5 | $0.23 | 40 s |
| Playwright CLI, every run | 18/19 | 559k | 8.4 | $0.28 | 38 s |
Tool calls count browser actions only. Claude Code also makes a few calls to load a tool's definitions (ToolSearch); Dejo's tools load up front so it makes none, and Playwright MCP made about 1.3 per task (7.8 with them).
- First run: 38% fewer tokens than Playwright MCP, 55% fewer than Playwright CLI.
- Every run after: 78% fewer tokens than Playwright MCP, and 17 of 19 repeats were a single tool call. The same task run 10 times costs 74% fewer tokens.
Three things the table should also tell you, because they are true:
- On a typical task the first run costs about the same as Playwright MCP (median 216k vs 257k). The first-run savings come from the hard sites: 324k against 1.36M on the college catalog, 398k against 983k on the museum's filters.
- 6 more tasks were on sites that block automated browsers (Cloudflare checks, plain 403s). Dejo and Playwright MCP were stopped on all six; Playwright CLI got through two of them at 1.2M and 1.7M tokens. They are in the results file, not in the table.
- Dejo's numbers are from the release code, after fixing what this benchmark found; the Playwright rows are from the same tasks and model, one day earlier.
Tasks: benchmarks/real_tasks.py. Results:
benchmarks/benchmark-real-2026-10-21.json (all three tools), -10-23.json and
-10-23-fixes.json (Dejo after the fixes). Run it yourself:
python benchmarks/benchmark_learning.py --bench real --tools dejo,playwright --runs 1.
Earlier: the same job ten times (Dejo 0.1.2)
The same job, done 10 times, each time in a brand-new chat. Three multi-step tasks on
well-known sites, three MCP browser tools, 90 fresh Claude sessions (Sonnet 5, medium):
@playwright/mcp@0.0.80, chrome-devtools-mcp@1.8.0.
| the task, done 10 times | Dejo | Playwright MCP | Chrome DevTools MCP |
|---|---|---|---|
| open a GitHub repo → Issues → count them | 28 calls · 1.4M tokens | 83 · 3.4M | 33 · 1.7M |
| open an author's page → read their birth date | 27 calls · 1.3M tokens | 60 · 2.6M | 60 · 2.6M |
| open a category → open a book → read the price | 41 calls · 1.9M tokens | 56 · 2.8M | 56 · 2.8M |
| all three | 96 calls · 4.6M tokens | 199 · 8.7M | 149 · 7.1M |
52% fewer tool calls and 47% fewer tokens than Playwright MCP. Against Chrome DevTools MCP: 36% fewer calls, 35% fewer tokens. Every tool answered in all 30 of its sessions.
The GitHub task, run by run:
run 1 2 3 4 5 6 7 8 9 10
Dejo 10 2 2 2 2 2 2 2 2 2
Playwright MCP 8 8 18 7 9 9 7 6 5 6
Dejo pays once to learn the route, then costs two calls however many steps that route has.
Two things this table should also tell you, because they are true:
- Dejo costs the most on run 1. It does the job and learns the site. That is the trade.
- On a single page with a one-line answer, Dejo is not cheaper. Opening one page and reading one line costs every tool two or three calls, and there is nothing left for memory to save.
Run it yourself: python benchmarks/benchmark_agents.py --journeys --runs 10. These results
were recorded while Dejo was still called Cairn, so the files say cairn where they mean Dejo.
Signing in
You sign in to a site once, by hand, in a real window:
dejo login --site posthog.com
Sign in however the site asks: a password, Google, a code on your phone. Dejo never types a password for you and never automates an SSO button. When you say you are done, Dejo checks the window first: if it still shows a sign-in page, it says so rather than pretending. The session is kept, so you stay signed in.
A task learned while signed out keeps its sign-in steps. Replayed once you are signed in, the site skips its sign-in page, so Dejo skips those steps too, says so, and runs the rest of the task. A task that is only a sign-in answers that there is nothing to do.
Passwords never go into memory. A step that needs one records only that a password goes
there. The value is read at run time from an environment variable
(DEJO_SECRET_BILLING_ACME_COM_PASSWORD) or from ~/.dejo/secrets.json:
{ "billing.acme.com": { "password": "..." } }
More than one identity at once
A profile is a whole signed-in browser: its own cookies, its own session. Keep as many as you need, side by side:
dejo --profile vendor login --site shop.example.com
dejo --profile admin login --site shop.example.com
From your agent, dejo_profile with name="vendor" switches, and without a name lists them. A suite
covering three roles never has to sign out and back in, so the order of the tests stops
mattering.
Each profile keeps its own routes. A customer's "show my orders" is a different page from a
vendor's, so the same task name learned in two profiles is two routes, and neither overwrites
the other. Every profile also reads the routes of the default profile, and every route saved
by 0.5.0 or older, so nothing is lost on upgrade. If such a route was learned as another
account, the profile learns its own instead of being stopped. A half-learned task is kept per
profile too. dejo forget forgets the site's routes in every profile.
Sharing a route
Memory is per agent. Agents on one machine can hand routes to each other:
dejo --agent alice share github.com # alice already knows the site
dejo --agent bob borrow github.com # bob has never opened it
dejo --agent bob run "count open issues" --site github.com # one call
What travels: the steps, the ranked locators, the checks, the notes about the site, and the map. What never travels: anything typed into a field, and which account was used. A shared sign-in step asks the borrower for their own credentials.
When a borrower repairs a broken step, the fix can be contributed back to the original.
Forgetting
dejo forget --site github.com
Dejo now has nothing to follow for that site, its map included. The next run says the memory is gone. It does not quietly fall back to exploring. Forgetting also withdraws anything you shared for that site, and Dejo will not quietly hand you somebody else's route for it afterwards: you have to borrow it on purpose.
Where your data lives
All on your machine.
| what | where |
|---|---|
| routes, site facts, maps, history | ~/.dejo/memory.db, one SQLite file. Every decision about what is remembered is in engine/src/dejo/store.py |
| signed-in browser profiles | ~/.dejo/browser-profile, ~/.dejo/profiles/ |
| downloads | ~/.dejo/downloads |
| screenshots and PDFs | ~/.dejo/captures |
saved sign-in states (state save) | ~/.dejo/states |
| videos and traces | ~/.dejo/recordings |
| the step log, one file a day | ~/.dejo/logs |
| passwords, if you choose to keep them in a file | ~/.dejo/secrets.json |
Being signed in is not the same as remembering. Forget a site and Dejo is still signed in, and has no idea what to click.
The memory needs no account and has no size limit. History, the one part that grows with every
run, keeps the newest 10,000 events per agent. If you would rather keep your memory in
Sibyl Memory, install pip install "dejo[sibyl]"
and set DEJO_MEMORY=sibyl.
Tools
Five MCP tools. One of them, dejo_run, does all of the browsing, because every tool name is
read on every turn and tool choice is the most fragile part of an agent's day.
| tool | what it does |
|---|---|
dejo_run | Every website task. A known task replays in one call. A new one shows the page; your agent sends the steps, one per line, in the next call, and the task is saved when a step reads the answer. |
dejo_login | Opens a real window so the user signs in, then done=true when they have. |
dejo_profile | Switch signed-in identity (customer, vendor, admin), or list them. |
dejo_memory | What Dejo remembers: sites, one task step by step (show), the site map, and tasks other agents shared (commons). |
dejo_manage | Change it: forget a site or task, note a fact, share a task, borrow one. |
Steps are plain lines, run in order. Dejo stops at the first one that fails and shows the page there, so the next call only sends the rest:
go https://billing.acme.com/invoices
click link "September 2026"
fill label "Search" "acme"
read css ".total" as total
Targets are a ref from the page (e12), or role "name" (link "Travel"), text "...",
label "...", css "...". A name is matched whole first; when nothing has exactly that
name, words inside a name count, in any case, as in Playwright, so link "Belt Sander" finds
the card link "Belt Sander $73.59". Two such matches are refused, never guessed. The page is
searched first, then each iframe. Add first, last or nth 3 to mean a place in a list
rather than one item. A step that buys, pays, sends, deletes or submits is marked with !
(click! button "Pay"); unmarked, Dejo will not do it, and marked, it never runs twice on its
own.
Every step, in one place:
| step | what it does |
|---|---|
go URL, back, forward, reload | move between pages |
click T, double_click T, hover T, tap T | press things |
fill T "text", type T "text", clear T, press KEY [T] | type; press Control+A sends a chord |
select T "option", check T, uncheck T, set_checked T true | choose |
upload T "file", drag T to T, focus T, blur T, select_text T | everything else a hand does |
scroll T, scroll down|up|left|right|top|bottom|PIXELS | bring an element into view, or scroll the page |
scroll T down|up|left|right [PIXELS] | scroll inside one element: a list, table or chat in its own box |
mouse click|dblclick|right|move X Y, mouse drag X Y to X2 Y2, mouse down|up, mouse wheel DX DY | the mouse at a place on the screen, for a chart, map or canvas with nothing inside it to name |
keydown KEY, keyup KEY | hold a key while other steps run |
options at the end of a line: at=X,Y, keys=Shift+Control, button=right|middle, delay=MS | click a spot on an element, hold keys while clicking, right click, type at a given speed (type T "text" delay=100) |
wait T, wait text:WORDS, wait url:PART, wait gone:CSS, wait idle, wait SECONDS | wait for something real |
wait "text:Saved|Error" | wait for whichever words come first |
block "**/*.png", block off [PATTERN] | stop the page loading what matches (images, an ad, a chat widget that slows every run). Kept in the trail |
allow notifications clipboard-read ..., allow off | grant permissions to every site (Dejo grants none unasked). Kept in the trail |
mock "**/api/user" 200 '{"name": "Asha"}', mock off | answer the page's requests to an address with a made-up response, to test a page. Not kept in a trail: a trail replays the real site |
script "JAVASCRIPT" | run your own JavaScript in every page from now on, before the page's own. Not kept, like js |
header set NAME "VALUE", header clear | send a header with every request, such as a test server's key. Not kept in a trail |
wait request:WORDS, wait response:WORDS | wait until the page asked for, or got an answer from, an address with these words, since the step before |
tab N|latest|main, tab new [URL], tab close [N] | move between tabs, open one, close one (read tabs lists them) |
emulate viewport 390x844, emulate device "iPhone 13", emulate timezone Asia/Tokyo, emulate locale de-DE, emulate color dark, emulate media print, emulate motion reduce, emulate offline on, emulate network slow3g, emulate cpu 4, emulate geolocation 18.52,73.85 | look like another browser from now on; emulate KIND off undoes one. Kept in the trail, and every replay starts from the browser as it is |
cookie set NAME "VALUE", cookie clear [NAME], storage set KEY "VALUE", storage clear [KEY] | change the browser's cookies or this page's local storage (read cookies, read storage read them). Not kept in a trail: a cookie is often a sign-in |
state save NAME, state load NAME|"/path/state.json" | save every cookie and the local storage to ~/.dejo/states/NAME.json (Playwright's storage-state file), or load one: a sign-in to keep, move to another machine, or take from another tool |
record video, record trace, record performance (one or more), record stop | record what happens next: a video of every tab, a trace that opens in Playwright's trace viewer (npx playwright show-trace FILE), or a performance trace that opens in Chrome DevTools' Performance panel. Saved under ~/.dejo/recordings. Set DEJO_RECORD=video,trace to record every replay |
dialog dismiss, dialog accept ["answer"] | how to answer the next alert, confirm or prompt box (Dejo accepts unless told) |
screenshot [T|full] [as NAME], pdf [as NAME] | save a picture of the screen, one element or the whole page, or the page as a PDF, under ~/.dejo/captures. Kept in a trail, so every replay saves a new one |
read fields '{"name": "h3", "price": ".price", "link": "a@href"}' css "article" as items | many fields of every matching element at once, as JSON: the agent names each field with CSS inside the element (@attr for an attribute). No model involved, and it replays |
read box T as where, read html T as markup | where an element is on the screen (X Y WIDTH HEIGHT, the numbers mouse click uses), or its HTML |
read metrics as speed | how fast the page loaded: first byte, first and largest paint, ready, load, layout shift, requests and bytes |
read table T as rows | a whole table as JSON, one object per row, named by its headings |
read [KIND] T as NAME | the answer: KIND is text, all_text, value, checked, visible, enabled, editable, attr NAME, passage "words", count; page-wide url, title, tabs, cookies, storage, downloads, workers, dialog, console, console_errors, requests ["words"], response "words" (the body of the latest answer from that address), failed_requests |
expect url|text|see|gone|count|value ... | check the step before landed |
look [WORDS|from REF|gN|all|raw] | show the page |
look image | send a picture of the screen in the reply, for a chart, map or canvas; click a spot in it with mouse click X Y |
dismiss css "...", note "fact", restart, set_time "2026-09-15", js CODE | site knowledge and escape hatches |
It handles what breaks recorded flows: shadow DOM, iframes, divs pretending to be buttons,
content that loads late, cookie banners, confirm dialogs, new tabs, and file pickers with no
visible input. A js step runs your own JavaScript when a site does something nobody
anticipated, and is deliberately never saved into a route, because a step made of code cannot
be repaired. The full capability list, with the reason for each choice, is in
docs/BROWSING.md. How Dejo compares with Playwright, Playwright MCP,
Chrome DevTools MCP, browser-use, Stagehand, Puppeteer and Selenium, capability by capability,
is in docs/CAPABILITIES.md.
Settings
Set these in the environment of dejo-mcp, in your agent's MCP config ("env": {...}), or
before a dejo command. All are optional.
| setting | what it does |
|---|---|
DEJO_HEADED=1 | show the browser window, to watch every run live. Headless otherwise. dejo run --headed does the same for one run |
DEJO_RECORD=video,trace | record every replay: a video, a Playwright trace, or both, under ~/.dejo/recordings |
DEJO_CDP_URL=http://127.0.0.1:9222 | drive a browser that is already running instead of starting one: a cloud browser, or your own Chrome started with --remote-debugging-port=9222. Its sign-ins are used as they are. Dejo works in a tab of its own, never one you have open, closes that tab when it stops, and leaves the browser running. dejo run --cdp URL does the same for one run |
DEJO_PROXY=http://proxy.corp:8080 | send every request through your company's proxy |
DEJO_IGNORE_HTTPS_ERRORS=1 | accept a site's own certificate, for an intranet or a test server |
DEJO_LOG=off | stop writing the step log, ~/.dejo/logs/DATE.log: one plain line for each step learned or replayed, what it did, how it found its element, and why it stopped |
DEJO_PROFILE, DEJO_PROFILES_DIR | where the browser profiles live |
DEJO_DB | which memory file |
DEJO_AGENT | which agent this is, when several share one memory |
DEJO_LOCALE | the language and region the browser tells sites, like en-IN |
DEJO_MEMORY=sibyl | keep the memory in Sibyl Memory |
When something goes wrong
Start with dejo doctor. It checks the browser, the profile, the memory and the downloads
folder, and prints the exact fix for anything missing.
"Dejo has no browser to drive yet." pip install brings the code, not a browser. Run
playwright install chromium.
"Dejo cannot open a window here: this machine has no screen." Signing in needs a real
window, so dejo login cannot work over plain SSH or in a container. Sign in on a computer with
a desktop and copy ~/.dejo/browser-profile across, or forward a display with ssh -X.
Normal runs are headless and unaffected.
"The browser profile ... is in use by another Dejo session." A profile can be open in one
browser at a time, and another Dejo run (another agent, another terminal) has it. The message
names that session's process. Close it, or work in another profile (dejo_profile with a new
name, or dejo --profile <name>).
"Dejo could not open its browser profile." Another Dejo run or sign-in window still has
it open, or the profile is in a state no browser accepts. Close any other run first. If that
was not it, deleting ~/.dejo/browser-profile fixes it, at the cost of signing you out.
To run two agents at once, give each its own profile: DEJO_PROFILE=~/.dejo/profile-b dejo-mcp.
"Nothing loads in Dejo's browser profile." A page timed out twice in the profile, Dejo
tried the same address in a fresh browser, and the site's real page loaded there (an error page
from a site that is down does not count). The site is fine; that profile is stuck. Switch to a
new profile (dejo --profile <name>, or dejo_profile from your agent) and sign in there.
Delete the profile folder the message names only if every site keeps timing out in it.
A site shows the wrong country, currency or date format. Dejo's browser tells sites the
language and region this machine is set to (on Linux and macOS, from LANG), and Chrome uses
the machine's own time zone. To choose another, set DEJO_LOCALE, like DEJO_LOCALE=en-IN.
A captcha. Dejo stops and says so. It does not mark the route broken and does not try to solve it. Clear it yourself once, and run again.
Coming from Cairn
Dejo was called Cairn until October 2026. On first start, Dejo moves an old ~/.cairn
folder to ~/.dejo, so every sign-in is kept, and copies the old memory across from Sibyl
Memory into ~/.dejo/memory.db, so every learned site is kept. The Sibyl file itself is left
as it was. The old CAIRN_* environment variables still work, and the old
cairn-browser-mcp package installs Dejo.
Development
git clone https://github.com/dejohq/dejo-mcp
cd dejo-mcp
python -m venv .venv
.venv/bin/python -m pip install -e "engine[dev]" -e "mcp[dev]" # Windows: .venv\Scripts\python
.venv/bin/python -m playwright install chromium
cd engine && ../.venv/bin/python -m pytest
cd ../mcp && ../.venv/bin/python -m pytest
engine/ the engine (PyPI: dejo): browser, memory, replay, repair, sharing, the dejo command
mcp/ the MCP server (PyPI: dejo-mcp): five tools over the engine
benchmarks/ the benchmark scripts and their raw results
docs/ design notes
Opening Claude Code inside the clone needs no setup: the checked-in .mcp.json offers the
server straight away.
FAQ
Is Dejo a cache? No. A cache replays and hopes. Every step Dejo saves carries a check that proves it landed, so Dejo can tell a changed site from a working one and fix only the step that moved.
Does it call an AI model? No. Replay is plain code with zero model calls, and there is no API key to set. The agent you already use does the thinking, on the first run and when a step needs fixing.
Is it cheaper on every task? No. The first run costs the most, because Dejo does the job and learns the route. On a single page with a one-line answer there is nothing to remember. It pays off on tasks with steps that you run again.
Does it store my passwords? No. You sign in yourself, in a real window. A step that needs a password records only that a password goes there.
Which agents does it work with? Any agent that can run an MCP server: Claude Code, Codex, Cursor, and others.
Contributing
Bug reports, sites Dejo gets wrong, and pull requests are all welcome. Start with CONTRIBUTING.md. Found a security problem? Please report it privately, as SECURITY.md explains, not in a public issue. Everyone taking part follows the Code of Conduct.
Prior work
- pig-dot-dev/muscle-mem, "a cache for AI agents to learn and replay complex behaviors". The closest existing idea. Dejo is not a cache: every step carries a check, so it can tell a changed site from a working one, repair the single step that moved, and hand a working route to another agent.
- @playwright/mcp, whose accessibility-tree snapshot approach Dejo's page reading is built on.
License
MIT. See LICENSE.