Before an organisation can classify its AI systems it has to know where they are. Procurement files and architecture diagrams are a start, but they lag behind the code, and the code is where a model was quietly added to a triage script last spring. Static detection over a repository is the fastest way to build the inventory. This article explains what such detection can find, what it genuinely cannot tell you, and why every finding should be treated as a question to ask rather than a verdict to record.
What the definition asks for
Art. 3(1) defines an AI system as a machine-based system that infers, from the input it receives, how to generate outputs such as predictions, content, recommendations or decisions. Inference is the thing to look for. In a codebase, inference shows up as a trained model being loaded or called: a machine-learning library, a call to a model provider, a serialised model file, or a framework that wraps any of those. A rules engine, however elaborate, is not inference. The Commission's guidelines on the definition of an AI system walk through the border cases.
Ten families of signal
A well-built scanner looks for a small number of pattern families, each with a different meaning:
- Machine-learning frameworks. Imports of training and inference libraries in Python or JavaScript. The strongest sign that a model runs in the product.
- LLM provider SDKs and inference hosts. A model vendor's client library, a cloud model runtime, or an inference API host referenced as a string. Evidence of a generative system, even where no model file exists in the repository.
- Orchestration frameworks. Retrieval and agent frameworks built on language models. They imply a generative system with the same classification and disclosure questions as a direct SDK call.
- Model artifacts. Serialised weights or model files checked in or referenced. A model in the repository is a model in production until proven otherwise.
- Scoring and automated-decision logic. Identifiers that describe decisions about people: risk scores, eligibility, probability of default, recidivism. Not generic words like "classifier" or "rank", which every library uses.
- Biometric and face recognition. Recognition libraries and cloud face APIs. Relevant to Annex III point 1 and to Art. 5(1)(g) and (h).
- Emotion recognition. Emotion or facial-expression models. Relevant to Art. 5(1)(f) and Art. 50(3).
- Agent and tool-calling frameworks. Libraries that let a model choose and call functions.
- Agent-protocol servers and clients. Endpoints that expose tools to a model or connect to other agents.
- Actions registered as tools. Functions that send, write, delete or pay, handed to a model as callable tools.
The last three do not change the risk classification. What they change is what human oversight under Art. 14 has to look like. A model that emits a paragraph is overseen by reading it. A model that calls tools and writes records needs a stop button, an allow-list of actions and a log of what it did.
What detection cannot tell you
A finding says that a component exists in the code. It does not say:
- Whether the component runs in production. An import in an experiment folder, or a dependency pulled in but never called, is not a deployed system.
- What the system is used for. Annex III is about purpose. The same scoring library serves a fraud model, a benefits eligibility model and a marketing model, and only one of those is Annex III point 5(a).
- Who the provider is. Whether you built, bought, or modified the system is a procurement fact.
- Whether a person decides. A
risk_scorevariable does not prove that the decision is taken by automated means alone. It proves that a score exists. - Whether an exclusion applies. Nothing in code says "used exclusively for defence".
A scanner that answered any of these from code alone would be guessing. The honest output is a suggestion attached to a question, with the file and line as evidence, for a person to confirm, edit or reject.
Why precision matters more than recall
A scanner that reports AI in every repository is worthless, because readers stop reading. Three engineering rules keep a scanner honest. Every identifier in a pattern is word-bounded, so "ttl" does not match inside "little" and "age" does not match inside "average". Comments and docstrings are masked before matching, so a docstring that shows an example import is documentation, not a deployed model; ordinary string literals are kept because inference hosts often appear only in strings. And a corpus of small files reproduces each known false positive and false negative, with the expected pattern ids pinned, so that a regression fails the build rather than reappearing in a customer's scan.
The negative control is as important as the positive one. A web application that depends on a package that happens to share a name with a machine-learning library should yield zero findings, and the test suite should say so.
A worked example
The fictional Schellingen Regional Transport Authority uploads a snapshot of its passenger-services monorepo. The scan reports four findings: a machine-learning framework import in a delay-prediction service, a language-model SDK in a customer-service module, a serialised model file under a models directory, and a fare_evasion_risk identifier in an inspection-planning script. It reports no biometric, emotion or agent signals.
The findings become four questions, not four conclusions. Does the transport authority deploy an AI system? Almost certainly yes, and the finding pre-fills that answer at high confidence for a person to confirm. Does the customer-service module talk to passengers directly? If so, Art. 50(1). Is the inspection-planning script deciding which passengers get checked? That is a question for the operations manager, and the answer determines whether Annex III is in play. Is the delay-prediction service about people at all? Probably not. Four findings; one classification conversation; no verdict from the scanner.
How Landfall helps
Landfall reads one snapshot of a repository from GitHub, GitLab or an uploaded archive, matches the ten pattern families above against the source, and stores each finding as a pattern id, file path, line number and matching line. Findings become suggestions on the questionnaire with the file and line as evidence, and nothing becomes an answer until a person confirms it. File contents never enter a prompt, an uploaded archive is read once and deleted, and the report lists the detected components as file and line references so a reviewer can check them.