Перейти к содержанию

agate-proxy

Ограниченный контекст прокси: встроенный обратный прокси, который инспектирует трафик LLM-агента и решает — по каждому событию — разрешить, запретить, преобразовать, буферизовать или завершить его.

agate-proxy — это плоскость данных. Ядро инспекции не зависит от протокола: проводной протокол (сначала AG-UI, позже адаптер агент ↔ LLM) входит через адаптер, который переводит проводные события в доменные. Полный дизайн см. в Модели угроз.

Ответственность

  • Терминировать TLS и принимать AG-UI-запрос (RunAgentInput).
  • «Нога» запроса (превентивная): валидировать, авторизовать и ограничивать размер запроса до пересылки — отвергать рано, чтобы агент не запускался на плохом вводе.
  • «Нога» ответа (потоковая): инкрементально парсить SSE-поток событий и по каждому событию применять вердикт — пересылая, редактируя или завершая.
  • Буферизовать фрагменты аргументов вызова инструмента между TOOL_CALL_START и TOOL_CALL_END, чтобы вердикт видел полные аргументы.
  • Питать каждой парой (событие, вердикт) приёмник аудита, вне «горячего» пути пересылки.

Конечный автомат инспекции

stateDiagram-v2
    [*] --> RequestValidation
    RequestValidation --> Rejected: невалидно / неавторизовано / слишком большое
    RequestValidation --> Streaming: переслано агенту
    Streaming --> Streaming: Allow / Transform (по событию)
    Streaming --> Buffering: TOOL_CALL_START
    Buffering --> Buffering: TOOL_CALL_ARGS
    Buffering --> Streaming: TOOL_CALL_END → вердикт по полному вызову
    Streaming --> Terminated: Deny / Terminate (RUN_ERROR)
    Streaming --> [*]: RUN_FINISHED
    Rejected --> [*]
    Terminated --> [*]

Шов «событие → вердикт»

Инспекция производит, по каждому событию (или по буферизованной логической единице), вердикт:

Вердикт Значение
Allow переслать без изменений
Deny(reason) заблокировать; на «ноге» ответа выдать как RUN_ERROR
Transform(replacement) переслать изменённое событие (например, редактированный контент)
Buffer нужно больше кадров перед решением (например, в середине вызова инструмента)
Terminate(reason) завершить run/поток

Этот единственный шов — место, куда через порты подключаются два контекста: agate-policy вычисляет вердикт (порт PolicyPort), а agate-audit записывает (событие, вердикт). Прокси зависит только от портов; конкретные адаптеры политики и аудита внедряются в корне композиции server. Первая веха поставляет тривиальный адаптер политики allow-all за PolicyPort.

Язык домена

  • Session / Run — агрегат(ы) инспекции.
  • InspectedEvent — объекты-значения событий, не зависящие от протокола.
  • Verdict — объект-значение решения, перечисленный выше.

Слои

Слой Содержимое
domain Чистый: агрегаты инспекции, InspectedEvent, Verdict. Без I/O.
application Сценарии и порты: PolicyPort (источник вердикта), приёмник аудита, клиент вышестоящего агента, рекордер ProxyMetrics.
infrastructure Адаптеры: SSE-кодек AG-UI (инкрементальный, сохраняющий порядок), валидация RunAgentInput, HTTP-клиент к агенту.
presentation HTTP/SSE-обработчики (axum/hyper), терминирование TLS, обвязка запрос/ответ.
setup Корень композиции: ProxyConfig (AGENT_ENDPOINT, BIND_ADDR), сборка.

Инспекция «ноги» запроса

Перед форвардингом прокси инспектирует входящий RunAgentInput (превентивно — агент никогда не запускается на отклонённом вводе):

  • Валидация — тело, не являющееся валидным JSON RunAgentInput, отклоняется с 400; его размер уже ограничен (см. Конфигурацию).
  • Авторизация инструментов — каждый инструмент, который клиент предлагает, оценивается тем же PolicyPort, что и на «ноге» ответа (проецируется на AgentEvent); предложенный инструмент, запрещённый политикой, отклоняет run с 403.
  • Маркеры секретов — пользовательское сообщение с настроенным маркером редакции трактуется как срабатывание политики и отклоняется с 403.
  • SSRF-гард — текст пользовательских сообщений сканируется на URL; не-http(s) схема либо loopback / приватный / link-local хост (включая адрес облачных метаданных) отклоняют run с 403. Best-effort и только по литеральному хосту — DNS не резолвится, поэтому DNS-rebinding вне области.

Каждый отказ записывается в приёмник аудита, поэтому отказы на «ноге» запроса лежат в журнале прозрачности рядом с решениями «ноги» ответа.

Наблюдаемость

Метрики плоскости данных идут через порт ProxyMetrics, а не через counter! из слоя presentation. Обработчик запуска и потоковый инспектор фиксируют agate_runs_total, agate_events_inspected_total{outcome} и agate_upstream_errors_total через внедрённый порт — поэтому inspect_stream принимает порт и юнит-тестируется с фейковым рекордером. Реальный адаптер пишет через фасад metrics (no-op, пока сервер не установит рекордер Prometheus).

Fail-open или fail-closed

Реальная политика может обращаться к внешним сервисам и зависнуть. FailModePolicy декорирует PolicyPort, ограничивая каждое решение таймаутом ([policy].decision_timeout_ms) и применяя настроенный режим при его превышении: fail-open пересылает событие, fail-closed останавливает его. По умолчанию — closed (безопасность важнее доступности). На «ноге» ответа fail-closed-таймаут завершает поток; на «ноге» запроса — отклоняет run до форвардинга. Декоратор оборачивает любую политику композицией, поэтому реализации политики ничего не знают о таймаутах — см. Конфигурацию.