Technical choices
Seven decisions that shape Dinoer, each with its reason and its cost, including the rule a real leak forced on 14 August 2026.
Each choice below has a reason and a cost. Both are given.
Text, never pixels
Diwall, which Dinoer comes from, falls back on a vision model for elements that have no text representation. Dinoer removed that layer when it was rebuilt: screenshots, Set-of-Mark, the vision model. No option brings it back.
The cost. A clickable canvas or an image without an accessible label is out of reach. Dinoer targets public text to read and summarise; keeping a vision model would have meant running one locally, or sending pages to a hosted one, for pages it does not target.
A browser only when a page needs one
campagne.py reads most pages with requests and BeautifulSoup: no browser,
no JavaScript. A page goes to the browser core (rpa.py/shot.py) only when
the light collection marks it insuffisante_legere: a page built by
JavaScript, with nothing readable in its HTML.
The cost. Escalation fires on an empty text, not on a useless one. On a real campaign, a code-hosting profile and a video channel returned a cookie banner as their text, and were never escalated. The case →
One process per call, not a server
Dinoer is a command, not a service. shot.py and rpa.py start a Playwright
browser, do what the scenario says, answer once and exit: nothing listens on
a port between two calls. Each call writes an entry to the operations
journal, success or failure, and goes through the guide lock: without a valid
--guide-version, and without the marker an earlier accepted one leaves
behind, it is refused.
campagne.py works differently: one process runs the whole manifest, calls
its light collection directly, launches rpa.py as a subprocess for the pages
that need a browser, then ends. Between steps it keeps only files: the
corpus, the operations journal, the search cache.
The cost. The live state of a page does not survive between two calls.
Cookies and local storage can be saved and resumed (--sauver-session,
--reprendre-session); an open dialog, a half-filled form or a scroll
position cannot. A sequence that depends on such state has to be a single
scenario, in a single process.
The recipe →
What this choice does not change. A secret is resolved inside the Playwright process, whatever launched it. And a hosted model that reads an output receives its content, whatever carried it there.
Local collection, a report written elsewhere
Collection runs where Dinoer runs: the requests to SearXNG (local or remote,
your choice), the light collection, the browser core. The report does not: it
is written by the model you name for OpenCode in DINOER_OPENCODE_MODEL,
either a hosted model or a local Ollama model declared to OpenCode. There is
no default model. The collected text goes to that model.
The cost. The pipeline as a whole is not local unless you choose a local
model. For a sensitive source, choose the model before pointing Dinoer at it,
and read lib/modeles.py.
The model that writes the report stays off the web
Every opencode run that Dinoer starts carries websearch and webfetch set
to deny in OPENCODE_CONFIG_CONTENT, whatever the directory. The rule
exists because on 14 August 2026 a campaign caught the model making twelve web
searches while it wrote, visible only in the record of its full stream of
events.
The cost. bash stays allowed, for scenarios that send a notification
with curl, and a model denied websearch reached the web through
bash curl during a test run. The opening is smaller, not closed.
Details →
Signals, never verdicts
etat.pret_a_agir: false means that something was noticed (a WAF signature,
a session drift), not that Dinoer refuses to work. WAF detection works by
keywords and can be wrong; it is a count, never an error that halts a run.
The cost. You have to read the answer. A caller that looks only at
succes gets nothing from these signals.
The lock that makes your first call fail
Dinoer refuses to execute until whoever drives it has read the guide and can quote its version number.
Dinoer is not in any model’s training data, and a model that has not read the guide invents commands and handles credentials in the shell. The two rules the guide starts with →
The cost. Your first call fails, on purpose, with
{"succes": false, "erreur": "guide_non_lu"}, and every documented command
carries --guide-version.
The guide is never translated. A translation could fall behind its original while its version number was updated mechanically; an agent would then pass the lock after reading outdated instructions. The guide exists in English only.
In short
- No vision, and no option to bring it back.
- A browser only when the light collection cannot read a page.
- One process per call: a sequence that needs live page state is one scenario.
- Collection is local; the report is written by the model you choose.
- The model that writes the report is kept off the web, except through
bash. - Signals, not verdicts: read the whole answer.
- The first call fails on purpose, and the guide that unlocks it stays in English.