AGENTS.md@backend/packages/harness/deerflow/typesafe · git:20261020.1fd266e · 2026-10-20 · sha256 775747ec355c5898
AGENTS.md@backend/packages/harness/deerflow/typesafe git:20261020.1fd266eA
Immutable. This exact content never changes and is served at /api/v1/blob/775747ec355c5898.
### Shared TypeSafe client `typesafe/` is the transport layer for every TypeSafe (Jev) call site: the guardrail risk gate (`guardrails/typesafe.py`), and — once enabled — memory pre-screening and signal classification. It owns exactly transport and lifecycle, authentication, retry and backoff, the deadline budget, response parsing and the error taxonomy, UTF-8 wire-size counting, and the request skeleton. Clients live for one evaluation: no cache, no connection pool, and `transport_factory` is always a factory because httpx closes the transport it was handed. **It must not gain, and no consumer may move into it:** state content, the questions/criteria/thresholds and which way they decide, the failure policy (the gate denies on error, the memory paths carry on), cache semantics, or business audit. Two consumers with different thresholds are expected to share one request, so policy parameters can never reach this layer — they belong to each consumer's `release_policy_parameters()`, while `sharing_key()` carries only the credential fingerprint, the connection settings, the consumer's input limit and the transport identity. Keeping that split is the whole point: contaminating either identity either merges requests that must stay apart or breaks assembly fingerprinting. Two-layer responses are load-bearing. A request-level failure (transport, deadline, non-200, missing envelope, unparsable-or-unbounded body) raises `TypeSafeError` with a `cause`, and the consumer decides deny / fall back. A question-level failure is **data** (`AnswerSet.errors_by_question`), never an exception, so one bad answer cannot discard the other valid answers in the same response; the tool gate maps its own question's error back to `TypeSafeGuardrailError` because it has no verdict without it. The reply is never read unbounded and never decoded: the client asks for an identity encoding, refuses a body that carries a non-identity `Content-Encoding`, and stops past `MAX_RESPONSE_BYTES` (64 KiB, a few hundred bytes is the expected envelope), so an endpoint that answers with something other than an answer set cannot be expanded into memory or accepted as a verdict. Deep nesting is mapped too — `json.loads` raises `RecursionError`, not `ValueError`, and it must not escape this taxonomy. The deadline covers the read, not only the headers: the sync path, which cannot preempt the read it is blocked in, re-checks the budget before each transport read, so a body that drips just under the read timeout cannot hold the caller for the whole of `MAX_RESPONSE_BYTES` past `deadline_seconds`. Limits and secrets must not move. `max_state_chars` stays a character count in every consumer — `wire_size` reports bytes and replaces no limit, and one CJK character is three of them. A credential never reaches a log, an error message, a policy identity, a reason message or `repr()` (`TypeSafeConnection` hides both the key and its environment-variable name); only `sha256` fingerprints are compared. Two rules keep that promise true at the edges: a credential that could not travel as a header value (surrounding whitespace, a non-printable character) is refused when the connection resolves, and a transport failure is raised without chaining the original exception, whose message can contain the request it refused. Credential resolution is per *layer* too — the first layer that sets `api_key` or `api_key_env` decides it, and inside one layer the literal key wins — so a consumer naming an environment variable is never silently switched to another layer's key. Likewise `base_url` must be an `http(s)` URL with no query, fragment or embedded credentials, checked at construction rather than on every request. **Dialects.** `deerflow/openai_decisions/` (issue #7005) speaks a second endpoint over this same client rather than copying it, because the rules above are security properties and a second copy of them can drift unnoticed. Two seams carry a dialect, and they are the only two: ``TypeSafeClient.provider_label``, which names the service in every request-level message (a failure must not tell an operator about a service they did not configure), and ``TypeSafeConnection.endpoint_path``, which the connection composes into ``url`` so an endpoint path belongs to the backend that resolves the connection rather than to the base URL it happens to share. The endpoint path is included in ``public_parameters()`` and therefore in both the sharing key and public policy identity: different dialects behind one gateway cannot share a combined memory request. Matching endpoints may still combine when the other settings match. ``resolve_connection(provider_label=...)`` also names the configured service in credential and model construction errors. ``openai_decisions.client.resolve_decisions_connection`` pairs the endpoint and service label, so a "Decisions" connection cannot be built pointing at System One. A dialect adds no threshold, no rubric and no failure policy: the three adapters override their connection resolution, their client and the value the endpoint judges (``input`` text instead of a ``state`` object), and inherit the rest.