redb.Route 3.1.0 — LLM как ещё один транспорт: .To(«llm://claude») и .AsLlmTool()

от автора

redb.route llm AI

redb.route llm AI

Серия: redb ecosystem (анонс, разбор позже)

В 3.1.0 у redb.Route вышло два новых транспортаredb.Route.Llm (24-й) и redb.Route.Exec (25-й). LLM теперь — обычный endpoint наравне с Kafka, RabbitMQ и HTTP: вызов модели — это шаг .To("llm://claude"), инструмент агента — это маршрут с .AsLlmTool("shell"), периодический агент — From("llm://factory?schedule=5m"). Exec — спавнер процессов с allowlist, working-dir и таймаутом; работает и как backend shell-инструментов агента, и как самостоятельный scheduled consumer (cron-less health-probes, бэкапы и т.п.). Никаких «отдельных AI-фреймворков рядом с ESB»: всё внутри той же DSL, тех же retry/throttle/circuit-breaker/audit, тех же OpenTelemetry-трейсов.

Это анонс. Подробный разбор внутренностей — отдельной статьёй позже. Здесь — что появилось, как это выглядит в коде, и что честно ещё не сделано.

Если читаете про redb.Route впервые — короткий контекст из предыдущих статей серии:


Самое короткое объяснение

From("kafka://orders")    .To(Llm.Factory("claude").Temperature(0.2).MaxTokens(1024).AsUri())    .To("kafka://orders.translated");

Эта одна строка — полный вызов LLM:

  • тело входящего сообщения уходит как user-промпт;

  • агент выполняет круг (модель → опционально tool-use → модель → …) до EndTurn или MaxIterations;

  • assistant-ответ ложится в exchange.Out.Body;

  • использование токенов, id модели, причина остановки, число итераций — в заголовки;

  • OpenTelemetry-трейсы и метрики поднимаются автоматически;

  • endpoint виден в dashboard’е tsak.web с messages/sec, средней длительностью, error rate и last-error — как любой другой коннектор.

Это весь смысл «LLM как коннектор, а не библиотека». Если у вас уже есть Apache Camel-уровневый ESB с retry, breaker, idempotent consumer и audit, то превращать LLM в ещё один endpoint — единственное честное решение. Не нужно заново писать ретраи, не нужно отдельные дашборды, не нужно тащить «AI-инфраструктуру» рядом с интеграционной.


Один провайдер-адаптер, 14 OpenAI-совместимых API

В пакете два production-провайдера:

  • OpenAiProvider — один универсальный транспорт для 14 OpenAI-совместимых API: openaianthropic/claude (через официальный OpenAI-compat endpoint Anthropic), groqcerebrasopenroutergemini (OpenAI-compat), github-modelsmistraltogetherhuggingfacedeepseekollamalmstudio + универсальный custom для self-hosted шлюзов.

  • StubProvider — детерминированный echo для unit-тестов без ключей.

Нативный AnthropicProvider (Messages API) — на очереди для фич, которых нет в OpenAI-compat surface.

Смена провайдера — это смена одной строки в DI:

services.AddLlmConnectionFactory("groq", f =>{    f.Provider = "groq";    f.ModelId  = "llama-3.3-70b-versatile";    f.ApiKey   = Environment.GetEnvironmentVariable("REDB_LLM_GROQ_KEY");});

То же самое с provider = "anthropic" и modelId = "claude-haiku-4-5" — и тот же .To("llm://...") уже звонит в Claude. DSL не меняется ни на символ.


Tools — это маршруты

Главное архитектурное решение: инструмент агента — это обычный RouteBuilder-маршрут плюс один DSL-аспект .AsLlmTool("name"). Из этого выпадает четыре свойства, которые иначе пришлось бы держать отдельно.

From("direct:tool-shell")    .AsLlmTool("shell")        .Description("Run a small shell command on the host. Input: {\"command\":\"<name>\",\"args\":[\"...\"]}.")        .Input("""            {              "type":"object",              "properties":{                "command":{"type":"string"},                "args":{"type":"array","items":{"type":"string"}}              },              "required":["command"]            }            """)        .SideEffect(ToolSideEffect.ReadOnly)        .Cost(ToolCostClass.Cheap)    .Then()    .To(ExecDsl.Run()        .AllowedCommands("cmd", "pwsh")        .WorkingDirectory(scratchDir)        .TimeoutMs(5_000)        .MaxStdoutBytes(8_192)        .MaxStderrBytes(8_192));

Что получаем бесплатно:

  1. Tools-as-routes — внутри инструмента доступны все 30+ EIP-паттернов (Splitter, Aggregator, CircuitBreaker, Throttle, Filter, TryCatch). Tool, который дёргает базу, можно завернуть в Transaction(), breaker и retry, не написав ни строки runtime-кода руками.

  2. Tool из существующего маршрута — берёшь любой From("http://...") или From("sql://...") и навешиваешь .AsLlmTool("name"). У него уже есть авторизация, аудит, метрики — потому что это просто маршрут.

  3. Inventory-as-data — IToolDescriptorRegistry знает все инструменты по имени с JSON-schema. Можно фильтровать ?tools=* или ?tools=lookup,shell, можно собирать «универсального агента из всех read-only-инструментов» одной строкой.

  4. Zero bumps на остальные коннекторы. Аспект AsLlmTool() живёт в redb.Route.Llm.Abstractions. Любой из 22 других коннекторов получает его без обновления своих NuGet-пакетов.


Реальный пример: standalone-демо «curl → Claude → shell → ответ»

Это полная программа. Два файла — Llm.HttpShell.csproj и Program.cs, top-level statements, никаких host-абстракций сверху. Вставляешь свой Anthropic-ключ в одно место и dotnet run поднимает HTTP-эндпоинт на 5088, в котором Claude Haiku умеет вызвать shell на хосте и ответить в контексте предыдущих реплик.

csproj — пять NuGet-пакетов

<Project Sdk="Microsoft.NET.Sdk">  <PropertyGroup>    <OutputType>Exe</OutputType>    <TargetFramework>net9.0</TargetFramework>    <ImplicitUsings>enable</ImplicitUsings>    <Nullable>enable</Nullable>  </PropertyGroup>  <ItemGroup>    <PackageReference Include="redb.Route" Version="3.1.0" />    <PackageReference Include="redb.Route.Http" Version="3.1.0" />    <PackageReference Include="redb.Route.Llm" Version="3.1.0" />    <PackageReference Include="redb.Route.Llm.Abstractions" Version="3.1.0" />    <PackageReference Include="redb.Route.Exec" Version="3.1.0" />    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />  </ItemGroup></Project>

redb.Route — ядро ESB, redb.Route.Http — HTTP-транспорт, redb.Route.Llm[.Abstractions] — LLM-коннектор и DSL-аспект .AsLlmTool()redb.Route.Exec — спавнер процессов с allowlist-ом и таймаутом. Никакого redb.Core/redb.Postgres — демо самодостаточен и держит conversation memory в памяти процесса.

1. Ключ

const string ApiKey = "<your-key>";

Тот, что виден в https://console.anthropic.com/. Поменять на Groq — это Provider = "groq"ModelId = "llama-3.3-70b-versatile" и ключ из console.groq.com; DSL ниже не двинется.

2. DI и RouteContext

RouteContext ctx = null!;var services = new ServiceCollection();services.AddLogging(b => b    .AddSimpleConsole(o => { o.SingleLine = true; o.TimestampFormat = "HH:mm:ss "; })    .SetMinimumLevel(LogLevel.Information));services.AddSingleton<IRouteContext>(_ => ctx);services.AddSingleton<ILogger>(sp => sp.GetRequiredService<ILoggerFactory>().CreateLogger("redb.Route"));var sp = services.BuildServiceProvider();ctx = new RouteContext(sp, contextId: "llm-http-shell");ctx.AddService(typeof(ILoggerFactory), sp.GetRequiredService<ILoggerFactory>());

RouteContext — runtime-сосуд: маршруты, компоненты, сервисы. Замыкание _ => ctx — чтобы зарегистрировать IRouteContext как DI-сервис до того, как сам контекст создан (он в свою очередь требует IServiceProvider). Последняя строка — кладём ILoggerFactory в context руками; без этого .Log(...)-шаги в маршрутах молча превращаются в no-op (внутренности — в отдельной статье).

3. Три компонента

ctx.AddComponent(new HttpComponent { ServerManager = new SharedHttpServerManager() });ctx.AddComponent(new LlmComponent());ctx.AddComponent(new ExecComponent());

Компонент в redb.Route — плагин транспорта, который владеет URI-схемой: http://llm://exec://. Просто регистрация; ничего бизнес-логического тут не происходит.

4. Connection factory для Claude Haiku

ctx.AddToRegistry("haiku", new LlmConnectionFactory{    Name        = "haiku",    Provider    = "anthropic",    ModelId     = "claude-haiku-4-5",    ApiKey      = ApiKey,    Temperature = 0.0,    MaxTokens   = 512});

"haiku" — лейбл, по которому маршрут потом скажет Llm.Factory("haiku"). Тот же приём, что у redb.Route на all-connectors уровне для именованных connection-factory.

5. Agent engine + tool registry

var toolRegistry = new ToolDescriptorRegistry();ctx.AddService(typeof(IToolDescriptorRegistry), toolRegistry);var producerTemplate = new ProducerTemplate(ctx);ctx.AddService(typeof(IProducerTemplate), producerTemplate);var engine = new AgentEngine(    logger:           null,    producerTemplate: producerTemplate,    observer:         new NoopAgentObserver(),    budget:           new NoopBudgetEnforcer(),    approval:         new AutoApproveGate(),    redaction:        new NoopRedactionFilter(),    shadow:           new NoopShadowRunner(),    conversation:     new InMemoryConversationStore(),    idempotency:      null,    approvalStore:    null);ctx.AddService(typeof(IAgentEngine), engine);

Все state-поверхности агента — observability, budget, approval, redaction, shadow, conversation, idempotency — за интерфейсами. В демо все Noop/InMemory: agent-loop работает, но ничего не персистится. Боевой вариант — AddRedbLlmStorage() (см. секцию ниже): подменяет InMemoryConversationStore на RedbConversationStore, прицепляет RedbAuditObserver и т.д.

6a. HTTP-маршрут

var isWindows  = OperatingSystem.IsWindows();var allowed    = isWindows ? new[] { "cmd", "pwsh", "powershell" } : new[] { "sh", "bash" };var scratchDir = Path.Combine(Path.GetTempPath(), "redb-llm-shell");Directory.CreateDirectory(scratchDir);var systemPrompt =    "You can run small shell commands through the 'shell' tool. " +    $"The host is {(isWindows ? "Windows (use cmd /c)" : "Linux (use sh -c)")}. " +    "Use the tool when the user asks about the system, files, or commands; " +    "then summarise what you learned in one short sentence.";ctx.AddRoutes(r =>{    r.From("http:0.0.0.0:5088/api/llm/shell?inOut=true")        .RouteId("llm-http-shell")        .ConvertBody<string>()        .Process(e =>        {            e.In.Headers[LlmHeaders.SystemPrompt] = systemPrompt;            e.In.Headers[LlmHeaders.ConversationId] =                e.In.Headers.TryGetValue("X-Chat-Id", out var v) && v is string s && s.Length > 0                    ? s : "default";        })        .Log("[LLM-SHELL] ▶ chat=${header.llm.conversation.id} prompt=${body}")        .To(LlmDsl.Factory("haiku")            .Tools("shell")            .ConversationFromHeader()            .MaxIterations(10)            .Temperature(0.0)            .AsUri())        .Log("[LLM-SHELL] ◀ iters=${header.llm.tool.iterations} stop=${header.llm.stop_reason} " +             "tokensIn=${header.llm.tokens.in} tokensOut=${header.llm.tokens.out}")        .Log("[LLM-SHELL] ◀ reply=${body}");

Тело POST’а конвертируется в строку и становится user-промптом. Process ставит system-промпт в стандартный заголовок LlmHeaders.SystemPrompt и резолвит conversation id из клиентского X-Chat-Id (без него — default). .Tools("shell") — единственный LLM-specific knob: «дай агенту инструмент с этим именем». MaxIterations(10) — потолок tool-loop’а (иначе бесконечный пинг между моделью и инструментом).

6b. Сам инструмент — это маршрут

    r.From("direct:tool-shell")        .AsLlmTool("shell")            .Description(                "Run a small shell command on the host. Input: " +                "{\"command\":\"<name>\",\"args\":[\"...\"]}. Output: " +                "{\"stdout\":\"...\",\"stderr\":\"...\",\"exitCode\":N}. " +                $"Allowed commands: {string.Join(", ", allowed)}. " +                $"Working directory is pinned to '{scratchDir}'.")            .Input("""                {                  "type": "object",                  "properties": {                    "command": { "type": "string" },                    "args":    { "type": "array", "items": { "type": "string" } }                  },                  "required": ["command"]                }                """)            .SideEffect(ToolSideEffect.ReadOnly)            .Cost(ToolCostClass.Cheap)        .Then()        .Log("[SHELL-TOOL] ▶ in=${body}")        .To(ExecDsl.Run()            .AllowedCommands(allowed)            .WorkingDirectory(scratchDir)            .TimeoutMs(5_000)            .MaxStdoutBytes(8_192)            .MaxStderrBytes(8_192))        .Log("[SHELL-TOOL] ◀ exit=${header.redbExec.ExitCode} body=${body}");});

Description + JSON-схема — то, что Claude увидит как «эта функция мне доступна, вот её сигнатура». SideEffect.ReadOnly и Cost.Cheap — гайдлайны для governance-хуков (бюджеты, требование апрува). После .Then() — обычный route: Log → ExecDsl.Run() с allowlist’ом, рабочим каталогом, таймаутом 5 сек и cap-ом на stdout/stderr → Log. Никакого LLM-specific кода ниже .Then(). Это всё ещё просто маршрут — поэтому в него можно завернуть CircuitBreakerThrottleTransactionWireTap-shadow, что угодно из 30+ EIP-паттернов redb.Route.

7. Старт и блокирование

await ctx.Start();producerTemplate.Start();var stop = new ManualResetEventSlim();Console.CancelKeyPress += (_, e) => { e.Cancel = true; stop.Set(); };stop.Wait();await ctx.DisposeAsync();

Запуск

dotnet runcurl -d "how much free disk space?" http://localhost:5088/api/llm/shellcurl -d "what did you just say?" -H "X-Chat-Id: my-chat" http://localhost:5088/api/llm/shell

Первый запрос — Claude видит system-промпт «у тебя есть shell», решает посмотреть свободное место, дёргает tool с чем-то вроде {"command":"cmd","args":["/c","fsutil","volume","diskfree","C:"]}, получает stdout, формулирует ответ человеку. Второй запрос — с X-Chat-Id: my-chat — видит предыдущий обмен в InMemoryConversationStore и отвечает в контексте.

В логах в этот момент видно полный путь: [LLM-SHELL] ▶ prompt=... → [SHELL-TOOL] ▶ in={"command":"cmd",...} → [SHELL-TOOL] ◀ exit=0 body={"stdout":"...","exitCode":0} → [LLM-SHELL] ◀ iters=2 stop=end_turn tokensIn=... tokensOut=... reply=.... Это всё — обычный .Log()-шаг redb.Route, не отдельный LLM-tracing.

Allowlist команд (cmdpwshshbash) — security envelope: всё вне списка redb.Route.Exec отвергает ещё до запуска процесса.


redb.Route.Exec — спавнер процессов как 25-й транспорт

В демо выше ExecDsl.Run() появился как «бэкенд shell-инструмента», но это самостоятельный коннектор, который вышел в 3.1.0 одновременно с redb.Route.Llm. Закрывает скучный, но повсеместный пробел: framework уже умел в 22+ транспорта (HTTP, Kafka, SQL, …), а до самой ОС — нет.

Producer — .To(ExecDsl.Run(...)) — синхронный спавн. Команду резолвит в порядке: JSON body → заголовки redbExec.Command/redbExec.Args → URI-опции ?command=.... Тело Out — JSON, удобный для LLM-tool ABI:

{ "stdout": "...", "stderr": "...", "exitCode": 0, "timedOut": false }

Ровно поэтому shell-инструмент в демо — это .To(ExecDsl.Run().AllowedCommands(...)) без единой строки склеечного кода. Модель присылает {"command":"cmd","args":[...]}, продьюсер парсит, исполняет, отдаёт структурированный JSON. Маршрутные фичи (CircuitBreaker, Throttle, OnException, audit) применяются автоматически — это endpoint как любой другой.

Consumer — From(ExecDsl.Run(...).Schedule("5m")) — тот же спавнер, но как первоклассный source-endpoint со встроенным шедулером. Без cron, без отдельного worker’а:

routes.From(ExecDsl.Run("./scripts/health-check.sh")                   .Schedule("5m")                   .TimeoutMs(30_000))      .Choice()        .When(e => e.In.Headers["redbExec.ExitCode"]?.ToString() != "0")          .To("http://alerts.internal/oncall")        .Otherwise()          .To("kafka://metrics.healthy");

Каждые 5 минут — запуск скрипта, ветвление по exit code, в одну сторону HTTP-вебхук дежурному, в другую — Kafka-топик. ?schedule= принимает простые интервалы (500ms30s5m1h). Для cron — From("quartz://<cron>").To(ExecDsl.Run(...)) (Quartz уже шедулер, дублировать его внутри Exec нет смысла).

Security-обвязка — то, что вообще даёт shell-as-tool право существовать:

  • AllowedCommands — case-insensitive по file-name, /usr/bin/git и git.exe оба матчат git. Команды вне списка отвергаются UnauthorizedAccessException ещё до запуска процесса.

  • WorkingDirectory — пинит cwd; процесс не может выйти за неё сам.

  • EnvironmentOverrides + ScrubEnvironment — старт с пустого окружения, применяем только нужные KEY=VALUE.

  • TimeoutMs — wall-clock kill-switch, убивает весь process tree.

  • MaxStdoutBytes / MaxStderrBytes — cap, защищает host от runaway-процесса.

Почему отдельный пакет, а не часть redb.Route.Llm — три причины: (1) Exec полезен и без LLM (scheduled health-probes, log rotation, deploy-glue, бэкапы); (2) redb.Route.Llm не должен тащить зависимость на спавн процессов в проектах, где агент дёргает только HTTP/SQL-инструменты; (3) тот же allowlist/timeout/cap-механизм будет переиспользован в будущих коннекторах с тем же классом security-проблем.


From(«llm://…») — периодический агент-консьюмер

Это та фича, которой нет в Camel langchain4j-* (там LLM — только продьюсер): LLM как первоклассный source-endpoint, у которого внутри уже сидит шедулер.

From("llm://groq?schedule=5m" +     "&systemPromptRef=#watchdog-system" +     "&initialBodyRef=#daily-brief" +     "&tools=*")    .To("rabbitmq://alerts");

Каждые 5 минут — свежий запуск агента с system-промптом из реестра #watchdog-system и user-промптом из #daily-brief. Ответ уходит в RabbitMQ. ?schedule= принимает простые интервалы (500ms30s5m1h); для cron — From("quartz://...").To("llm://..."), у Quartz это уже его работа.

Что хорошо ложится в эту форму: watchdog-агенты, периодическая генерация отчётов, self-improving агенты с conversation memory (та же история между запусками).


#-промпты — динамический реестр

Префикс # на параметре превращает URI-значение в lookup. Любой другой маршрут может переписать промпт по имени — следующий запуск агента подхватит свежее значение без редеплоя:

host.Context.AddToRegistry("style.terse", "Reply in fewer than 5 words.");r.From("direct:chat")    .To("llm://scripted?systemPromptRef=#style.terse")    .To("mock:done");// ... позже, другой маршрут ...host.Context.AddToRegistry("style.terse", "Reply in French only.");// следующий запуск увидит новое значение

Резолвинг идёт сначала через IPromptTemplateRegistry (версионированное хранилище — нужно для replay в eval-прогонах), потом через generic-registry с обычной строкой. Без # — литерал, передаётся как есть.

Это тот же #name-механизм, что используется в redb.Route framework-wide для connection-factory: единая конвенция, не отдельный «promptref-DSL».


Память агента — AddRedbLlmStorage()

По умолчанию все state-поверхности — in-memory (стенограммы диалогов, tool idempotency, approvals, cost ledgers, audit). AddRedbRouteLlm() остаётся zero-dependency — годится для тестов и stateless-агентов, но всё теряется на рестарте.

Альтернатива — одна строка:

services.AddRedbRoute(route =>{    route.Services.AddRedbRouteLlm();    route.Services.AddRedbIdempotentRepository();   // нужен для idempotency    route.Services.AddRedbLlmStorage();             // ← REDB-стораджи на все поверхности});

AddRedbLlmStorage() заменяет дефолтные синглтоны:

Интерфейс

Default

REDB store

IConversationStore

InMemoryConversationStore

RedbConversationStore — дерево, parent-id нативный, message-id на индексированной value_string

IToolIdempotencyStore

InMemoryToolIdempotencyStore

RedbToolIdempotencyStore (поверх IIdempotentRepository)

IApprovalStore

InMemoryApprovalStore

RedbApprovalStore

ICostBudgetStore

InMemoryCostBudgetStore

RedbCostBudgetStore — running totals per tenant/window

IAgentObserver

NoopAgentObserver

RedbAuditObserver — одна строка на каждый tool-call

Per-row бизнес-идентификаторы лежат на индексированной value_string, не внутри JSON. Lookup — O(log n) по одной колонке, не full-scan-with-JSON-decode. Tree integrity для transcripts — через нативный parent_id (IRedbService.CreateChildAsync), не soft FK в props. Стораджи lazy-синкают схему на первом использовании — никакого migration step.


Что наследуется от движка бесплатно

Это центральный аргумент за «коннектор, а не библиотеку». Всё, что redb.Route уже умеет, применяется к LLM-вызовам без отдельного кода:

Concern

DSL-примитив

Retry / backoff

RedeliveryPolicyOnException

Rate limiting

Throttle

Resilience

CircuitBreaker

Idempotency

IdempotentConsumer

Compensation

Saga

Audit / shadow

WireTapMulticast

Tracing & метрики

RouteActivitySourceRouteMetrics

Персистенс

redb schemes (typed object engine)

Tool, который ходит в дорогой API — оборачиваешь в CircuitBreaker и Throttle. Хочешь shadow-запуск нового системного промпта параллельно со старым — Multicast + WireTap. Это не «фичи LLM-коннектора», это движок, к которому LLM приставлен как endpoint.


Что live-протестировано, а что — нет

Честно про статус провайдеров (это в README, дублирую тут):

  • Groq + Llama 3.3 70B — самый надёжный free-tier из доступных. Идёт со strict-ассертом (модель должна выдать буквально требуемое слово) в BasicChatTests и ToolRouteTests.

  • Mistral small-latest — стабильно отвечает на short-form, но tool-use на free-tier плавает; в тестах с tools ассертим философски.

  • Gemini 2.0 Flash — 15 RPM на free-tier, отвечает на спокойном репозитории.

  • Anthropic Claude (Haiku 4.5 / Sonnet 4.6) — через OpenAI-compat endpoint, live-протестирован в ClaudeChatTests и в redb.Route.Demo.

  • OpenRouter / Cerebras — каркас рабочий, отдельные free-маршруты дают rate-limit, в тестах помечены [EnvFact] и пропускаются без ключа.

Honest skip-list, что ещё не сделано:

  • Embeddings и vector store — Phase 2 (embed://vector:// запланированы).

  • RAG-примитивы, document loaders, web search — пока нет (для web search есть отдельный Tavily-tool в redb.Route.Llm.Tools, который уже работает как обычный .AsLlmTool("web_search")).

  • Sliding-window memory shapes (window-by-N-messages, window-by-K-tokens) — пока не first-class. Persistent transcripts через AddRedbLlmStorage() есть; windowed-shape поверх них реализуется маршрутом Process + conversation store, но не одной опцией.

  • Native AnthropicProvider — OpenAI-compat surface закрывает большинство сценариев; нативный Messages API нужен для фич, которых там нет.


Ссылки

GitHub впереди NuGet. Свежие баг-фиксы (например, починка tool_use/tool_result пар при перезагрузке диалога и декодирование OEM- кодировок Windows в redb.Route.Exec) уже зафиксированы в main под версией 3.1.1 — см. CHANGELOG.md в публичной репе. В NuGet эти фиксы выходят пачками с очередным релизом, так что если в production уперлись в баг — сначала смотрите main и тег последнего pre-release, и только потом ждите пакет на nuget.org. Это нормальная практика, не баг процесса.

redb.Route на GitHub

github.com/redbase-app/redb-route

redb.Route.Llm на NuGet

nuget.org/packages/redb.Route.Llm

redb.Route.Exec на NuGet

nuget.org/packages/redb.Route.Exec

Полный README пакета

redb.Route.Llm/README.md

USER-GUIDE (подробный гайд по DSL, governance, conversation)

redb.Route.Llm/doc/USER-GUIDE.md

STORAGE — REDB-схемы под conversation / approval / audit / idempotency

redb.Route.Llm/doc/STORAGE.md

README Exec-коннектора

redb.Route.Exec/README.md

Standalone-демо Llm.HttpShell (curl → Claude → shell → ответ)

demo/Llm.HttpShell/Program.cs

Полная демо-репа (22+ маршрутов)

demo/redb.Route.Demo/Routes/LlmHttpRoutes.cs

Discussions

github.com/redbase-app/redb-route/discussions

Всё под Apache 2.0. Подробный разбор tool-loop, governance-хуков и REDB-стораджей — отдельной статьёй в серии. Вопросы про конкретные сценарии — в комментариях; именно так пишутся следующие части.

ссылка на оригинал статьи https://habr.com/ru/articles/1045356/