Dinoer

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.