Your own detection service¶
Some data cannot be recognised by its shape or by a list of names. A client's number looks like any other number until someone looks it up in the customer database. For such data the guard (Donkit's security layer, described in Agent security) can ask a small web service of your own whether a text contains it. This page is for the engineers who build and run that service. Detectors that work by examples or by a list run on the platform and need none of this. The people who connect the service to the guard use Your own data on the Security page — see Your own data.
What the guard sends¶
The guard calls your service with POST, at the address entered on the Security page, with a JSON body: the texts to check, and how long it is willing to wait.
{
"segments": [
{"id": "s1", "text": "Contract CN-123456 was signed", "lang": null}
],
"deadline_ms": 1600
}
- Each entry of
segmentsis one text. Itsidmeans nothing to you: return it as thesegment_idof everything you find in that text. - The texts are what the organization's rules need checked: the parts of a request to a model — the agent's instructions, the conversation, the results of tools — or the values in the arguments of a tool call. Expect personal data and secrets in them.
- Nothing else leaves — no user, agent, organization, tool or rule information — except the token described below.
- One call carries all the new texts of one action. A text longer than 32 KiB is cut at line boundaries, and each piece is a segment with an
idthe guard made up; offsets are into the piece you received. langis a hint:rufor a text written mostly in Cyrillic,nullotherwise. Do not rely on it.deadline_msis 80% of the Response time set for your service, so 1600 for the default of 2000 ms.
What your service answers¶
{
"spans": [
{"segment_id": "s1", "start": 9, "end": 18, "entity_type": "CONTRACT", "score": 0.95}
]
}
startandendcount Unicode code points, as a Pythonstrindex does, andendis exclusive. In😀 CN-123456the number starts at 2: the emoji is one code point, not two UTF-16 units or four bytes. A service written in a language that indexes strings differently converts before it answers. The guard drops a span that lies outside the text it names, so wrong offsets cost you findings, not errors.entity_typeis a name of your own for what you found. The person who connects the service maps each name to a kind of data and a subtype on the Security page; names that are not mapped are ignored.scoreis a confidence from 0 to 1.- Nothing found is an empty
spans— and only when you did analyse the text. - A service that cannot answer says so:
"failed": trueand a short"error". Out of time, out of memory, a model that did not load — none of these is an emptyspans, which the guard reads as "nothing found". Thespansof a failed answer are ignored. Both fields may be left out when all went well. - Answer with status 200 (any 2xx is accepted), also when
failedis true, and answer at the address you were given: redirects are not followed. - Answer without compression and within 1 MiB. The guard asks for
Accept-Encoding: identityand refuses an answer that comes with any otherContent-Encoding, so leave this endpoint out of a proxy that compresses on its own. - Fields the guard does not know are ignored, so you may add your own.
GET /healthzis optional; the guard never calls it.
Keep the texts out of your logs
The texts can hold personal data and secrets. Do not log or store them unless your own policy says you may.
This page is the short version. The full contract, with every field, lives in the open-source core of the guard, donkit-guard: see Remote detectors, and the contract as an OpenAPI file. The path carries the contract's version, /v1/; changes that keep every conforming service working stay inside it.
The token¶
If the service needs one, an account owner or admin enters its token on the Security page. The platform stores it encrypted, and the guard sends it with every call as Authorization: Bearer <token>. Without a token the guard sends no Authorization header at all.
The token is bound to the address it was saved for — the scheme, host and port of the service's address — and the guard sends it to that address and to no other. When the service moves to another address, the token has to be saved again for the new one: until then the guard does not call the service there, and every check it would have made counts as a failed call.
Whether to require it is up to you: a service that does answers 401 to a call without a valid token, and the guard counts that as a failed call. Use a token of plain printable characters; one that cannot go into an HTTP header is refused before anything is sent. A new token takes effect within about half a minute, so to rotate it without a gap, accept both for a while.
Where the service may live¶
- On Donkit's hosted platform the address must be
httpsand public. The platform calls it from outside your network, so it refuses plainhttp, addresses on private networks (such as10.x,192.168.x, loopback or link-local) and names that resolve to them. The certificate has to be valid for the name and issued by a public authority. - On a closed / on-prem installation internal addresses can be allowed,
httpincluded, so that the service can run next to the platform. If the installation has its own certificate authority, its administrator makes the platform trust it.
An address the platform may not call is refused when the rules are saved, with the reason. Anyone can reach a public address, so protect it with the token above.
Load, speed and failures¶
- When the guard asks. Only for actions where a rule could use what your service finds. For the rules the Security page makes, that is model requests when the finds are masked, tool calls when they are blocked, and both when they are only recorded. A model request carries the whole conversation each time, but the guard asks about each text once: an answer is reused for an hour, and a failed answer never is. So a call is made only for an action that brings new text, and it carries only that text. Busy agents call in parallel.
- How fast. When the finds are masked or blocked, the request waits for your answer, for at most the Response time (from 100 to 30000 ms, 2000 by default). A fast service keeps agents fast. A service that knows it will not make it should answer
failed: trueinstead of making the guard wait. - When a call fails. No answer in time, an error status, a redirect, a compressed or oversize answer, a body that does not follow the contract, and
failed: trueall count as a failed call, and so does a refusal on the platform's side — an address it may not call, a token that is not stored or is saved for another address. The choice under If the service does not answer then decides. Do not let the request through, the default, stops the request in Enforce mode, with the notice that the guard could not finish checking. Let it through and mark it unchecked lets it go on and marks the entry not checked in Decisions. A service that only feeds recording rules never stops anything; its failures are marked not checked too.
Check the service before you connect it¶
donkit-guard ships a check that your service keeps to the contract. It needs only the service's address:
pip install 'donkit-guard[http]'
python -m donkit_guard.conformance https://detector.example.com/v1/analyze
It prints OK and exits with 0, or prints one problem per line and exits with 1. A service that needs a token gets the name of the environment variable that holds it, as --token-env NAME.
The check sends an empty request, texts that begin with an emoji (offsets that do not count code points show up as spans that run past the end of their text), and a request with a one-second deadline, which must be answered, or failed, within a second and a half. It checks structure only: it cannot tell whether your service finds the right things.
Once it passes, connect the service on the Security page. Check connection calls it with a made-up sample text and lists the entity types it reports. Test on text runs it on a text you paste in; that text goes to your service, so use it for your own test cases.
An example to start from¶
examples/remote-detector in the repository of the core is a service to copy: a FastAPI application in one file that finds contract numbers such as ДГ-123456 and reports them as the entity type CONTRACT. It implements POST /v1/analyze and GET /healthz, answers failed: true when it runs out of time, and requires a bearer token when the environment variable DETECTOR_TOKEN is set. Replace its detection with your own and keep the rest.
What to hand to the person who connects it¶
The address, the token, the entity types your service reports with a word on what each one stands for, and the response time you can keep.